Socket.IO离开房间后仍接收该房间事件的问题解决
问题
用PeerJS和Socket.IO开发视频通话应用时遇到异常:用户A同时加入Room 1和Room 2等待接听,接通Room 1后调用socket.leave('room2')成功离开,但当A结束通话刷新页面时,Room 2却触发了user-disconnected事件。排查发现socket.leave()只是让socket退出房间,没移除之前绑定的disconnect监听器,而移除所有disconnect监听器又会导致正常场景失效。
原代码
io.on('connection', socket => { socket.on('join-room', (roomId, userId) => { socket.join(roomId) socket.broadcast.to(roomId).emit("user-connected", userId); console.log("socketId " + socket.id + " connected to room " + roomId); socket.on('leave-room', (roomId) => { console.log(socket.adapter.rooms); socket.leave(roomId); console.log("socketId " + socket.id + " left room " + roomId); console.log(socket.adapter.rooms); // Tried this but if it disconnects later, other user in the room won't receive the message as // disconnect event does not trigger //socket.removeAllListeners("disconnect"); }) socket.on('disconnect', () => { socket.broadcast.to(roomId).emit("user-disconnected", userId); console.log("socketId " + socket.id + " disconnected from room " + roomId); }) }) })
解决方案
核心问题是每次调用join-room都会给socket新增一个disconnect监听器,离开房间时没移除对应房间的监听器,导致断开连接时所有绑定过的房间都会收到事件。下面两种方法可以解决:
方法1:跟踪当前加入的房间,断开时只通知有效房间
给每个socket维护一个已加入房间的集合,离开时从集合中删除,断开连接时遍历集合里的房间发送事件:
io.on('connection', socket => { // 用Set存储当前socket有效的房间,自动去重 socket.joinedRooms = new Set(); socket.on('join-room', (roomId, userId) => { socket.join(roomId); socket.joinedRooms.add(roomId); // 把userId存到socket实例上,方便断开时使用 socket.currentUserId = userId; socket.broadcast.to(roomId).emit("user-connected", userId); console.log(`socketId ${socket.id} connected to room ${roomId}`); socket.on('leave-room', (targetRoomId) => { socket.leave(targetRoomId); socket.joinedRooms.delete(targetRoomId); console.log(`socketId ${socket.id} left room ${targetRoomId}`); }) }) // 只绑定一次disconnect监听器,断开时遍历当前有效的房间 socket.on('disconnect', () => { const userId = socket.currentUserId; socket.joinedRooms.forEach(roomId => { socket.broadcast.to(roomId).emit("user-disconnected", userId); console.log(`socketId ${socket.id} disconnected from room ${roomId}`); }); }) })
方法2:给每个房间绑定独立的监听器,离开时移除对应监听器
给每个房间的disconnect逻辑单独绑定函数,离开房间时精准移除该监听器:
io.on('connection', socket => { socket.on('join-room', (roomId, userId) => { socket.join(roomId); socket.broadcast.to(roomId).emit("user-connected", userId); console.log(`socketId ${socket.id} connected to room ${roomId}`); // 定义当前房间专属的断开回调 const disconnectHandler = () => { socket.broadcast.to(roomId).emit("user-disconnected", userId); console.log(`socketId ${socket.id} disconnected from room ${roomId}`); }; // 绑定监听器,并把回调存到socket的Map里,用roomId做key socket.on('disconnect', disconnectHandler); if (!socket.roomDisconnectHandlers) { socket.roomDisconnectHandlers = new Map(); } socket.roomDisconnectHandlers.set(roomId, disconnectHandler); socket.on('leave-room', (targetRoomId) => { socket.leave(targetRoomId); // 找到对应房间的回调并移除 const handler = socket.roomDisconnectHandlers.get(targetRoomId); if (handler) { socket.off('disconnect', handler); socket.roomDisconnectHandlers.delete(targetRoomId); } console.log(`socketId ${socket.id} left room ${targetRoomId}`); }) }) })
关键提醒
socket.leave()只负责把socket从房间的成员列表中移除,不会自动清理之前绑定的事件监听器,必须手动管理监听器的生命周期。- 方法1更简洁,适合大多数常规场景;方法2更灵活,适合需要对每个房间事件做特殊处理的场景。
内容的提问来源于stack exchange,提问作者Mallvin2000
相关产品推荐
相关产品推荐

