
What are WebSockets:
But why do we need webSockets we already have AJAX? WebSockets represent a standard for bi-directional realtime communication between servers and clients. Firstly in web browsers, but ultimately between any server and any client. The standards first approach means that as developers we can finally create functionality that works consistently across multiple platforms. Connection limitations are no longer a problem since WebSockets represent a single TCP socket connection. Cross domain communication has been considered from day one and is dealt with within the connection handshake.
Now as we know what WebSockets are lets dive into setting up a Node.js Cluster.
Node.js cluster API:
Node.Js processes runs on a single process,While it’s still very fast in most cases, this really doesn’t take advantage of multiple processors if they’re available. If you have an 8 core CPU and run a Node.Js program via
1 |
$ node app.js |
it will run in a single process, wasting the rest of CPUs. Hopefully for us NodeJS offers the “cluster” module that allows you to create a small network of separate processes which can share server ports; this gives your Node.js app access to the full power of your server.
uhh! That’s enough of talking lets see a real example which:
- Creates a master process that retrives the number of CPUs and forks a worker process for each CPU, and
- Each child process prints a message in console and exit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
const http = require('http'); const cluster = require('cluster'); const numCPUs = require('os').cpus().length; if (cluster.isMaster) { masterProcess(); } else { childProcess(); } function masterProcess() { console.log(`Master ${process.pid} is running`); for (let i = 0; i < numCPUs; i++) { console.log(`Forking process number ${i}...`); cluster.fork(); } process.exit(); } function childProcess() { console.log(`Worker ${process.pid} started and finished`); process.exit(); } |
Save the code in
1 |
app.js |
file and run executing:
1 |
$ node app.js |
The output should be something similar to:
1 2 3 4 5 6 7 8 9 |
Master 8463 is running Forking process number 0... Forking process number 1... Forking process number 2... Forking process number 3... Worker 8464 started and finished Worker 8465 started and finished Worker 8467 started and finished Worker 8466 started and finished |
simple isn’t it? Indeed it is.
Making socket.io work with node.js cluster API:
Handshake
When creating a WebSocket connection, the first step is a handshake over TCP in which the client and server agree to use the WebSocket Protocol.
1 2 3 4 5 6 7 8 |
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 |
The handshake from the server:
1 2 3 4 5 |
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: chat |
Create a WebSocket Connection
A WebSocket connection is established by upgrading from the HTTP protocol to the WebSocket Protocol during the initial handshake between the client and the server, “over the same underlying TCP connection“. An Upgrade header is included in this request that informs the server that the client wishes to establish a WebSocket connection.
1 |
Error during WebSocket handshake: Unexpected response code: 400 |
Which means that the upgrade request was sent to a node(one among all the available cluster nodes) which did not know the given socket id, hence the HTTP 400 response.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
const workers = []; const cluster = require('cluster'); const cpus = require('os').cpus().length; const port = process.env.PORT || 3001; const path = require('path'); const net = require('net'); const socketio = require('socket.io'); const farmhash = require('farmhash'); const net = require('net'); if(cluster.isMaster){ console.log('Master started process id', process.pid); for(let i=0;i<cpus;i++){ workers.push( cluster.fork()); console.log('worker strated '+workers[i].id); workers[i].on('disconnect',() => { console.log('worker '+ workers[i].id+'died'); }); } // get worker index based on Ip and total no of workers so that it can be tranferred to same worker const getWorker_index = (ip,len) => { return farmhash.fingerprint32(ip)%len; } // ceating TCP server const server = net.createServer({ // seting pauseOnCOnnect to true , so that when we receive a connection pause it // and send to corresponding worker pauseOnConnect: true }, (connection) => { // We received a connection and need to pass it to the appropriate // worker. Get the worker for this connection's source IP and pass // it the connection. we use above defined getworker_index func for that const worker = workers[getWorker_index(connection.remoteAddress,cpus)]; // send the connection to the worker and send resume it there using .resume() worker.send({ cmd:'sticky-session' },connection); }).listen(port); } else{ // listning for message event sent by master to catch the connection and resume cluster.worker.on('message',(obj,data) => { switch(obj.cmd){ case "sticky-session": Expserver.emit('connection',data); data.resume(); break; default: return; } }); } |
As you can see requests originating from same IP address goes to same node in the cluster hence sticky balancing the requests.
There you go. This is how you make socket.io work with Node.js cluster API:)
Here’s the repo of sample chat application using Socket.io and node cluster API:
https://github.com/ANURAGVASI/socket.io-multiserver-chatAp