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,否则统计会完全失真:
- 调整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; } - 初始化Socket.io服务时开启信任代理配置,让服务端可以解析Nginx透传的IP头:
const { Server } = require("socket.io"); const io = new Server(你的服务端口, { cors: { /* 保留你原有的跨域配置即可 */ }, // 开启信任反向代理,否则无法读取透传的真实IP trustProxy: true });
第二步:替换在线数统计逻辑
使用全局Map存储在线IP的关联连接,实现同IP多标签页仅计数1次,只有当该IP下所有连接都断开后,才移除对应IP的统计:
- 新增全局存储结构和通用推送方法:
// 全局存储:key = 客户端真实IP,value = 该IP下所有活跃Socket连接的ID集合 const onlineIpMap = new Map(); // 统一广播最新独立IP在线数 function broadcastOnlineCount() { const onlineUserCount = onlineIpMap.size; io.emit("numberOfOnline", onlineUserCount); } - 改造连接、断开的事件处理逻辑,你原有的业务逻辑(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
相关产品推荐
相关产品推荐

