Skip to content

io.of('/').connected[socketId] does not always return socket object #283

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
joanmooreban opened this issue Jun 16, 2018 · 16 comments
Closed
Labels
documentation Improvements or additions to documentation

Comments

@joanmooreban
Copy link

joanmooreban commented Jun 16, 2018

Before implementing socket.io-redis I used the following to get the socket object:

var socket = io.sockets.connected[socketId];

Now that I've implemented socket.io-redis I guess the command to replace it is:

var socket = io.of('/).connected[socketId];

However, it returns undefined in many, many cases where the socket actually exists.

I need to know how to get a socket's object by its id but without using any callback function since in my initial implementation I used as a return value as showed above and it is being currently used in many parts of the app.

UPDATE Jun 20th

I've also unsucessfully tried with io.of('/').adapter.nsp.connected[socket_id];. It replies with the right socket object for just some cores, not for all of them, returning undefined most of the time.

@joanmooreban
Copy link
Author

Anyone on this issue?

@joanmooreban
Copy link
Author

Anyone on this?

@darrachequesne
Copy link
Member

The socket is not shared accross all nodes:

// node1
io.on('connect', (socket) => {
  // io.sockets.connected[socket.id] === socket
});

// node2
// io.sockets.connected[socket.id] == undefined

@jaymehtasa
Copy link

@darrachequesne any suggestion to resolve this problem?

@HsinHeng
Copy link

HsinHeng commented Nov 8, 2018

@joanmooreban @jaymehtasa
if you used multiple nodes architecture, the connected socket object was "only" existed in connected node scope. You may get "undefined" if your wanted socket is not in local instance scope.

therefore, if you really want some "attributes" of socket object you can use...

solutions: customHook, customRequest,

You can use customRequest to communicate with other node instance and retrieve some attributes of "socket object" by specific socketId.

hook the function on your application, and use customRequest to get you wanted.

@Manjukb
Copy link

Manjukb commented Aug 27, 2020

can anyone post working example for this issue ?

@jaymehtasa
Copy link

jaymehtasa commented Aug 27, 2020

@Manjukb you need to use Redis for sharing Socket instance across node cluster.

You may use package for NodeJS - https://github.com/NodeRedis/node-redis

@Manjukb
Copy link

Manjukb commented Aug 27, 2020

@jaymehtasa thanks for your replay
I tried saving socket connection object to redis but it fails
Do you have working example ??

@jaymehtasa
Copy link

@Manjukb

NodeJS server.js file

const socketIoRedis = require('socket.io-redis');
const redis = require('redis');

const redisClient = redis.createClient(<Redis_Port>, <Redis_Host>, {
      no_ready_check: true,
});


if (process.env.REDIS_URL && process.env.REDIS_PORT) {
  IO.adapter(socketIoRedis({ host: <Redis_Host>, port: <Redis_Port>, key: 'socket.io' }));
  logger.info('Connecting Redis');
} else {
  logger.warn('REDIS_URL and REDIS_PORT are not set, socket.io messages will not be shared across servers.');
}

const onConnection = (socket) => {
    const userId = socket.user.id; // Some unique id of the system...

    // Save Socket ID of User to Redis
    redisClient.hset("REDIS_LIVE_SOCKET_USERS", userId, socket.id);

    socket.on('listenToEventSomeGoodEvent', (data) => {
        console.log('listenToEventSomeGoodEvent Fired...');
       
        // Read Socket ID of User from Redis
        redisClient.hget("REDIS_LIVE_SOCKET_USERS", userId, (err, socketId) => {
           if (socketId) {
              io.to(`${socketId}`).emit('sendDataUserEvent', data);
           }
      });
  });
};

const nspChat = io.of('/');
  socketIo = nspChat;
    .on('connection', onConnection);

@Manjukb
Copy link

Manjukb commented Aug 27, 2020

ok, i use socket.io-redis to solve above problem
i have a problem with getting socket object !! what will io.to(${socketId}) give is it an socket instance ?? i don't thinks so
i use socket.io-request to pull data from socket client through http
so in an express API i need to find a specific socket object and communicate with it
here is the code

var ioreq = require('socket.io-request');
const socketId = await req.app.redisclient.get('edge-'.concat(req.body.edge_name));
const ws = req.app.socketio.to(socketId);
ioreq(ws).request(req.body.method, {body: req.body}).then(function(res) {
logger.info(res, 'success result ');
result.json(res);
});

when i pass ws instance to I get Callbacks are not supported when broadcasting error from socket.io-request lib i was thinking that socket object what i pass is wrong ?? any idea ?? am i doing some mistake here ?

thanks a lot for your help

@jaymehtasa
Copy link

jaymehtasa commented Aug 27, 2020

@Manjukb I will not able to help further as I have never worked with socket.io-request.

FYI, the code I have provided works 100% because it's in production and being used by 3M people, we have tons of NodeJS servers running in parallel in cluster mode.

We have never faced any issue, you may change your architecture the way I have suggested, it must work.

@darrachequesne
Copy link
Member

@jaymehtasa I don't understand your example, couldn't you just use a plain emit in that case?

redisClient.hget("REDIS_LIVE_SOCKET_USERS", userId, (err, socketId) => {
  if (socketId) {
    // io.to(`${socketId}`).emit('sendDataUserEvent', data);
    socket.emit('sendDataUserEvent', data);
  }
});

Did you mean storing something like the socket.handshake object in Redis?

@Manjukb currently, I'm afraid you won't be able to use socket.io-request in order to fetch data from a socket connected to another server.

That's a known limitation in the current implementation of acknowledgements.

To share information between servers (though I'm not sure that's the use case here), customHook/customRequest is the way to go (credits to HsinHeng's anwser):

// on every node
io.of('/').adapter.customHook = (socketId, cb) => {
  const socket = io.sockets.connected[socketId];
  cb(socket ? socket.handshake : null);
}

// then
const socketId = 'abcd';
io.of('/').adapter.customRequest(socketId, function(err, replies){
  console.log(replies); // an array [null, <handshake>, null, ...] with one element per node
});

@jaymehtasa
Copy link

jaymehtasa commented Sep 1, 2020

@darrachequesne

My example is for sending an event to Recipient User, it might be different than the sender, and can be on any node cluster, so we need to read from the central Redis server, Hope that makes sense.

 socket.on('listenToEventSomeGoodEvent', (data) => {
        console.log('listenToEventSomeGoodEvent Fired...');
       
        // Read Socket ID of Recipient User from Redis
        redisClient.hget("REDIS_LIVE_SOCKET_USERS", <recipient_user>, (err, socketId) => {
           if (socketId) {
              io.to(`${socketId}`).emit('sendDataUserEvent', data);
           }
      });

@darrachequesne
Copy link
Member

@jaymehtasa that does make sense! I think you should also be able to use a room for that:

const onConnection = (socket) => {
  const userId = socket.user.id; // Some unique id of the system...
  socket.join(userId);

  socket.on('listenToEventSomeGoodEvent', (data) => {
    console.log('listenToEventSomeGoodEvent Fired...');
       
    io.to(<recipient_user>).emit('sendDataUserEvent', data);
  });
};

@Manjukb
Copy link

Manjukb commented Sep 2, 2020

@darrachequesne yes i ditched socket.io-request and implemented own way to fetch data from a socket connected to another server
everything else is working as expected , Thanks @darrachequesne @jaymehtasa

@jaymehtasa
Copy link

@darrachequesne sorry being straight, but I still think the approach you suggested may not work in case of node cluster / horizontally scalable architecture, you need something to centralize socket connections which can Redis or similar service.

the room approach is good when you want to broadcast something for more than one person, in case of one to one communication, I still prefer to go with sending a direct message to the user... even if we use rooms, we need Redis to centralize otherwise, one user might have joined on server-1 and another user might have joined on server-2, they cannot share the room unless it is shared across servers/clusters.

@darrachequesne darrachequesne added the documentation Improvements or additions to documentation label Sep 2, 2020
darrachequesne added a commit to socketio/socket.io that referenced this issue Mar 2, 2021
This commit adds the following methods:

- fetchSockets: returns the matching socket instances

Syntax:

```js
// return all Socket instances
const sockets = await io.fetchSockets();

// return all Socket instances of the "admin" namespace in the "room1" room
const sockets = await io.of("/admin").in("room1").fetchSockets();
```

- socketsJoin: makes the matching socket instances join the specified rooms

Syntax:

```js
// make all Socket instances join the "room1" room
io.socketsJoin("room1");

// make all Socket instances of the "admin" namespace in the "room1" room join the "room2" room
io.of("/admin").in("room1").socketsJoin("room2");
```

- socketsLeave: makes the matching socket instances leave the specified rooms

Syntax:

```js
// make all Socket instances leave the "room1" room
io.socketsLeave("room1");

// make all Socket instances of the "admin" namespace in the "room1" room leave the "room2" room
io.of("/admin").in("room1").socketsLeave("room2");
```

- disconnectSockets: makes the matching socket instances disconnect

Syntax:

```js
// make all Socket instances disconnect
io.disconnectSockets();

// make all Socket instances of the "admin" namespace in the "room1" room disconnect
io.of("/admin").in("room1").disconnectSockets();
```

Those methods share the same semantics as broadcasting. They will also
work with multiple Socket.IO servers when using the Redis adapter. In
that case, the fetchSockets() method will return a list of RemoteSocket
instances, which expose a subset of the methods and attributes of the
Socket class (the "request" attribute cannot be mocked, for example).

Related:

- #3042
- #3418
- #3570
- socketio/socket.io-redis-adapter#283
dzad pushed a commit to dzad/socket.io that referenced this issue May 29, 2023
This commit adds the following methods:

- fetchSockets: returns the matching socket instances

Syntax:

```js
// return all Socket instances
const sockets = await io.fetchSockets();

// return all Socket instances of the "admin" namespace in the "room1" room
const sockets = await io.of("/admin").in("room1").fetchSockets();
```

- socketsJoin: makes the matching socket instances join the specified rooms

Syntax:

```js
// make all Socket instances join the "room1" room
io.socketsJoin("room1");

// make all Socket instances of the "admin" namespace in the "room1" room join the "room2" room
io.of("/admin").in("room1").socketsJoin("room2");
```

- socketsLeave: makes the matching socket instances leave the specified rooms

Syntax:

```js
// make all Socket instances leave the "room1" room
io.socketsLeave("room1");

// make all Socket instances of the "admin" namespace in the "room1" room leave the "room2" room
io.of("/admin").in("room1").socketsLeave("room2");
```

- disconnectSockets: makes the matching socket instances disconnect

Syntax:

```js
// make all Socket instances disconnect
io.disconnectSockets();

// make all Socket instances of the "admin" namespace in the "room1" room disconnect
io.of("/admin").in("room1").disconnectSockets();
```

Those methods share the same semantics as broadcasting. They will also
work with multiple Socket.IO servers when using the Redis adapter. In
that case, the fetchSockets() method will return a list of RemoteSocket
instances, which expose a subset of the methods and attributes of the
Socket class (the "request" attribute cannot be mocked, for example).

Related:

- socketio#3042
- socketio#3418
- socketio#3570
- socketio/socket.io-redis-adapter#283
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

5 participants