Buildtide
Author: Hussain Mir Ali

I am interested in web and mobile technologies.

If you have any questions or feedback then message me at devtips@Buildtide.com.

WebSocket Server with JavaScript

Server:

Handshake Client/Server:

Fig 1.0 HTTP Handshake

Before the connection is established the client sends a handshake request to the server via GET method. It passes 'Set-WebSocket-Version' header to let server know if a specific  version of WebSocket is required for communication. It also passes 'Set-WebSocket-Key' which is then used by the server to generate a new key which it sends via the 'Set-WebSocket-Accept' header. The key that server sends back to the client is generated from concatenating '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' with the  key set from the client and taking SHA-1 hash of the resulting string. Then the server sends back base64 encoded version of the hash. 

Ping/Pong:

To ensure the connection is alive the client or server may send a ping with opcode 0x9 to each other after the initial HTTP handshake. The client or server need to send a pong with opcode 0xA right after they receive a ping. The pong should have the same payload as it was received in the ping. A pong received without sending a ping should be ignored.

Client Tracking:

To prevent multiple handshakes for the same client each client needs to be tracked by the server. Tracking clients will also help recognize DOS attacks. This is where Socket.io comes into picture as a server side framework that makes creating a websocket server less cumbersome.  

Socket.io:

installation:
Socket.io can be easily installed from the npm repository.
npm install socket.io --save
server setup: 
The server can be setup using either http module in nodejs or using express. In this case http module is used.
let app = require('http').createServer(handler)
let io = require('socket.io')(app);
let fs = require('fs');

app.listen(80);

function handler(req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}

res.writeHead(200);
res.end(data);
});
}

io.on('connection', function (socket) {
//handle communication here
});

In the sample code above the 'handler' functions serves the static content like index.html file. This is where the UI for the application may reside. The 'server' object is passed to the 'socket.io' object so it may also start listening. The 'connection' event is fired as soon as the connection with the client is established.

It should be noted that the port where the app listens should either be 80 or 443 for best results. Other ports may cause issues due to firewalls and proxies.

receiving:
Information can be received from the 'socket' event.
io.on('connection', function (socket) {
socket.on('message', function(data){
console.log(data);
});
});

In the above code the 'message' event is used to receive data. But in practice the client can send data via any event ('abc', 'new york', 'hello').

message single client:
Socket.io allows developers to send message to a single client. This can be done two ways: 

1. Sending message back to the sender-client.
io.on('connection', function (socket) {
socket.on('message', function(data) { socket.emit('message', 'testing');
});
});
2. Sending message to a specific client using socketId. In the sample code below developers can replace '<socketid>' with specific id. 
io.on('connection', function (socket) {
socket.on('message', function(data){
socket.broadcast.to(<socketid>).emit('message', 'message to specific client');
});
});

broadcasting:
While broadcasting the message is sent to all other clients except the one which has sent the message to be broadcast.
io.on('connection', function(socket) {
socket.on('message', function(data) {
socket.broadcast.emit('message', "this is a test");
});
});
client tracking:
Clients can be tracked using their specific socket ids. When the client makes a connection the socket object contains the id associated with that particular client.
io.on('connection', function (socket) {
let socketId= socket.id;
//do something with socket id.
});


Fig 2.0 Namespaces and Room Tree


Namespace:

In Socket.io 'Namespace' is a specific endpoint on the same TCP/IP connection. This is helpful if a single connection is to be used for different use cases. By default the clients can connect to '/' endpoint but custom endpoints like '/gaming' and '/messaging' can be specified for the same connection. 
let app = require('http').createServer(handler)
let io = require('socket.io')(app);
let chatNameSpace = io.of('/chat');
let gamingNameSpace = io.of('/gaming');
let fs = require('fs');

app.listen(80);

function handler(req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}

res.writeHead(200);
res.end(data);
});
}

chatNameSpace.on('connection', function (socket) {
//handle chat communication here
});

gamingNameSpace.on('connection', function (socket) {
//handle gaming communication here
});

In the sample code above custom 'Namespaces' have been defined using 'io.of' method. So '/chat' and '/gaming' name  spaces can have separate communication endpoints where the clients connect.

Rooms:

Within each 'Namespace' the communication channels can be further separated into 'rooms'. Clients can join or leave the 'room' which makes the application more dynamic and flexible. 
io.on('connection', function(socket){
socket.join('myroom');
//send message
io.to('myroom').emit('message', 'mymessage');
socket.leave('myroom');

});

The above code snippet shows how socket can join a room using '.join' method. It can then send message message to 'myroom' and the leave the room using '.leave()' method.

'io' vs 'socket' object:

The 'socket' variable represents a single connection. Each time a new client connects a new 'socket' object is created and passed in the callback and it can only be used to communicate on that connection. The 'io' variable on the other hand represents a group of 'socket' objects for all connections.