Javascript Web Socket
What are Websockets?
Over the past few years, a new type of communication started to emerge on the web and in mobile apps, called websockets. This protocol has been long-awaited and was finally standardized by the IETF in 2011, paving the way for widespread use.
This new protocol opens up a much faster and more efficient line of communication to the client. Like HTTP, websockets run on top of a TCP connection, but they're much faster because we don't have to open a new connection for each time we want to send a message since the connection is kept alive for as long as the server or client wants.
Even better, since the connection never dies we finally have full-duplex communication available to us, meaning we can push data to the client instead of having to wait for them to ask for data from the server. This allows for data to be communicated back and forth, which is ideal for things like real-time chat applications, or even games.
How do Websockets Work?
At its core, a websocket is just a TCP connection that allows for full-duplex communication, meaning either side of the connection can send data to the other, even at the same time.
To establish this connection, the protocol actually initiates the handshake as a normal HTTP request, but then gets 'upgraded' using the upgrade request HTTP header, like this:
GET /ws/chat HTTP/1.1
Host: chat.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: q1PZLMeDL4EwLkw4GGhADm==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 15
Origin: http://example.com
The server then sends back an HTTP 101 "Switching Protocols" response, acknowledging that the connection is going to be upgraded. Once the this connection has been made, it switches to a bidirectional binary protocol, at which point the application data can be sent.
All the protocol has to do to keep the connection open is send some ping/pong packets, which tells the other side that they're still there. To close the connection, a simple "close connection" packet is sent.
Some Websocket Examples
Of the many different websocket libraries for Node.js available to us, I chose to use socket.io throughout this article because it seems to be the most popular and is, in my opinion, the easiest to use. While each library has its own unique API, they also have many similarities since they're all built on top of the same protocol, so hopefully you'll be able to translate the code below to any library you want to use.
For the HTTP server, I'll be using Express, which is the most popular Node server out there. Keep in mind that you can also just use the plain http module if you don't need all of the features of Express. Although, since most applications will use Express, that's what we'll be using as well.
Note: Throughout these examples I have removed much of the boilerplate code, so some of this code won't work out of the box. In most cases you can refer to the first example to get the boilerplate code.
Establishing the Connection
In order for a connection to be established between the client and server, the server must do two things:
Hook in to the HTTP server to handle websocket connections
Serve up the socket.io.js client library as a static resource
In the code below, you can see item (1) being done on the 3rd line. Item (2) is done for
you (by default) by the socket.io library and is served on the path
/socket.io/socket.io.js
. By default, all websocket connections and resources are
served within the /socket.io path.
Server
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
app.get('/', function(req, res) {
res.sendFile(__dirname + '/index.html');
});
server.listen(8080);
The client needs to do two things as well:
Load the library from the server
Call .connect()
to the server address and websocket path
Client
If you navigate your browser to http://localhost:8080
and inspect the HTTP requests behind the
scenes using your browser's developer tools, you should be able to see the handshake being
executed, including the GET requests and resulting HTTP 101 Switching Protocols response.
Sending Data from Server to Client Okay, now on to some of the more interesting parts. In this example we'll be showing you the most common way to send data from the server to the client. In this case, we'll be sending a message to a channel, which can be subscribed to and received by the client. So, for example, a client application might be listening on the 'announcements' channel, which would contain notifications about system-wide events, like when a user joins a chat room.
On the server this is done by waiting for the new connection to be established, then by calling the socket.emit() method to send a message to all connected clients.
Server
io.on('connection', function(socket) {
socket.emit('announcements', { message: 'A new user has joined!' });
});
Client
Sending Data from Client to Server
But what would we do when we want to send data the other way, from client to server? It is very similar to the last example, using both the socket.emit() and socket.on() methods.
Server
io.on('connection', function(socket) { socket.on('event', function(data) { console.log('A client sent us this dumb message:', data.message); }); });
Client
Counting Connected Users
This is a nice example to learn since it shows a few more features of socket.io (like the disconnect event), it's easy to implement, and it is applicable to many webapps. We'll be using the connection and disconnect events to count the number of active users on our site, and we'll update all users with the current count.
Server
var numClients = 0;
io.on('connection', function(socket) {
numClients++;
io.emit('stats', { numClients: numClients });
console.log('Connected clients:', numClients);
socket.on('disconnect', function() {
numClients--;
io.emit('stats', { numClients: numClients });
console.log('Connected clients:', numClients);
});
});
Client
A much simpler way to track the user count on the server would be to just use this:
var numClients = io.sockets.clients().length;
But apparently there are some issues surrounding this, so you might have to keep track of the client count yourself.
Rooms and Namespaces
Chances are as your application grows in complexity, you'll need more customization with your websockets, like sending messages to a specific user or set of users. Or maybe you want need strict separation of logic between different parts of your app. This is where rooms and namespaces come in to play.
Note: These features are not part of the websocket protocol, but added on top by socket.io.
By default, socket.io uses the root namespace (/) to send and receive data. Programmatically, you can access this namespace via io.sockets, although many of its methods have shortcuts on io. So these two calls are equivalent:
io.sockets.emit('stats', { data: 'some data' });
io.emit('stats', { data: 'some data' });
To create your own namespace, all you have to do is the following:
var iosa = io.of('/stackabuse');
iosa.on('connection', function(socket){
console.log('Connected to Stack Abuse namespace'):
});
iosa.emit('stats', { data: 'some data' });
Also, the client must connect to your namespace explicitly:
Now any data sent within this namespace will be separate from the default / namespace, regardless of which channel is used.
Going even further, within each namespace you can join and leave 'rooms'. These rooms provide another layer of separation on top of namespaces, and since a client can only be added to a room on the server side, they also provide some extra security. So if you want to make sure users aren't snooping on certain data, you can use a room to hide it.
To be added to a room, you must .join() it:
io.on('connection', function(socket){
socket.join('private-message-room');
});
Then from there you can send messages to everyone belonging to the given room:
io.to('private-message-room').emit('some event');
And finally, call .leave() to stop getting event messages from a room:
socket.leave('private-message-room');