【问题标题】:How do I let socket.io server re-emit message to a particular socket when that socket re-connect?当套接字重新连接时,如何让 socket.io 服务器向特定套接字重新发送消息?
【发布时间】: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 感谢您回答我的问题。我更新了问题,你可以看看吗?

标签: node.js socket.io


【解决方案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】:

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

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

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

    1. 记录我要发送的消息
    2. 使用ack回调删除消息,所以没有删除的就是失败的消息。
    3. 要使用 ack 我不能 io.to(socketId).emit('xxx'); 因为"acknowledgements are not supported when emitting from namespace." 所以我需要首先使用 io.sockets.connected[socketid] 从 id 获取套接字对象
    4. 我可以启动一个计时器来检查消息是否仍然存储在 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)
    }) 
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-20
      • 2012-12-27
      相关资源
      最近更新 更多