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

Socket.io连接时DOM正常更新,多开同用户标签页或断开时更新失败,原因是什么?

现有代码存在的核心问题

服务端问题

  • 登录逻辑时序错误:socket连接时优先读取session中的user_id,但login事件是连接建立后才由客户端触发,新登录用户首次连接时无法正确写入users字典
  • 多标签页状态判断逻辑冗余:未在用户断开连接时清理users字典中对应失效的socket.id,每次都全量遍历所有在线socket判断有效性,长期运行会产生大量无效数据堆积
  • 事件发送逻辑错误:断开连接时触发的disconnectList事件传递的是当前在线用户列表,而非离线用户ID,逻辑完全倒置
  • 重复事件推送:用户连接时循环给所有客户端逐个推送单个在线用户信息,会产生大量冗余事件,用户量上升后性能损耗严重

客户端问题

  • 无DOM删除逻辑:收到disconnectList事件时仍调用新增用户的outputActiveUsers方法,自然无法实现离线用户移除
  • 数据与DOM无关联:仅用record数组存储已添加的用户ID,未关联对应DOM节点,需要删除时无法定位到对应li元素
  • 去重逻辑不完整:仅新增时做了ID去重,没有兜底的同步逻辑,服务端推送异常时容易出现列表与实际在线状态不一致

更优实现方案

推荐采用服务端推送全量在线列表 + 客户端全量重渲染的方案,逻辑最简单不易出错,完全可以覆盖100人以下的在线场景,不需要处理复杂的增量同步逻辑。

服务端优化代码

let users = {}; // 结构:{user_id: [socket_id1, socket_id2...]}
io.on("connection", async (socket) => {
    // 登录成功后绑定用户和socket
    socket.on("login", async (user_id) => {
        socket.handshake.session.user_id = user_id;
        socket.handshake.session.save();
        // 写入users字典
        if (!users[user_id]) users[user_id] = [];
        users[user_id].push(socket.id);
        // 推送最新在线列表
        await broadcastOnlineUsers();
    });

    // 断开连接处理
    socket.on("disconnect", async () => {
        const userId = socket.handshake.session.user_id;
        if (!userId) return;
        // 清理当前失效的socket.id
        if (users[userId]) {
            users[userId] = users[userId].filter(id => id !== socket.id);
            // 该用户所有连接都断开了,删除对应key
            if (users[userId].length === 0) {
                delete users[userId];
                // 可选:推送用户离线通知
                const userInfo = await utils.getUserInfo(userId);
                io.emit("msg", utils.formatMessage("Bot", `${userInfo.name} 已离开聊天室`));
            }
        }
        // 推送最新在线列表
        await broadcastOnlineUsers();
    });
    
    // 封装:获取全量在线用户信息并广播
    async function broadcastOnlineUsers() {
        const onlineUserIds = Object.keys(users);
        const onlineUsers = await Promise.all(onlineUserIds.map(id => utils.getUserInfo(id)));
        io.emit("updateOnlineUsers", onlineUsers);
    }
});

客户端优化代码

let ul = document.querySelector(".the-ul");
let socket = io();

// 收到全量在线用户列表直接重渲染
socket.on("updateOnlineUsers", (onlineUsers) => {
    // 清空现有列表
    ul.innerHTML = '';
    // 循环渲染所有在线用户
    onlineUsers.forEach(user => {
        outputActiveUsers(user);
    });
});

// 其他原有逻辑(消息收发、提交等)保持不变
function outputActiveUsers(msg){
    let li = document.createElement("li");
    li.classList.add("active-people-li");
    // 给li加用户ID属性,后续要做增量操作时可以直接定位
    li.dataset.userId = msg.id;
    li.innerHTML= `<div class="active-sidebar">
                        <div class="div-of-img">
                            <img src="../files/profile.png" alt="" class="active-profile-img">
                        </div>
                        <div>
                            <a href=""><h4>${msg.name}</h4></a>
                            <h5>Hi. Is this you, bro?</h5>
                        </div>
                    </div>`;
    ul.appendChild(li);
}

DOM操作可选优化(适合在线用户量较大的场景)

如果在线用户超过100人,全量重渲染会有轻微性能损耗,可以改为增量操作:

  • 用户上线时:检查是否存在[data-user-id="${user.id}"]的节点,不存在则新增
  • 用户下线时:直接找到对应data-user-id的节点调用remove()删除即可

内容的提问来源于stack exchange,提问作者Tom

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 02:03:02