当套接字重新连接时,如何让 socket.io 服务器向特定套接字重新发送消息?

Posted

技术标签:

【中文标题】当套接字重新连接时,如何让 socket.io 服务器向特定套接字重新发送消息?【英文标题】:How do I let socket.io server re-emit message to a particular socket when that socket re-connect? 【发布时间】:2020-11-09 13:22:17 【问题描述】:

我的服务器代码使用io.to(socketId).emit('xxx'); 向特定套接字发送消息,参考https://socket.io/docs/emit-cheatsheet/

但是当连接不好并且客户端断开连接并再次连接时,socketId会发生变化并且io.to(socketId).emit('xxx');然后失败。那么如何实现 re-emit 方法来处理重新连接?

我的问题与socket.io stop re-emitting event after x seconds/first failed attempt to get a response 的问题有点相反,从中我了解到socket.io client 可以在重新连接时重新发出。但服务器代码似乎缺乏这种支持。

---更新---

正如评论所说,我在这里显示“您保存过时的 socketId 值的代码上下文。”

首先,每个用户在我们的系统中都有一个唯一的 id,当 socket 连接时,客户端会发送一个登录消息,所以我保存了 userId 和 socketId 对。当我收到断开连接事件时,我会清除该信息。

第二,用户将通过redis pubsub(来自其他服务)获取消息,我将通过我记录的socketid将该消息发送给客户端。因此,当套接字连接时,我将订阅由 userid 标识的频道并在断开连接事件中取消订阅。

第三,当我收到 redis pub 事件而 socketId 值过时时,就会出现我的问题。实际上socketid的值是最新的,但是socket可能发送消息失败。

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

【问题讨论】:

您需要向我们展示您的代码上下文,您将在其中保存过时的 socketId 值。 Socket.io 连接可以在用户导航或网络故障期间来来去去,因此,一般来说,您希望向已连接的用户发出信号,然后使用某种中间机制将用户映射到他们当前的 socketID,并保持该中间机制正常运行迄今为止,连接来来去去。 @jfriend00 感谢您回答我的问题。我更新了问题,你可以看看吗? 【参考方案1】:

您可以通过覆盖默认方法来为您的套接字客户端生成自定义 socket.id 以生成套接字 id。在这种情况下,socket.id 将为您所知,并且可以在相同的 id 上线时重新发出。

//以节点请求对象(http.IncomingMessage)作为第一个参数调用该函数。 As per socket.io docs

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

【讨论】:

【参考方案2】:

我找不到任何现有的解决方案,所以这是我想出的:

    记录未能发送到套接字的消息。 我想不出办法来处理这些失败的消息,例如如果他们失败了,我不知道重新发送是否会有所不同。 当我收到套接字连接事件时,我会检查这是否来自同一个客户端,如果是,则检查我是否有旧套接字的失败消息,如果是,则将这些消息发送到新套接字。

如何记录失败的消息是我发现 socket.io 不支持的另一项任务,这就是我所做的,

    记录我要发送的消息 使用ack回调删除消息,所以没有删除的就是失败的消息。 要使用 ack 我不能 io.to(socketId).emit('xxx'); 因为"acknowledgements are not supported when emitting from namespace." 所以我需要首先使用 io.sockets.connected[socketid] 从 id 获取套接字对象 我可以启动一个计时器来检查消息是否仍然存储在 redis 中,如果是,我会重新发送它。但我不知道重新发送是否会有所作为。所以我还没有这样做。

记录失败的代码有点像这样

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

如果有人能提出更好的解决方案,我会全力以赴!谢谢。

【讨论】:

以上是关于当套接字重新连接时,如何让 socket.io 服务器向特定套接字重新发送消息?的主要内容,如果未能解决你的问题,请参考以下文章

断开连接/错误时清除 socket.io 缓冲区

在重新连接、socket.io、node.js 时重用套接字 id

Socket.io - 客户端断开连接后手动重新连接

Node.js Socket.io 设置多个连接

一次与两个用户随机聊天(Socket.io)

应用程序进入后台时如何保持 Swift Socket IO 运行?