Socket.IO
Must Watch!
MustWatch
Overview
Socket.IO is a JavaScript library for real-time web applications.
It enables real-time, bi-directional communication between web clients and servers.
It has two parts: a client-side library that runs in the browser, and a server-side library for node.js.
Both components have an identical API.
Real-time applications
A real-time application (RTA) is an application that functions within a period that the user senses as immediate or current.
Some examples of real-time applications are -
Instant messengers - Chat apps like Whatsapp, Facebook Messenger, etc.
You need not refresh your app/website to receive new messages.
Push Notifications - When someone tags you in a picture on Facebook, you receive a notification instantly.
Collaboration Applications - Apps like google docs, which allow multiple people to update same documents simultaneously and apply changes to all people's instances.
Online Gaming - Games like Counter Strike, Call of Duty, etc., are also some examples of real-time applications.
Why Socket.IO?
Writing a real-time application with popular web applications stacks like LAMP (PHP) has traditionally been very hard.
It involves polling the server for changes, keeping track of timestamps, and it is a lot slower than it should be.
Sockets have traditionally been the solution around which most real-time systems are architected, providing a bi-directional communication channel between a client and a server.
This means that the server can push messages to clients.
Whenever an event occurs, the idea is that the server will get it and push it to the concerned connected clients.
Socket.IO is quite popular, it is used by Microsoft Office, Yammer, Zendesk, Trello, and numerous other organizations to build robust real-time systems.
It one of the most powerful JavaScript frameworks on GitHub, and most depended-upon NPM (Node Package Manager) module.
Socket.IO also has a huge community, which means finding help is quite easy.
ExpressJS
We will be using express to build the web server that Socket.IO will work with.
Any other node-server-side framework or even node HTTP server can be used.
However, ExpressJS makes it easy to define routes and other things.
To read more about express and get a basic idea about it, head to ExpressJS tutorial.
Environment
To get started with developing using the Socket.IO, you need to have Node and npm (node package manager) installed.
If you do not have these, head over to Node setup to install node on your local system.
Confirm that node and npm are installed by running the following commands in your terminal.
node --version
npm --version
You should get an output similar to -
v5.0.0 3.5.2
Open your terminal and enter the following in your terminal to create a new folder and enter the following commands -
$ mkdir test-project
$ cd test-proect
$ npm init
It will ask you some questions; answer them in the following way -
This will create a ‘package.json node.js’ configuration file.
Now we need to install Express and Socket.IO.
To install these and save them to package.json file, enter the following command in your terminal, into the project directory.
npm install --save express socket.io
One final thing is that we should keep restarting the server.
When we make changes, we will need a tool called nodemon.
To install nodemon, open your terminal and enter the following command -
npm install -g nodemon
Whenever you need to start the server, instead of using the node app.js use, nodemon app.js.
This will ensure that you do not need to restart the server whenever you change a file.
It speeds up the development process.
Now, we have our development environment set up.
Let us now get started with developing real-time applications with Socket.IO.
Hello World
Create a file called app.js and enter the following code to set up an express application -
var app = require('express')();
var http = require('http').Server(app);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
http.listen(3000, function() {
console.log('listening on *:3000');
});
We will need an index.html file to serve, create a new file called index.html and enter the following code in it -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<body>Hello world</body>
</html>
To test if this works, go to the terminal and run this app using the following command -
nodemon app.js
This will run the server on localhost:3000.
Go to the browser and enter localhost:3000 to check this.
This sets up our express application and is now serving a HTML file on the root route.
Now we will require Socket.IO and will log "A user connected", every time a user goes to this page and "A user disconnected", every time someone navigates away/closes this page.
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
//Whenever someone connects this gets executed
io.on('connection', function(socket) {
console.log('A user connected');
//Whenever someone disconnects this piece of code executed
socket.on('disconnect', function () {
console.log('A user disconnected');
});
});
http.listen(3000, function() {
console.log('listening on *:3000');
});
The require('socket.io')(http) creates a new socket.io instance attached to the http server.
The io.on event handler handles connection, disconnection, etc., events in it, using the socket object.
We have set up our server to log messages on connections and disconnections.
We now have to include the client script and initialize the socket object there, so that clients can establish connections when required.
The script is served by our io server at '/socket.io/socket.io.js'.
After completing the above procedure, the index.html file will look as follows -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
</script>
<body>Hello world</body>
</html>
If you go to localhost:3000 now (make sure your server is running), you will get Hello World printed in your browser.
Now check your server console logs, it will show the following message -
A user connected
If you refresh your browser, it will disconnect the socket connection and recreate.
You can see the following on your console logs -
A user connected
A user disconnected
A user connected
We now have socket connections working.
This is how easy it is to set up connections in Socket.IO.
Event Handling
Sockets work based on events.
There are some reserved events, which can be accessed using the socket object on the server side.
These are -
Connect
Message
Disconnect
Reconnect
Ping
Join and
Leave
The client-side socket object also provides us with some reserved events, which are -
Connect
Connect_error
Connect_timeout
Reconnect, etc
In the Hello World example, we used the connection and disconnection events to log when a user connected and left.
Now we will be using the message event to pass message from the server to the client.
To do this, modify the io.on ('connection', function(socket)) call to include the following -
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
io.on('connection', function(socket) {
console.log('A user connected');
//Send a message after a timeout of 4seconds
setTimeout(function() {
socket.send('Sent a message 4seconds after connection!');
}, 4000);
socket.on('disconnect', function () {
console.log('A user disconnected');
});
});
http.listen(3000, function() {
console.log('listening on *:3000');
});
This will send an event called message(built in) to our client, four seconds after the client connects.
The send function on socket object associates the 'message' event.
Now, we need to handle this event on our client side.
So, edit your index.html script tag to include the following code -
<script>
var socket = io();
socket.on('message', function(data){document.write(data)});
</script>
We are now handling the 'message' event on the client.
When you go to the page in your browser now, you will be presented with the following screenshot.
After 4 seconds pass and the server sends the message event, our client will handle it and produce the following output -
Note - We sent a string of text here; we can also send an object in any event.
Message was a built-in event provided by the API, but is of not much use in a real application, as we need to be able to differentiate between events.
To allow this, Socket.IO provides us the ability to create custom events.
You can create and fire custom events using the socket.emit function.
For example, the following code emits an event called testerEvent -
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
io.on('connection', function(socket) {
console.log('A user connected');
//Send a message when
setTimeout(function() {
//Sending an object when emmiting an event
socket.emit('testerEvent', { description: 'A custom event named testerEvent!'});
}, 4000);
socket.on('disconnect', function () {
console.log('A user disconnected');
});
});
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
To handle this custom event on client we need a listener that listens for the event testerEvent.
The following code handles this event on the client -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.on('testerEvent', function(data){document.write(data.description)});
</script>
<body>Hello world</body>
</html>
This will work in the same way as our previous example, with the event being testerEvent in this case.
When you open your browser and go to localhost:3000, you'll be greeted with -
Hello world
After four seconds, this event will be fired and the browser will have the text changed to -
A custom event named testerEvent!
We can also emit events from the client.
To emit an event from your client, use the emit function on the socket object.
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.emit('clientEvent', 'Sent an event from the client!');
</script>
<body>Hello world</body>
</html>
To handle these events, use the on function on the socket object on your server.
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
io.on('connection', function(socket) {
socket.on('clientEvent', function(data) {
console.log(data);
});
});
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
So, now if we go to localhost:3000, we will get a custom event called clientEvent fired.
This event will be handled on the server by logging -
Sent an event from the client!
Broadcasting
Broadcasting means sending a message to all connected clients.
Broadcasting can be done at multiple levels.
We can send the message to all the connected clients, to clients on a namespace and clients in a particular room.
To broadcast an event to all the clients, we can use the io.sockets.emit method.
Note - This will emit the event to ALL the connected clients (event the socket that might have fired this event).
In this example, we will broadcast the number of connected clients to all the users.
Update the app.js file to incorporate the following.
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
var clients = 0;
io.on('connection', function(socket) {
clients++;
io.sockets.emit('broadcast',{ description: clients + ' clients connected!'});
socket.on('disconnect', function () {
clients--;
io.sockets.emit('broadcast',{ description: clients + ' clients connected!'});
});
});
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
On the client side, we just need to handle the broadcast event -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.on('broadcast',function(data) {
document.body.innerHTML = '';
document.write(data.description);
});
</script>
<body>Hello world</body>
</html>
If you connect four clients, you will get the following result -
This was to send an event to everyone.
Now, if we want to send an event to everyone, but the client that caused it (in the previous example, it was caused by new clients on connecting), we can use the socket.broadcast.emit.
Let us send the new user a welcome message and update the other clients about him/her joining.
So in your app.js file, on connection of client send him a welcome message and broadcast connected client number to all others.
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
var clients = 0;
io.on('connection', function(socket) {
clients++;
socket.emit('newclientconnect',{ description: 'Hey, welcome!'});
socket.broadcast.emit('newclientconnect',{ description: clients + ' clients connected!'})
socket.on('disconnect', function () {
clients--;
socket.broadcast.emit('newclientconnect',{ description: clients + ' clients connected!'})
});
});
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
And your html to handle this event -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.on('newclientconnect',function(data) {
document.body.innerHTML = '';
document.write(data.description);
});
</script>
<body>Hello world</body>
</html>
Now, the newest client gets a welcome message and others get how many clients are connected currently to the server.
Namespaces
Socket.IO allows you to “namespace” your sockets, which essentially means assigning different endpoints or paths.
This is a useful feature to minimize the number of resources (TCP connections) and at the same time separate concerns within your application by introducing separation between communication channels.
Multiple namespaces actually share the same WebSockets connection thus saving us socket ports on the server.
Namespaces are created on the server side.
However, they are joined by clients by sending a request to the server.
Default Namespaces
The root namespace '/' is the default namespace, which is joined by clients if a namespace is not specified by the client while connecting to the server.
All connections to the server using the socket-object client side are made to the default namespace.
For example -
var socket = io();
This will connect the client to the default namespace.
All events on this namespace connection will be handled by the io object on the server.
All the previous examples were utilizing default namespaces to communicate with the server and back.
Custom Namespaces
We can create our own custom namespaces.
To set up a custom namespace, we can call the ‘of’ function on the server side -
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
var nsp = io.of('/my-namespace');
nsp.on('connection', function(socket) {
console.log('someone connected');
nsp.emit('hi', 'Hello everyone!');
});
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
Now, to connect a client to this namespace, you need to provide the namespace as an argument to the io constructor call to create a connection and a socket object on client side.
For example, to connect to the above namespace, use the following HTML -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io('/my-namespace');
socket.on('hi',function(data) {
document.body.innerHTML = '';
document.write(data);
});
</script>
<body></body>
</html>
Every time someone connects to this namespace, they will receive a ‘hi’ event.
Rooms
Within each namespace, you can also define arbitrary channels that sockets can join and leave.
These channels are called rooms.
Rooms are used to further-separate concerns.
Rooms also share the same socket connection like namespaces.
One thing to keep in mind while using rooms is that they can only be joined on the server side.
Joining Rooms
You can call the join method on the socket to subscribe the socket to a given channel/room.
For example, let us create rooms called 'room-<room-number>' and join some clients.
As soon as this room is full, create another room and join clients there.
Note - We are currently doing this on the default namespace, i.e.
'/'.
You can also implement this in custom namespaces in the same fashion.
To join a room you need to provide the room name as the argument to your join function call.
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
var roomno = 1;
io.on('connection', function(socket) {
//Increase roomno 2 clients are present in a room.
if(io.nsps['/'].adapter.rooms["room-"+roomno] && io.nsps['/'].adapter.rooms["room-"+roomno].length > 1) roomno++;
socket.join("room-"+roomno);
//Send this event to everyone in the room.
io.sockets.in("room-"+roomno).emit('connectToRoom', "You are in room no.
"+roomno);
})
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
Just handle this connectToRoom event on the client.
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.on('connectToRoom',function(data) {
document.body.innerHTML = '';
document.write(data);
});
</script>
<body></body>
</html>
Now if you connect three clients, the first two will get the following message -
You are in room no. 1
The last client will get the following message -
You are in room no. 2
Leaving a room
To leave a room, you need to call the leave function just as you called the join function on the socket.
For example - To leave room 'room-1',
socket.leave("room-"+roomno);
Error Handling
We have worked on local servers until now, which will almost never give us errors related to connections, timeouts, etc.
However, in real life production environments, handling such errors are of utmost importance.
Therefore, we will now discuss how we can handle connection errors on the client side.
The client API provides us with following built in events -
Connect - When the client successfully connects.
Connecting - When the client is in the process of connecting.
Disconnect - When the client is disconnected.
Connect_failed - When the connection to the server fails.
Error - An error event is sent from the server.
Message - When the server sends a message using the send function.
Reconnect - When reconnection to the server is successful.
Reconnecting - When the client is in the process of connecting.
Reconnect_failed - When the reconnection attempt fails.
To handle errors, we can handle these events using the out-socket object that we created on our client.
For example - If we have a connection that fails, we can use the following code to connect to the server again -
socket.on('connect_failed', function() {
document.write("Sorry, there seems to be an issue with the connection!");
})
Logging and Debugging
Socket.IO uses a very famous debugging module developed by ExpresJS's main author, called debug.
Earlier Socket.IO used to log everything to the console making it quite difficult to debug the problem.
After the v1.0 release, you can specify what you want to log.
Server-side
The best way to see what information is available is to use the * -
DEBUG=* node app.js
This will colorize and output everything that happens to your server console.
For example, we can consider the following screenshot.
Client-side
Paste this to console, click enter and refresh your page.
This will again output everything related to Socket.io to your console.
localStorage.debug = '*';
You can limit the output to get the debug info with incoming data from the socket using the following command.
localStorage.debug = 'socket.io-client:socket';
You can see the result like the following screenshot, if you use the second statement to log the info -
There is a very good blog post related to socket.io debugging here.
Internals
In this chapter, we will discuss regarding Fallbacks, Connection using Socket.IO, Events and Messages.
Fallbacks
Socket.IO has a lot of underlying transport mechanisms, which deal with various constraints arising due to cross browser issues, WebSocket implementations, firewalls, port blocking, etc.
Though W3C has a defined specification for WebSocket API, it is still lacking in implementation.
Socket.IO provides us with fallback mechanisms, which can deal with such issues.
If we develop apps using the native API, we have to implement the fallbacks ourselves.
Socket.IO covers a large list of fallbacks in the following order -
WebSockets
FlashSocket
XHR long polling
XHR multipart streaming
XHR polling
JSONP polling
iframes
Connection using Socket.IO
The Socket.IO connection begins with the handshake.
This makes the handshake a special part of the protocol.
Apart from the handshake, all the other events and messages in the protocol are transferred over the socket.
Socket.IO is intended for use with web applications, and therefore it is assumed that these applications will always be able to use HTTP.
It is because of this reasoning that the Socket.IO handshake takes place over HTTP using a POST request on the handshake URI (passed to the connect method).
Events and messages
WebSocket native API only sends messages across.
Socket.IO provides an addition layer over these messages, which allows us to create events and again helps us develop apps easily by separating the different types of messages sent.
The native API sends messages only in plain text.
This is also taken care of by Socket.IO.
It handles the serialization and deserialization of data for us.
We have an official client API for the web.
For other clients such as native mobile phones, other application clients also we can use Socket.IO using the following steps.
Step 1 - A connection needs to be established using the same connection protocol discussed above.
Step 2 - The messages need to be in the same format as specified by Socket.IO.
This format enables Socket.IO to determine the type of the message and the data sent in the message and some metadata useful for operation.
The message format is -
[type] : [id ('+')] : [endpoint] (: [data]
The parameters in the above command are explained below -
Type is a single digit integer, specifying what type message it is.
ID is message ID, an incremental integer used for acknowledgements.
Endpoint is the socket endpoint that the message is intended to be delivered to...
Data is the associated data to be delivered to the socket.
In case of messages, it is treated as plain text, for other events, it is treated as JSON.
In the next chapter, we will write a chat application in Socket.IO.
Chat Application
Now that we are well acquainted with Socket.IO, let us write a chat application, which we can use to chat on different chat rooms.
We will allow users to choose a username and allow them to chat using them.
So first, let us set up our HTML file to request for a username -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
</script>
<body>
<input type = "text" name = "name" value = "" placeholder = "Enter your name!">
<button type = "button" name = "button">Let me chat!</button>
</body>
</html>
Now that we have set up our HTML to request for a username, let us create the server to accept connections from the client.
We will allow people to send their chosen usernames using the setUsername event.
If a user exists, we will respond by a userExists event, else using a userSet event.
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
users = [];
io.on('connection', function(socket) {
console.log('A user connected');
socket.on('setUsername', function(data) {
if(users.indexOf(data) > -1) {
users.push(data);
socket.emit('userSet', {username: data});
} else {
socket.emit('userExists', data + ' username is taken! Try some other username.');
}
})
});
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
We need to send the username to the server when people click on the button.
If user exists, we show an error message; else, we show a messaging screen -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
function setUsername() {
socket.emit('setUsername', document.getElementById('name').value);
};
var user;
socket.on('userExists', function(data) {
document.getElementById('error-container').innerHTML = data;
});
socket.on('userSet', function(data) {
user = data.username;
document.body.innerHTML = '<input type = "text" id = "message">\
<button type = "button" name = "button" onclick = "sendMessage()">Send</button>\
<div id = "message-container"></div>';
});
function sendMessage() {
var msg = document.getElementById('message').value;
if(msg) {
socket.emit('msg', {message: msg, user: user});
}
}
socket.on('newmsg', function(data) {
if(user) {
document.getElementById('message-container').innerHTML += '<div><b>' +
data.user + '</b>: ' + data.message + '</div>'
}
})
</script>
<body>
<div id = "error-container"></div>
<input id = "name" type = "text" name = "name" value = ""
placeholder = "Enter your name!">
<button type = "button" name = "button" onclick = "setUsername()">
Let me chat!
</button>
</body>
</html>
Now if you connect two clients with same username, it will give you an error as shown in the screenshot below -
Once you have provided an acceptable username, you will be taken to a screen with a message box and a button to send messages.
Now, we have to handle and direct the messages to the connected client.
For that, modify your app.js file to include the following changes -
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
users = [];
io.on('connection', function(socket) {
console.log('A user connected');
socket.on('setUsername', function(data) {
console.log(data);
if(users.indexOf(data) > -1) {
socket.emit('userExists', data + ' username is taken! Try some other username.');
} else {
users.push(data);
socket.emit('userSet', {username: data});
}
});
socket.on('msg', function(data) {
//Send message to everyone
io.sockets.emit('newmsg', data);
})
});
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
Now connect any number of clients to your server, provide them a username and start chatting! In the following example, we have connected two clients with names Ayush and Harshit and sent some messages from both the clients -
Useful Links on Socket.IO
Socket.io - Official website for socket.io, its you complete detail including socket.io library and associated documentation.
Socket.io at npm socket.io repository at npm
Useful Books on Socket.IO
Introduction
What Socket.IO is
Socket.IO is a library that enables real-time, bidirectional and event-based communication between the browser and the server.
It consists of:
a Node.js server: Source | API
a Javascript client library for the browser (which can be also run from Node.js): Source | API
How does that work?
The client will try to establish a WebSocket connection if possible, and will fall back on HTTP long polling if not.
WebSocket is a communication protocol which provides a full-duplex and low-latency channel between the server and the browser.
More information can be found here.
So, in the best-case scenario, provided that:
the browser supports WebSocket (97% of all browsers in 2020)
there is no element (proxy, firewall, …) preventing WebSocket connections between the client and the server
you can consider the Socket.IO client as a “slight” wrapper around the WebSocket API.
Instead of writing:
const socket = new WebSocket("ws://localhost:3000");
socket.onopen = () => {
socket.send("Hello!");
};
socket.onmessage = (data) => {
console.log(data);
};
You will have, on the client-side:
const socket = io("ws://localhost:3000");
socket.on("connect", () => {
// either with send()
socket.send("Hello!");
// or with emit() and custom event names
socket.emit("salutations", "Hello!", { "mr": "john" }, Uint8Array.from([1, 2, 3, 4]));
});
// handle the event sent with socket.send()
socket.on("message", data => {
console.log(data);
});
// handle the event sent with socket.emit()
socket.on("greetings", (elem1, elem2, elem3) => {
console.log(elem1, elem2, elem3);
});
The API on the server-side is similar, you also get a socket
object which extends the Node.js EventEmitter class:
const io = require("socket.io")(3000);
io.on("connection", socket => {
// either with send()
socket.send("Hello!");
// or with emit() and custom event names
socket.emit("greetings", "Hey!", { "ms": "jane" }, Buffer.from([4, 3, 3, 1]));
// handle the event sent with socket.send()
socket.on("message", (data) => {
console.log(data);
});
// handle the event sent with socket.emit()
socket.on("salutations", (elem1, elem2, elem3) => {
console.log(elem1, elem2, elem3);
});
});
Socket.IO provides additional features over a plain WebSocket object, which are listed below.
But first, let’s detail what the Socket.IO library is not.
What Socket.IO is not
Socket.IO is NOT a WebSocket implementation.
Although Socket.IO indeed uses WebSocket as a 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");
If you are looking for a plain WebSocket server, please take a look at ws or uWebSockets.js.
There are also talks to include a WebSocket server in the Node.js core.
On the client-side, you might be interested by the robust-websocket package.
Namespaces
A Namespace is a communication channel that allows you to split the logic of your application over a single shared connection (also called “multiplexing”).
Introduction
Each namespace has its own:
event handlers
io.of("/orders").on("connection", (socket) => {
socket.on("order:list", () => {});
socket.on("order:create", () => {});
});
io.of("/users").on("connection", (socket) => {
socket.on("user:list", () => {});
});
rooms
const orderNamespace = io.of("/orders");
orderNamespace.on("connection", (socket) => {
socket.join("room1");
orderNamespace.to("room1").emit("hello");
});
const userNamespace = io.of("/users");
userNamespace.on("connection", (socket) => {
socket.join("room1"); // distinct from the room in the "orders" namespace
userNamespace.to("room1").emit("holà");
});
middlewares
const orderNamespace = io.of("/orders");
orderNamespace.use((socket, next) => {
// ensure the socket has access to the "orders" namespace, and then
next();
});
const userNamespace = io.of("/users");
userNamespace.use((socket, next) => {
// ensure the socket has access to the "users" namespace, and then
next();
});
Possible use cases:
you want to create a special namespace that only authorized users have access to, so the logic related to those users is separated from the rest of the application
const adminNamespace = io.of("/admin");
adminNamespace.use((socket, next) => {
// ensure the user has sufficient rights
next();
});
adminNamespace.on("connection", socket => {
socket.on("delete user", () => {
// ...
});
});
your application has multiple tenants so you want to dynamically create one namespace per tenant
const workspaces = io.of(/^\/\w+$/);
workspaces.on("connection", socket => {
const workspace = socket.nsp;
workspace.emit("hello");
});
Main namespace
Until now, you interacted with the main namespace, called /
.
The io
instance inherits all of its methods:
io.on("connection", (socket) => {});
io.use((socket, next) => { next() });
io.emit("hello");
// are actually equivalent to
io.of("/").on("connection", (socket) => {});
io.of("/").use((socket, next) => { next() });
io.of("/").emit("hello");
Some tutorials may also mention io.sockets
, it’s simply an alias for io.of("/")
.
io.sockets === io.of("/")
Custom namespaces
To set up a custom namespace, you can call the of
function on the server-side:
const nsp = io.of("/my-namespace");
nsp.on("connection", socket => {
console.log("someone connected");
});
nsp.emit("hi", "everyone!");
Client initialization
Same-origin version:
const socket = io(); // or io("/"), the main namespace
const orderSocket = io("/orders"); // the "orders" namespace
const userSocket = io("/users"); // the "users" namespace
Cross-origin/Node.js version:
const socket = io("https://example.com"); // or io("https://example.com/"), the main namespace
const orderSocket = io("https://example.com/orders"); // the "orders" namespace
const userSocket = io("https://example.com/users"); // the "users" namespace
In the example above, only one WebSocket connection will be established, and the packets will automatically be routed to the right namespace.
Please note that multiplexing will be disabled in the following cases:
multiple creation for the same namespace
const socket1 = io();
const socket2 = io(); // no multiplexing, two distinct WebSocket connections
different domains
const socket1 = io("https://first.example.com");
const socket2 = io("https://second.example.com"); // no multiplexing, two distinct WebSocket connections
usage of the forceNew option
const socket1 = io();
const socket2 = io("/admin", { forceNew: true }); // no multiplexing, two distinct WebSocket connections
Dynamic namespaces
It is also possible to dynamically create namespaces, either with a regular expression:
io.of(/^\/dynamic-\d+$/);
or with a function:
io.of((name, auth, next) => {
next(null, true); // or false, when the creation is denied
});
You can have access to the new namespace in the connection
event:
io.of(/^\/dynamic-\d+$/).on("connection", (socket) => {
const namespace = socket.nsp;
});
The return value of the of()
method is what we call the parent namespace, from which you can:
register middlewares
const parentNamespace = io.of(/^\/dynamic-\d+$/);
parentNamespace.use((socket, next) => { next() });
The middleware will automatically be registered on each child namespace.
broadcast events
const parentNamespace = io.of(/^\/dynamic-\d+$/);
parentNamespace.emit("hello"); // will be sent to users in /dynamic-1, /dynamic-2, ...
Complete API
The complete API exposed by the Namespace instance can be found here.
Client API
https://socket.io/docs/v3/client-api/index.html
IO
Exposed as the io
namespace in the standalone build, or the result of calling require("socket.io-client")
.
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io("http://localhost");
</script>
const io = require("socket.io-client");
// or with import syntax
import { io } from "socket.io-client";
io.protocol
(Number)
The protocol revision number (currently: 5).
The protocol defines the format of the packets exchanged between the client and the server.
Both the client and the server must use the same revision in order to understand each other.
You can find more information here.
io([url][, options])
url
(String) (defaults to window.location
)
options
(Object)
forceNew
(Boolean) whether to reuse an existing connection
Returns Socket
Creates a new Manager
for the given URL, and attempts to reuse an existing Manager
for subsequent calls, unless the multiplex
option is passed with false
.
Passing this option is the equivalent of passing "force new connection": true
or forceNew: true
.
A new Socket
instance is returned for the namespace specified by the pathname in the URL, defaulting to /
.
For example, if the url
is http://localhost/users
, a transport connection will be established to http://localhost
and a Socket.IO connection will be established to /users
.
Query parameters can also be provided, either with the query
option or directly in the url (example: http://localhost/users?token=abc
).
const io = require("socket.io-client");
const socket = io("ws://example.com/my-namespace", {
reconnectionDelayMax: 10000,
auth: {
token: "123"
},
query: {
"my-key": "my-value"
}
});
is the short version of:
const { Manager } = require("socket.io-client");
const manager = new Manager("ws://example.com", {
reconnectionDelayMax: 10000,
query: {
"my-key": "my-value"
}
});
const socket = manager.socket("/my-namespace", {
auth: {
token: "123"
}
});
See new Manager(url[, options]) for the list of available options
.
Please note: manager.socket("/my-namespace", options )
will only read the auth
key in the options
object.
query: {…}
and other optional values are only used when passed via a new Manager(uri, options)
instance.
Manager
The Manager
manages the Engine.IO client instance, which is the low-level engine that establishes the connection to the server (by using transports like WebSocket or HTTP long-polling).
The Manager
handles the reconnection logic.
A single Manager
can be used by several Sockets.
Please note that, in most cases, you won’t use the Manager directly but use the Socket instance instead.
new Manager(url[, options])
url
(String)
options
(Object)
Returns Manager
Available options:
Option
Default value
Description
path
/socket.io
name of the path that is captured on the server side
reconnection
true
whether to reconnect automatically
reconnectionAttempts
Infinity
number of reconnection attempts before giving up
reconnectionDelay
1000
how long to initially wait before attempting a new reconnection.
Affected by +/- randomizationFactor
, for example the default initial delay will be between 500 to 1500ms.
reconnectionDelayMax
5000
maximum amount of time to wait between reconnections.
Each attempt increases the reconnection delay by 2x along with a randomization factor.
randomizationFactor
0.5
0 <= randomizationFactor <= 1
timeout
20000
connection timeout before an error
event is emitted
autoConnect
true
by setting this false, you have to call manager.open
whenever you decide it’s appropriate
query
{}
additional query parameters that are sent when connecting a namespace (then found in socket.handshake.query
object on the server-side)
parser
-
the parser to use.
Defaults to an instance of the Parser
that ships with socket.io.
See socket.io-parser.
Available options for the underlying Engine.IO client:
Option
Default value
Description
upgrade
true
whether the client should try to upgrade the transport from long-polling to something better.
forceJSONP
false
forces JSONP for polling transport.
jsonp
true
determines whether to use JSONP when necessary for polling.
If disabled (by settings to false) an error will be emitted (saying “No transports available”) if no other transports are available.
If another transport is available for opening a connection (e.g.
WebSocket) that transport will be used instead.
forceBase64
false
forces base 64 encoding for polling transport even when XHR2 responseType is available and WebSocket even if the used standard supports binary.
enablesXDR
false
enables XDomainRequest for IE8 to avoid loading bar flashing with click sound.
default to false
because XDomainRequest has a flaw of not sending cookie.
timestampRequests
-
whether to add the timestamp with each transport request.
Note: polling requests are always stamped unless this option is explicitly set to false
timestampParam
t
the timestamp parameter
transports
["polling", "websocket"]
a list of transports to try (in order).
Engine
always attempts to connect directly with the first one, provided the feature detection test for it passes.
transportOptions
{}
hash of options, indexed by transport name, overriding the common options for the given transport
rememberUpgrade
false
If true and if the previous websocket connection to the server succeeded, the connection attempt will bypass the normal upgrade process and will initially try websocket.
A connection attempt following a transport error will use the normal upgrade process.
It is recommended you turn this on only when using SSL/TLS connections, or if you know that your network does not block websockets.
onlyBinaryUpgrades
false
whether transport upgrades should be restricted to transports supporting binary data
requestTimeout
0
timeout for xhr-polling requests in milliseconds (0
) (only for polling transport)
protocols
-
a list of subprotocols (see MDN reference) (only for websocket transport)
Node.js-only options for the underlying Engine.IO client:
Option
Default value
Description
agent
false
the http.Agent
to use
pfx
-
Certificate, Private key and CA certificates to use for SSL.
key
-
Private key to use for SSL.
passphrase
-
A string of passphrase for the private key or pfx.
cert
-
Public x509 certificate to use.
ca
-
An authority certificate or array of authority certificates to check the remote host against.
ciphers
-
A string describing the ciphers to use or exclude.
Consult the cipher format list for details on the format.
rejectUnauthorized
true
If true, the server certificate is verified against the list of supplied CAs.
An “error” event is emitted if verification fails.
Verification happens at the connection level, before the HTTP request is sent.
perMessageDeflate
true
parameters of the WebSocket permessage-deflate extension (see ws module api docs).
Set to false
to disable.
extraHeaders
{}
Headers that will be passed for each request to the server (via xhr-polling and via websockets).
These values then can be used during handshake or for special proxies.
forceNode
false
Uses NodeJS implementation for websockets - even if there is a native Browser-Websocket available, which is preferred by default over the NodeJS implementation.
(This is useful when using hybrid platforms like nw.js or electron)
localAddress
-
the local IP address to connect to
manager.reconnection([value])
value
(Boolean)
Returns Manager|Boolean
Sets the reconnection
option, or returns it if no parameters are passed.
manager.reconnectionAttempts([value])
value
(Number)
Returns Manager|Number
Sets the reconnectionAttempts
option, or returns it if no parameters are passed.
manager.reconnectionDelay([value])
value
(Number)
Returns Manager|Number
Sets the reconnectionDelay
option, or returns it if no parameters are passed.
manager.reconnectionDelayMax([value])
value
(Number)
Returns Manager|Number
Sets the reconnectionDelayMax
option, or returns it if no parameters are passed.
manager.timeout([value])
value
(Number)
Returns Manager|Number
Sets the timeout
option, or returns it if no parameters are passed.
manager.open([callback])
callback
(Function)
Returns Manager
If the manager was initiated with autoConnect
to false
, launch a new connection attempt.
The callback
argument is optional and will be called once the attempt fails/succeeds.
manager.connect([callback])
Synonym of manager.open([callback]).
manager.socket(nsp, options)
nsp
(String)
options
(Object)
Returns Socket
Creates a new Socket
for the given namespace.
Only auth
({ auth: {key: "value"} }
) is read from the options
object.
Other keys will be ignored and should be passed when instancing a new Manager(nsp, options)
.
Event: ‘error’
error
(Object) error object
Fired upon a connection error.
Event: ‘reconnect’
attempt
(Number) reconnection attempt number
Fired upon a successful reconnection.
Event: ‘reconnect_attempt’
attempt
(Number) reconnection attempt number
Fired upon an attempt to reconnect.
Event: ‘reconnect_error’
error
(Object) error object
Fired upon a reconnection attempt error.
Event: ‘reconnect_failed’
Fired when couldn’t reconnect within reconnectionAttempts
.
Event: 'ping'
Fired when a ping packet is received from the server.
Socket
A Socket
is the fundamental class for interacting with the server.
A Socket
belongs to a certain Namespace (by default /
) and uses an underlying Manager to communicate.
A Socket
is basically an EventEmitter which sends events to — and receive events from — the server over the network.
socket.emit("hello", { a: "b", c: [] });
socket.on("hey", (...args) => {
// ...
});
More information can be found here.
socket.id
(String)
An unique identifier for the socket session.
Set after the connect
event is triggered, and updated after the reconnect
event.
const socket = io("http://localhost");
console.log(socket.id); // undefined
socket.on("connect", () => {
console.log(socket.id); // "G5p5..."
});
socket.connected
(Boolean)
Whether or not the socket is connected to the server.
const socket = io("http://localhost");
socket.on("connect", () => {
console.log(socket.connected); // true
});
socket.disconnected
(Boolean)
Whether or not the socket is disconnected from the server.
const socket = io("http://localhost");
socket.on("connect", () => {
console.log(socket.disconnected); // false
});
socket.open()
Returns Socket
Manually opens the socket.
const socket = io({
autoConnect: false
});
// ...
socket.open();
It can also be used to manually reconnect:
socket.on("disconnect", () => {
socket.open();
});
socket.connect()
Synonym of socket.open().
socket.send([…args][, ack])
args
ack
(Function)
Returns Socket
Sends a message
event.
See socket.emit(eventName[, …args][, ack]).
socket.emit(eventName[, …args][, ack])
eventName
(String)
args
ack
(Function)
Returns true
Emits an event to the socket identified by the string name.
Any other parameters can be included.
All serializable datastructures are supported, including Buffer
.
socket.emit("hello", "world");
socket.emit("with-binary", 1, "2", { 3: "4", 5: Buffer.from([6, 7, 8]) });
The ack
argument is optional and will be called with the server answer.
socket.emit("ferret", "tobi", (data) => {
console.log(data); // data will be "woot"
});
// server:
// io.on("connection", (socket) => {
// socket.on("ferret", (name, fn) => {
// fn("woot");
// });
// });
socket.on(eventName, callback)
eventName
(String)
callback
(Function)
Returns Socket
Register a new handler for the given event.
socket.on("news", (data) => {
console.log(data);
});
// with multiple arguments
socket.on("news", (arg1, arg2, arg3, arg4) => {
// ...
});
// with callback
socket.on("news", (cb) => {
cb(0);
});
The socket actually inherits every method of the Emitter class, like hasListeners
, once
or off
(to remove an event listener).
socket.onAny(callback)
callback
(Function)
Register a new catch-all listener.
socket.onAny((event, ...args) => {
console.log(`got ${event}`);
});
socket.prependAny(callback)
callback
(Function)
Register a new catch-all listener.
The listener is added to the beginning of the listeners array.
socket.prependAny((event, ...args) => {
console.log(`got ${event}`);
});
socket.offAny([listener])
listener
(Function)
Removes the previously registered listener.
If no listener is provided, all catch-all listeners are removed.
const myListener = () => { /* ...
*/ };
socket.onAny(myListener);
// then, later
socket.offAny(myListener);
socket.offAny();
socket.listenersAny()
Returns Function[]
Returns the list of registered catch-all listeners.
const listeners = socket.listenersAny();
socket.compress(value)
value
(Boolean)
Returns Socket
Sets a modifier for a subsequent event emission that the event data will only be compressed if the value is true
.
Defaults to true
when you don’t call the method.
socket.compress(false).emit("an event", { some: "data" });
socket.close()
Returns Socket
Disconnects the socket manually.
socket.disconnect()
Synonym of socket.close().
Event: 'connect'
Fired upon connection to the Namespace (including a successful reconnect {
// ...
});
// note: you should register event handlers outside of connect,
// so they are not registered again on reconnection
socket.on("myevent", () => {
// ...
});
Event: ‘disconnect’
reason
(String)
Fired upon disconnection.
The list of possible disconnection reasons:
Reason
Description
io server disconnect
The server has forcefully disconnected the socket with socket.disconnect()
io client disconnect
The socket was manually disconnected using socket.disconnect()
ping timeout
The server did not send a PING within the pingInterval + pingTimeout
range
transport close
The connection was closed (example: the user has lost connection, or the network was changed from WiFi to 4G)
transport error
The connection has encountered an error (example: the server was killed during a HTTP long-polling cycle)
In the first two cases (explicit disconnection), the client will not try to reconnect and you need to manually call socket.connect()
.
In all other cases, the client will wait for a small random delay and then try to reconnect:
socket.on("disconnect", (reason) => {
if (reason === "io server disconnect") {
// the disconnection was initiated by the server, you need to reconnect manually
socket.connect();
}
// else the socket will automatically try to reconnect
});
Event: ‘connect_error’
connect_error
(Object) error object
Fired when an namespace middleware error occurs.
socket.on("connect_error", (error) => {
// ...
});
Rooms
https://socket.io/docs/v3/rooms/
A room is an arbitrary channel that sockets can join
and leave
.
It can be used to broadcast events to a subset of clients:
Please note that rooms are a server-only concept (i.e. the client does not have access to the list of rooms it has joined).
Joining and leaving
You can call join
to subscribe the socket to a given channel:
io.on('connection', socket => {
socket.join('some room');
});
And then simply use to
or in
(they are the same) when broadcasting or emitting:
io.to('some room').emit('some event');
You can emit to several rooms at the same time:
io.to('room1').to('room2').to('room3').emit('some event');
In that case, a union is performed: every socket that is at least in one of the rooms will get the event once (even if the socket is in two or more rooms).
You can also broadcast to a room from a given socket:
io.on('connection', function(socket){
socket.to('some room').emit('some event');
});
In that case, every socket in the room excluding the sender will get the event.
To leave a channel you call leave
in the same fashion as join
.
Default room
Each Socket
in Socket.IO is identified by a random, unguessable, unique identifier Socket#id.
For your convenience, each socket automatically joins a room identified by its own id.
This makes it easy to implement private messages:
io.on("connection", socket => {
socket.on("private message", (anotherSocketId, msg) => {
socket.to(anotherSocketId).emit("private message", socket.id, msg);
});
});
Sample use cases
broadcast data to each device / tab of a given user
io.on('connection', async (socket) => {
const userId = await fetchUserId(socket);
socket.join(userId);
// and then later
io.to(userId).emit('hi');
});
send notifications about a given entity
io.on('connection', async (socket) => {
const projects = await fetchProjects(socket);
projects.forEach(project => socket.join('project:' + project.id));
// and then later
io.to('project:4321').emit('project updated');
});
Usage with asynchronous code
Please make sure to use io.to(...).emit(...)
(or socket.to(...).emit(...)
) in a synchronous manner.
Example with callback:
// BAD
const room = socket.to('room1');
saveProduct(() => {
room.emit('product-updated');
});
// GOOD
saveProduct(() => {
socket.to('room1').emit('product-updated');
});
Example with async/await
:
// BAD
io.to('room2').emit('details', await fetchDetails());
// GOOD
const details = await fetchDetails();
io.to('room2').emit('details', details);
Explanation: the to()
method does not return a new object, it mutates an attribute on the io
(respectively, the socket
) object.
This also applies to other broadcast modifiers:
local
broadcast
volatile
Disconnection
Upon disconnection, sockets leave
all the channels they were part of automatically, and no special teardown is needed on your part.
You can fetch the rooms the Socket was in by listening to the disconnecting
event:
io.on('connection', socket => {
socket.on('disconnecting', () => {
console.log(socket.rooms); // the Set contains at least the socket ID
});
socket.on('disconnect', () => {
// socket.rooms.size === 0
});
});
With multiple Socket.IO servers
Like global broadcasting, broadcasting to rooms also works with multiple Socket.IO servers.
You just need to replace the default Adapter by the Redis Adapter.
More information about it here.
Implementation details
The “room” feature is implemented by what we call an Adapter.
This Adapter is a server-side component which is responsible for:
storing the relationships between the Socket instances and the rooms
broadcasting events to all (or a subset of) clients
You can find the code of the default in-memory adapter here.
Basically, it consists in two ES6 Maps:
sids
: Map<SocketId, Set<Room>>
rooms
: Map<Room, Set<SocketId>>
Calling socket.join("the-room")
will result in:
in the ̀sids
Map, adding “the-room” to the Set identified by the socket ID
in the rooms
Map, adding the socket ID in the Set identified by the string “the-room”
Those two maps are then used when broadcasting:
a broadcast to all sockets (io.emit()
) loops through the sids
Map, and send the packet to all sockets
a broadcast to a given room (io.to("room21").emit()
) loops through the Set in the rooms
Map, and sends the packet to all matching sockets
You can access those objects with:
// main namespace
const rooms = io.of("/").adapter.rooms;
const sids = io.of("/").adapter.sids;
// custom namespace
const rooms = io.of("/my-namespace").adapter.rooms;
const sids = io.of("/my-namespace").adapter.sids;
Notes:
those objects are not meant to be directly modified, you should always use socket.join(...)
and socket.leave(...)
instead.
in a multi-server setup, the rooms
and sids
objects are not shared between the Socket.IO servers (a room may only “exist” on one server and not on another).
Room events
Starting with socket.io@3.1.0
, the underlying Adapter will emit the following events:
create-room
(argument: room)
delete-room
(argument: room)
join-room
(argument: room, id)
leave-room
(argument: room, id)
Example:
io.of("/").adapter.on("create-room", (room) => {
console.log(`room ${room} was created`);
});
io.of("/").adapter.on("join-room", (room, id) => {
console.log(`socket ${id} has joined room ${room}`);
});
Using multiple nodes
https://socket.io/docs/v3/using-multiple-nodes/index.html
When deploying multiple Socket.IO servers, there are two things to take care of:
enabling sticky session, if HTTP long-polling is enabled (which is the default): see below
using the Redis adapter (or another compatible Adapter): see below
Sticky load balancing
If you plan to distribute the load of connections among different processes or machines, you have to make sure that all requests associated with a particular session ID reach the process that originated them.
Why is sticky-session required
This is because the HTTP long-polling transport sends multiple HTTP requests during the lifetime of the Socket.IO session.
In fact, Socket.IO could technically work without sticky sessions, with the following synchronization (in dashed lines):
While obviously possible to implement, we think that this synchronization process between the Socket.IO servers would result in a big performance hit for your application.
Remarks:
without enabling sticky-session, you will experience HTTP 400 errors due to “Session ID unknown”
the WebSocket transport does not have this limitation, since it relies on a single TCP connection for the whole session.
Which means that if you disable the HTTP long-polling transport (which is a perfectly valid choice in 2021), you won’t need sticky sessions:
const socket = io("https://io.yourhost.com", {
// WARNING: in that case, there is no fallback to long-polling
transports: [ "websocket" ] // or [ "websocket", "polling" ] (the order matters)
});
Documentation: transports
Enabling sticky-session
To achieve sticky-session, there are two main solutions:
routing clients based on a cookie (recommended solution)
routing clients based on their originating address
You will find below some examples with common load-balancing solutions:
NginX (IP-based)
Apache HTTPD (cookie-based)
HAProxy (cookie-based)
Traefik (cookie-based)
Node.js cluster
module
For other platforms, please refer to the relevant documentation:
Kubernetes: https://kubernetes.github.io/ingress-nginx/examples/affinity/cookie/
AWS (Application Load Balancers): https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html#sticky-sessions
GCP: https://cloud.google.com/load-balancing/docs/backend-service#session_affinity
Heroku: https://devcenter.heroku.com/articles/session-affinity
Important note: if you are in a CORS situation (the front domain is different from the server domain) and session affinity is achieved with a cookie, you need to allow credentials:
Server
const io = require("socket.io")(httpServer, {
cors: {
origin: "https://front-domain.com",
methods: ["GET", "POST"],
credentials: true
}
});
Client
const io = require("socket.io-client");
const socket = io("https://server-domain.com", {
withCredentials: true
});
Without it, the cookie will not be sent by the browser and you will experience HTTP 400 “Session ID unknown” responses.
More information here.
NginX configuration
Within the http { }
section of your nginx.conf
file, you can declare a upstream
section with a list of Socket.IO process you want to balance load between:
http {
server {
listen 3000;
server_name io.yourhost.com;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://nodes;
# enable WebSockets
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
upstream nodes {
# enable sticky session with either "hash" (uses the complete IP address)
hash $remote_addr consistent;
# or "ip_hash" (uses the first three octets of the client IPv4 address, or the entire IPv6 address)
# ip_hash;
# or "sticky" (needs commercial subscription)
# sticky cookie srv_id expires=1h domain=.example.com path=/;
server app01:3000;
server app02:3000;
server app03:3000;
}
}
Notice the hash
instruction that indicates the connections will be sticky.
Make sure you also configure worker_processes
in the topmost level to indicate how many workers NginX should use.
You might also want to look into tweaking the worker_connections
setting within the events { }
block.
Links:
Example
NginX Documentation
Apache HTTPD configuration
Header add Set-Cookie "SERVERID=sticky.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
<Proxy "balancer://nodes_polling">
BalancerMember "http://app01:3000" route=app01
BalancerMember "http://app02:3000" route=app02
BalancerMember "http://app03:3000" route=app03
ProxySet stickysession=SERVERID
</Proxy>
<Proxy "balancer://nodes_ws">
BalancerMember "ws://app01:3000" route=app01
BalancerMember "ws://app02:3000" route=app02
BalancerMember "ws://app03:3000" route=app03
ProxySet stickysession=SERVERID
</Proxy>
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) balancer://nodes_ws/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /(.*) balancer://nodes_polling/$1 [P,L]
ProxyTimeout 3
Links:
Example
Documentation
HAProxy configuration
# Reference: http://blog.haproxy.com/2012/11/07/websockets-load-balancing-with-haproxy/
listen chat
bind *:80
default_backend nodes
backend nodes
option httpchk HEAD /health
http-check expect status 200
cookie io prefix indirect nocache # using the `io` cookie set upon handshake
server app01 app01:3000 check cookie app01
server app02 app02:3000 check cookie app02
server app03 app03:3000 check cookie app03
Links:
Example
Documentation
Traefik
Using container labels:
# docker-compose.yml
services:
traefik:
image: traefik:2.4
volumes:
- /var/run/docker.sock:/var/run/docker.sock
links:
- server
server:
image: my-image:latest
labels:
- "traefik.http.routers.my-service.rule=PathPrefix(`/`)"
- traefik.http.services.my-service.loadBalancer.sticky.cookie.name=server_id
- traefik.http.services.my-service.loadBalancer.sticky.cookie.httpOnly=true
With the File provider:
## Dynamic configuration
http:
services:
my-service:
rule: "PathPrefix(`/`)"
loadBalancer:
sticky:
cookie:
name: server_id
httpOnly: true
Links:
Example
Documentation
Using Node.js Cluster
Just like NginX, Node.js comes with built-in clustering support through the cluster
module.
There are several solutions, depending on your use case:
NPM package
How it works
@socket.io/sticky
the routing is based on the sid
query parameter
sticky-session
the routing is based on connection.remoteAddress
socketio-sticky-session
the routing based on the x-forwarded-for
header)
Example with @socket.io/sticky
:
const cluster = require("cluster");
const http = require("http");
const { Server } = require("socket.io");
const redisAdapter = require("socket.io-redis");
const numCPUs = require("os").cpus().length;
const { setupMaster, setupWorker } = require("@socket.io/sticky");
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
const httpServer = http.createServer();
setupMaster(httpServer, {
loadBalancingMethod: "least-connection", // either "random", "round-robin" or "least-connection"
});
httpServer.listen(3000);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on("exit", (worker) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork();
});
} else {
console.log(`Worker ${process.pid} started`);
const httpServer = http.createServer();
const io = new Server(httpServer);
io.adapter(redisAdapter({ host: "localhost", port: 6379 }));
setupWorker(io);
io.on("connection", (socket) => {
/* ...
*/
});
}
Passing events between nodes
The Redis adapter
Now that you have multiple Socket.IO nodes accepting connections, if you want to broadcast events to all clients (or to the clients in a certain room) you’ll need some way of passing messages between processes or computers.
The interface in charge of routing messages is what we call the Adapter.
You can implement your own on top of the socket.io-adapter (by inheriting from it) or you can use the one we provide on top of Redis: socket.io-redis:
const io = require("socket.io")(3000);
const redis = require("socket.io-redis");
io.adapter(redis({ host: "localhost", port: 6379 }));
Then the following call:
io.emit("hi", "all sockets");
will be broadcast to every clients through the Pub/Sub mechanism of Redis:
Sending messages from the outside world
Using the Redis adapter has another benefit: you can now emit events from outside the context of your Socket.IO processes.
This emitter is available in several languages:
Javascript: https://github.com/socketio/socket.io-emitter
Java: https://github.com/sunsus/socket.io-java-emitter
Python: https://pypi.org/project/socket.io-emitter/
PHP: https://github.com/rase-/socket.io-php-emitter
Golang: https://github.com/yosuke-furukawa/socket.io-go-emitter
Perl: https://metacpan.org/pod/SocketIO::Emitter
Rust: https://github.com/epli2/socketio-rust-emitter
The Socket instance (client-</side)
https://socket.io/docs/v3/client-socket-instance/
Besides emitting and listening to events, the Socket instance has a few attributes that may be of use in your application:
Socket#id
Each new connection is assigned a random 20-characters identifier.
This identifier is synced with the value on the server-side.
// server-side
io.on("connection", (socket) => {
console.log(socket.id); // x8WIv7-mJelg7on_ALbx
});
// client-side
socket.on("connect", () => {
console.log(socket.id); // x8WIv7-mJelg7on_ALbx
});
socket.on("disconnect", () => {
console.log(socket.id); // undefined
});
Socket#connected
This attribute describes whether the socket is currently connected to the server.
socket.on("connect", () => {
console.log(socket.connected); // true
});
socket.on("disconnect", () => {
console.log(socket.connected); // false
});
Lifecycle
Events
The Socket instance emits three special events:
connect
connect_error
disconnect
Please note that since Socket.IO v3, the Socket instance does not emit any event related to the reconnection logic anymore.
You can listen to the events on the Manager instance directly:
socket.io.on("reconnection_attempt", () => {
// ...
});
socket.io.on("reconnect", () => {
// ...
});
More information can be found in the migration guide.
connect
This event is fired by the Socket instance upon connection and reconnection.
socket.on("connect", () => {
// ...
});
Please note that you shouldn’t register event handlers in the connect
handler itself, as a new handler will be registered every time the Socket reconnects:
// BAD
socket.on("connect", () => {
socket.on("data", () => { /* ...
*/ });
});
// GOOD
socket.on("connect", () => {
// ...
});
socket.on("data", () => { /* ...
*/ });
connect_error
This event is fired when:
the low-level connection cannot be established
the connection is denied by the server in a middleware function
In the first case, the Socket will automatically try to reconnect, after a given delay.
In the latter case, you need to manually reconnect.
You might need to update the credentials:
// either by directly modifying the `auth` attribute
socket.on("connect_error", () => {
socket.auth.token = "abcd";
socket.connect();
});
// or if the `auth` attribute is a function
const socket = io({
auth: (cb) => {
cb(localStorage.getItem("token"));
}
});
socket.on("connect_error", () => {
setTimeout(() => {
socket.connect();
}, 1000);
});
disconnect
This event is fired upon disconnection.
socket.on("disconnect", (reason) => {
// ...
});
Here is the list of possible reasons:
Reason
Description
io server disconnect
The server has forcefully disconnected the socket with socket.disconnect()
io client disconnect
The socket was manually disconnected using socket.disconnect()
ping timeout
The server did not send a PING within the pingInterval + pingTimeout
range
transport close
The connection was closed (example: the user has lost connection, or the network was changed from WiFi to 4G)
transport error
The connection has encountered an error (example: the server was killed during a HTTP long-polling cycle)
In the first two cases (explicit disconnection), the client will not try to reconnect and you need to manually call socket.connect()
.
In all other cases, the client will wait for a small random delay and then try to reconnect:
socket.on("disconnect", (reason) => {
if (reason === "io server disconnect") {
// the disconnection was initiated by the server, you need to reconnect manually
socket.connect();
}
// else the socket will automatically try to reconnect
});
Note: those events, along with disconnecting
, newListener
and removeListener
, are special events that shouldn’t be used in your application:
// BAD, will throw an error
socket.emit("disconnect");
Complete API
The complete API exposed by the Socket instance can be found here.
Server API
Server
Exposed by require("socket.io")
.
Related documentation pages:
installation
initialization
details of the server instance
new Server(httpServer[, options])
httpServer
(http.Server) the server to bind to.
options
(Object)
Works with and without new
:
const io = require("socket.io")();
// or
const { Server } = require("socket.io");
const io = new Server();
Available options:
Option
Default value
Description
path
/socket.io
name of the path to capture
serveClient
true
whether to serve the client files
adapter
-
the adapter to use.
Defaults to an instance of the Adapter
that ships with socket.io which is memory based.
See socket.io-adapter
parser
-
the parser to use.
Defaults to an instance of the Parser
that ships with socket.io.
See socket.io-parser.
connectTimeout
45000
the number of ms before closing a client that has not successfully joined a namespace.
Available options for the underlying Engine.IO server:
Option
Default value
Description
pingTimeout
5000
how many ms without a pong packet to consider the connection closed
pingInterval
25000
how many ms before sending a new ping packet
upgradeTimeout
10000
how many ms before an uncompleted transport upgrade is cancelled
maxHttpBufferSize
1e6
how many bytes or characters a message can be, before closing the session (to avoid DoS).
allowRequest
A function that receives a given handshake or upgrade request as its first parameter, and can decide whether to continue or not.
The second argument is a function that needs to be called with the decided information: fn(err, success)
, where success
is a boolean value where false means that the request is rejected, and err is an error code.
transports
["polling", "websocket"]
transports to allow connections to
allowUpgrades
true
whether to allow transport upgrades
perMessageDeflate
false
parameters of the WebSocket permessage-deflate extension (see ws module api docs).
Set to true
to enable.
httpCompression
true
parameters of the http compression for the polling transports (see zlib api docs).
Set to false
to disable.
wsEngine
ws
what WebSocket server implementation to use.
Specified module must conform to the ws
interface (see ws module api docs).
Default value is ws
.
An alternative c++ addon is also available by installing the eiows module.
cors
the list of options that will be forwarded to the cors module
cookie
the list of options that will be forwarded to the cookie module
allowEIO3
false
whether to enable compatibility with Socket.IO v2 clients
More information here.
new Server(port[, options])
port
(Number) a port to listen to (a new http.Server
will be created)
options
(Object)
See above for the list of available options
.
const io = require("socket.io")(3000, {
path: "/test",
serveClient: false,
// below are engine.IO options
pingInterval: 10000,
pingTimeout: 5000,
cookie: false
});
new Server(options)
options
(Object)
See above for the list of available options
.
const io = require("socket.io")({
path: "/test",
serveClient: false,
});
// either
const server = require("http").createServer();
io.attach(server, {
pingInterval: 10000,
pingTimeout: 5000,
cookie: false
});
server.listen(3000);
// or
io.attach(3000, {
pingInterval: 10000,
pingTimeout: 5000,
cookie: false
});
server.sockets
(Namespace)
An alias for the default (/
) namespace.
io.sockets.emit("hi", "everyone");
// is equivalent to
io.of("/").emit("hi", "everyone");
server.serveClient([value])
value
(Boolean)
Returns Server|Boolean
If value
is true
the attached server (see Server#attach
) will serve the client files.
Defaults to true
.
This method has no effect after attach
is called.
If no arguments are supplied this method returns the current value.
// pass a server and the `serveClient` option
const io = require("socket.io")(http, { serveClient: false });
// or pass no server and then you can call the method
const io = require("socket.io")();
io.serveClient(false);
io.attach(http);
server.path([value])
value
(String)
Returns Server|String
Sets the path value
under which engine.io
and the static files will be served.
Defaults to /socket.io
.
If no arguments are supplied this method returns the current value.
const io = require("socket.io")();
io.path("/myownpath");
// client-side
const socket = io({
path: "/myownpath"
});
server.adapter([value])
value
(Adapter)
Returns Server|Adapter
Sets the adapter value
.
Defaults to an instance of the Adapter
that ships with socket.io which is memory based.
See socket.io-adapter.
If no arguments are supplied this method returns the current value.
const io = require("socket.io")(3000);
const redis = require("socket.io-redis");
io.adapter(redis({ host: "localhost", port: 6379 }));
server.attach(httpServer[, options])
httpServer
(http.Server) the server to attach to
options
(Object)
Attaches the Server
to an engine.io instance on httpServer
with the supplied options
(optionally).
server.attach(port[, options])
port
(Number) the port to listen on
options
(Object)
Attaches the Server
to an engine.io instance on a new http.Server with the supplied options
(optionally).
server.listen(httpServer[, options])
Synonym of server.attach(httpServer[, options]).
server.listen(port[, options])
Synonym of server.attach(port[, options]).
server.bind(engine)
engine
(engine.Server)
Returns Server
Advanced use only.
Binds the server to a specific engine.io Server
(or compatible API) instance.
server.onconnection(socket)
socket
(engine.Socket)
Returns Server
Advanced use only.
Creates a new socket.io
client from the incoming engine.io (or compatible API) Socket
.
server.of(nsp)
nsp
(String|RegExp|Function)
Returns Namespace
Initializes and retrieves the given Namespace
by its pathname identifier nsp
.
If the namespace was already initialized it returns it immediately.
const adminNamespace = io.of("/admin");
A regex or a function can also be provided, in order to create namespace in a dynamic way:
const dynamicNsp = io.of(/^\/dynamic-\d+$/).on("connection", (socket) => {
const newNamespace = socket.nsp; // newNamespace.name === "/dynamic-101"
// broadcast to all clients in the given sub-namespace
newNamespace.emit("hello");
});
// client-side
const socket = io("/dynamic-101");
// broadcast to all clients in each sub-namespace
dynamicNsp.emit("hello");
// use a middleware for each sub-namespace
dynamicNsp.use((socket, next) => { /* ... */ });
With a function:
io.of((name, query, next) => {
// the checkToken method must return a boolean, indicating whether the client is able to connect or not.
next(null, checkToken(query.token));
}).on("connection", (socket) => { /* ... */ });
server.close([callback])
callback
(Function)
Closes the Socket.IO server.
The callback
argument is optional and will be called when all connections are closed.
Note: this also closes the underlying HTTP server.
const Server = require("socket.io");
const PORT = 3030;
const server = require("http").Server();
const io = Server(PORT);
io.close(); // Close current server
server.listen(PORT); // PORT is free to use
io = Server(server);
server.engine.generateId
Overwrites the default method to generate your custom socket id.
The function is called with a node request object (http.IncomingMessage
) as first parameter.
const uuid = require("uuid");
io.engine.generateId = (req) => {
return uuid.v4(); // must be unique across all Socket.IO servers
}
Namespace
Represents a pool of sockets connected under a given scope identified by a pathname (eg: /chat
).
More information can be found here.
namespace.name
(String)
The namespace identifier property.
namespace.sockets
(Map<SocketId, Socket>)
A map of Socket instances that are connected to this namespace.
// number of sockets in this namespace (on this node)
const socketCount = io.of("/admin").sockets.size;
namespace.adapter
(Adapter)
The “Adapter” used for the namespace.
Useful when using the Adapter
based on Redis, as it exposes methods to manage sockets and rooms across your cluster.
Note: the adapter of the main namespace can be accessed with io.of("/").adapter
.
Please see the explanation here.
namespace.to(room)
room
(String)
Returns Namespace
for chaining
Sets a modifier for a subsequent event emission that the event will only be broadcasted to clients that have joined the given room
.
To emit to multiple rooms, you can call to
several times.
const io = require("socket.io")();
const adminNamespace = io.of("/admin");
adminNamespace.to("level1").emit("an event", { some: "data" });
namespace.in(room)
Synonym of namespace.to(room).
namespace.emit(eventName[, …args])
eventName
(String)
args
Returns true
Emits an event to all connected clients.
The following two are equivalent:
const io = require("socket.io")();
io.emit("an event sent to all connected clients"); // main namespace
const chat = io.of("/chat");
chat.emit("an event sent to all connected clients in chat namespace");
Note: acknowledgements are not supported when emitting from namespace.
namespace.allSockets()
Returns Promise<Set<SocketId>>
Gets a list of socket IDs connected to this namespace (across all nodes if applicable).
// all sockets in the main namespace
const ids = await io.allSockets();
// all sockets in the main namespace and in the "user:1234" room
const ids = await io.in("user:1234").allSockets();
// all sockets in the "chat" namespace
const ids = await io.of("/chat").allSockets();
// all sockets in the "chat" namespace and in the "general" room
const ids = await io.of("/chat").in("general").allSockets();
namespace.use(fn)
fn
(Function)
Registers a middleware, which is a function that gets executed for every incoming Socket
, and receives as parameters the socket and a function to optionally defer execution to the next registered middleware.
Errors passed to middleware callbacks are sent as special connect_error
packets to clients.
// server-side
io.use((socket, next) => {
const err = new Error("not authorized");
err.data = { content: "Please retry later" }; // additional details
next(err);
});
// client-side
socket.on("connect_error", err => {
console.log(err instanceof Error); // true
console.log(err.message); // not authorized
console.log(err.data); // { content: "Please retry later" }
});
Event: ‘connection’
socket
(Socket) socket connection with client
Fired upon a connection from client.
io.on("connection", (socket) => {
// ...
});
io.of("/admin").on("connection", (socket) => {
// ...
});
Event: ‘connect’
Synonym of Event: “connection”.
Flag: ‘volatile’
Sets a modifier for a subsequent event emission that the event data may be lost if the clients are not ready to receive messages (because of network slowness or other issues, or because they’re connected through long polling and is in the middle of a request-response cycle).
io.volatile.emit("an event", { some: "data" }); // the clients may or may not receive it
Flag: ‘local’
Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node (when the Redis adapter is used).
io.local.emit("an event", { some: "data" });
Socket
A Socket
is the fundamental class for interacting with browser clients.
A Socket
belongs to a certain Namespace
(by default /
) and uses an underlying Client
to communicate.
It should be noted the Socket
doesn’t relate directly to the actual underlying TCP/IP socket
and it is only the name of the class.
Within each Namespace
, you can also define arbitrary channels (called room
) that the Socket
can join and leave.
That provides a convenient way to broadcast to a group of Socket
s (see Socket#to
below).
The Socket
class inherits from EventEmitter.
The Socket
class overrides the emit
method, and does not modify any other EventEmitter
method.
All methods documented here which also appear as EventEmitter
methods (apart from emit
) are implemented by EventEmitter
, and documentation for EventEmitter
applies.
More information can be found here.
socket.id
(String)
A unique identifier for the session, that comes from the underlying Client
.
socket.rooms
(Set)
A Set of strings identifying the rooms this client is in.
io.on("connection", (socket) => {
console.log(socket.rooms); // Set { <socket.id> }
socket.join("room1");
console.log(socket.rooms); // Set { <socket.id>, "room1" }
});
socket.client
(Client)
A reference to the underlying Client
object.
socket.conn
(engine.Socket)
A reference to the underlying Client
transport connection (engine.io Socket
object).
This allows access to the IO transport layer, which still (mostly) abstracts the actual TCP/IP socket.
socket.request
(Request)
A getter proxy that returns the reference to the request
that originated the underlying engine.io Client
.
Useful for accessing request headers such as Cookie
or User-Agent
.
const cookie = require("cookie");
io.on("connection", (socket) => {
const cookies = cookie.parse(socket.request.headers.cookie || ");
});
socket.handshake
(Object)
The handshake details:
{
headers: /* the headers sent as part of the handshake */,
time: /* the date of creation (as string) */,
address: /* the ip of the client */,
xdomain: /* whether the connection is cross-domain */,
secure: /* whether the connection is secure */,
issued: /* the date of creation (as unix timestamp) */,
url: /* the request URL string */,
query: /* the query params of the first request */,
auth: /* the authentication payload */
}
Usage:
io.use((socket, next) => {
let handshake = socket.handshake;
// ...
});
io.on("connection", (socket) => {
let handshake = socket.handshake;
// ...
});
socket.send([…args][, ack])
args
ack
(Function)
Returns Socket
Sends a message
event.
See socket.emit(eventName[, …args][, ack]).
socket.emit(eventName[, …args][, ack])
(overrides EventEmitter.emit
)
eventName
(String)
args
ack
(Function)
Returns true
Emits an event to the socket identified by the string name.
Any other parameters can be included.
All serializable datastructures are supported, including Buffer
.
socket.emit("hello", "world");
socket.emit("with-binary", 1, "2", { 3: "4", 5: Buffer.from([6]) });
The ack
argument is optional and will be called with the client’s answer.
io.on("connection", (socket) => {
socket.emit("an event", { some: "data" });
socket.emit("ferret", "tobi", (data) => {
console.log(data); // data will be "woot"
});
// the client code
// client.on("ferret", (name, fn) => {
// fn("woot");
// });
});
socket.on(eventName, callback)
(inherited from EventEmitter
)
eventName
(String)
callback
(Function)
Returns Socket
Register a new handler for the given event.
socket.on("news", (data) => {
console.log(data);
});
// with several arguments
socket.on("news", (arg1, arg2, arg3) => {
// ...
});
// or with acknowledgement
socket.on("news", (data, callback) => {
callback(0);
});
socket.once(eventName, listener)
socket.removeListener(eventName, listener)
socket.removeAllListeners([eventName])
socket.eventNames()
Inherited from EventEmitter
(along with other methods not mentioned here).
See the Node.js documentation for the events module.
socket.onAny(callback)
callback
(Function)
Register a new catch-all listener.
socket.onAny((event, ...args) => {
console.log(`got ${event}`);
});
socket.prependAny(callback)
callback
(Function)
Register a new catch-all listener.
The listener is added to the beginning of the listeners array.
socket.prependAny((event, ...args) => {
console.log(`got ${event}`);
});
socket.offAny([listener])
listener
(Function)
Removes the previously registered listener.
If no listener is provided, all catch-all listeners are removed.
const myListener = () => { /* ... */ };
socket.onAny(myListener);
// then, later
socket.offAny(myListener);
socket.offAny();
socket.listenersAny()
Returns Function[]
Returns the list of registered catch-all listeners.
const listeners = socket.listenersAny();
socket.join(room)
room
(string) | (string[])
Returns void
| Promise
Adds the socket to the given room
or to the list of rooms.
io.on("connection", (socket) => {
socket.join("room 237");
console.log(socket.rooms); // Set { <socket.id>, "room 237" }
socket.join(["room 237", "room 238"]);
io.to("room 237").emit("a new user has joined the room"); // broadcast to everyone in the room
});
The mechanics of joining rooms are handled by the Adapter
that has been configured (see Server#adapter
above), defaulting to socket.io-adapter.
For your convenience, each socket automatically joins a room identified by its id (see Socket#id
).
This makes it easy to broadcast messages to other sockets:
io.on("connection", (socket) => {
socket.on("say to someone", (id, msg) => {
// send a private message to the socket with the given id
socket.to(id).emit("my message", msg);
});
});
socket.leave(room)
room
(String)
Returns void
| Promise
Removes the socket from the given room
.
io.on("connection", (socket) => {
socket.leave("room 237");
io.to("room 237").emit(`user ${socket.id} has left the room`);
});
Rooms are left automatically upon disconnection.
socket.to(room)
room
(String)
Returns Socket
for chaining
Sets a modifier for a subsequent event emission that the event will only be broadcasted to clients that have joined the given room
(the socket itself being excluded).
To emit to multiple rooms, you can call to
several times.
io.on("connection", (socket) => {
// to one room
socket.to("others").emit("an event", { some: "data" });
// to multiple rooms
socket.to("room1").to("room2").emit("hello");
// a private message to another socket
socket.to(/* another socket id */).emit("hey");
// WARNING: `socket.to(socket.id).emit()` will NOT work, as it will send to everyone in the room
// named `socket.id` but the sender.
Please use the classic `socket.emit()` instead.
});
Note: acknowledgements are not supported when broadcasting.
socket.in(room)
Synonym of socket.to(room).
socket.compress(value)
value
(Boolean) whether to following packet will be compressed
Returns Socket
for chaining
Sets a modifier for a subsequent event emission that the event data will only be compressed if the value is true
.
Defaults to true
when you don’t call the method.
io.on("connection", (socket) => {
socket.compress(false).emit("uncompressed", "that's rough");
});
socket.disconnect(close)
close
(Boolean) whether to close the underlying connection
Returns Socket
Disconnects this socket.
If value of close is true
, closes the underlying connection.
Otherwise, it just disconnects the namespace.
io.on("connection", (socket) => {
setTimeout(() => socket.disconnect(true), 5000);
});
Flag: ‘broadcast’
Sets a modifier for a subsequent event emission that the event data will only be broadcast to every sockets but the sender.
io.on("connection", (socket) => {
socket.broadcast.emit("an event", { some: "data" }); // everyone gets it but the sender
});
Flag: ‘volatile’
Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to receive messages (because of network slowness or other issues, or because they’re connected through long polling and is in the middle of a request-response cycle).
io.on("connection", (socket) => {
socket.volatile.emit("an event", { some: "data" }); // the client may or may not receive it
});
Event: ‘disconnect’
reason
(String) the reason of the disconnection (either client or server-side)
Fired upon disconnection.
io.on("connection", (socket) => {
socket.on("disconnect", (reason) => {
// ...
});
});
Possible reasons:
Reason
Description
server namespace disconnect
The socket was forcefully disconnected with socket.disconnect()
client namespace disconnect
The client has manually disconnected the socket using socket.disconnect()
server shutting down
The server is, well, shutting down
ping timeout
The client did not send a PONG packet in the pingTimeout
delay
transport close
The connection was closed (example: the user has lost connection, or the network was changed from WiFi to 4G)
transport error
The connection has encountered an error
Event: ‘disconnecting’
reason
(String) the reason of the disconnection (either client or server-side)
Fired when the client is going to be disconnected (but hasn’t left its rooms
yet).
io.on("connection", (socket) => {
socket.on("disconnecting", (reason) => {
console.log(socket.rooms); // Set { ... }
});
});
Note: those events, along with connect
, connect_error
, newListener
and removeListener
, are special events that shouldn’t be used in your application:
// BAD, will throw an error
socket.emit("disconnect");
Client
The Client
class represents an incoming transport (engine.io) connection.
A Client
can be associated with many multiplexed Socket
s that belong to different Namespace
s.
client.conn
(engine.Socket)
A reference to the underlying engine.io
Socket
connection.
client.request
(Request)
A getter proxy that returns the reference to the request
that originated the engine.io connection.
Useful for accessing request headers such as Cookie
or User-Agent
.
Emit cheatsheet
io.on("connection", (socket) => {
// sending to the client
socket.emit("hello", "can you hear me?", 1, 2, "abc");
// sending to all clients except sender
socket.broadcast.emit("broadcast", "hello friends!");
// sending to all clients in "game" room except sender
socket.to("game").emit("nice game", "let's play a game");
// sending to all clients in "game1" and/or in "game2" room, except sender
socket.to("game1").to("game2").emit("nice game", "let's play a game (too)");
// sending to all clients in "game" room, including sender
io.in("game").emit("big-announcement", "the game will start soon");
// sending to all clients in namespace "myNamespace", including sender
io.of("myNamespace").emit("bigger-announcement", "the tournament will start soon");
// sending to a specific room in a specific namespace, including sender
io.of("myNamespace").to("room").emit("event", "message");
// sending to individual socketid (private message)
io.to(socketId).emit("hey", "I just met you");
// WARNING: `socket.to(socket.id).emit()` will NOT work, as it will send to everyone in the room
// named `socket.id` but the sender. Please use the classic `socket.emit()` instead.
// sending with acknowledgement
socket.emit("question", "do you think so?", (answer) => {});
// sending without compression
socket.compress(false).emit("uncompressed", "that's rough");
// sending a message that might be dropped if the client is not ready to receive messages
socket.volatile.emit("maybe", "do you really need it?");
// sending to all clients on this node (when using multiple nodes)
io.local.emit("hi", "my lovely babies");
// sending to all connected clients
io.emit("an event sent to all connected clients");
});
Note: The following events are reserved and should not be used as event names by your application:
connect
connect_error
disconnect
disconnecting
newListener
removeListener
// BAD, will throw an error
socket.emit(disconnecting);
List of Socket.io Events
Client-side events for socket.io
object:
connect
. Fired upon a successful connection.
connect_error
. Fired upon a connection error.
Parameters:
Object
error object
connect_timeout
. Fired upon a connection timeout.
reconnect
. Fired upon a successful reconnection.
Parameters:
Number
reconnection attempt number
reconnect_attempt
. Fired upon an attempt to reconnect.
reconnecting
. Fired upon an attempt to reconnect.
Parameters:
Number
reconnection attempt number
reconnect_error
. Fired upon a reconnection attempt error.
Parameters:
Object
error object
reconnect_failed
. Fired when couldn’t reconnect within reconnectionAttempts
Client-side events for socket
object:
Client-side events for socket
object:
connect
. Fired upon connecting.
error
. Fired upon a connection error
Parameters:
Object
error data
disconnect
. Fired upon a disconnection.
reconnect
. Fired upon a successful reconnection.
Parameters:
Number
reconnection attempt number
reconnect_attempt
. Fired upon an attempt to reconnect.
reconnecting
. Fired upon an attempt to reconnect.
Parameters:
Number
reconnection attempt number
reconnect_error
. Fired upon a reconnection attempt error.
Parameters:
Object
error object
reconnect_failed
. Fired when couldn’t reconnect within reconnectionAttempts
Server-side events:
Server-side events:
connection
/ connect
. Fired upon a connection.
Parameters:
Socket
the incoming socket.
Edit:
For the current version (1.3.4
) the reconnect_attempt
and reconnecting
client-side events are synonyms.