工作原理
Socket.IO 服务器 (Node.js) 和 Socket.IO 客户端 (浏览器、Node.js 或 其他编程语言) 之间的双向通道使用 WebSocket 连接 建立(如果可能),并将使用 HTTP 长轮询作为备用。
Socket.IO 代码库分为两个不同的层
- 低级管道:我们称之为 Engine.IO,是 Socket.IO 内部的引擎
- 高级 API:Socket.IO 本身
Engine.IO
Engine.IO 负责建立服务器和客户端之间的低级连接。它处理
可以在 此处 找到 Engine.IO 协议的详细版本。
参考实现的源代码(用 TypeScript 编写)可以在此处找到
- 服务器:https://github.com/socketio/engine.io
- 客户端:https://github.com/socketio/engine.io-client
- 解析器:https://github.com/socketio/engine.io-parser
传输
目前已实现两种传输
HTTP 长轮询
HTTP 长轮询传输(也简称为“轮询”)由连续的 HTTP 请求组成
- 长时间运行的
GET
请求,用于接收来自服务器的数据 - 短时间运行的
POST
请求,用于向服务器发送数据
由于传输的性质,连续的 emit 可能被连接起来并发送在同一个 HTTP 请求中。
WebSocket
WebSocket 传输由 WebSocket 连接 组成,它在服务器和客户端之间提供双向低延迟通信通道。
由于传输的性质,每个 emit 都发送在它自己的 WebSocket 帧中(某些 emit 甚至可能导致两个不同的 WebSocket 帧,更多信息 此处)。
握手
在 Engine.IO 连接开始时,服务器会发送一些信息
{
"sid": "FSDjX-WRwSA4zTZMALqx",
"upgrades": ["websocket"],
"pingInterval": 25000,
"pingTimeout": 20000
}
sid
是会话的 ID,它必须包含在所有后续 HTTP 请求的sid
查询参数中upgrades
数组包含服务器支持的所有“更好”传输的列表pingInterval
和pingTimeout
值用于心跳机制
升级机制
默认情况下,客户端使用 HTTP 长轮询传输建立连接。
但是,为什么呢?
虽然 WebSocket 显然是建立双向通信的最佳方式,但经验表明,由于公司代理、个人防火墙、防病毒软件等原因,并非总是能够建立 WebSocket 连接。
从用户角度来看,WebSocket 连接失败会导致实时应用程序最多等待 10 秒才能开始交换数据。这会明显损害用户体验。
总而言之,Engine.IO 首先关注可靠性和用户体验,其次关注潜在的 UX 改进和更高的服务器性能。
要升级,客户端将
- 确保其输出缓冲区为空
- 将当前传输置于只读模式
- 尝试使用其他传输建立连接
- 如果成功,关闭第一个传输
您可以在浏览器中的网络监视器中查看
- 握手(包含会话 ID — 此处为
zBjrh...AAAK
— 用于后续请求) - 发送数据(HTTP 长轮询)
- 接收数据(HTTP 长轮询)
- 升级(WebSocket)
- 接收数据(HTTP 长轮询,在 4. 中成功建立 WebSocket 连接后关闭)
断开连接检测
当以下情况发生时,Engine.IO 连接被视为已关闭
- 一个 HTTP 请求(GET 或 POST)失败(例如,当服务器关闭时)
- WebSocket 连接关闭(例如,当用户关闭浏览器中的选项卡时)
- 在服务器端或客户端调用
socket.disconnect()
还有一种心跳机制,用于检查服务器和客户端之间的连接是否仍然正常运行
在给定的间隔(握手时发送的 pingInterval
值)内,服务器会发送 PING 数据包,客户端有几秒钟(pingTimeout
值)时间发送 PONG 数据包作为回复。如果服务器没有收到 PONG 数据包作为回复,它将认为连接已关闭。相反,如果客户端在 pingInterval + pingTimeout
内没有收到 PING 数据包,它将认为连接已关闭。
Socket.IO
Socket.IO 在 Engine.IO 连接之上提供了一些额外的功能
可以在 此处 找到 Socket.IO 协议的详细版本。
参考实现的源代码(用 TypeScript 编写)可以在此处找到