This document describes the Socket.IO protocol. For a reference JavaScript implementation, take a look at socket.io-parser, socket.io-client and socket.io.
This is the revision 4 of the Socket.IO protocol, included in [email protected]
.
The 3rd revision (included in [email protected]
) can be found here: https://github.com/socketio/socket.io-protocol/tree/v3
Both the 1st and the 2nd revisions were part of the work towards Socket.IO 1.0 but were never included in a Socket.IO release.
It is built on top of the 3rd revision of the Engine.IO protocol.
While the Engine.IO protocol describes the low-level plumbing with WebSocket and HTTP long-polling, the Socket.IO protocol adds another layer above in order to provide the following features:
- multiplexing (what we call Namespace)
Example of the 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", () => {});
- acknowledgement of packets
Example of the Javascript API:
// on one side
socket.emit("hello", 1, () => { console.log("received"); });
// on the other side
socket.on("hello", (a, cb) => { cb(); });
A packet contains the following fields:
- a type (integer, see below)
- a namespace (string)
- optionally, a payload (string | Array)
- optionally, an acknowledgment id (integer)
This event is sent:
- by the client when requesting access to a namespace
- by the server when accepting the connection to a namespace
It does not contain any payload nor acknowledgement id.
Example:
{
"type": 0,
"nsp": "/admin"
}
The client may include additional information (i.e. for authentication purpose) in the namespace field. Example:
{
"type": 0,
"nsp": "/admin?token=1234&uid=abcd"
}
This event is used when one side wants to disconnect from a namespace.
It does not contain any payload nor acknowledgement id.
Example:
{
"type": 1,
"nsp": "/admin"
}
This event is used when one side wants to transmit some data (without binary) to the other side.
It does contain a payload, and an optional acknowledgement id.
Example:
{
"type": 2,
"nsp": "/",
"data": ["hello", 1]
}
With an acknowledgment id:
{
"type": 2,
"nsp": "/admin",
"data": ["project:delete", 123],
"id": 456
}
This event is used when one side has received an EVENT or a BINARY_EVENT with an acknowledgement id.
It contains the acknowledgement id received in the previous packet, and may contain a payload (without binary).
{
"type": 3,
"nsp": "/admin",
"data": [],
"id": 456
}
This event is sent by the server when the connection to a namespace is refused.
It may contain a payload indicating the reason of the refusal.
Example:
{
"type": 4,
"nsp": "/admin",
"data": "Not authorized"
}
This event is used when one side wants to transmit some data (including binary) to the other side.
It does contain a payload, and an optional acknowledgement id.
Example:
{
"type": 5,
"nsp": "/",
"data": ["hello", <Buffer 01 02 03>]
}
With an acknowledgment id:
{
"type": 5,
"nsp": "/admin",
"data": ["project:delete", <Buffer 01 02 03>],
"id": 456
}
This event is used when one side has received an EVENT or a BINARY_EVENT with an acknowledgement id.
It contains the acknowledgement id received in the previous packet, and contain a payload including binary.
Example:
{
"type": 6,
"nsp": "/admin",
"data": [<Buffer 03 02 01>],
"id": 456
}
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.
The JS 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.
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 4
when sent over the wire (in the request/response body with HTTP
long-polling, or in the WebSocket frame).
<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 default namespace (
/
)
CONNECT
packet for the default namespace
{
"type": 0,
"nsp": "/"
}
is encoded to 0
CONNECT
packet for the/admin
namespace
{
"type": 0,
"nsp": "/admin"
}
is encoded to 0/admin,
DISCONNECT
packet for the/admin
namespace
{
"type": 1,
"nsp": "/admin"
}
is encoded to 1/admin,
EVENT
packet
{
"type": 2,
"nsp": "/",
"data": ["hello", 1]
}
is encoded to 2["hello",1]
EVENT
packet with an acknowledgement id
{
"type": 2,
"nsp": "/admin",
"data": ["project:delete", 123],
"id": 456
}
is encoded to 2/admin,456["project:delete",123]
ACK
packet
{
"type": 3,
"nsp": "/admin",
"data": [],
"id": 456
}
is encoded to 3/admin,456[]
ERROR
packet
{
"type": 4,
"nsp": "/admin",
"data": "Not authorized"
}
is encoded to 4/admin,"Not authorized"
BINARY_EVENT
packet
{
"type": 5,
"nsp": "/",
"data": ["hello", <Buffer 01 02 03>]
}
is encoded to 51-["hello",{"_placeholder":true,"num":0}]
+ <Buffer 01 02 03>
BINARY_EVENT
packet with an acknowledgement id
{
"type": 5,
"nsp": "/admin",
"data": ["project:delete", <Buffer 01 02 03>],
"id": 456
}
is encoded to 51-/admin,456["project:delete",{"_placeholder":true,"num":0}]
+ <Buffer 01 02 03>
BINARY_ACK
packet
{
"type": 6,
"nsp": "/admin",
"data": [<Buffer 03 02 01>],
"id": 456
}
is encoded to 61-/admin,456[{"_placeholder":true,"num":0}]
+ <Buffer 03 02 01>
The server always send a CONNECT
packet for the default namespace (/
) when the connection is established.
That is, even if the client requests access to a non-default namespace, it will receive a CONNECT
packet for the
default namespace first.
Server > { type: CONNECT, nsp: "/" }
No response is expected from the client.
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" }
And vice versa. No response is expected from the other-side.
Client > { type: EVENT, nsp: "/admin", data: ["hello"], id: 456 }
Server > { type: ACK, nsp: "/admin", data: [], id: 456 }
or
Server > { type: BINARY_ACK, nsp: "/admin", data: [ <Buffer 01 02 03> ], id: 456 }
And vice versa.
Here is an example of what is sent over the wire when combining both the Engine.IO and the Socket.IO protocols.
- Request n°1 (open packet)
GET /socket.io/?EIO=3&transport=polling&t=N8hyd6w
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
96:0{"sid":"lv_VI97HAXpY6yYWAAAC","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000}2:40
Details:
96 => number of characters (not bytes) of the first message
: => separator
0 => Engine.IO "open" packet type
{"sid":... => the Engine.IO handshake data
2 => number of characters of the 2nd message
: => separator
4 => Engine.IO "message" packet type
0 => Socket.IO "CONNECT" packet type
Note: the t
query param is used to ensure that the request is not cached by the browser.
- Request n°2 (message in):
socket.emit('hey', 'Jude')
is executed on the server:
GET /socket.io/?EIO=3&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
16:42["hey","Jude"]
Details:
16 => number of characters
: => separator
4 => Engine.IO "message" packet type
2 => Socket.IO "EVENT" packet type
[...] => content
- Request n°3 (message out)
socket.emit('hello'); socket.emit('world');
is executed on the client:
POST /socket.io/?EIO=3&transport=polling&t=N8hzxke&sid=lv_VI97HAXpY6yYWAAAC
> Content-Type: text/plain; charset=UTF-8
11:42["hello"]11:42["world"]
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
ok
Details:
11 => number of characters of the 1st packet
: => separator
4 => Engine.IO "message" packet type
2 => Socket.IO "EVENT" packet type
["hello"] => the 1st content
11 => number of characters of the 2nd packet
: => separator
4 => Engine.IO "message" packet type
2 => Socket.IO "EVENT" packet type
["world"] => the 2nd content
- Request n°4 (WebSocket upgrade)
GET /socket.io/?EIO=3&transport=websocket&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 101 Switching Protocols
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, => 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
- add a
BINARY_ACK
packet type
Previously, an ACK
packet was always treated as if it may contain binary objects, with recursive search for such
objects, which could hurt performance.
- remove the usage of msgpack to encode packets containing binary objects (see also 299849b)
- add a
BINARY_EVENT
packet type
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.
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.
MIT