ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Socket.IO 协议 v3 详解:包类型、编码格式与交换流程(含 socket.io-parser 源码印证)

2026/9/4 23:39:15 拓冰建站 浏览量
Socket.IO 协议 v3 详解:包类型、编码格式与交换流程(含 socket.io-parser 源码印证) Socket.IO 协议 v3 详解包类型、编码格式与交换流程含 socket.io-parser 源码印证【免费下载链接】socket.ioBidirectional and low-latency communication for every platform项目地址: https://gitcode.com/gh_mirrors/so/socket.io本文基于 Socket.IO 仓库中 Socket.IO 协议 v3 规范 撰写。读完本文你将掌握 Socket.IO 协议第 3 版revision 3对应socket.io1.0.0...1.0.2早期版本的完整包格式、六种包类型的语义、线上编码wire format规则、连接/确认/断开的交换协议并能对照仓库中的 socket.io-parser 参考实现理解每一段编码是如何被编解码的。协议定位Socket.IO 协议建立在 Engine.IO 协议之上Socket.IO 协议文档开篇即明确了协议的分层设计底层由 Engine.IO 协议本文对应的 Engine.IO 第 3 版负责 WebSocket 与 HTTP 长轮询long-polling的低层管道、心跳与升级Socket.IO 协议则在其上再封装一层提供两个核心能力多路复用Multiplexing即 Socket.IO 中的「命名空间Namespace」概念——同一条底层连接上可以并发接入多个逻辑连接包的确认机制Acknowledgement发送方可以为一个事件请求接收方回调确认。原文档给出的 JavaScript API 示例// server-side const nsp io.of(/admin); nsp.on(connect, socket {}); // client-side const socket1 io(); // default namespace const socket2 io(/admin); socket2.on(connect, () {});// on one side socket.emit(hello, 1, () { console.log(received); }); // on the other side socket.on(hello, (a, cb) { cb(); });这些高层 API 落到线上就是本文后面详述的CONNECT、EVENT、ACK等包。当前仓库的参考实现即 socket.io-parser编解码、socket.io-client 与 socket.io 服务端。包格式Packet Format一个 Socket.IO v3 协议包包含以下字段type类型整数取值见下方包类型表nsp命名空间字符串data可选payload字符串或数组id可选确认 IDacknowledgment id整数。包类型一览v3 版本共 6 种类型ID用途CONNECT0客户端请求接入某命名空间服务器接受连接时也发送DISCONNECT1某一方断开与某命名空间的连接EVENT2传输不含二进制的普通数据ACK3响应带确认 ID 的 EVENT 或 BINARY_EVENTERROR4服务器拒绝某命名空间的连接请求BINARY_EVENT5传输含二进制的普通数据注意与协议 v4/v5 的区别v4 新增了BINARY_ACK类型 6v5 将ERROR更名为CONNECT_ERROR且CONNECT包可以携带 payload。完整的版本演进对照可参考同目录下的 v4 规范与 v5 规范。0 - CONNECT发送方有两种客户端请求接入某命名空间时发送服务器接受该命名空间的连接时回复。它不携带 payload也不携带确认 ID。示例{ type: 0, nsp: /admin }客户端还可以在命名空间字段中附带额外信息典型用途是认证例如{ type: 0, nsp: /admin?token1234uidabcd }即把认证参数塞在 nsp 字段的查询串里——这正是 v3 阶段的认证方式到了 v5 协议这类 payload 被正式移入包的data字段。1 - DISCONNECT当某一方希望断开与某命名空间的连接时使用无 payload、无确认 ID{ type: 1, nsp: /admin }2 - EVENT用于传输不含二进制的数据携带 payload确认 ID 可选{ type: 2, nsp: /, data: [hello, 1] }带确认 ID 时{ type: 2, nsp: /admin, data: [project:delete, 123], id: 456 }3 - ACK当某一方收到了带确认 ID 的EVENT或BINARY_EVENT后用它回应。ACK 包包含从上一包收到的确认 IDpayload 可选且不含二进制{ type: 3, nsp: /admin, data: [], id: 456 }4 - ERROR当服务器拒绝某命名空间的连接时发送payload 可指示拒绝原因{ type: 4, nsp: /admin, data: Not authorized }5 - BINARY_EVENT用于传输包含二进制的数据携带 payload确认 ID 可选{ type: 5, nsp: /, data: [hello, Buffer 01 02 03] }带确认 ID 时{ type: 5, nsp: /admin, data: [project:delete, Buffer 01 02 03], id: 456 }包编码Packet Encoding这一节描述的是随 Socket.IO 服务端与客户端内置的默认解析器的编码规则参考实现即本仓库的 packages/socket.io-parser。文档还指出 JS 实现支持自定义解析器如 socket.io-json-parser、socket.io-msgpack-parser供不同权衡的场景选择。编码格式packet type[# of binary attachments-][namespace,][acknowledgment id][JSON-stringified payload without binary] binary attachments extracted即线上字符串由以下部分按序拼接包类型数字 →若含二进制附件附件数量加-→若非默认命名空间命名空间加,→ 确认 ID → 去除二进制后的 JSON payload二进制附件则从字符串中剥离作为独立数据块跟在后面传输。注意命名空间只有在不同于默认命名空间/时才会被写入编码字符串。对照当前仓库源码 encodeAsString 可以逐段印证该格式// first is type let str obj.type; // attachments if we have them if (obj.type PacketType.BINARY_EVENT || obj.type PacketType.BINARY_ACK) { str obj.attachments -; } // if we have a namespace other than / // we append it followed by a comma , if (obj.nsp / ! obj.nsp) { str obj.nsp ,; } // immediately followed by the id if (null ! obj.id) { str obj.id; } // json data if (null ! obj.data) { str JSON.stringify(obj.data, this.replacer); }反向的解析逻辑在 decodeString先取首字符为包类型再依次识别附件数-分隔、命名空间以/开头、逗号结束、确认 ID纯数字串和 JSON payload。另一个重要的线上细节每个 Socket.IO 包都会被封装进一个 Engine.IOmessage包发送因此编码结果在网络上会被加上前缀4出现在 HTTP 长轮询的 request/response body 中或 WebSocket 帧里。编码示例完整继承自 v3 规范包编码结果CONNECT默认命名空间{ type: 0, nsp: / }0CONNECT/admin命名空间0/adminDISCONNECT/admin命名空间1/adminEVENT{ type: 2, nsp: /, data: [hello, 1] }2[hello,1]EVENT带确认 ID{ type: 2, nsp: /admin, data: [project:delete, 123], id: 456 }2/admin,456[project:delete,123]ACK{ type: 3, nsp: /admin, data: [], id: 456 }3/admin,456[]ERROR{ type: 4, nsp: /admin, data: Not authorized }4/admin,Not authorizedBINARY_EVENT{ type: 5, nsp: /, data: [hello, Buffer 01 02 03] }51-[hello,{_placeholder:true,num:0}]Buffer 01 02 03BINARY_EVENT带确认 ID/adminid: 45651-/admin,456[project:delete,{_placeholder:true,num:0}]Buffer 01 02 03其中51-的含义是「包类型 5 1 个二进制附件」payload 中的{_placeholder:true,num:0}是第 0 个二进制附件的占位符。占位符机制在源码中的实现值得展开编码侧deconstructPacket 递归遍历 payload将每个Buffer/ArrayBuffer/Blob等二进制对象替换为{ _placeholder: true, num: 序号 }同时把真实二进制收集进buffers数组并把附件数量写入pack.attachments。若对象实现了toJSON会先序列化再递归。解码侧reconstructPacket 依据占位符的num字段把buffers中的二进制数据按序还原回 payload 对应位置若num越界则抛出illegal attachments错误。而 BinaryReconstructor 负责跨帧收集二进制数据——只有当收到的缓冲区数量等于包中声明的attachments数时才输出最终还原后的包。也就是说一个含二进制的 BINARY_EVENT 在线上实际是「一段字符串 若干二进制块」的组合解码端需要状态机式的重组这也是attachments计数和占位符序号必须严格一致的原因。与 Engine.IO 编码的关系由于 Socket.IO 包外层套着 Engine.IO 包HTTP 长轮询场景下还会再经过 Engine.IO 的 payload 编码。以 v3 时期的 Engine.IO 为例参见 Engine.IO v3 规范不支持 XHR2 时字符串 payload 格式为length1:packet1[length2:packet2[...]]其中 length 是字符数而非字节数。因此服务端收到2[hello,1]这样的 Socket.IO EVENT 编码后完整线上形态大致是4:42[hello,1]外层4表示 Engine.IO message 包前缀42中的4即该前缀。仓库中 docs/socket.io-protocol/v3.md 的交换示例即按此展开。交换协议Exchange Protocol连接默认命名空间只要底层连接建立服务器总是先向客户端发送一个默认命名空间/的CONNECT包Server { type: CONNECT, nsp: / }也就是说即使客户端请求的是非默认命名空间它也会先收到默认命名空间的CONNECT包。客户端无需响应。这是 v3/v4 时代的显著特征——默认命名空间的连接是隐式建立的到了 v5 协议这一隐式行为被移除客户端必须显式发送CONNECT参见 v5 规范的 History 章节。连接非默认命名空间Client { type: CONNECT, nsp: /admin } Server { type: CONNECT, nsp: /admin } (if the connection is successful) or Server { type: ERROR, nsp: /admin, data: Not authorized }断开非默认命名空间Client { type: DISCONNECT, nsp: /admin }服务器向客户端发起同理。对端不需要响应。确认AcknowledgementClient { type: EVENT, nsp: /admin, data: [hello], id: 456 } Server { type: ACK, nsp: /admin, data: [], id: 456 }双向同理服务器发的 EVENT 带 ID客户端则以 ACK 回应。ACK 包中的id必须与所确认的包一致。在参考实现中Decoder.add 会对解码出的 BINARY_EVENT/BINARY_ACK 包先转换为 EVENT/ACK 并挂起重组直到所有二进制附件收齐才通过decoded事件对外发出——确认回包同理受此机制保护。历史演进v3 在协议谱系中的位置v3 规范文档末尾的 History 章节给出了完整的版本脉络理解它对判断「哪些行为属于 v3、哪些是后来才有的」很关键v3 与 v2 的差异移除了使用 msgpack 编码含二进制对象包的做法。此前v2的二进制包用 msgpack 序列化v3 起改为本文所述的「JSON 占位符 独立二进制附件」方案减少了对 msgpack 库的依赖编码更透明。v2 与 v1 的差异新增了BINARY_EVENT包类型这是 Socket.IO 1.0 开发期间为支持二进制对象而引入的。初版v1是 Engine.IO 协议WebSocket / 长轮询、心跳等低层管道与 Socket.IO 协议分层的产物从未随任何 Socket.IO 正式版本发布但为后续迭代奠定了基础。v3 之后的两个版本也值得对照v4socket.io1.0.3...latest即此后长期使用的版本新增BINARY_ACK包类型类型 6。在此之前 ACK 包始终被当作「可能含二进制」来处理需要递归搜索二进制对象可能拖累性能v4 通过独立包类型消除了这一递归。另注意 v4 的编码中命名空间后固定带逗号0/admin,而 v3 示例中为0/admin两者格式已有差异。v5当前版本对应 Socket.IO v3 及以上socket.io3.0.0于 2020 年 11 月发布移除默认命名空间的隐式连接、ERROR更名为CONNECT_ERROR、CONNECT可携带 payload认证数据与sid、CONNECT_ERROR的 payload 由字符串变为对象。当前仓库 socket.io-parser 中的protocol 5与PacketType枚举含CONNECT_ERROR、BINARY_ACK即 v5 的体现。因此本文的 v3 规范应视为历史参考文档它精确对应socket.io1.0.0...1.0.2这一早期窗口若你在维护 2014 年前后的 Socket.IO v1 早期版本并与非 JS 生态的自研实现对齐线上抓包本文的包类型编号、编码格式与交换顺序即可作为权威依据。小结Socket.IO 协议 v3 以极简的「类型号 命名空间 确认 ID JSON payload」字符串格式在 Engine.IO 的message包之上实现了命名空间多路复用与事件确认两大特性其二进制传输采用占位符加独立附件、附件计数校验的机制。规范中定义的每一种包与每条编码规则都能在当前仓库的 packages/socket.io-parser/lib/index.ts 与 packages/socket.io-parser/lib/binary.ts 中找到一一对应的编解码实现这也是该规范作为「参考实现reference implementation」契约价值的体现。【免费下载链接】socket.ioBidirectional and low-latency communication for every platform项目地址: https://gitcode.com/gh_mirrors/so/socket.io创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考