You need to enable JavaScript to run this app. Socket.io+Node.js+Nginx聊天室如何按独立IP统计在线用户数
优惠活动
大模型
产品
解决方案
定价
更多

Socket.io+Node.js+Nginx聊天室如何按独立IP统计在线用户数

问题根源

你现在的在线数统计完全依赖io.allSockets().size,这个值本质是所有活跃WebSocket连接的总数,和用户IP没有任何绑定关系。每个浏览器标签页都会和服务端建立独立的Socket连接,因此打开10个标签页自然就会累计10个计数,完全达不到按IP去重的统计目标。

改造实现(按独立IP统计在线数)

整个改造分两部分:先解决Nginx反向代理下的真实IP获取问题,再替换统计逻辑,实现同一IP下多标签页仅计1个在线用户。

第一步:配置真实IP透传

因为你用Nginx做反向代理,直接从Socket连接里拿到的IP会是Nginx的内网地址,必须先做好配置才能拿到用户真实IP,否则统计会完全失真:

  1. 调整Nginx站点配置,给WebSocket的代理段加上IP透传头:
    # WebSocket代理位置配置
    location /socket.io/ {
        proxy_pass http://127.0.0.1:你的Node服务端口;
        proxy_http_version 1.1;
        # WebSocket连接升级必要配置
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        # 透传真实客户端IP
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
    }
    
  2. 初始化Socket.io服务时开启信任代理配置,让服务端可以解析Nginx透传的IP头:
    const { Server } = require("socket.io");
    const io = new Server(你的服务端口, {
        cors: { /* 保留你原有的跨域配置即可 */ },
        // 开启信任反向代理,否则无法读取透传的真实IP
        trustProxy: true
    });
    

第二步:替换在线数统计逻辑

使用全局Map存储在线IP的关联连接,实现同IP多标签页仅计数1次,只有当该IP下所有连接都断开后,才移除对应IP的统计:

  1. 新增全局存储结构和通用推送方法:
    // 全局存储:key = 客户端真实IP,value = 该IP下所有活跃Socket连接的ID集合
    const onlineIpMap = new Map();
    
    // 统一广播最新独立IP在线数
    function broadcastOnlineCount() {
        const onlineUserCount = onlineIpMap.size;
        io.emit("numberOfOnline", onlineUserCount);
    }
    
  2. 改造连接、断开的事件处理逻辑,你原有的业务逻辑(sockets、searching、notAvailable数组的维护)全部保留,仅替换统计相关部分:
    io.on("connection", (socket) => {
        // 获取当前连接的客户端真实IP
        const clientIp = socket.handshake.address;
    
        // 维护IP和Socket连接的映射关系
        if (onlineIpMap.has(clientIp)) {
            // 该IP已经存在统计记录,仅追加当前连接的ID
            const ipSocketSet = onlineIpMap.get(clientIp);
            ipSocketSet.add(socket.id);
        } else {
            // 首次出现的IP,新建统计记录
            onlineIpMap.set(clientIp, new Set([socket.id]));
        }
    
        // 以下为你原有业务逻辑,直接保留即可
        sockets.push(socket);
    
        // 推送最新在线数
        broadcastOnlineCount();
    
        // 监听当前连接的断开事件
        socket.on("disconnect", () => {
            // 移除断开连接对应的映射关系
            const ipSocketSet = onlineIpMap.get(clientIp);
            if (ipSocketSet) {
                ipSocketSet.delete(socket.id);
                // 该IP下所有连接都已断开时,删除对应统计记录
                if (ipSocketSet.size === 0) {
                    onlineIpMap.delete(clientIp);
                }
            }
    
            // 以下为你原有断开清理逻辑,直接保留即可
            sockets = sockets.filter((user) => user.id !== socket.id);
            searching = searching.filter((user) => user.id !== socket.id);
            notAvailable = notAvailable.filter((user) => user.id !== socket.id);
    
            // 推送最新在线数
            broadcastOnlineCount();
        });
    });
    
注意事项
  • 必须先完成Nginx透传、trustProxy配置,否则所有连接的IP都会被识别为Nginx的内网地址,导致所有用户被错误统计为1个在线。
  • 如果你后续采用多Node实例部署,不能使用内存Map做存储,需要替换为Redis这类跨实例共享的存储层,否则不同实例的IP统计数据不互通,结果会失真。
  • 原有的sockets、searching、notAvailable数组功能不受影响,不需要删除原有业务逻辑,仅替换在线数计算的部分即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.04 11:06:09