Websockets 101
#javascript #websockets

WebSockets实现了由ws(s)://表示的全双工,BI方向,基于TCP的协议,该协议启用了客户端与服务器之间的持久连接。

为什么需要Websocket?

返回WebSocket不是一件事情时,HTTP轮询被用于类似的目的。 HTTP基本上是一个单向协议,其中a client request发送到 server server 接受请求并发送response。服务器不能发送响应,客户端未提出任何请求。简而言之,它只响应它的要求。

这种类型的行为为实时应用程序带来了问题。如果服务器需要将一些信息发送给客户端,但客户端还不知道该信息怎么办?没有请求就无法发起响应。

为了克服这些类型的情况,使用了解决方法,称为polling。客户端假设可能会有一些时间从服务器开始,并以特定时间间隔发送到被称为 poll请求的服务器来检查是否有新事物。如果服务器没有什么新鲜的发送,它只会以空的响应做出响应。这种方法称为简短的轮询

short polling illustration

长轮询是一种与简短轮询相似的方法,只是服务器在客户端请求上没有空的响应响应。取而代之的是,它收到请求,保持连接打开,并且只有在实际需要发送给客户端的新事物时才响应它。服务器发送带有某些数据的响应后,客户端会立即或延迟后发送另一个轮询请求。这就是服务器实际上能够启动传统HTTP协议无法实现的通信。

long polling illustration

上述两种技术都有自己的drawbacks,它导致使用Websocket。

Websocket的工作

WebSockets允许客户端以及服务器启动消息的发送。 Websocket协议涉及两个部分的过程。第一部分涉及a 握手,后一部分涉及 data

的交换。

websocket illustration

当客户端将HTTP 1.1请求发送给服务器时,将会发生初始握手。这仅意味着客户端正在告知服务器此连接不是正常的HTTP连接,而是需要升级到WebSocket连接。

客户的请求看起来像这样:

GET ws://localhost:5000/ HTTP/1.1
Host: localhost:5000
Connection: Upgrade
Upgrade: websocket
Origin: http://localhost:3000
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: VloOROMIOo0curA7dETByw==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

上述请求中的连接类型设置为升级,升级协议设置为 websocket upgrade标头可以在http 1.1中使用 升级到其他协议的请求。

sec-websocket-versionsec-websocket-keysec-websocket-extensions是客户发送的特殊标题,以进一步描述WebSocket连接。

现在发送客户端请求,服务器将验证请求(以确保它是真正的Websocket连接),如果请求支持Websocket连接,请接受该请求,并返回验证响应。

请求验证如下:

  • 服务器需要两个信息 - sec-websocket-keyGUID来验证请求。
  • 然后,它将对此信息执行必要的操作,并得出sec-websocket-accept值,后来将其作为响应标头发送给客户端。此值告诉客户端服务器已接受该连接,现在可以验证值。

sec-websocket-accept标头并不是唯一需要知道服务器是否接受连接所需的东西。还有一个101的状态代码,必须存在,以呼应服务器对连接的接受。除101以外的任何状态代码都告诉Websocket连接尚未完成。

服务器响应看起来像这样:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 30RLwsqJ/mc0ojx6XVmAQTDJSvY=

现在,在此阶段,客户端和服务器都准备接收彼此的消息。

WebSocket实例可以访问各种事件,例如onopenoncloseonmessage等。在发生这些事件时执行一些操作。

要更好地了解消息和各种事件的流动,让我们构建一个实现Websocket的小型应用程序。

构建Websocket应用程序

为了实现Websocket,您可以使用名为koude19的Nodejs库。它提供了建立WebSocket连接的快速简单方法。

WebSocket服务器

npm install ws

首先,您需要服务器来处理WebSocket请求。 ws库提供了一个名为WebSocketServer的接口来创建WebSocket服务器。

// server.mjs
import { WebSocketServer } from "ws"
const wsServer = new WebSocketServer({ port: 5000 })

然后,您可以开始将事件连接到该服务器。

wsServer.on("connection", (req, ws) => {
    //...
})

每当服务器从客户端接收新连接请求时,上述事件将触发。它提供了使用websocket实例(特定客户端)和请求对象的回调函数。

wsServer.on("connection", (req, ws) => {
    const currentClient = req.headers['sec-websocket-key']
    console.log(`\n\n${currentClient} just got connected\nclients connected: ${wsServer.clients.size}\n`)
})

您可以将请求对象用于我用来识别客户端的sec-websocket-key标头值。在生产中,您必须自己生成一个独特的ID。这仅是出于演示目的。使用上述代码,您可以在服务器上记录客户端连接。

接下来,让我们看看如何向除当前客户端以外连接到服务器的所有客户端广播消息。

因此,这是一个接受消息对象并将其广播给所有客户以外的函数。

function broadcast(message) {

    const stringifiedMessage = JSON.stringify(message)

    wsServer.clients.forEach(client => {
        if (client !== ws && client.readyState === WebSocket.OPEN) {
            client.send(stringifiedMessage, (err) => {
                if (err) {
                    console.log(err)
                    return;
                }
            })
        }
    })
}

websocket服务器wsServer,可以访问与其连接的所有客户端。 ws Websocket实例本身描述了客户端。因此,您可以根据当前的ws实例验证客户端并相应地发送消息。

另外,仅当Websocket连接仍打开时,才应发送该消息。如果客户端断开连接,则不会发送消息。

但是,如果我们只想向当前客户发送消息怎么办?为此,您只需要这样做:

ws.send(message, err => console.log)

Websocket的error事件将允许您记录任何问题。

ws.on("error", console.error)

每当客户端向服务器发送消息时,message事件将被触发,如果您愿意,您可以通过该消息将消息广播。

ws.on('message', (data) => {

    const incomingMessage = data.toString('utf8')

    const outgoingMessage = {
        from: currentClient,
        data: incomingMessage,
        type: {
            isConnectionMessage: false
        }
    }

    broadcast(outgoingMessage)
})

您在消息事件中获得的data将是一个缓冲区,因此您需要将其解析为字符串。

您也可以在特定客户端断开连接时向所有连接的客户端播放客户端断开消息。

ws.on("close", () => {
    console.log(`\n\n${currentClient} closed the connection\nRemaining clients ${wsServer.clients.size}\n`)

    broadcast({
        from: currentClient,
        data: `${currentClient} just left the chat`,
        type: {
            isConnectionMessage: false,
            isDisconnectionMessage: true
        }
    })
})

WebSocket客户端

Websocket客户端不过是一个带有某些客户端JavaScript的网页。您必须使用浏览器提供的本机koude30 API来建立WebSocket连接。

const ws = new WebSocket("ws://localhost: 5000")

客户端的ws实例可以访问相同的事件,例如openclosemessage等。

ws.onopen = () => { }
ws.onclose = () => { }

ws.onmessage = () => {
    console.log(message)
}

ws.send(message)

连接到同一WebSocket服务器的多个浏览器实例(或TABS )可以为多个客户端提供目的。

就是这样。您现在可以将消息发送到服务器,并观察它们如何被广播到多个连接的客户端。

用例

  • 实时协作
  • 聊天应用程序
  • 多人游戏
  • 实时提要
  • 实时浏览器重新加载

这是包含整个代码的github repository