原文地址:https://dev.to/this-is-learning/websockets-101-2mja
作者:Murtuzaali Surti
WebSockets 实现了一个全双工、双向、基于 TCP 的协议,表示为 ws(s)://
,它使客户端和服务器之间建立了一个持久连接。
为什么需要 WebSockets?
在 WebSockets 未出现之前,HTTP 轮询(polling)被用于类似的目的。HTTP 基本上是一个单向协议,其中 客户端 发送一个 请求
给 服务器,服务器 接受请求并发送一个 响应
。服务器不能发送客户端未请求的响应。简单来说,它只响应被请求的内容。
这种行为对于实时应用程序来说是一个问题。如果服务器需要向客户端发送一些信息,但客户端还不知道这些信息,该怎么办?它不能在没有请求的情况下启动响应。
为了克服这些类型的情况,使用了一种解决方法,称为 轮询(polling)
。客户端假设可能会在稍后的时间从服务器中获取一些内容,并在特定的时间间隔内发送周期性请求到服务器,称为 轮询请求,以检查是否有新内容。如果服务器没有新内容要发送,它就会用空响应进行响应。这种方法称为 短轮询。
长轮询 是一种类似于 短轮询 的方法,除了服务器不会在客户端的轮询请求上发送空响应。相反,它接收请求,保持连接打开,并仅在实际需要向客户端发送新内容时才对其进行响应。在服务器发送新内容后,它会关闭连接。
当服务器用一些数据响应时,客户端会立即或延迟发送另一个轮询请求。这就是服务器实际上能够“发起”通信的方式,这在传统的 HTTP 协议中是不可能的。
上述两种技术都有其缺点,这导致了 Websockets 的使用。
Websockets 的工作原理
Websockets 允许客户端和服务器都发起消息的发送。Websocket 协议涉及两个部分的过程。第一部分涉及握手,后一部分涉及数据交换。
当客户端发送一个 HTTP 1.1 请求到服务器时,初始握手就发生了,其中一个upgrade
头设置为websocket
。这意味着客户端通知服务器,这个连接不是普通的 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
上述请求中的连接类型设置为upgrade,升级协议设置为 websocket。upgrade
头只能在 HTTP 1.1 请求中用于升级到 不同的协议。
sec-websocket-version
、sec-websocket-key
和sec-websocket-extensions
是客户端发送的特殊头,用于进一步描述 websocket 连接。
现在客户端请求已发送,服务器将验证请求(以确保它是真正的 websocket 连接),如果支持 websocket 连接,则接受请求并返回验证响应。
请求验证如下:
- 服务器需要两个信息——
sec-websocket-key
和GUID
来验证请求。 - 然后它将对这些信息执行必要的操作,并派生一个
sec-websocket-accept
值,稍后作为响应头发送给客户端。该值告诉客户端服务器已接受连接,现在可以验证该值。
sec-websocket-accept
头不是唯一需要知道服务器是否接受连接的内容。还有一个状态码101
,必须存在以回显服务器接受连接。除101
之外的任何状态码都表示 websocket 连接不完整。
服务器响应如下:
HTTP/1.1 101 Switching ProtocolsUpgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 30RLwsqJ/mc0ojx6XVmAQTDJSvY=
现在,在这个阶段,客户端和服务器都准备好互相接收消息了。
websocket 实例可以访问各种事件,如onopen
、onclose
、onmessage
等,在这些事件发生时执行一些操作。
为了更好地理解消息流和各种事件,让我们构建一个实现 websocket 的小应用程序。
构建 WebSocket 应用程序
为了实现 WebSocket,你可以使用一个名为 ws
的 nodejs 库。它提供了一种快速简单的方式来建立 WebSocket 连接。
文章示例在 GitHub 存储库。
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} 刚刚连接\n已连接的客户端数:${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 的网页。你必须使用浏览器提供的原生 WebSocket
API 来建立 WebSocket 连接。
const ws = new WebSocket("ws://localhost: 5000")
客户端的 ws
实例可以访问与 WebSocket 连接实例相同的事件,如 open
、close
、message
等。
ws.onopen = () => { }
ws.onclose = () => { }
ws.onmessage = () => {
console.log(message)
}
ws.send(message)
连接到同一 WebSocket 服务器的多个浏览器实例(或选项卡)可以用作多个客户端的目的,你现在可以向服务器发送消息并观察它们如何广播到多个连接的客户端。
在 Apifox 中调试 WebSocket
为了更方便的进行调试,我们可以在 Apifox 中连接 WebSocket 服务,详情如下:
1、在本地开启(例如:node server.mjs
)一个 WebSocket 服务
2、打开 Apifox,新建 WebSocket 接口
3、填写接口路径,点击 “连接” 按钮,即可建立通信。连接成功之后,Apifox 有成功的提示。