Socket.io on node, cluster, express and socket.io/sticky creating multiple connections

87
June 02, 2021, at 8:50 PM

I have a node.js server running with cluster, socket.io, express, and to create stickiness @socket.io/sticky. (We also use Redis for cross communication but I am not sure that is an issue). I have one client type (iOS React-Native) that uses polling, hence the need for sticky. The socket connection created from this client uses polling and is making multiple socket connections to the server. Here is an example from my logs (6680 and 6674 are PIDS from the workers)

 process: 6680, 2021-06-01T15:48:02.941Z, socketManager, io.sockets.on(connect), Will broadcast HELLO JfLCgyTZmvMcpMgzAAFZ
 process: 6674, 2021-06-01T15:48:05.127Z, socketManager, io.sockets.on(connect), Will broadcast HELLO MCAoSsuLNuiGe2o3AAED
process: 6680, 2021-06-01T15:48:06.989Z, socketManager, io.sockets.on(connect), Will broadcast HELLO LzxAW4_1Fhk3D4cdAAFa

It initially connects to the process on 6680 and then 6674 and then back to 6680. It was my understanding that "sticky" should have prevented this hopping around.

Here are some relevant bits from my server setup:

const express = require('express');
const app = express();
const cluster = require('cluster');
...
const { setupMaster, setupWorker } = require('@socket.io/sticky');
...
if (cluster.isMaster) {
  console.log(`Master is started with process ID ${process.pid}`);
  const serverhttp =
    process.env.NODE_ENV === 'production'
      ? https.createServer(options, app)
      : http.Server(app);
  redisCache.setRedisUsers([]);
  setupMaster(serverhttp, {
    loadBalancingMethod: 'least-connection', // either "random", "round-robin" or "least-connection"
  });
 for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  cluster.on('exit', (worker) => {
    console.log(`Worker died on process ID ${worker.process.pid}`);
    cluster.fork();
  });
} else {
console.log(`Worker started at process ID ${process.pid}`);
  app.use(bodyParser.urlencoded({ extended: false }));
  app.use(bodyParser.json());
  const serverhttp =
    process.env.NODE_ENV === 'production'
      ? https.createServer(options, app)
      : http.Server(app);
/* app.use routes, cors, static routes; db connection initialization */
const io = socketManager(serverhttp, redisCache); // initialize socket
  setupWorker(io);
  serverhttp.listen(port, () => {
    console.log(`Starting server on ${port}`);
  });
  serverhttp.keepAliveTimeout = 65 * 1000;
  serverhttp.headersTimeout = 66 * 1000;
}

socketManager here encapsulates the socket connection creation and all of the events tied to it. (redisCache is simply a user management cache.

The module simply exports the socket configuration code:

 module.exports = function (serverhttp, redisClient) {
  const config = {
    pingTimeout: 60000,
    pingInterval: 25000,
    transports: ['polling','websocket'], 
  };
 
  const io = socketio.listen(serverhttp, config);
//Redis Adapter
  io.adapter(
    redisAdapter({
      host: REDIS_HOST,
      port: REDIS_PORT,
    })
  );
//Support functions....

 io.sockets.on('connection', function (client) {
    logIt('io.sockets.on(connect)', `Will broadcast HELLO ${client.id}`);
    io.emit('HELLO', `from io.emit -- ${client.id}`);
    client.broadcast.emit('HELLO', client.id);
    client.on(USER_CONNECTED, async (user) => {
      logIt('client.on(USER_CONNECTED)', ` ${user.name} ${client.id}`);
      user.socketId = client.id;
   
      const redisContent= await redisClient.addRedisUser({
        name: user.name,
        id: user.id,
        socketId: client.id,
      });
     io.emit(USER_CONNECTED, user); // broadcast to other clients
     });
     ... 
   //other events
  });
return io; //End of module exports function
};

My configuration is not that different than code sample at socket.io multiple nodes. Except for two things:

  1. My master is NOT listening, but my workers are. (And this is a big difference, I will admit)
  2. My io.sockets.on (or io.on) are initiated before the setupWorker is called.

On 1 have tried shifting to listen only on the master but then I do not get the static routes and I get a web error (cannot GET / ). When I shift the app.use content to be configured with the master the page comes up but I never see the socket connections being made and the DB connection times out even though it is initialized. (The "Hello" log from above are never issued.) The one thing missing from the socket.io site example is express.

On 2, I am not sure why this would make a difference.

So, is there some way to shift my master/worker configuration around such that it is using the sticky functionality and uses express but can also get the worker instances to respond on a client connection?
Is my express/https server/socket io implementation misconfigured in some way? Is there a way to get express/https to listen at both the master and the worker level. It would seem to me that while I am connecting through the master it is not being passed to a worker.

Thanks -- Josef

READ ALSO
Load an html element from amazon&#39;s s3

Load an html element from amazon's s3

I am aware it is possible to reference images stored in s3 via the src property, But what if I want to load something more complex, like a section of static htmlAre there any ways to go about simply pulling html down for use/insertion into the DOM (similar...

42
Object Polymorphism

Object Polymorphism

I have a project for school that has a logIn and shows a table using a method that is corresponding with the account but when I log in with the other accounts it also calls the main class' table please help me, Im a first year IT student and this is my first project...

64
How to check for available places with if statement?

How to check for available places with if statement?

I’m working on a reservation system for a camping websiteThere are several places where you can sit, but it shouldn't be possible to have 2 different family’s on one place

50