1

My server codes use io.to(socketId).emit('xxx'); to a message to a particular socket, refer to https://socket.io/docs/emit-cheatsheet/

But when the connection is bad and the client disconnects and connects again, socketId changes and io.to(socketId).emit('xxx'); then fails. So how do I implement a re-emit method to handle the reconnection ?

My question is kind of opposite to this one, socket.io stop re-emitting event after x seconds/first failed attempt to get a response, from it I learn that socket.io client can re-emit when it reconnects. But the server code seems to lack that support.

--- update ---

As the comment said I show here "the code context where you're saving the socketId value that gets stale."

First, each user has a unique id in our system, when socket connects the client will send a login message so I save the userId and socketId pair. When I get disconnect event I clear that info.

Second, the user will get messages through redis pubsub (from other service), and I will send that message to the client throught the socketid I record. So when socket connects I will subscribe a channel identified by userid and unsubscribe it in disconnect event.

Third, my problem happens when I get redis pub event while socketId value gets stale. Actually the value of socketid is the latest one but the socket may fail to send the message.

io.on('connection', async function (socket) {
  socket.on('login', async function (user_id, fn) {
    ...
    await redis
      .multi()
      .hset('websocket:socket_user', socket.id, user_id)
      .hset(
        `websocket:${user_id}`,
        'socketid',
        socket.id
      )
      .exec()
    await sub.subscribe(user_id)
    ...
  }
                
  socket.on('disconnect', async function (reason) {     
    let user_id = await redis.hget('websocket:socket_user', socket.id)
    await redis.del(`websocket:${user_id}`)
    sub.unsubscribe(user_id)
    ...
  }
  
  //Here is what problem happens when socket is trying to reconnect 
  //while I got redis pub message

  sub.on('message', async function (channel, message) {
  let socketid = await redis.hget(`websocket:${channel}`, 'socketid')
  if (!socketid) {
    return
  }
  //so now I assume the socketid is valid, 
  //but it turns out clients still complain they didn't receive message
  io.to(socketid).emit('msg', message) //When it fails how do I re-emit?
})
Qiulang
  • 10,295
  • 11
  • 80
  • 129
  • You would need to show us your code context where you're saving the `socketId` value that gets stale. Socket.io connections can come and go as the user navigates or during network hiccups so, in general, you want to emit to a connected user and then use some intermediate mechanism to map a user to their current socketID and you keep that intermediate mechanism up to date as connections come and go. – jfriend00 Jul 21 '20 at 20:27
  • @jfriend00 thanks for answer my question. I updated the question so can you take a look ? – Qiulang Jul 22 '20 at 02:46

3 Answers3

1

I would suggest you use your unique id as a room, that is the best solution I come up with, no matter how many times the user is joined the user will be joined in a single room only(with their unique id).

see my answer on NodeJs Socket.io Rooms

Dhaval Italiya
  • 386
  • 2
  • 7
  • Using room is indeed a solution. When I asked the question 2 years ago I think I was focusing on one-to-one communication so using room didn't come across my mine. But now the codes have been running well in the past 2 years I don't have incentive to rewrite it using room too. But thanks anyway. – Qiulang Nov 03 '22 at 03:03
  • You don't have to rewrite your code entirely, whenever any socket request comes 1st of all one event with data as userID should be emitted from the client side and the server should listen to that event and assign a user to a room, now wherever you are emitting data from server emit to that room id that's 2 or 3 changes need be rewritten. – Dhaval Italiya Nov 03 '22 at 03:23
  • I know what you said. But what I mean rewriting is that my current processing is all based on one-on-one communication. There is no room concept at all in my current codes. I know adding room is just 2 lines of codes in itself but I need to be more careful for a codebase had running for 2 years. I will keep an eye on your solution! – Qiulang Nov 03 '22 at 03:31
0

I can't find any existing solution for that so this is what I come up with:

  1. Record the messages that fail to send to the socket.
  2. I can't figure out a way to deal with these failed messages, e.g. if they have failed I don't know if re-send will make any difference.
  3. When I get a socket connect event I check if this is from the same client, if yes then check if I have failed messages for the old socket, if yes send those messages to the new socket.

How to record the failed message is another task I find socket.io doesn't support and this is what I do,

  1. Record the message I want to send
  2. use ack callback to delete the message, so the messages are not deleted are the failed ones.
  3. to use ack I can't io.to(socketId).emit('xxx'); because "acknowledgements are not supported when emitting from namespace." so I need to first get socket object from id using io.sockets.connected[socketid]
  4. I can start a timer to check if the message is still stored in redis, if yes I re-emit it. But I don't know if re-emit will make any difference. So I have not done that yet.

The codes to record failed one are somewhat like this

let socket = io.sockets.connected[socketid]
if (!socket) return
let list_key = `websocket:${socketid}`
await redis.sadd(list_key, message)
socket.emit('msg', message, async (ack) => {
  await redis.srem(list_key, message)
}) 

If someone can come up with a better solution I am all ears! Thanks.

Qiulang
  • 10,295
  • 11
  • 80
  • 129
0

You can generate custom socket.id for your socket clients by overwriting default method to generate socket id. in this case socket.id will be known for you and can be re-emit when same id comes online.

//The function is called with a node request object (http.IncomingMessage) as first parameter. As per socket.io docs

io.engine.generateId = (req) => {
  return "custom:id:" + user_unique_id; // custom id must be unique
}
lokee
  • 1