当套接字重新连接时,如何让 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 服务器向特定套接字重新发送消息?的主要内容,如果未能解决你的问题,请参考以下文章