WebSocket 101
WebSockets 实现全双工、双向、基于 TCP 的协议,用 表示ws(s)://
,它支持客户端和服务器之间的持久连接。
为什么需要 websockets?
在 WebSocket 尚未出现的时候,HTTP 轮询也被用于类似的目的。HTTP 本质上是一种单向协议,客户端request
向服务器发送一个,服务器接受请求并发送一个。服务器无法在客户端未发出任何请求的情况下发送响应。简而言之,它只会响应客户端发出的请求。response
这种行为会给实时应用带来问题。如果服务器需要向客户端发送一些信息,但客户端还不知道怎么办?如果没有请求,它就无法发起响应。
为了解决这类情况,我们使用了一种称为 的变通方法polling
。客户端假设稍后可能需要服务器提供某些内容,并以特定间隔定期向服务器发送请求(称为轮询请求),以检查是否有新内容。如果服务器没有新内容可发送,则仅返回空响应。这种方法称为短轮询。
长轮询与短轮询类似,不同之处在于服务器不会对客户端的轮询请求返回空响应。相反,服务器会接收请求,保持连接打开,并且仅在有新内容需要发送给客户端时才响应。服务器发送包含数据的响应后,客户端会立即或延迟一段时间后发送另一个轮询请求。这就是服务器能够真正发起通信的方式,而这在传统的 HTTP 协议中是不可能的。
上述两种技术都有各自的缺点,因此需要使用 websockets。
Websockets 的工作原理
WebSocket 允许客户端和服务器发起消息发送。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
上述请求中的连接类型设置为升级,升级协议设置为websocket。此upgrade
标头只能在HTTP 1.1 请求中使用,以升级到其他协议。
、、和是客户端发送的特殊标头sec-websocket-version
,用于进一步描述 websocket 连接。sec-websocket-key
sec-websocket-extensions
现在客户端请求已发送,服务器将验证该请求(以确保它是真正的 websocket 连接),如果它支持 websocket 连接,则接受该请求,并返回验证响应。
请求验证如下:
- 服务器需要两条信息——
sec-websocket-key
并GUID
验证请求。 - 然后,服务器将对这些信息执行必要的操作,并得出一个
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实例可以访问各种事件例如onopen
、、等onclose
,onmessage
并在这些事件发生时执行一些操作。
为了更好地理解消息流和各种事件,让我们构建一个实现 websockets 的小应用程序。
构建 Websocket 应用程序
为了实现 websocket,您可以使用名为 的 nodejs 库ws
。它提供了一种快速简便的建立 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
可以访问所有连接到它的客户端。WebSocketws
实例本身描述了客户端。因此,您可以根据当前ws
实例验证客户端,并相应地发送消息。
此外,只有当 WebSocket 连接仍然处于打开状态时,才应发送该消息。如果客户端断开连接,则不会发送该消息。
但是,如果我们只想向当前客户端发送消息怎么办?为此,你只需要这样做:
ws.send(message, err => console.log)
error
如果出现任何问题,websocket 事件将允许您进行记录。
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
实例可以访问相同的事件open
,如close
、、message
等,因为它本质上是一个 websocket 连接实例。
ws.onopen = () => { }
ws.onclose = () => { }
ws.onmessage = () => {
console.log(message)
}
ws.send(message)
连接到同一个 websocket 服务器的多个浏览器实例(或标签)可以满足多个客户端的需求。
就是这样。现在你可以向服务器发送消息,并观察它们是如何广播到多个连接的客户端的。
用例
- 实时协作
- 聊天应用程序
- 多人游戏
- 实时信息
- 实时浏览器重新加载
这是包含完整代码的github 存储库。
文章来源:https://dev.to/this-is-learning/websockets-101-2mja