How to use socket.io-redis with multiple servers? - node.js

i have following code on two machines
var server = require('http').createServer(app);
io = require('socket.io')(server);
var redisAdapter = require('socket.io-redis');
io.adapter(redisAdaptebr({host: config.redis.host, port: config.redis.port}));
server.listen(config.port, function () {
and I store socket.id of every client connected to these two machines on central db, ID of sockets is being saved and event sending on same server works flawlessly, but when I try to send message to the socket of other server it doesn't work..
subSocket = io.sockets.connected[userSocketID];
subSocket.emit('hello',{a:'b'})
How can i know that redis is wokring good.
How to send message to socket connected on another server.

You can't. Socket.IO requires sticky sessions. The socket must communicate solely with the originating process.
docs
You can have the socket.io servers communicate to each other to pass events around, but the client must continue talking to the process with which it originated.

I'm in a similar issue but I can answer your first question.
you can monitor all the commands processed by redis using that command on the terminal:
redis-cli monitor
http://redis.io/commands/MONITOR
Unfortunately I cannot help you further as I am still having issues even though both server are sending something to redis.

Related

Sticky Session on Heroku

We have have a NodeJS application running with SocketIO and clustering on heroku. To get SocketIO working we use the redis-adapter like discussed here: https://socket.io/docs/using-multiple-nodes/.
Then we've implemented sticky sessions like shown in the sticky session documentation here: https://github.com/elad/node-cluster-socket.io.
Turns out that when we deploy to Heroku, the connection.remoteAddress in:
// Create the outside facing server listening on our port.
var server = net.createServer({ pauseOnConnect: true }, function(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.
var index = worker_index(connection.remoteAddress, num_processes);
var worker = workers[index];
worker.send('sticky-session:connection', connection);
}).listen(port);
is actually the IP address of some heroku routing server and NOT the client IP. I've seen that the request header "x-forwarded-for" could be used to get the client IP, but when we pause the connection in this way, we don't even have the headers yet?
We searched all over for a solution, but apparently there's no good solutions.
Here are some of the better suggestions:
https://github.com/indutny/sticky-session/issues/6
https://github.com/indutny/sticky-session/pull/45
None of them seemed good performance wise and therefore we ended up changing SocketIO communication to Websockets only. This eliminates the need for sticky sessions all together.

How to check socket is alive (connected) in socket.io with multiple nodes and socket.io-redis

I am using socket.io with multiple nodes, socket.io-redis and nginx. I follow this guide: http://socket.io/docs/using-multiple-nodes/
I am trying to do: At a function (server site), I want to query by socketid that this socket is connected or disconnect
I tried io.of('namespace').connected[socketid], it only work for current process ( it mean that it can check for current process only).
Anyone can help me? Thanks for advance.
How can I check socket is alive (connected) with socketid I tried
namespace.connected[socketid], it only work for current process.
As you said, separate process means that the sockets are only registered on the process that they first connected to. You need to use socket.io-redis to connect all your nodes together, and what you can do is broadcast an event each time a client connects/disconnects, so that each node has an updated real-time list of all the clients.
Check out here
as mentioned above you should use socket.io-redis to get it work on multiple nodes.
var io = require('socket.io')(3000);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
I had the same problem and no solution at my convenience. So I made a log of the client to see the different methods and variable that I can use. there is the client.conn.readystate property for the state of the connection "open/closed" and the client.onclose() function to capture the closing of the connection.
const server = require('http').createServer(app);
const io = require('socket.io')(server);
let clients = [];
io.on('connection', (client)=>{
clients.push(client);
console.log(client.conn.readyState);
client.onclose = ()=>{
// do something
console.log(client.conn.readyState);
clients.splice(clients.indexOf(client),1);
}
});
When deploying Socket.IO application on a multi-nodes cluster, that means multiple SocketIO servers, there are two things to take care of:
Using the Redis adapter and Enabling the sticky session feature: when a request comes from a SocketIO client (browser) to your app, it gets associated with a particular session-id, these requests must be kept connecting with the same process (Pod in Kubernetes) that originated their ids.
you can learn more about this from this Medium story (source code available) https://saphidev.medium.com/socketio-redis...

How io.adapter works under the hood?

I'm working on 1-1 chat rooms application powered by node.js + express + socket.io.
I am following the article: Socket.IO - Rooms and Namespaces
In the article they demonstrate how to initiate the io.adapter using the module socket.io-redis:
var io = require('socket.io')(3000);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
Two questions:
In the docs, They are mentioning two more arguments: pubClient and subClient. Should I supply them? What's the difference?
How the io.adapter behaves? For example, if user A is connected to server A and user B is server B, and they want to "talk" with each other. What's going under the hood?
Thanks.
You do not need to pass your own pubClient/subClient. If you pass host/port, they will be created for you. But, if you want to create them yourself, for any reason (e.g. you want to tweak reconnection timeouts), you create those 2 clients and pass it to adapter.
The adapter broadcasts all emits internally. So, it gives you the cluster feature. E.g. lets suppose that you have chat application, and you have 3 node.js servers behind load balancer (so they share single URL). Lets also assume that 6 different browsers connect to load balancer URL and they are routed to 3 separate node.js processes, 2 users per node.js server. If client #1 sends a message, node.js #1 will do something like io.to('chatroom').emit('msg from user #1'). Without adapter, both server #1 users will receive the emit, but not the remaining 4 users. If you use adapter, however, remaining node.js #2 and node.js #3 will receive info that emit was done and will issue identical emit to their clients - and all 6 users will receive initial message.
I've been struggling with this same issue, but have found an answer that seems to be working for me, at least in my initial testing phases.
I have a clustered application running 8 instances using express, cluster , socket.io , socket.io-redis and NOT sticky-sessions -> because using sticky seemed to cause a ton of bizarre bugs.
what I think is missing from the socket.io docs is this:
io.adapter(redis({ host: 'localhost', port: 6379 })); only supports web sockets ( well at the very least it doesn't support long polling ) , and so the client needs to specify that websockets are the only transport available. As soon as I did that I was able to get it going. So on the client side, I added {transports:['websockets']} to the socket constructor... so instead of this...
var socketio = io.connect( window.location.origin );
use this
var socketio = io.connect( window.location.origin , {transports:['websocket']} );
I haven't been able to find any more documentation from socket.io to support my theory but adding that got it going.
I forked this great chat example that wasn't working and got it working here: https://github.com/squivo/chat-example-cluster so there's finally a working example online :D

emits from client must be called again in server?

I have a problem with understanding how socket.io and node.js works.
I tried few examples with emitting messages and it worked. What I don't understand is emitting from clients.
I use emit in client, for example: socket.emit('custom_event');, but it doesn't work in other clients unless I add something like this in my server:
var io = require('socket.io').listen(app);
io.sockets.on('connection', function(socket) {
socket.on('custom_event', function() {
socket.emit('custom_event');
});
});
Am I doing something wrong or do I need to always add definition on server side for something that client should be emitting to other clients?
this is not possible, socketio need the server logic between your clients.
You can do:
socket.broadcast.emit
Broadcasting means sending a message to everyone else except for the socket that starts it.
io.sockets.emit
Emits to all, (inc the socket that starts the action.)
But allways from the server
Can you connect client to client via web sockets without touching the server?

node.js + socket.io + redis architecture - horizontal serverscaling socket connections?

i'm using node.js for the first time and hoping for an advice:
i installed the following programs on my server:
node.js v0.11.3-pre
express v3.3.4
socket.io v0.9.14
connect-redis v1.4.5
Redis server v=2.6.14
redis-cli 2.6.14
First of all, i created an express app:
express testApplication
In the created "package.json" i defined all neccessary depencies.
From the start i defined a cluster for vertically scaling (multi-processes) in a file called "cluster.js":
var cluster = require('cluster');
if( cluster.isMaster ) {
var noOfWorkers = process.env.NODE_WORKERS || require('os').cpus().length;
console.log("Workers found: " + noOfWorkers);
for (var i = 0; i < noOfWorkers; i += 1) {
cluster.fork();
}
} else {
require('./app.js');
}
cluster.on('exit', function(worker, code, signal) {
var exitCode = worker.process.exitCode;
console.log('worker' + worker.process.pid + ' died (' + exitCode + '). restarting...');
if( typeof cluster.workers[worker.id] != "undefined" )
cluster.workers[worker.id].delete();
cluster.fork();
});
In my "app.js" file i defined REDIS for socket.io storing:
io.set('store', new RedisStore({
redisPub: pub,
redisSub: sub,
redisClient: client
}));
So far, so good, all this works pretty nice.
When a client connects to the socket.io server, the cluster handles the connections with different workers.
My intention is, that a client can send a message to a specific another client, so the socket.io server have to find the socket from the receipient to send the message only to this user. The solution for me is, that i store all created socket ids for every user in an array and when sending a message, i select the relevant socket ids in the array, gets the sockets by id, and send the message to the socket(s).
This works very fine for a socket.io application, which is running only on one server.
Now, i want to configure another server with the same programs, modules and packages.
The load balancing will probably be handled by HAProxy. So the socket.io connections (sockets) will be stored and managed on Server A and Server B.
Example scenario:
User A connects to Server A and User B connects to Server B. That means, that User A has a socket on Server A und User B on Server B.
How is it possible, that the application knows, that it has to look for the socket of User B on Server B to send the message? On Server A it won't find the socket, because it was created on Server B.
Thx a lot!
When you're horizontally scaling, you need to share a datastore between your servers. Conveniently, you already have an ideal one, which is Redis. Instead of keeping your socket mapping in an array, you need to push it into Redis, and do lookups from there.
Then, you can either decide to have servers send messages to each other, or, more scalably, send the messages through Redis as well. So, each server would look at it's queue on Redis to see what messages it should send to which sockets. When a server receives a message, it will push it into Redis addressed to the server that should deliver it. If the ordering of messages matters to you, I would recommend looking at https://github.com/learnboost/kue as layer on top of Redis.
Don't forget to include NGINX in front of NodeJS and use PM2!

Resources