You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 15:22:50