Socket.io存储客户端对象至Redis后丢失send()方法的解决请求
解决Socket.io客户端对象存入Redis后send()方法失效的问题
问题根源
Socket.io的客户端socket对象是包含原型方法、内部状态和网络连接引用的复杂实例。当你用CircularJSON.stringify序列化它时,只有对象自身的可枚举属性会被保存,原型链上的send()等方法、事件监听器、底层连接引用都会丢失。反序列化后得到的只是一个普通数据对象,自然没有send()方法。
正确解决方案
不要存储整个socket对象到Redis,只存储唯一标识socket.id和对应的关联信息(比如你用到的channel),然后通过socket.id获取真实的socket实例来发送消息,或者利用socket.io-redis适配器的原生跨进程通信能力。
步骤1:存储socket.id而非整个socket对象
在客户端连接时,仅将socket.id与你的channel关联存入Redis:
socket.on('connection', (client) => { const channel = value.toUpperCase(); // 订阅目标channel(若需手动处理订阅逻辑) io.of('/').adapter.subClient.subscribe(channel); // 存储socket.id到Redis,关联对应的channel io.of('/').adapter.pubClient.set(channel, client.id, (err) => { if (err) { console.error(err); } else { console.log(`Saved socket ID ${client.id} for channel ${channel}`); } }); });
步骤2:通过socket.id获取真实socket实例发送消息
在监听Redis消息时,通过socket.id获取当前进程中的socket实例,若socket在其他进程则使用socket.io-redis的跨进程发送能力:
io.of('/').adapter.subClient.on('message', (channel, message) => { const redisClient = io.of('/').adapter.pubClient; redisClient.get(channel, (err, socketId) => { if (err) { console.error(err); return; } // 尝试获取当前进程中的socket实例 const targetSocket = io.sockets.sockets.get(socketId); if (targetSocket) { // 当前进程存在该socket,直接调用send() targetSocket.send(JSON.parse(message)); } else { // socket在其他进程,利用socket.io-redis适配器的跨进程路由能力 io.to(socketId).send(JSON.parse(message)); } }); });
额外说明
- socket.io-redis适配器已经实现了跨进程的socket消息路由,
io.to(socketId).send()会自动将消息转发到socket所在的进程,无需手动处理Redis的pub/sub(除非你有自定义业务场景需要)。 - 若业务需要通过channel关联socket,只需维护
channel -> socket.id的映射即可,无需操作整个socket对象。
内容的提问来源于stack exchange,提问作者Jahangeer Ahmed
相关产品推荐
相关产品推荐

