Socket.io

I am having some problems with node/ws, and I read that the socket.io library handles common problems with WebSockets, so I am going to try to reimplement the WebSocket functionality of this site with socket.io to try to improve performance.

Date Created:
1 31

References



Notes


Introduction

Socket.io is a library that enables low-latency, bidirectional, and event-based communication between a client and server.

The Socket.io connection can be established with different low-level transports:

Socket.io will automatically pick the best available option, depending on:

  • the capabilities of the browser
  • the network (some networks block WebSocket and/or WebTransport connections)

Although Socket.io uses WebSocket for transport when possible, it adds additional metadata to each packet. That is why a WebSocket client will not be able to successfully connect to a Socket.io server, and a Socket.io client will not be able to connect to a plain WebSocket server either.

// WARNING: the client will NOT be able to connect!
const socket = io("ws://echo.websocket.org");

The Socket.io library keeps an open TCP connection to the server, which may result in a high battery drain for users.

Features

HTTP Long-Polling Fallback

The connection will fall back to HTTP long-polling in case the WebSocket connection cannot be established. Even if most browsers now support WebSockets, it is still a great feature as we still receive reports from users that cannot establish a WebSocket connection because they are behind some misconfigured proxy.

Automatic Reconnection

Socket.io includes a heartbeat connection which periodically checks the state of the connection - so that the server and client can both become aware of the broken status of the link. When the client eventually gets disconnected, it automatically reconnects with an exponential back-off delay, in order to not overwhelm the server.

Packet Buffering

The packets are automatically buffered when the client is disconnected and will be sent upon reconnection.

Acknowledgements

Socket.io provides a convenient way to send and receive a response:

Sender
socket.emit("hello", "world", (response) => {
console.log(response); // "got it"
});
Receiver
socket.on("hello", (arg, callback) => {
console.log(arg); // "world"
callback("got it");
});
Broadcasting

On the server side, you can send an event to all connected clients or to a subset of connected clients.

// to all connected clients
io.emit("hello");

// to all connected clients in the "news" room
io.to("news").emit("hello");
Multiplexing

Namespaces allow you to split the logic of your application over a single shared connection. This can be useful if you want to create an admin channel that only authorized users can join.

io.on("connection", (socket) => {
// classic users
});

io.of("/admin").on("connection", (socket) => {
// admin users
});

How it works

The bidirectional channel between the Socket.io server (Node.js) and the Socket.io client (browser, Node.js, or another) is established with a WebSocket connection whenever connection, and will use HTTP long-polling as fallback.

The codebase is split into two distinct layers:

  • the low-level plumbing: what is called Engine.io, the engine inside Socket.io
  • the high-level API: the Socket.IO itself

Engine.io is responsible for establishing the low-level connection between the server and the client. It handles:

There are currently two implemented transports:

The HTTP long-polling transport (also simply referred as "polling") consists of successive HTTP requests:

  • long-running GET requests ,for receiving data from the server
  • short-running POST requests, for sending data to the server

Due to the nature of the transport, successive emits may be concatenated and sent within the same HTTP request.

The WebSocket transport consists of a WebSocket connection, which provides a bidirectional and low-latency communication channel between the server and the client. Due to the nature of the transport, each emit is sent in its own WebSocket frame (some emits may even result in two distinct WebSocket frames).

Handshake

At the beginning of the Engine.io connection, the server sends some information:

 {
"sid": "FSDjX-WRwSA4zTZMALqx",
"upgrades": ["websocket"],
"pingInterval": 25000,
"pingTimeout": 20000
}
  • the sid is the ID of the session, it must be included in the sid parameter in all subsequent HTTP requests
  • the upgrades array contains the list of all better transports supported by the server
  • the pingInterval and pingTimeout values are used in the heartbeat mechanism

Upgrade Mechanism

By default, the client established the connection with the HTTP long-polling transport. While WebSocket is clearly the best way to establish a bidirectional communication, experience has shown that is not always possible, due to corporate proxies, personal firewall, antivirus software...

An unsuccessful WebSocket connection can translate in up to 10 seconds of waiting for the realtime application to begin exchanging data. Engine.IO focuses on reliability and user experience first, marginal potential UX improvements and increased server performance second. To upgrade, the client will:

  • ensure its outgoing buffer is empty
  • put the current transport in read-only mode
  • try to establish a connection with the other transport
  • if successful, close the first transport

Example Network Connection

  1. handshake (contains the session ID (sid) that is used in subsequent requests)
  2. send data (HTTP long-polling)
  3. receive data (HTTP long-polling)
  4. upgrade (WebSocket)
  5. receive data (HTTP long-polling, closed once the WebSocket connection in 4. is successfully established)

The Engine.io connection is considered as closed when:

  • one HTTP request (either GET or POST) fails
  • the WebSocket connection is closed
  • socket.disconnect() is called on the server-side or the client-side

There is also a heartbeat mechanism that checks that the connection between the server and the client is still up and running. At a given interval (the pingInterval value sent in the handshake) the server sends a PING packet and the client has a few seconds (the pingTimeout value) to send a PONG packet back. If the server does not receive a PONG packet back, it will consider that the connection is closed. Conversely, if the client does not receive a PING packet within pingInterval + pingTimeout, it will consider the connection is closed.

Socket.IO provides some additional features over the Engine.IO connection:

  • automatic reconnection
  • packet buffering
  • acknowledgements
  • broadcasting to all or a subset of clients
  • multiplexing

Delivery Guarantees

Socket.IO does guarantee message ordering, no matter which low-level transport is used. This is achieved thanks to:

  • the guarantees provided by the underlying TCP connection
  • the careful design of the upgrade mechanism

Socket.io provides an at most once guarantee of delivery:

  • if the connection is broken while an event is being sent, then there is no guarantee that the other side has received it, and there will be no retry upon reconnection
  • A disconnected client will buffer events until reconnection
  • There is no such buffer on the server, which means that any event that was missed by a disconnected client will not be transmitted to the client upon reconnection.

From the client side, you can achieve an at least once guarantee with acknowledgements and timeouts:

function emit(socket, event, arg) {
socket.timeout(2000).emit(event, arg, (err) => {
if (err) {
// no ack from the server, let's retry
emit(socket, event, arg);
}
});
}

emit(socket, "foo", "bar");

Connection State Recovery

Connection state recovery is a feature which allows restoring a client's state after a temporary connection, including any missed packets. Under real conditions, a Socket.IO client will inevitably experience temporary disconnections, regardless of the quality of the connection. This feature will help you cope with such disconnections, but unless you want to store the packets and sessions forever (by setting maxDisconnectionDuration to Infinity), you can't be assured that the recovery will always be successful.

Client stat recovery must be enabled by the server:

const io = new Server(httpServer, {
connectionStateRecovery: {
// the backup duration of the sessions and the packets
maxDisconnectionDuration: 2 * 60 * 1000,
// whether to skip middlewares upon successful recovery
skipMiddlewares: true,
}
});

Upon an unexpected disconnection, the server will store the id, the rooms and the data attribute of the socket. Then, upon reconnection, the server will try to restore the state of the client. The recovered attribute indicates whether this recovery was successful:

Server
io.on("connection", (socket) => {
if (socket.recovered) {
// recovery was successful: socket.id, socket.rooms and socket.data were restored
} else {
// new or unrecoverable session
}
});
Client
socket.on("connect", () => {
if (socket.recovered) {
// any event missed during the disconnection period will be received now
} else {
// new or unrecoverable session
}
});

Memory Usage

The resources consumed by your Socket.io server will mainly depend on:

  • the number of connected clients
  • the number of messages received and sent per second

Socket.io Memory Usage Comparison

Comments

You have to be logged in to add a comment

User Comments

Insert Math Markup

ESC
About Inserting Math Content
Display Style:

Embed News Content

ESC
About Embedding News Content

Embed Youtube Video

ESC
Embedding Youtube Videos

Embed TikTok Video

ESC
Embedding TikTok Videos

Embed X Post

ESC
Embedding X Posts

Embed Instagram Post

ESC
Embedding Instagram Posts

Insert Details Element

ESC

Example Output:

Summary Title
You will be able to insert content here after confirming the title of the <details> element.

Insert Table

ESC
Customization
Align:
Preview:

Insert Horizontal Rule

#000000

Preview:


View Content At Different Sizes

ESC

Edit Style of Block Nodes

ESC

Edit the background color, default text color, margin, padding, and border of block nodes. Editable block nodes include paragraphs, headers, and lists.

#ffffff
#000000

Edit Selected Cells

Change the background color, vertical align, and borders of the cells in the current selection.

#ffffff
Vertical Align:
Border
#000000
Border Style:

Edit Table

ESC
Customization:
Align:

Upload Lexical State

ESC

Upload a .lexical file. If the file type matches the type of the current editor, then a preview will be shown below the file input.

Upload 3D Object

ESC

Upload Jupyter Notebook

ESC

Upload a Jupyter notebook and embed the resulting HTML in the text editor.

Insert Custom HTML

ESC

Edit Image Background Color

ESC
#ffffff

Insert Columns Layout

ESC
Column Type:

Select Code Language

ESC
Select Coding Language

Insert Chart

ESC

Use the search box below

Upload Previous Version of Article State

ESC