Backend Development 18 min read

Engine.IO, Socket.IO & WebSocket: How Real-Time Communication Works

This article explains the relationships among WebSocket, Engine.IO, and Socket.IO, details Engine.IO’s features, handshake, upgrade and disconnect mechanisms, and provides practical code examples for building real‑time communication in Node.js applications, including packet encoding, transport options, and debugging techniques.

Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Engine.IO, Socket.IO & WebSocket: How Real-Time Communication Works

Part of a series on WebSocket basics and applications, this article reviews the relationships between WebSocket, Engine.IO, and Socket.IO, and demonstrates how to capture WebSocket packets.

1. Relationship between WebSocket, Engine.IO, and Socket.IO

WebSocket is a full‑duplex protocol over a single TCP connection, enabling simple client‑server data exchange and server‑initiated pushes after a single handshake.

Socket.IO builds a real‑time bidirectional channel on top of WebSocket (or falls back to HTTP long‑polling) for Node.js and browsers.

Engine.IO is an abstraction layer used by Socket.IO to handle the transport layer, supporting multiple browsers, devices, and networks. It wraps WebSocket and HTTP long‑polling, providing a unified socket protocol.

2. Engine.IO Features

Engine.IO establishes the underlying connection and provides:

Multiple transport channels and upgrade mechanisms

Disconnect detection

2.1 Transport Channels

Two main transport implementations are used:

HTTP long‑polling

WebSocket

2.1.1 HTTP long‑polling

Long‑polling consists of a series of HTTP requests:

Long‑running GET requests for receiving data

Short‑running POST requests for sending data

Multiple emits may be combined into a single request.

2.1.2 WebSocket

The WebSocket transport provides a low‑latency, bidirectional channel. Each emit is sent as a separate WebSocket frame, sometimes split into two frames.

2.2 Handshake

When Engine.IO establishes a connection, the server sends a JSON handshake packet:

<code>{
  "sid": "FSDjX-WRwSA4zTZMALqx",
  "upgrades": ["websocket"],
  "pingInterval": 25000,
  "pingTimeout": 20000
}</code>

sid : session ID, included in all subsequent requests

upgrades : list of transports the server can upgrade to

pingInterval and pingTimeout : heartbeat configuration

2.3 Upgrade Mechanism

By default the client starts with HTTP long‑polling. If the WebSocket upgrade succeeds, the client switches to WebSocket after flushing the polling buffer.

Ensure the send queue is empty

Mark the current transport as read‑only

Open a new connection with the alternative transport

Close the original transport if the upgrade succeeds

2.4 Disconnect Detection

Engine.IO considers the connection closed when any of the following occurs:

A HTTP request fails

The WebSocket connection closes

socket.disconnect() is called on either side

The heartbeat (ping/pong) fails

The server sends a ping every

pingInterval

ms; the client must reply with a pong within

pingTimeout

ms, otherwise the connection is considered closed.

3. Engine.IO Protocol

3.1 Session Flow

Transport channel is opened via an Engine.IO URL

Server sends a JSON handshake containing

sid

,

upgrades

,

pingInterval

, and

pingTimeout

Client replies to server pings with pong packets

Message packets are exchanged

Polling transports can send a close packet to terminate the socket

Session Example

Open request (polling):

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

Message in (server → client):

<code>GET /engine.io/?EIO=4&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC
HTTP/1.1 200 OK
Content-Type: text/plain; charset=UTF-8
4hey</code>

Message out (client → server):

<code>POST /engine.io/?EIO=4&transport=polling&t=N8hzxke&sid=lv_VI97HAXpY6yYWAAAC
Content-Type: text/plain; charset=UTF-8
4hello\x1e4world</code>

WebSocket upgrade request:

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

3.2 URLs

Engine.IO URLs have the form

/engine.io/[?query]

. Reserved query keys include

transport

,

j

(JSONP),

sid

,

EIO

(protocol version), and

t

(cache‑busting).

3.3 Encoding

Two encoding types are used:

packet : a single UTF‑8 string or binary blob, prefixed with a type identifier

payload : a series of encoded packets separated by the record separator

\x1e

Binary data in a payload is base64‑encoded and preceded by a

b

marker.

3.4 Transport Channels

Engine.IO must support three transport types:

WebSocket

Server‑Sent Events (SSE)

Polling (XHR, JSONP)

Polling

Polling uses periodic GET requests to receive data and POST requests to send data. CORS support is required for XHR, and JSONP responses must wrap the payload in a JavaScript callback.

Server‑Sent Events

Clients receive data via an

EventSource

object and send data via

XMLHttpRequest

.

WebSocket

WebSocket frames carry messages directly; the payload encoding described above is not used.

3.5 Transport Upgrade

Connections start with polling. The client probes WebSocket with a

2probe

packet; if the server replies

3probe

, the client sends an upgrade packet (

5

) and switches to WebSocket after flushing buffers.

3.6 Timeouts

Both client and server use

pingInterval

and

pingTimeout

from the handshake to detect unresponsive peers. Missing a pong within the timeout marks the connection as closed.

4. Key Points

Engine.IO is the transport layer underlying Socket.IO.

Both sides must use matching implementations (Engine.IO ↔ Socket.IO).

Engine.IO’s heartbeat (ping/pong) is sent as regular message packets, not as WebSocket control frames.

5. Simple Example

Server (Node.js)

<code>const engine = require('engine.io');
const server = engine.listen(3000, { cors: { origin: '*' } });

server.on('listen', () => console.log('listening on 3000'));

server.on('connection', socket => {
  console.log('new connection');
  socket.send('utf 8 string');
  socket.send(Buffer.from('hello world')); // binary data
});
</code>

Client (Node.js)

<code>const { Socket } = require('engine.io-client');
const socket = new Socket('ws://localhost:3000');

socket.on('open', () => {
  socket.emit('message from client');
  socket.on('message', data => {
    console.log('receive message: ' + data);
    socket.send('ack from client.');
  });
  socket.on('close', e => console.log('socket close', e));
});
</code>

When the client specifies

{ transports: ['websocket'] }

, the initial polling phase is skipped and the handshake occurs directly over WebSocket.

<code>const socket = new Socket('ws://localhost:3000', { transports: ['websocket'] });
</code>
real-timeJavaScriptnode.jswebsocketprotocolsocket.ioEngine.IO
Tencent IMWeb Frontend Team
Written by

Tencent IMWeb Frontend Team

IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.