• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

如何使用Node.js制作实时体育应用程序

如何使用Node.js制作实时体育应用程序  第1张您将要创建的内容

在今天的文章中,我将演示如何制作一个显示 NHL 现场比赛比分的 Web 应用程序。随着比赛的进行,分数会自动更新。

这对我来说是一篇非常令人高兴的文章,因为它让我有机会将我最喜欢的两种激情结合在一起:发展和运动。

将用于创建应用程序的技术是:

  1. 节点.js

  2. 套接字.io

  3. MySportsFeed.com

如果您没有安装 node.js,请立即访问他们的下载页面并进行设置,然后再继续。

什么是 Socket.io?

Socket.io 是一种将客户端连接到服务器的技术。在此示例中,客户端是 Web 浏览器,服务器是 Node.js 应用程序。服务器可以在任何给定时间有多个客户端连接到它。

建立连接后,服务器可以向所有客户端或单个客户端发送消息。作为回报,客户端可以向服务器发送消息,从而实现双向实时通信。

在 Socket.io 之前,Web 应用程序通常会使用ajax,并且客户端和服务器都会相互轮询以查找event例如,每隔 10 秒就会发生一次 AJAX 调用,以查看是否有任何消息要处理。

轮询消息会在客户端和服务器上造成大量开销,因为它会在没有消息时不断寻找消息。

使用 Socket.io,即时接收消息,无需查找消息,从而减少开销。

示例 Socket.io 应用程序

在我们使用实时体育数据之前,让我们创建一个示例应用程序来演示 Socket.io 的工作原理。

首先,我将创建一个新的 Node.js 应用程序。控制台窗口中,我将导航到 C:\GitHub\NodeJS,为我的应用程序创建一个新文件夹,然后创建一个新应用程序:

cd \GitHub\NodeJS
mkdir SocketExample
cd SocketExample
npm init

我使用了所有默认设置。

因为我们正在制作一个 Web 应用程序,所以我将使用一个名为 Express 的 NPM 包来简化设置。在命令提示符下,按如下方式安装它: npm install express --save

当然,我们需要安装 Socket.io 包: npm install socket.io --save

让我们从创建 Web 服务器开始。创建一个名为 index.js 的新文件并将以下代码放入其中以使用 Express 创建 Web 服务器:

var app = require('express')();
var http = require('http').Server(app);

app.get('/', function(req, res){
    res.sendFile(__dirname + '/index.html');
});

http.listen(3000, function(){
	console.log('HTTP server started on port 3000');
});

如果你不熟悉 Express,上面的代码示例包含 Express 库并创建了一个新的 HTTP 服务器。在此示例中,HTTP 服务器正在***端口 3000,例如http://localhost:3000在站点“/”的根目录下创建一个路由。路由的结果返回一个 HTML 文件:index.html。

在我们创建 index.html 文件之前,让我们通过设置 Socket.io 来完成服务器。将以下内容附加到您的 index.js 文件以创建 Socket 服务器:

var io = require('socket.io')(http);

io.on('connection', function(socket){
    console.log('Client connection received');
});

与 Express 类似,代码从导入 Socket.io 库开始。这存储在一个名为 的变量中io接下来,使用该io变量,使用该函数创建一个事件处理程序on正在***的事件是连接。每次客户端连接到服务器时都会调用此事件。

现在让我们创建我们非常基本的客户端。创建一个名为 index.html 的新文件并将以下代码放入其中:

<!doctype html>
<html>
    <head>
		<title>Socket.IO Example</title>
	</head>
	<body>
		<script src="/socket.io/socket.io.js"></script>
		<script>
			var socket = io();
		</script>
	</body>
</html>

上面的 HTML 加载 Socket.io 客户端javascript并初始化与服务器的连接。要查看示例,请启动您的 Node 应用程序:node index.js

然后,在您的浏览器中,导航到http://localhost:3000页面上不会出现任何内容;但是,如果您查看运行 Node 应用程序的控制台,则会记录两条消息:

  1. HTTP 服务器在端口 3000 上启动

  2. 收到客户端连接

现在我们有一个成功的套接字连接,让我们使用它。让我们首先从服务器向客户端发送消息。然后,当客户端收到消息时,它可以将响应发送回服务器。

让我们看一下缩写的 index.js 文件:

io.on('connection', function(socket){
    console.log('Client connection received');
	
	socket.emit('sendToClient', { hello: 'world' });
	
	socket.on('receivedFromClient', function (data) {
		console.log(data);
	});
});

之前的io.on函数已更新为包含几行新代码。第一个,socket.emit,将消息发送给客户端。sendToClient是事件的名称通过命名事件,您可以发送不同类型的消息,以便客户端可以不同地解释它们。第二个添加是socket.on,它还包含一个事件名称:receivedFromClient这将创建一个接受来自客户端的数据的函数。在这种情况下,数据将记录到控制台窗口。

这样就完成了服务器端的修改;它现在可以从任何连接的客户端发送和接收数据。

sendToClient让我们通过更新客户端来接收事件来完成这个示例。当它接收到事件时,它可以将receivedFromClient事件返回给服务器。

这是在 HTML 的 JavaScript 部分完成的,因此在 index.html 文件中,我更新了 javaScript,如下所示:

var socket = io();

socket.on('sendToClient', function (data) {
    console.log(data);
	
	socket.emit('receivedFromClient', { my: 'data' });
});

使用实例化的套接字变量,我们在服务器上具有非常相似的逻辑与socket.on函数。对于客户端,它正在监听sendToClient事件。一旦客户端连接,服务器就会发送此消息。当客户端收到它时,它会记录到浏览器的控制台中。然后,客户端使用与socket.emit服务器用于发送原始事件的相同。在这种情况下,客户端将receivedFromClient事件发送回服务器。当服务器接收到消息时,它会被记录到控制台窗口。

自己试试吧。首先,在控制台中,运行您的 Node 应用程序:node index.js然后在浏览器中加载http://localhost:3000 。

检查 Web 浏览器控制台,您应该会看到记录了以下 JSON 数据:{hello: "world"}

然后,在运行 Node 应用程序的命令提示符中,您应该看到以下内容:

HTTP server started on port 3000
Client connection received
{ my: 'data' }

客户端和服务器都可以使用接收到的 JSON 数据来执行特定任务。一旦我们连接到实时体育数据,我们将了解更多相关信息。

体育数据

现在我们已经掌握了如何在客户端和服务器之间发送和接收数据,可以利用它来提供实时更新。我选择使用运动数据,虽然同样的理论不限于运动。在我开始这个项目之前,我研究了不同的运动数据。我选择的一个是MySportsFeeds,因为他们提供免费的开发者帐户 (我与他们没有任何关系)。为了访问实时数据,我注册了一个帐户,然后做了一笔小额捐款。捐款起价为 1 美元,每 10 分钟更新一次数据。这对于示例很有用。

设置帐户后,您可以继续设置对其api的访问权限。为了帮助解决这个问题,我将使用他们的 NPM 包:npm install mysportsfeeds-node --save

安装包后,可以按如下方式调用 API:

var MySportsFeeds = require("mysportsfeeds-node");

var msf = new MySportsFeeds("1.2", true);
msf.authenticate("********", "*********");

var today = new Date();

msf.getData('nhl', '2017-2018-regular', 'scoreboard', 'json', { 
    fordate: today.getFullYear() + 
		('0' + parseInt(today.getMonth() + 1)).slice(-2) + 
		('0' + today.getDate()).slice(-2),
	force: true
});

在上面的示例中,请务必使用您的用户名和密码替换对身份验证函数的调用。

以下代码执行 API 调用以获取今天的 NHL 记分牌。fordate变量是今天指定的我还设置forcetrue始终返回响应,即使数据没有更改。

使用当前设置,API 调用的结果将写入文本文件。在最后一个例子中,这将被改变;但是,出于演示目的,可以在文本编辑器中查看结果文件以了解响应的内容。结果包含一个记分牌对象。该对象包含一个名为 的数组gameScore该对象存储每个游戏的结果。每个对象都包含一个名为 的子对象game此对象提供有关谁在玩的信息。

在游戏对象之外,还有一些变量可以提供游戏的当前状态。数据会根据游戏状态而变化。例如,当游戏还没有开始时,只有几个变量告诉我们游戏没有进行中并且还没有开始。

当游戏进行时,会提供有关分数、游戏时间和剩余时间的附加数据。当我们在下一节中使用 HTML 来展示游戏时,我们将看到这一点。

实时更新

我们已经掌握了拼图的所有部分,所以现在是时候将拼图拼凑在一起以揭示最终的画面了。目前,MySportsFeeds 对向我们推送数据的支持有限,因此我们必须从他们那里轮询数据。幸运的是,我们知道数据每 10 分钟只更改一次,因此我们不需要通过过于频繁地轮询更改来增加开销。一旦我们从他们那里轮询数据,我们就可以将这些更新从服务器推送到所有连接的客户端。

为了执行轮询,我将使用 JavaScriptsetInterval函数每 10 分钟调用一次 API(在我的例子中)以查找更新。接收到数据后,将向所有连接的客户端发送一个事件。当客户端收到事件时,游戏分数将在 Web 浏览器中使用 JavaScript 更新。

当 Node 应用程序首次启动时,也会调用 MySportsFeeds。此数据将用于在前 10 分钟间隔之前连接的任何客户端。这存储在全局变量中。这个相同的全局变量作为间隔轮询的一部分进行更新。这将确保在轮询后任何新客户端连接时,它们将拥有最新数据。

为了帮助主 index.js 文件中的一些代码清洁,我创建了一个名为 data.js 的新文件。此文件将包含一个导出的函数(在 index.js 文件中可用),该函数执行对 MySportsFeeds API 的先前调用。以下是该文件的完整内容:

var MySportsFeeds = require("mysportsfeeds-node");

var msf = new MySportsFeeds("1.2", true, null);
msf.authenticate("*******", "******");

var today = new Date();

exports.getData = function() {

    	return msf.getData('nhl', '2017-2018-regular', 'scoreboard', 'json', { 
		fordate: today.getFullYear() + 
			('0' + parseInt(today.getMonth() + 1)).slice(-2) + 
			('0' + today.getDate()).slice(-2),
		force: true
		});

};

一个getData函数被导出并返回调用的结果,在本例中是一个将在主应用程序中解析的 Promise。

现在让我们看看 index.js 文件的最终内容:

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var data = require('./data.js');

// Global variable to store the latest NHL results
var latestData;

// Load the NHL data for when client's first connect
// This will be updated every 10 minutes
data.getData().then((result) => { 
    latestData = result;
});

app.get('/', function(req, res){
	res.sendFile(__dirname + '/index.html');
});

http.listen(3000, function(){
	console.log('HTTP server started on port 3000');
});

io.on('connection', function(socket){
	// when clients connect, send the latest data
	socket.emit('data', latestData);
});

// refresh data
setInterval(function() {
	data.getData().then((result) => { 
		// Update latest results for when new client's connect
		latestData = result; 
	
		// send it to all connected clients
		io.emit('data', result);
		
		console.log('Last updated: ' + new Date());
	});
}, 300000);

上面的前七行代码实例化了所需的库和全局latestData变量。最终使用的库列表是:Express、使用 Express 创建的 Http Server、Socket.io 以及刚刚创建的上述 data.js 文件。

处理好必需品后,应用程序latestData会为首次启动服务器时将连接的客户端填充:

// Global variable to store the latest NHL results
var latestData;

// Load the NHL data for when client's first connect
// This will be updated every 10 minutes
data.getData().then((result) => { 
    latestData = result;
});

接下来的几行为网站的根页面 ( http://localhost:3000/ ) 设置了一个路由,并启动 HTTP 服务器以***端口 3000。

接下来,设置 Socket.io 以查找连接。当接收到新连接时,服务器会发出一个名为 data 的事件,其中包含latestData变量的内容。

最后,最后一段代码创建了轮询间隔。当间隔发生时,该latestData变量将使用 API 调用的结果进行更新。然后,此数据向所有客户端发出相同的数据事件。

// refresh data
setInterval(function() {
    data.getData().then((result) => { 
		// Update latest results for when new client's connect
		latestData = result; 
	
		// send it to all connected clients
		io.emit('data', result);
		
		console.log('Last updated: ' + new Date());
	});
}, 300000);

您可能会注意到,当客户端连接并发出事件时,它会使用套接字变量发出事件。这种方法只会将事件发送到该连接的客户端。在区间内,全局io用于发出事件。这会将事件发送给所有客户端。

这样就完成了服务器。让我们在客户端前端工作。在前面的示例中,我创建了一个基本的 index.html 文件,用于设置客户端连接,该连接将记录来自服务器的事件并发送回一个。我将扩展该文件以包含完整的示例。

因为服务器正在向我们发送一个 JSON 对象,所以我将使用jquery并利用一个名为JsRender的 jQuery 扩展。这是一个模板库。它将允许我创建一个带有 HTML 的模板,该模板将用于以易于使用、一致的方式显示每个 NHL 比赛的内容。一会儿,你就会看到这个图书馆的力量。最终代码有 40 多行代码,所以我将把它分解成更小的块,然后在最后一起显示完整的 HTML。

第一部分创建将用于显示游戏数据的模板:

<script id="gameTemplate" type="text/x-jsrender">
<div class="game">
    <div>
		{{:game.awayTeam.City}} {{:game.awayTeam.Name}} at {{:game.homeTeam.City}} {{:game.homeTeam.Name}}
	</div>
	<div>
		{{if isUnplayed == "true" }}
		
		Game starts at {{:game.time}}
		
		{{else isCompleted == "false"}}
		
		<div>Current Score: {{:awayScore}} - {{:homeScore}}</div>
		
		<div>
			{{if currentIntermission}}
				{{:~ordinal_suffix_of(currentIntermission)}} Intermission
			{{else currentPeriod}}
				{{:~ordinal_suffix_of(currentPeriod)}}<br/>
				{{:~time_left(currentPeriodSecondsRemaining)}}
			{{else}}
				1st
			{{/if}}
		</div>
		
		{{else}}
		
		Final Score: {{:awayScore}} - {{:homeScore}}
		
		{{/if}}
	</div>
</div>
</script>

模板是使用脚本标签定义的。它包含模板的 id 和一个特殊的脚本类型,称为text/x-jsrender该模板为每个包含类游戏的游戏定义了一个容器 div,以应用一些基本样式。在这个 div 中,模板开始了。

在下一个 div 中,将显示客队和主队。这是通过将来自 MySportsFeed 数据的游戏对象中的城市和团队名称连接在一起来完成的。

{{:game.awayTeam.City}}是我如何定义一个在渲染模板时将被物理值替换的对象。此语法由 JsRender 库定义。

显示团队后,下一段代码会执行一些条件逻辑。当游戏unPlayed开始时,将输出一个字符串,游戏将在此开始{{:game.time}}

游戏未完成时,显示当前分数:Current Score: {{:awayScore}} - {{:homeScore}}最后,一些棘手的小逻辑来确定曲棍球比赛处于哪个时期或是否处于中场休息状态。

如果在结果中提供了变量currentIntermission,那么我使用我定义的一个名为 的函数ordinal_suffix_of,它将周期数转换为:第 1 次(第 2 次、第 3 次等)间歇。

当它不是中场休息时,我寻找currentPeriod价值。这也使用ordinal_suffix_of  来表示游戏处于第 1(第 2、第 3 等)周期。

在此之下,我定义的另一个函数称为time_left,用于将剩余秒数转换为该期间剩余的分钟数和秒数。例如:10:12。

代码的最后部分显示最终得分,因为我们知道游戏已经完成。

这是一个混合了已完成游戏、进行中游戏和尚未开始的游戏的示例(我不是一个很好的设计师,所以当开发人员制作他们自己的用户界面)。

如何使用Node.js制作实时体育应用程序  第2张

接下来是一段 JavaScript,用于创建套接字、辅助函数ordinal_suffix_oftime_left,以及一个引用创建的 jQuery 模板的变量。

<script>
    var socket = io();
	var tmpl = $.templates("#gameTemplate");
	
	var helpers = {
		ordinal_suffix_of: function(i) {
			var j = i % 10,
			k = i % 100;
			if (j == 1 && k != 11) {
				return i + "st";
			}
			if (j == 2 && k != 12) {
				return i + "nd";
			}
			if (j == 3 && k != 13) {
				return i + "rd";
			}
			return i + "th";
		},
		time_left: function(time) {
			var minutes = Math.floor(time / 60);
			var seconds = time - minutes * 60;
			
			return minutes + ':' + ('0' + seconds).slice(-2);
		}
	};
</script>

最后一段代码是接收套接字事件并渲染模板的代码:

socket.on('data', function (data) {
    console.log(data);
	
	$('#data').html(tmpl.render(data.scoreboard.gameScore, helpers));
});

我有一个带有数据 ID 的占位符 div。模板渲染 ( tmpl.render) 的结果将 HTML 写入此容器。真正巧妙的是 JsRender 库可以接受一个数据数组,在这种情况下data.scoreboard.gameScore,它遍历数组中的每个元素并为每个元素创建一个游戏。

这是最终的 HTML 和 JavaScript:

<!doctype html>
<html>
    <head>
		<title>Socket.IO Example</title>
	</head>
	<body>
		<div id="data">
		
		</div>
	
<script id="gameTemplate" type="text/x-jsrender">
<div class="game">
	<div>
		{{:game.awayTeam.City}} {{:game.awayTeam.Name}} at {{:game.homeTeam.City}} {{:game.homeTeam.Name}}
	</div>
	<div>
		{{if isUnplayed == "true" }}
		
		Game starts at {{:game.time}}
		
		{{else isCompleted == "false"}}
		
		<div>Current Score: {{:awayScore}} - {{:homeScore}}</div>
		
		<div>
			{{if currentIntermission}}
				{{:~ordinal_suffix_of(currentIntermission)}} Intermission
			{{else currentPeriod}}
				{{:~ordinal_suffix_of(currentPeriod)}}<br/>
				{{:~time_left(currentPeriodSecondsRemaining)}}
			{{else}}
				1st
			{{/if}}
		</div>
		
		{{else}}
		
		Final Score: {{:awayScore}} - {{:homeScore}}
		
		{{/if}}
	</div>
</div>
</script>
	
		<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrender/0.9.90/jsrender.min.js"></script>
		<script src="/socket.io/socket.io.js"></script>
		<script>
			var socket = io();
			
			var helpers = {
				ordinal_suffix_of: function(i) {
					var j = i % 10,
					k = i % 100;
					if (j == 1 && k != 11) {
						return i + "st";
					}
					if (j == 2 && k != 12) {
						return i + "nd";
					}
					if (j == 3 && k != 13) {
						return i + "rd";
					}
					return i + "th";
				},
				time_left: function(time) {
					var minutes = Math.floor(time / 60);
					var seconds = time - minutes * 60;
					
					return minutes + ':' + ('0' + seconds).slice(-2);
				}
			};
			
			var tmpl = $.templates("#gameTemplate");
			
			socket.on('data', function (data) {
				console.log(data);
				
				$('#data').html(tmpl.render(data.scoreboard.gameScore, helpers));
			});
		</script>
		
		<style>
		.game {
			border: 1px solid #000;
			float: left;
			margin: 1%;
			padding: 1em;
			width: 25%;
		}
		</style>
	</body>
</html>

启动 Node 应用程序并浏览到http://localhost:3000以亲自查看结果!

每 X 分钟,服务器将向客户端发送一个事件。客户端将使用更新的数据重新绘制游戏元素。因此,当您让网站保持打开状态并定期查看它时,您会看到当前游戏正在进行时游戏数据会刷新。

结论

最终产品使用 Socket.io 创建客户端连接的服务器。服务器获取es 数据并将其发送给客户端。客户端收到数据后,可以无缝更新显示。这减少了服务器上的负载,因为客户端仅在从服务器接收到事件时才执行工作。

套接字不限于一个方向;客户端也可以向服务器发送消息。服务器收到消息后,可以进行一些处理。

聊天应用程序通常会以这种方式工作。服务器会收到来自客户端的消息,然后向所有连接的客户端广播以显示有人发送了新消息。


文章目录
  • 什么是 Socket.io?
  • 示例 Socket.io 应用程序
  • 体育数据
  • 实时更新
  • 结论