Persiste NodeJS Sockets - node.js

Hello community,
Since the morning I am faced with an idea not to say a problem I want to store clients (sockets) in order to re-invoke them later, I know that it is not too clear I will explain in detail:
First I have a server interface (ServerSocket) net.Server() which will receive clients (sockets) net.Socket() and this one will be stored in a Map() with a unique ID for each client, and each time I want to communicate with one of them I call map.get(id).write ... or other function,
Everything works fine until I close the server, automatically the sockets will be killed ... saying that I have found a solution to store the clients for my case (vuex) or localStorage to simplify what I want is when I restart the server and I invoke one of the client it will always be active.
So my main questions:
How can I keep clients still active after the server is closed?
How can I Store sockets and check if they are active after restarting server?
var net = require("net");
var server = new net.Server();
var sockets = new Map();
/**
* This events is called when server is successfully listened.
*/
server.on("listening", () => {
console.log("Server Listen in port 4444");
});
/**
* This events is called when error occur in server
*/
server.on("error", (e) => {
if (e.code === "EADDRINUSE") {
console.log("Address in use, retrying...");
}
});
/**
* This events is called when a new client is connected to server.
*/
server.on("connection", (socket) => {
var alreadyExist = false;
sockets.forEach((soc) => {
if (soc.remoteAddress === socket.remoteAddress) {
alreadyExist = true;
}
});
if (alreadyExist) {
socket.end();
} else {
socket.setKeepAlive(true, Infinity);
socket.setDefaultEncoding("utf8");
socket.id = nanoid(10);
sockets.set(socket.id, socket);
socket.on("error", (e) => {
console.log(e);
if (e.code === "ECONNRESET") {
console.log("Socket end shell with CTRL+C");
console.log("DEL[ERROR]: " + socket.id);
}
});
socket.on("close", () => {
console.log("DEL[CLOSE]: " + socket.id);
});
socket.on("end", () => {
console.log("DEL[END]: " + socket.id);
});
socket.on("timeout", () => {
console.log("timeout !");
});
var child = sockets.get(res.id);
child.write(/* HERE I SEND COMMAND NOT IMPORTANT ! */);
socket.on("data", (data) => {
console.log("Received data from socket " + data);
});
}
});

How can I keep clients still active after the server is closed?
A client can't maintain a connection to a server that is not running. The client is free to do whatever it wants on its own when the server shuts down, but it cannot maintain a connection to that server that is down. The whole definition of a "connection" is between two live endpoints.
How can I Store sockets and check if they are active after restarting server?
You can't store sockets when the server goes down. A socket is an OS representation of a live connection, TCP state, etc... When the server goes down and then restarts, that previous socket is gone. If it wasn't closed by the server before it shut-down, then it was cleaned up by the OS when the server process closed. It's gone.
I would make a suggestion that you're asking for the wrong thing here. Sockets don't outlive their process and don't stay alive when one end of the connection goes down.
Instead, the usual architecture for this is automatic reconnection. When the client gets notified that the server is no longer there, the client attempts to reconnect on some time interval. When, at some future time, the server starts up again, the client can then connect back to it and re-establish the connection.
If part of your connection initiation is an exchange of some sort of clientID, then a client can reconnect, present it's identifier and the server can know exactly which client it is and things can then continue as before with a new socket connection, but everything else proceeding as if the previous socket connection shut-down never happened. You just rebuild your Map object on the server as the clients reconnect.
For scalability reasons, your clients would generally implement some sort of back-off (often with some random jitter added) so that when your server comes back online, it doesn't get immediately hammered by hundreds of clients all trying to connect at the exact same moment.

Related

Socket IO doesn't get emitted from client - Node JS

I am working on Socket IO, the connection between the client and the server is established successfully. I am facing two problems:
1 - When the initial connection is made between the client and the server, the socket.client.id on server and socket.id on client side, both are the same, but when I refresh the client page, the id of the client changes to other one, but on the server it is still the same. Does it makes any issue / problem while communicating with the server or even with the client using sockets, while not having the same ids ? or does the id on the server get changed when the client page is refreshed ?
2 - On the initial connection establishment the socket passes a messages, using socket.emit() from server and receives as socket.on() on client. But when I try to emit anything from client it doesn't get received on server.
Socket Connections
function Globals() {
this.socketConnection = async function() {
let p = new Promise(function(res, rej) {
io.on("connection", function(socket) {
if (socket.connected) {
res(socket);
} else {
rej("Socket Connection Error !");
}
})
})
return await p;
}
}
new Globals().socketConnection().then(function(soc) {
console.log(soc.client.id);
socket = soc;
soc.emit("Hi");
soc.on("Nady", function() {
console.log("I am called");
})
})
Client Side Connection
function Globals() {
this.socketConnection = async function() {
var socket = io('http://localhost:8080');
let p = new Promise(function(res, rej) {
socket.on('connect', function() {
if (socket.connected) {
console.log(socket.id);
res(socket);
}
})
})
return await p;
}
}
var socket;
new App().socketConnection().then(function(s) {
socket = s;
});
function ScrapJobs() {
var socket;
new App().socketConnection().then(function(s) {
socket = s;
});
var _this = this;
this.attachListeners = function() {
qs("#init-scrap").ev("click", _this.startScrapping);
}
this.startScrapping = function() {
console.log("I am cliced");
socket.on("Hi", function() {
console.log("Hi Nadeem");
})
socket.emit("Nady");
}
}
When the initial connection is made between the client and the server, the socket.client.id on server and socket.id on client side, both are the same, but when I refresh the client page, the id of the client changes to other one, but on the server it is still the same. Does it makes any issue
The client side socket.id value is set on the client socket object after the connect event is received and is updated (e.g. modified) upon a reconnect event.
It appears that the socket.io infrastructure will keep them the same on client and server. If the client disconnects and then reconnects, there will be a new connection with a new id on both client and server. It is possible you are attempting to hang onto the old socket object on the server after the client has disconnected it (we can't really see enough of your server code to evaluate that).
On the initial connection establishment the socket passes a messages, using socket.emit() from server and receives as socket.on() on client. But when I try to emit anything from client it doesn't get received on server.
You'd have to show us a reproducible case. This does not happen if you are coding things correctly. I would guess that you do not have the right listeners for messages on the right socket in order to see the messages you are sending. I promise you that sending a message from client to server works just fine when implemented properly.
A general comment about your code. Both code blocks you show appear to be stuffing a socket object into a higher scoped (or perhaps even global) variable. That is likely part the cause of your problem because that socket object can become dead if the client reconnects for any reason. Plus putting any sort of socket object into a global or module level variable makes your server only capable of serving one client - it's simply not how you design multi-client servers.

How to send message to a specific client with socket.io if the application uses the cluster up and running in several processes on different ports?

The application starts in cluster mode, each worker is to establish a connection to the socket, using redis adapter:
app.set('port', httpPort);
let server = http.createServer(app);
let io = require('./socketServer')(server);
io.adapter(redis({host: host, port: port}));
app.set('io', io);
then we connect the main socket.io file (socketServer), where after authorization of the socket and on.connection event, we save sessionID in variable socketID, and store current socket connection in array io.clients
io.sockets.on('connection', (socket) =>{
var socketID = socket.handshake.user.sid;
io.clients[socketID] = socket;
io.clients[socketID].broadcast.emit('loggedIn',socket.handshake.user.data);
socket.on('disconnect', () =>{
delete io.clients[socketID];
});
});
Before nodejs app, we have nginx with customized "upstream" to organize a "sticky sessions" (http://socket.io/docs/using-multiple-nodes/#nginx-configuration).
Then, when we want to send a message to a particular customer, already from the controller we get id user, and get session-id for id (we pre-authorization keep these correspondences in redis), and then just send a message:
this.redis.getByMask(`sid_clients:*`,(err,rdbData) =>{
Async.each(clients,(client,next)=>{
let sid = `sid_clients:${client}`;
let currentClient = rdbData[sid];
if(!currentClient || !this.io.clients[currentClient]) return next();
this.io.clients[currentClient].emit(event,data);
return next();
});
It works fine when we run the application in a single process. But this don't work when running in a cluster mode. Connection message "loggedIn" is send to all customers on all processes. But if a single process to send a message to the client that connects to a server in another process - does not work, because that each process has own array io.clients and they are always have different content, so the message does not can reach the right customer.
So, how send events to the specific client in a cluster mode? How to keep all connected sockets in one place to avoid situations such as mine?

Need help to verify if this socket.io code will work on multiple server instances

Imagine there is a Node.js chatting app.
It is going to be hosted on AWS that will scale across multiple instances.
The chat server app has this piece of codes that tracks who is online or offline using a variable called "sockets".
This code works on single instance. But will this code fail when the server is scaled across multiple instances?
var sockets = {};
io.configure(function () {
...
});
io.sockets.on('connection', function(socket) {
socket.on('online', function(data){
if(data.userId){
sockets[userId] = socket;
console.log("user " + userId + " connected.");
}
});
socket.on('disconnect', function () {
for(var key in sockets){
if(sockets[key] == socket)
{
delete sockets[key];
console.log("user " + key + " disconnected.");
break;
}
}
});
});
// check if a userId is connected
// will this still work when multiple server instances
function isUserIdOnline(userId)
{
return (sockets[userId] != null);
}
No, it will not work with multiple instances. sockets represent only the users who connected to this particular server, not all the other servers.
The likely answer to this question involves storing who is connected and which server they are connected to in a central database server so any socket server can find out if someone is connected and which server they are connected to by contacting the central database. Since the data is temporal, an in-memory database like redis could be used.
A realated discussion of various architectural options for using multiple servers to handle connections while solving the "who is online problem and how do I contact them" is in this answer.

Security on Socket.io

Using Socket.io what is the best way to require some sort of authentication for pushing to the socket. For example I have one group listeners who should only be allowed to received updates and I have admins who are allowed to push updates (and listen).
I could use some sort of passphrase that only the "admin" knows.
I could use separate sockets for pushing vs listening and block all listeners to the pushing port
?
Otherwise the listeners would just need to come up with some creative javascript in order to do the correct push. We can also assume that the server doesn't know if someone is an admin or a listner.
For example:
//Server
socket.on('foo', function (data) {
io.sockets.emit('message', data);
}
//listner client
socket.on('message', function (data) {
alert("admin sent new update");
});
//admin client
someButton.onclick = sendMessage = function() {
socket.emit('foo', {'update'});
};

How can I send packets between the browser and server with socket.io, but only when there is more than one client?

In my normal setup, the client will emit data to my server regardless of whether or not there is another client to receive it. How can I make it so that it only sends packets when the user-count is > 1? I'm using node with socket.io.
To do this you would want to listen to the connection event on your server (as well as disconnect) and maintain a list of clients which are connected in a 'global' variable. When more than 1 client is connected send out a message to all connected clients to know they can start sending messages, like so:
var app = require('express').createServer(),
io = require('socket.io').listen(app);
app.listen(80);
//setup express
var clients = [];
io.sockets.on('connection', function (socket) {
clients.push(socket);
if (clients.length > 1) {
io.socket.emit('start talking');
}
socket.on('disconnect', function () {
var index = clients.indexOf(socket);
clients = clients.slice(0, index).concat(clients.slice(index + 1));
if (clients.length <= 1) {
io.sockets.emit('quiet time');
};
});
});
Note: I'm making an assumption here that the socket is passed to the disconnect event, I'm pretty sure it is but haven't had a chance to test.
The disconnect event wont receive the socket passed into it but because the event handler is registered within the closure scope of the initial connection you will have access to it.

Resources