Skip to main content
Version: 4.x

Socket.IO 协议

本文档描述了 Socket.IO 协议的第五版。

¥This document describes the 5th version of the Socket.IO protocol.

该文档的来源可以找到 此处

¥The source of this document can be found here.

表中的内容

¥Table of content

介绍

¥Introduction

Socket.IO 协议支持客户端和服务器之间的 full-duplex 和低开销通信。

¥The Socket.IO protocol enables full-duplex and low-overhead communication between a client and a server.

它构建在 Engine.IO 协议 之上,使用 WebSocket 和 HTTP 长轮询处理底层管道。

¥It is built on top of the Engine.IO protocol, which handles the low-level plumbing with WebSocket and HTTP long-polling.

Socket.IO 协议增加了以下功能:

¥The Socket.IO protocol adds the following features:

  • 多路复用(在 Socket.IO 术语中称为 "namespace"

    ¥multiplexing (referred as "namespace" in the Socket.IO jargon)

JavaScript API 的示例:

¥Example with the JavaScript API:

服务器

¥Server

// declare the namespace
const namespace = io.of("/admin");
// handle the connection to the namespace
namespace.on("connection", (socket) => {
// ...
});

客户端

¥Client

// reach the main namespace
const socket1 = io();
// reach the "/admin" namespace (with the same underlying WebSocket connection)
const socket2 = io("/admin");
// handle the connection to the namespace
socket2.on("connect", () => {
// ...
});
  • 数据包确认

    ¥acknowledgement of packets

JavaScript API 的示例:

¥Example with the JavaScript API:

// on one side
socket.emit("hello", "foo", (arg) => {
console.log("received", arg);
});

// on the other side
socket.on("hello", (arg, ack) => {
ack("bar");
});

参考实现写在 TypeScript 中:

¥The reference implementation is written in TypeScript:

交换协议

¥Exchange protocol

Socket.IO 数据包包含以下字段:

¥A Socket.IO packet contains the following fields:

  • 数据包类型(整数)

    ¥a packet type (integer)

  • 命名空间(字符串)

    ¥a namespace (string)

  • 可选的,有效负载(对象|数组)

    ¥optionally, a payload (Object | Array)

  • 可选,确认 ID(整数)

    ¥optionally, an acknowledgment id (integer)

以下是可用数据包类型的列表:

¥Here is the list of available packet types:

类型ID用法
连接0到命名空间的连接 期间使用。
断开1与命名空间断开连接 时使用。
事件2习惯了 发送数据 到另一边。
确认3用于 acknowledge 事件。
连接错误4到命名空间的连接 期间使用。
BINARY_EVENT5习惯了 发送二进制数据 到另一边。
二进制确认6用于 acknowledge 事件(响应包括二进制数据)。

连接到命名空间

¥Connection to a namespace

在 Socket.IO 会话开始时,客户端必须发送 CONNECT 数据包:

¥At the beginning of a Socket.IO session, the client MUST send a CONNECT packet:

服务器必须响应:

¥The server MUST respond with either:

  • 如果连接成功,则发送 CONNECT 数据包,负载中包含会话 ID

    ¥a CONNECT packet if the connection is successful, with the session ID in the payload

  • CONNECT_ERROR 数据包(如果不允许连接)

    ¥or a CONNECT_ERROR packet if the connection is not allowed

CLIENT                                                      SERVER

│ ───────────────────────────────────────────────────────► │
│ { type: CONNECT, namespace: "/" } │
│ ◄─────────────────────────────────────────────────────── │
│ { type: CONNECT, namespace: "/", data: { sid: "..." } } │

如果服务器没有首先收到 CONNECT 数据包,则它必须立即关闭连接。

¥If the server does not receive a CONNECT packet first, then it MUST close the connection immediately.

客户端可以使用相同的底层 WebSocket 连接同时连接到多个命名空间。

¥A client MAY be connected to multiple namespaces at the same time, with the same underlying WebSocket connection.

示例:

¥Examples:

  • 与主命名空间(名为 "/"

    ¥with the main namespace (named "/")

Client > { type: CONNECT, namespace: "/" }
Server > { type: CONNECT, namespace: "/", data: { sid: "wZX3oN0bSVIhsaknAAAI" } }
  • 具有自定义命名空间

    ¥with a custom namespace

Client > { type: CONNECT, namespace: "/admin" }
Server > { type: CONNECT, namespace: "/admin", data: { sid: "oSO0OpakMV_3jnilAAAA" } }
  • 具有额外的有效负载

    ¥with an additional payload

Client > { type: CONNECT, namespace: "/admin", data: { "token": "123" } }
Server > { type: CONNECT, namespace: "/admin", data: { sid: "iLnRaVGHY4B75TeVAAAB" } }
  • 如果连接被拒绝

    ¥in case the connection is refused

Client > { type: CONNECT, namespace: "/" }
Server > { type: CONNECT_ERROR, namespace: "/", data: { message: "Not authorized" } }

发送和接收数据

¥Sending and receiving data

一旦 到命名空间的连接 建立,客户端和服务器就可以开始交换数据:

¥Once the connection to a namespace is established, the client and the server can begin exchanging data:

CLIENT                                                      SERVER

│ ───────────────────────────────────────────────────────► │
│ { type: EVENT, namespace: "/", data: ["foo"] } │
│ │
│ ◄─────────────────────────────────────────────────────── │
│ { type: EVENT, namespace: "/", data: ["bar"] } │

有效负载是强制性的,并且必须是非空数组。如果情况并非如此,则接收方必须关闭连接。

¥The payload is mandatory and MUST be a non-empty array. If that's not the case, then the receiver MUST close the connection.

示例:

¥Examples:

  • 与主命名空间

    ¥with the main namespace

Client > { type: EVENT, namespace: "/", data: ["foo"] }
  • 具有自定义命名空间

    ¥with a custom namespace

Server > { type: EVENT, namespace: "/admin", data: ["bar"] }
  • 与二进制数据

    ¥with binary data

Client > { type: BINARY_EVENT, namespace: "/", data: ["baz", <Buffer <01 02 03 04>> ] }

回执

¥Acknowledgement

发送方可以包含事件 ID 以请求接收方确认:

¥The sender MAY include an event ID in order to request an acknowledgement from the receiver:

CLIENT                                                      SERVER

│ ───────────────────────────────────────────────────────► │
│ { type: EVENT, namespace: "/", data: ["foo"], id: 12 } │
│ ◄─────────────────────────────────────────────────────── │
│ { type: ACK, namespace: "/", data: ["bar"], id: 12 } │

接收方必须使用具有相同事件 ID 的 ACK 数据包进行响应。

¥The receiver MUST respond with an ACK packet with the same event ID.

有效负载是强制性的,并且必须是一个数组(可能为空)。

¥The payload is mandatory and MUST be an array (possibly empty).

示例:

¥Examples:

  • 与主命名空间

    ¥with the main namespace

Client > { type: EVENT, namespace: "/", data: ["foo"], id: 12 }
Server > { type: ACK, namespace: "/", data: [], id: 12 }
  • 具有自定义命名空间

    ¥with a custom namespace

Server > { type: EVENT, namespace: "/admin", data: ["foo"], id: 13 }
Client > { type: ACK, namespace: "/admin", data: ["bar"], id: 13 }
  • 与二进制数据

    ¥with binary data

Client > { type: BINARY_EVENT, namespace: "/", data: ["foo", <buffer <01 02 03 04> ], id: 14 }
Server > { type: ACK, namespace: "/", data: ["bar"], id: 14 }

or

Server > { type: EVENT, namespace: "/", data: ["foo" ], id: 15 }
Client > { type: BINARY_ACK, namespace: "/", data: ["bar", <buffer <01 02 03 04>], id: 15 }

与命名空间断开连接

¥Disconnection from a namespace

任何时候,一侧都可以通过发送 DISCONNECT 数据包来结束与命名空间的连接:

¥At any time, one side can end the connection to a namespace by sending a DISCONNECT packet:

CLIENT                                                      SERVER

│ ───────────────────────────────────────────────────────► │
│ { type: DISCONNECT, namespace: "/" } │

预计对方不会做出回应。如果客户端连接到另一个名称空间,则底层连接可以保持活动状态。

¥No response is expected from the other side. The low-level connection MAY be kept alive if the client is connected to another namespace.

数据包编码

¥Packet encoding

本节详细介绍了 Socket.IO 服务器和客户端中包含的默认解析器使用的编码,其源代码可以在 此处 中找到。

¥This section details the encoding used by the default parser which is included in Socket.IO server and client, and whose source can be found here.

JavaScript 服务器和客户端实现还支持自定义解析器,它们具有不同的权衡,可能有利于某些类型的应用。例如,请参见 socket.io-json-parsersocket.io-msgpack-parser

¥The JavaScript server and client implementations also supports custom parsers, which have different tradeoffs and may benefit to certain kind of applications. Please see socket.io-json-parser or socket.io-msgpack-parser for example.

另请注意,每个 Socket.IO 数据包均作为 Engine.IO message 数据包(更多信息 此处)发送,因此在通过线路发送时,编码结果将以字符 "4" 为前缀(在 HTTP 长的请求/响应正文中) -轮询,或在 WebSocket 框架中)。

¥Please also note that each Socket.IO packet is sent as a Engine.IO message packet (more information here), so the encoded result will be prefixed by the character "4" when sent over the wire (in the request/response body with HTTP long-polling, or in the WebSocket frame).

格式

¥Format

<packet type>[<# of binary attachments>-][<namespace>,][<acknowledgment id>][JSON-stringified payload without binary]

+ binary attachments extracted

注意:仅当命名空间与主命名空间 (/) 不同时才包含该命名空间

¥Note: the namespace is only included if it is different from the main namespace (/)

示例

¥Examples

连接到命名空间

¥Connection to a namespace

  • 与主命名空间

    ¥with the main namespace

¥Packet

{ type: CONNECT, namespace: "/" }

编码

¥Encoded

0
  • 具有自定义命名空间

    ¥with a custom namespace

¥Packet

{ type: CONNECT, namespace: "/admin", data: { sid: "oSO0OpakMV_3jnilAAAA" } }

编码

¥Encoded

0/admin,{"sid":"oSO0OpakMV_3jnilAAAA"}
  • 如果连接被拒绝

    ¥in case the connection is refused

¥Packet

{ type: CONNECT_ERROR, namespace: "/", data: { message: "Not authorized" } }

编码

¥Encoded

4{"message":"Not authorized"}

发送和接收数据

¥Sending and receiving data

  • 与主命名空间

    ¥with the main namespace

¥Packet

{ type: EVENT, namespace: "/", data: ["foo"] }

编码

¥Encoded

2["foo"]
  • 具有自定义命名空间

    ¥with a custom namespace

¥Packet

{ type: EVENT, namespace: "/admin", data: ["bar"] }

编码

¥Encoded

2/admin,["bar"]
  • 与二进制数据

    ¥with binary data

¥Packet

{ type: BINARY_EVENT, namespace: "/", data: ["baz", <Buffer <01 02 03 04>> ] }

编码

¥Encoded

51-["baz",{"_placeholder":true,"num":0}]

+ <Buffer <01 02 03 04>>
  • 有多个附件

    ¥with multiple attachments

¥Packet

{ type: BINARY_EVENT, namespace: "/admin", data: ["baz", <Buffer <01 02>>, <Buffer <03 04>> ] }

编码

¥Encoded

52-/admin,["baz",{"_placeholder":true,"num":0},{"_placeholder":true,"num":1}]

+ <Buffer <01 02>>
+ <Buffer <03 04>>

请记住,每个 Socket.IO 数据包都封装在 Engine.IO message 数据包中,因此通过线路发送时,它们将以字符 "4" 为前缀。

¥Please remember that each Socket.IO packet is wrapped in a Engine.IO message packet, so they will be prefixed by the character "4" when sent over the wire.

示例:{ type: EVENT, namespace: "/", data: ["foo"] } 将作为 42["foo"] 发送

¥Example: { type: EVENT, namespace: "/", data: ["foo"] } will be sent as 42["foo"]

回执

¥Acknowledgement

  • 与主命名空间

    ¥with the main namespace

¥Packet

{ type: EVENT, namespace: "/", data: ["foo"], id: 12 }

编码

¥Encoded

212["foo"]
  • 具有自定义命名空间

    ¥with a custom namespace

¥Packet

{ type: ACK, namespace: "/admin", data: ["bar"], id: 13 }

编码

¥Encoded

3/admin,13["bar"]`
  • 与二进制数据

    ¥with binary data

¥Packet

{ type: BINARY_ACK, namespace: "/", data: ["bar", <Buffer <01 02 03 04>>], id: 15 }

编码

¥Encoded

61-15["bar",{"_placeholder":true,"num":0}]

+ <Buffer <01 02 03 04>>

与命名空间断开连接

¥Disconnection from a namespace

  • 与主命名空间

    ¥with the main namespace

¥Packet

{ type: DISCONNECT, namespace: "/" }

编码

¥Encoded

1
  • 具有自定义命名空间

    ¥with a custom namespace

{ type: DISCONNECT, namespace: "/admin" }

编码

¥Encoded

1/admin,

示例会话

¥Sample session

下面是结合 Engine.IO 和 Socket.IO 协议时通过线路发送的示例。

¥Here is an example of what is sent over the wire when combining both the Engine.IO and the Socket.IO protocols.

  • 请求 n°1(打开数据包)

    ¥Request n°1 (open packet)

GET /socket.io/?EIO=4&transport=polling&t=N8hyd6w
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
0{"sid":"lv_VI97HAXpY6yYWAAAC","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000,"maxPayload":1000000}

细节:

¥Details:

0           => Engine.IO "open" packet type
{"sid":... => the Engine.IO handshake data

注意:t 查询参数用于确保浏览器不会缓存该请求。

¥Note: the t query param is used to ensure that the request is not cached by the browser.

  • 请求 n°2(命名空间连接请求):

    ¥Request n°2 (namespace connection request):

POST /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
40

细节:

¥Details:

4           => Engine.IO "message" packet type
0 => Socket.IO "CONNECT" packet type
  • 请求 n°3(命名空间连接批准)

    ¥Request n°3 (namespace connection approval)

GET /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
40{"sid":"wZX3oN0bSVIhsaknAAAI"}
  • 请求 n°4

    ¥Request n°4

socket.emit('hey', 'Jude') 在服务器上执行:

¥socket.emit('hey', 'Jude') is executed on the server:

GET /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
42["hey","Jude"]

细节:

¥Details:

4           => Engine.IO "message" packet type
2 => Socket.IO "EVENT" packet type
[...] => content
  • 请求 n°5(消息输出)

    ¥Request n°5 (message out)

socket.emit('hello'); socket.emit('world'); 在客户端执行:

¥socket.emit('hello'); socket.emit('world'); is executed on the client:

POST /socket.io/?EIO=4&transport=polling&t=N8hzxke&sid=lv_VI97HAXpY6yYWAAAC
> Content-Type: text/plain; charset=UTF-8
42["hello"]\x1e42["world"]
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
ok

细节:

¥Details:

4           => Engine.IO "message" packet type
2 => Socket.IO "EVENT" packet type
["hello"] => the 1st content
\x1e => separator
4 => Engine.IO "message" packet type
2 => Socket.IO "EVENT" packet type
["world"] => the 2nd content
  • 请求 n°6(WebSocket 升级)

    ¥Request n°6 (WebSocket upgrade)

GET /socket.io/?EIO=4&transport=websocket&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 101 Switching Protocols

WebSocket 帧:

¥WebSocket frames:

< 2probe                                        => Engine.IO probe request
> 3probe => Engine.IO probe response
> 5 => Engine.IO "upgrade" packet type
> 42["hello"]
> 42["world"]
> 40/admin, => request access to the admin namespace (Socket.IO "CONNECT" packet)
< 40/admin,{"sid":"-G5j-67EZFp-q59rADQM"} => grant access to the admin namespace
> 42/admin,1["tellme"] => Socket.IO "EVENT" packet with acknowledgement
< 461-/admin,1[{"_placeholder":true,"num":0}] => Socket.IO "BINARY_ACK" packet with a placeholder
< <binary> => the binary attachment (sent in the following frame)
... after a while without message
> 2 => Engine.IO "ping" packet type
< 3 => Engine.IO "pong" packet type
> 1 => Engine.IO "close" packet type

历史

¥History

v5 和 v4 之间的区别

¥Difference between v5 and v4

Socket.IO 协议的第 5 版(当前)用于 Socket.IO v3 及更高版本(v3.0.0 于 2020 年 11 月发布)。

¥The 5th revision (current) of the Socket.IO protocol is used in Socket.IO v3 and above (v3.0.0 was released in November 2020).

它建立在 Engine.IO 协议 第 4 版的基础上(因此有 EIO=4 查询参数)。

¥It is built on top of the 4th revision of the Engine.IO protocol (hence the EIO=4 query parameter).

变更列表:

¥List of changes:

  • 删除与默认命名空间的隐式连接

    ¥remove the implicit connection to the default namespace

在以前的版本中,客户端始终连接到默认命名空间,即使它请求访问另一个命名空间。

¥In previous versions, a client was always connected to the default namespace, even if it requested access to another namespace.

现在不再是这种情况了,客户端无论如何都必须发送 CONNECT 数据包。

¥This is not the case anymore, the client must send a CONNECT packet in any case.

提交:09b6f23(服务器)和 249e0be(客户端)

¥Commits: 09b6f23 (server) and 249e0be (client)

  • ERROR 重命名为 CONNECT_ERROR

    ¥rename ERROR to CONNECT_ERROR

含义和代号(4)不变:当与命名空间的连接被拒绝时,服务器仍使用此数据包类型。但我们觉得这个名字更具有自我描述性。

¥The meaning and the code number (4) are not modified: this packet type is still used by the server when the connection to a namespace is refused. But we feel the name is more self-descriptive.

提交:d16c035(服务器)和 13e1db7c(客户端)。

¥Commits: d16c035 (server) and 13e1db7c (client).

  • CONNECT 数据包现在可以包含有效负载

    ¥the CONNECT packet now can contain a payload

客户端可以发送用于身份验证/授权目的的有效负载。示例:

¥The client can send a payload for authentication/authorization purposes. Example:

{
"type": 0,
"nsp": "/admin",
"data": {
"token": "123"
}
}

如果成功,服务器会使用包含 Socket ID 的有效负载进行响应。示例:

¥In case of success, the server responds with a payload contain the ID of the Socket. Example:

{
"type": 0,
"nsp": "/admin",
"data": {
"sid": "CjdVH4TQvovi1VvgAC5Z"
}
}

这一更改意味着 Socket.IO 连接的 ID 现在将不同于基础 Engine.IO 连接的 ID(在 HTTP 请求的查询参数中找到的连接)。

¥This change means that the ID of the Socket.IO connection will now be different from the ID of the underlying Engine.IO connection (the one that is found in the query parameters of the HTTP requests).

提交:2875d2c(服务器)和 BBE94AD(客户端)

¥Commits: 2875d2c (server) and bbe94ad (client)

  • 有效负载 CONNECT_ERROR 数据包现在是一个对象而不是纯字符串

    ¥the payload CONNECT_ERROR packet is now an object instead of a plain string

提交:54bf4a4(服务器)和 0939395(客户端)

¥Commits: 54bf4a4 (server) and 0939395 (client)

v4 和 v3 之间的区别

¥Difference between v4 and v3

Socket.IO 协议的第四个修订版用于 Socket.IO v1(v1.0.3 于 2014 年 6 月发布)和 v2(v2.0.0 于 2017 年 5 月发布)。

¥The 4th revision of the Socket.IO protocol is used in Socket.IO v1 (v1.0.3 was released in June 2014) and v2 (v2.0.0 was released in May 2017).

修订的详细信息可以在这里找到:https://github.com/socketio/socket.io-protocol/tree/v4

¥The details of the revision can be found here: https://github.com/socketio/socket.io-protocol/tree/v4

它建立在 Engine.IO 协议 第三版的基础上(因此有 EIO=3 查询参数)。

¥It is built on top of the 3rd revision of the Engine.IO protocol (hence the EIO=3 query parameter).

变更列表:

¥List of changes:

  • 添加 BINARY_ACK 数据包类型

    ¥add a BINARY_ACK packet type

以前,ACK 数据包始终被视为可能包含二进制对象,并递归搜索此类对象,这可能会损害性能。

¥Previously, an ACK packet was always treated as if it may contain binary objects, with recursive search for such objects, which could hurt performance.

参考:https://github.com/socketio/socket.io-parser/commit/ca4f42a922ba7078e840b1bc09fe3ad618acc065

¥Reference: https://github.com/socketio/socket.io-parser/commit/ca4f42a922ba7078e840b1bc09fe3ad618acc065

v3 和 v2 之间的区别

¥Difference between v3 and v2

Socket.IO 协议的第三个修订版用于早期 Socket.IO v1 版本 (socket.io@1.0.0...1.0.2)(2014 年 5 月发布)。

¥The 3rd revision of the Socket.IO protocol is used in early Socket.IO v1 versions (socket.io@1.0.0...1.0.2) (released in May 2014).

修订的详细信息可以在这里找到:https://github.com/socketio/socket.io-protocol/tree/v3

¥The details of the revision can be found here: https://github.com/socketio/socket.io-protocol/tree/v3

变更列表:

¥List of changes:

  • 删除使用 msgpack 对包含二进制对象的数据包进行编码(另请参见 299849b

    ¥remove the usage of msgpack to encode packets containing binary objects (see also 299849b)

v2 和 v1 之间的区别

¥Difference between v2 and v1

变更列表:

¥List of changes:

  • 添加 BINARY_EVENT 数据包类型

    ¥add a BINARY_EVENT packet type

这是在 Socket.IO 1.0 的开发过程中添加的,目的是添加对二进制对象的支持。BINARY_EVENT 数据包使用 msgpack 进行编码。

¥This was added during the work towards Socket.IO 1.0, in order to add support for binary objects. The BINARY_EVENT packets were encoded with msgpack.

初步修订

¥Initial revision

第一个修订是 Engine.IO 协议(使用 WebSocket / HTTP 长轮询、心跳的底层管道)和 Socket.IO 协议之间分离的结果。它从未包含在 Socket.IO 版本中,但为下一次迭代铺平了道路。

¥This first revision was the result of the split between the Engine.IO protocol (low-level plumbing with WebSocket / HTTP long-polling, heartbeat) and the Socket.IO protocol. It was never included in a Socket.IO release, but paved the way for the next iterations.

测试套件

¥Test suite

test-suite/ 目录中的测试套件可让你检查服务器实现的合规性。

¥The test suite in the test-suite/ directory lets you check the compliance of a server implementation.

用法:

¥Usage:

  • 在 Node.js 中:npm ci && npm test

    ¥in Node.js: npm ci && npm test

  • 在浏览器中:只需在浏览器中打开 index.html 文件

    ¥in a browser: simply open the index.html file in your browser

作为参考,以下是 JavaScript 服务器通过所有测试的预期配置:

¥For reference, here is expected configuration for the JavaScript server to pass all tests:

import { Server } from "socket.io";

const io = new Server(3000, {
pingInterval: 300,
pingTimeout: 200,
maxPayload: 1000000,
cors: {
origin: "*"
}
});

io.on("connection", (socket) => {
socket.emit("auth", socket.handshake.auth);

socket.on("message", (...args) => {
socket.emit.apply(socket, ["message-back", ...args]);
});

socket.on("message-with-ack", (...args) => {
const ack = args.pop();
ack(...args);
})
});

io.of("/custom").on("connection", (socket) => {
socket.emit("auth", socket.handshake.auth);
});