skip to Main Content

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?
})

3

Answers


  1. Chosen as BEST ANSWER

    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.


  2. 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
    }
    
    Login or Signup to reply.
  3. 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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search