Websockets 教程:创建实时 Websocket 服务器

我们用 Javascript 做的很多工作都涉及到从服务器来回发送信息。您可能熟悉API的概念,它以特定格式将数据发送到服务器或网站,以获取特定的响应。

这些被称为REST API。虽然有用,但它们并不擅长持续的数据流。如果您尝试使用 REST API 实时执行某些操作,那么您将度过一段糟糕的时光。幸运的是,如果我们想要与用户建立实时连接,我们有一个替代方案,称为WebSockets

WebSocket 的工作原理#

对于本教程,我们假设您熟悉Node.JS。WebSockets 本质上是在服务器和您的计算机之间建立的持续连接。当你访问一个网站时,它可以向服务器发送一个GET请求,在用户和服务器之间发起一个WebSocket连接。

WebSocket 与 REST API

如果用户离开网站,则连接被切断,因此用户只要继续使用网站,就只能访问 WebSocket。

WebSocket 可以保持打开状态多长时间?

一旦创建了 WebSocket,理论上它可以永远保持打开状态。有几个例外:

  • 服务器宕机– 这将破坏 WebSocket,但我们可以尝试重新连接到它。
  • 停电或互联网连接问题 – 如果用户的互联网停止,连接将中断。
  • 不活动– 如果用户不通过 WebSocket 交互或发送数据,连接不可避免地会超时。

因此,当我们设计我们的 WebSocket 时,我们需要考虑如果用户的连接由于某种原因停止,我们如何重新连接到它们,以免中断用户的体验。

制作一个 WebSocket#

因此,WebSocket 由两部分组成——服务器和用户正在使用的本地机器。对于我们正在做的事情,我们将使用 Node.JS 作为我们的服务器,但其他语言也支持 WebSockets。

当用户访问我们的网站时,我们会加载一个带有一些 Javascript 的文件,其中包含到我们的 WebSocket 的连接字符串。同时,在我们的后端,我们将设置用户将连接到的 WebSocket。如下图所示:

第 1 步:创建我们的服务器#

让我们首先为 WebSocket 连接创建 Node.JS Web 服务器。为此,我们将使用带有一个名为express-ws. 这个额外的包将允许我们以与 expressws相同的方式使用。get

如果您没有安装 Node.JS,您需要先通过此链接进行安装。安装后,创建一个名为server-websocket. 打开终端,然后使用cd进入该文件夹(如果您不知道cd,请在此处阅读我们的文章!)。

进入文件夹后,您需要安装依赖包。通过运行以下每个命令开始安装依赖项:

npm i express
npm i express-ws
npm i path
npm i url

之后,创建一个名为index.js并放入以下代码的文件:

// Import path and url dependencies
import path from 'path'
import { fileURLToPath } from 'url'

// Get the directory and file path
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Import express, expressWs, and http
import express from 'express'
import expressWs from 'express-ws'
import http from 'http'

// Our port
let port = 3000;

// App and server
let app = express();
let server = http.createServer(app).listen(port);    

// Apply expressWs
expressWs(app, server);

app.use(express.static(__dirname + '/views'));

// Get the route / 
app.get('/', (req, res) => {
    res.status(200).send("Welcome to our app");
});

// This lets the server pick up the '/ws' WebSocket route
app.ws('/ws', async function(ws, req) {
    // After which we wait for a message and respond to it
    ws.on('message', async function(msg) {
        // If a message occurs, we'll console log it on the server
        console.log(msg);
        // Start listening for messages
    });
});

最后一个子句 ,app.ws指的是 WebSocket,这就是我们将尝试在前端连接的内容。目前,只要 WebSocket 从前端收到一条消息,它就只记录一条消息。让我们改变它,让它发回一些东西:

// Get the /ws WebSocket route
app.ws('/ws', async function(ws, req) {
    ws.on('message', async function(msg) {
        // What was the message?
        console.log(msg);
        // Send back some data
        ws.send(JSON.stringify({
            "append" : true,
            "returnText" : "I am using WebSockets!"
        }));
    });
});

现在,只要这个 WebSocket 连接接收到数据,它就会发回包含appendand的对象returnText。我们还将控制台记录服务器收到的消息。

然后我们可以在前端操作这个对象,为用户显示或更改视图。

将该文件保存在您的websocket-server文件夹中为index.js. 然后从您的终端,在websocket-server文件夹中,运行以下命令:

node index.js

第 2 步:在前端连接#

现在我们有一个正在运行的 websocket 服务器,但无法连接到它。我们想要实现这样的目标:

  • 一位用户访问我们的网站
  • 我们从 Javascript 文件启动 WebSocket 连接
  • 用户成功连接到 WebSocket,一旦连接,就会向 WebSocket 发送消息。
  • 然后,我们可以将数据发送回用户,因为他们与我们的 WebSocket 服务器建立了实时连接,从而创建了实时数据交换

对于我们的演示,让我们从创建两个文件开始:index.htmllocal.js,这两个文件都是前端文件。接下来,让我们将以下内容放入我们的index.html文件中:

<script src="local.js"></script>
<p>Welcome to WebSockets. Click here to start receiving messages.</p>
<button id="websocket-button">Click me</button>
<div id="websocket-returns"></div>

接下来,我们需要通过local.js文件将用户连接到我们的 WebSocket。我们的local.js文件最终将如下所示:

// @connect
// Connect to the websocket
let socket;
// This will let us create a connection to our Server websocket.
// For this to work, your websocket needs to be running with node index.js
const connect = function() {
    // Return a promise, which will wait for the socket to open
    return new Promise((resolve, reject) => {
        // This calculates the link to the websocket. 
        const socketProtocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:')
        const port = 3000;
        const socketUrl = `${socketProtocol}//${window.location.hostname}:${port}/ws/`
        
        // Define socket
        // If you are running your websocket on localhost, you can change 
        // socketUrl to 'http://localhost:3000', as we are running our websocket
        // on port 3000 from the previous websocket code.
        socket = new WebSocket(socketUrl);

        // This will fire once the socket opens
        socket.onopen = (e) => {
            // Send a little test data, which we can use on the server if we want
            socket.send(JSON.stringify({ "loaded" : true }));
            // Resolve the promise - we are connected
            resolve();
        }

        // This will fire when the server sends the user a message
        socket.onmessage = (data) => {
            console.log(data);
            // Any data from the server can be manipulated here.
            let parsedData = JSON.parse(data.data);
            if(parsedData.append === true) {
                const newEl = document.createElement('p');
                newEl.textContent = parsedData.returnText;
                document.getElementById('websocket-returns').appendChild(newEl);
            }
        }

        // This will fire on error
        socket.onerror = (e) => {
            // Return an error if any occurs
            console.log(e);
            resolve();
            // Try to connect again
            connect();
        }
    });
}

// @isOpen
// check if a websocket is open
const isOpen = function(ws) { 
    return ws.readyState === ws.OPEN 
}

// When the document has loaded
document.addEventListener('DOMContentLoaded', function() {
    // Connect to the websocket
    connect();
    // And add our event listeners
    document.getElementById('websocket-button').addEventListener('click', function(e) {
        if(isOpen(socket)) {
            socket.send(JSON.stringify({
                "data" : "this is our data to send",
                "other" : "this can be in any format"
            }))
        }
    });
});

这可能看起来很多,但让我们分解一下。在我们的连接函数中,我们首先构建我们的 WebSocket URL。这可以简单地写成ws://localhost:3000/,因为我们的 WebSocket 服务器在端口 3000 上运行。上面,它被配置为在您使用HTTP或时自动调整HTTPS

然后我们将一些事件监听器传递给我们新创建的WebSocket. 我们所有的事件监听器以及连接到 WebSocket 服务器的 URL 都位于我们的connect()函数中——其目的是连接到我们的 WebSocket 服务器。

我们的WebSocket事件监听器如下所示:

  • socket.onopen– 如果连接成功并打开,则会触发。
  • socket.onmessage– 每当服务器向我们发送消息时,都会触发。append在我们的示例中,如果用户收到设置为的数据,我们将在用户的 HTML 中附加一个新元素true
  • socket.onerror– 如果连接失败或发生错误,则会触发。

现在我们有了一个connect()可以让我们连接到 Websocket 服务器的函数,我们必须运行它。我们首先等待页面加载,使用DOMContentLoadedconnect()然后我们使用该函数连接到我们的 websocket 。

最后,我们在 HTML 页面上的按钮上附加一个事件监听器,当单击该按钮时,现在将使用该socket.send()函数向我们的 WebSocket 发送一些数据。当服务器接收到这些数据时,它会发回自己的数据,因为服务器 ‘ message‘ 事件将触发。

// When the document has loaded
document.addEventListener('DOMContentLoaded', function() {
    // Connect to the websocket
    connect();
    // And add our event listeners
    document.getElementById('websocket-button').addEventListener('click', function(e) {
        if(isOpen(socket)) {
            socket.send(JSON.stringify({
                "data" : "this is our data to send",
                "other" : "this can be in any format"
            }))
        }
    });
});

由于每当新数据来自 WebSocket 服务器时我们的onmessage事件处理程序就会触发,因此单击该按钮会导致 WebSocket 服务器向我们发送回一条消息 – 从而创建一个新的 HTML元素。WebSocket<p>

结论#

现在我们有一个正常运行的 websocket,它允许您将数据发送到服务器,然后返回给用户。这种双向连接可用于允许用户与服务器本身进行交互,甚至可以根据需要向其他用户发送数据。以下是一些有用的链接,可帮助您了解有关 WebSocket 的更多信息: