在今天的文章中,我將示範如何製作一個 Web 應用程式來顯示 NHL 的現場比賽得分。分數將隨著遊戲的進展自動更新。
這對我來說是一篇非常令人興奮的文章,因為它讓我有機會將我最喜歡的兩個愛好結合在一起:發展和運動。
將用於創建應用程式的技術是:
如果您尚未安裝 Node.js,請立即訪問其下載頁面並進行設置,然後再繼續。
Socket.io 是一種使用 WebSocket 將客戶端連接到伺服器的技術。在此範例中,客戶端是 Web 瀏覽器,伺服器是 Node.js 應用程式。伺服器可以在任何給定時間有多個客戶端連接到它。
一旦建立連接,伺服器就可以向所有客戶端或單一客戶端發送訊息。作為回報,客戶端可以向伺服器發送訊息,從而實現雙向即時通訊。
在 Socket.io 之前,Web 應用程式通常使用 AJAX,客戶端和伺服器都會互相輪詢以查找事件。例如,每 10 秒就會發生一次 AJAX 調用,以查看是否有任何訊息需要處理。
輪詢訊息會為客戶端和伺服器帶來大量開銷,因為當沒有訊息時,它會不斷地尋找訊息。
使用 Socket.io,可以即時接收訊息,無需查找訊息,從而減少了開銷。
在我們使用即時運動資料之前,讓我們先建立一個範例應用程式來示範 Socket.io 的工作原理。
首先,我將建立一個新的 Node.js 應用程式。導航到您想要專案的資料夾,為應用程式建立一個新資料夾,然後建立一個新應用程式:
cd ~/Documents/Nodejs mkdir SocketExample cd SocketExample npm init
我使用了所有預設設定。
因為我們正在製作一個 Web 應用程序,所以我將使用一個名為 Express 的 NPM 套件來簡化設定。在命令提示字元中,如下安裝: npm installexpress
--save
#當然,我們需要安裝 Socket.io 套件: npm install
socket.io --save
#讓我們從建立 Web 伺服器開始。建立一個名為 index.js 的新文件,並將以下程式碼放入其中以使用 Express 建立 Web 伺服器:
const app = require("express")(); const 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,例如https://localhost:3000。路由在網站的根目錄“/”處建立。路由結果回傳一個 HTML 檔案:index.html。
在建立index.html檔案之前,讓我們先透過設定Socket.io來完成伺服器。將以下內容附加到您的 index.js 檔案中以建立 Socket 伺服器:
const 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 type="module"> const socket = io(); </script> </body> </html>
上面的 HTML 載入 Socket.io 客戶端 JavaScript 並初始化與伺服器的連線。若要查看範例,請啟動 Node 應用程式: node index.js
然後,在瀏覽器中導航至 http://localhost:3000。頁面上不會出現任何內容;但是,如果您查看執行 Node 應用程式的控制台,則會記錄兩個訊息:
現在我們已經成功建立了套接字連接,讓我們使用它。讓我們先從伺服器向客戶端發送訊息。然後,當客戶端收到訊息時,它可以將回應發送回伺服器。
讓我們來看看縮寫的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,如下所示:
const 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:
“世界”}
然后,在运行 Node 应用程序的命令提示符中,您应该看到以下内容:
HTTP server started on port 3000 Client connection received { my: 'data' }
客户端和服务器都可以使用接收到的 JSON 数据来执行特定任务。一旦我们连接到实时体育数据,我们将了解更多信息。
现在我们已经掌握了如何向客户端和服务器发送和接收数据,可以利用它来提供实时更新。我选择使用体育数据,尽管同样的理论并不限于体育。在开始这个项目之前,我研究了不同的运动数据。我选择的是 MySportsFeeds,因为他们提供免费的开发者帐户(我与他们没有任何关系)。为了访问实时数据,我注册了一个帐户,然后做了一笔小额捐款。捐款起价为 1 美元,数据每 10 分钟更新一次。这对于示例来说是有好处的。
您的帐户设置完毕后,您就可以继续设置对其 API 的访问权限。为了帮助实现这一点,我将使用他们的 NPM 包: npm install
mysportsfeeds-node --save
安装包后,可以按如下方式进行 API 调用:
const MySportsFeeds = require("mysportsfeeds-node"); const msf = new MySportsFeeds("1.2", true); msf.authenticate("********", "*********"); const 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
变量是今天指定的。我还将 force
设置为 true
,以便始终返回响应,即使数据没有更改。
使用当前设置,API 调用的结果将写入文本文件。在最后一个例子中,这将被改变;但是,出于演示目的,可以在文本编辑器中查看结果文件以了解响应的内容。结果包含一个记分板对象。该对象包含一个名为 gameScore
的数组。该对象存储每场比赛的结果。每个对象都包含一个名为 game
的子对象。该对象提供有关谁正在玩的信息。
在游戏对象之外,还有一些变量提供游戏的当前状态。数据根据游戏状态而变化。例如,当游戏还没有开始时,只有几个变量告诉我们游戏没有进行并且还没有开始。
当游戏进行时,会提供有关得分、游戏进行的时间段以及剩余时间的附加数据。当我们进入下一节中显示游戏的 HTML 时,我们将看到这一点。
我们已经掌握了拼图的所有碎片,所以现在是时候将拼图拼凑起来以揭示最终图片了。目前,MySportsFeeds 对向我们推送数据的支持有限,因此我们必须从他们那里轮询数据。幸运的是,我们知道数据每 10 分钟只更改一次,因此我们不需要通过过于频繁地轮询更改来增加开销。一旦我们轮询它们的数据,我们就可以将这些更新从服务器推送到所有连接的客户端。
为了执行轮询,我将使用 JavaScript setInterval
函数每 10 分钟调用一次 API(在我的例子中)以查找更新。收到数据后,会将一个事件发送到所有连接的客户端。当客户端收到事件时,游戏分数将在网络浏览器中使用 JavaScript 进行更新。
当 Node 应用程序首次启动时,MySportsFeeds 也会被调用。此数据将用于在第一个 10 分钟间隔之前连接的任何客户端。这存储在全局变量中。这个相同的全局变量作为间隔轮询的一部分进行更新。这将确保当任何新客户端在轮询后连接时,他们将拥有最新的数据。
为了帮助主 index.js 文件中的一些代码整洁,我创建了一个名为 data.js 的新文件。该文件将包含一个导出的函数(可在 index.js 文件中找到),该函数执行先前对 MySportsFeeds API 的调用。以下是该文件的完整内容:
const MySportsFeeds = require("mysportsfeeds-node"); const msf = new MySportsFeeds("1.2", true, null); msf.authenticate("*******", "******"); const 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文件的最终内容:
const app = require("express")(); const http = require("http").Server(app); const io = require("socket.io")(http); const data = require("./data.js"); // Global variable to store the latest NHL results let 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、HTTP、Socket.io 以及刚刚创建的上述 data.js 文件。
完成必要的处理后,应用程序会为服务器首次启动时将连接的客户端填充 latestData
:
// Global variable to store the latest NHL results const 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 对象,所以我将使用 Preact,它就像 React 的优化版本(如果您不熟悉 React,那也没关系)。此外,我将使用 HTM。 HTM 将允许我使用像 React 的 JSX 这样的语法,而无需构建工具。此外,它还包括与 Preact 的集成。
首先,我需要创建一个 id 为 games
的 div
<div id="games"></div>
然后,我将创建模板。以下是模板的完整脚本(您需要将其放入主要 HTML 脚本中):
import { html, render } from "https://esm.sh/htm/preact"; import { signal } from "https://esm.sh/@preact/signals"; const games = signal([]); const socket = io(); socket.on("data", function (data) { games.value = data; }); function ordinalSuffix(input) { const tenRemainder = input % 10, hundredRemainer = input % 100; if (tenRemainder == 1 && hundredRemainer != 11) { return input + "st"; } if (tenRemainder == 2 && hundredRemainer != 12) { return input + "nd"; } if (tenRemainder == 3 && hundredRemainer != 13) { return input + "rd"; } return input + "th"; } function timeLeft(time) { const minutes = Math.floor(time / 60); const seconds = time - minutes * 60; return minutes + ":" + ("0" + seconds).slice(-2); } function stats() { return html`${games.value.forEach( (game) => html`<div class="game"> <div> ${game.game.awayTeam.City} ${game.game.awayTeam.Name} at at ${game.game.homeTeam.City} ${game.game.homeTeam.Name} </div> <div> ${(() => { if (game.isUnplayed) { return `Game Starts at ${game.game.time}`; } else if (game.isCompleted === "false") { return html`<div> Current Score: ${game.awayScore} - ${game.homeScore} </div> <div> ${(() => { if (game.currentIntermission) { return `${ordinalPrefix( game.currentIntermission )} Intermission`; } else if (game.currentPeriod) { return html`${ordinalPrefix( game.currentPeriod )}<br />${timeLeft( game.currentPeriodSecondsRemaining )}`; } else { return `1st`; } })()} </div>`; } else { return `Final Score: ${game.awayScore} - ${game.homeScore}`; } })()} </div> </div>` )}`; } render(stats, document.getElementById("games"));
这已经很多了!让我们一步步来看看。首先,我们导入 Preact、HTM 和称为 Preact Signals 的东西。我们稍后会详细讨论这一点。
接下来,我们建立 WebSocket 连接。此代码与我们之前的代码相同,除了事件名称和我们对数据执行的操作不同之外。您可能会注意到我们分配数据的对象是一个信号。这是在 Preact 中管理状态的快速方法。您可以在 Preact Signals 页面上阅读更多相关信息。
接下来,我们有一些辅助函数,稍后我们将在实际模板中使用它们。之后,我们就有了模板组件。首先,我们迭代所有游戏并返回每个游戏的标记。
标记的第一部分显示团队。然后,我们进入游戏数据的主要部分。
在下一节中,我们首先检查游戏是否已经开始。如果没有,我们将显示比赛何时开始。如果已经开始,我们会显示当前分数,以及当前时段和剩余时间。这是使用辅助函数的地方。
最后,如果比赛结束,我们只显示最终得分。脚本的最后一行只是在我们之前创建的 div 中渲染模板。
下面是混合了已完成的游戏、正在进行的游戏和尚未开始的游戏时的情况的示例。我不是一个很好的设计师,所以当开发人员制作自己的用户界面时,它看起来就像你所期望的那样。如果需要,您可以创建自己的 CSS 样式。
这是 HTML 和 JavaScript 的结合体。
Socket.IO Example <div id="games"></div>
启动 Node 应用程序并浏览到 http://localhost:3000 亲自查看结果!
每隔 X 分鐘,伺服器就會傳送一個事件到客戶端。客戶端將使用更新的資料重新繪製遊戲元素。因此,當您保持網站打開並定期查看它時,您會看到遊戲正在進行時遊戲資料刷新。
最終產品使用 Socket.io 建立客戶端連接的伺服器。伺服器獲取資料並將其發送給客戶端。當客戶端收到資料時,可以無縫更新顯示。這減少了伺服器上的負載,因為客戶端僅在從伺服器接收到事件時才執行工作。
套接字不限於一個方向;客戶端也可以向伺服器發送訊息。當伺服器收到訊息後,可以進行一些處理。
聊天應用程式通常會以這種方式運作。伺服器將從客戶端接收訊息,然後廣播到所有連接的客戶端以顯示有人發送了新訊息。
希望您喜歡這篇文章,因為我為我最喜歡的運動之一創建了這個即時運動應用程式!
這篇文章已根據 Jacob Jackson 的貢獻進行了更新。 Jacob 是 Web 開發人員、技術作家、自由工作者和開源貢獻者。
以上是使用 Node.js 創建即時體育應用程序的詳細內容。更多資訊請關注PHP中文網其他相關文章!