Socket.io+React/Node:如何更新排除当前连接的在线用户列表?
问题:Socket.io在线列表实时更新异常
需求:实现基础聊天功能,实时更新客户端Socket连接在线列表。示例场景:客户端一登录为x,客户端二登录为y。客户端一应在客户端二登录后看到在线列表为[y](看不到自己);客户端二应看到在线列表为[x]。
当前问题:第二个客户端连接后,双方都显示x;刷新第二个客户端页面后,双方都显示y。聊天功能未实现,卡在在线列表实时更新。
服务端代码
// Server const express = require('express'); const http = require('http'); const { Server } = require('socket.io'); const cors = require('cors'); require('dotenv').config(); const app = express(); const server = http.createServer(app); const io = new Server(server, { cors: { origin: process.env.CLIENT_ADDRESS, methods: ['*'], }, }); const PORT = process.env.SERVER_PORT; app.use(cors()); const connections = {}; io.on('connection', (socket) => { console.log(`User ${socket.id} connected`); socket.on("store_user", (data) => { connections[socket.id] = { userName: data.userName, socket: socket, }; const onlineUsers = Object.values(connections) .filter((user) => user.socket.id !== socket.id) .map((user) => user.userName); socket.emit('get_users', onlineUsers); // Send the list only to the current user. socket.broadcast.emit('get_users', onlineUsers); //update everyone with the changes. }) socket.on("disconnect", () => { console.log(`User disconnected: ${socket.id}`); delete connections[socket.id]; const onlineUsers = Object.values(connections).map((user) => user.userName); io.emit('get_users', onlineUsers); }) }); server.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); });
客户端登录页面代码
import {useState} from 'react' import { useNavigate } from 'react-router-dom'; const Login = () => { const [input, setInput] = useState(""); const navigate = useNavigate(); const setUserName = () => { sessionStorage.setItem("userName", input); navigate('/chat') } return ( <div> <input type="text" onChange={(e)=>{setInput(e.target.value)}}/> <button onClick={setUserName}>login</button> </div> ) } export default Login
客户端聊天页面代码
// Client import { useEffect, useState } from 'react'; import { io } from 'socket.io-client'; const serverURL = 'http://localhost:8000/'; const socket = io.connect(serverURL); const Chat = () => { const sessionUserName = sessionStorage.getItem('userName'); const [users, setUsers] = useState([]); useEffect(() => { const handleDisconnect = () => { console.log('Disconnected'); }; const handleGetUsers = (data) => { setUsers(data); }; socket.on('disconnect', handleDisconnect); socket.emit('store_user', { userName: sessionUserName }); socket.on('get_users', handleGetUsers); }, [sessionUserName, socket]); return ( <> <button onClick={() => console.log(users)}>Show users</button> <ul> {users.map((user) => ( <li key={user}>{user}</li> ))} </ul> </> ); }; export default Chat;
问题原因与修复方案
核心问题
- 广播列表逻辑错误:新用户加入时,你给所有在线用户广播的是「过滤掉新用户的列表」,导致老用户看不到新用户。比如x先登录,y加入后,服务端给x发送的列表是[x](过滤掉y),而不是[y]。
- 客户端全局socket实例重复监听:socket实例在组件外全局声明,每次组件挂载都会重复绑定事件,导致状态更新混乱。
- 断开连接时未过滤用户自身:断开连接后给所有用户发送的列表未过滤自身,会导致用户看到自己。
修复步骤
服务端修改
调整store_user和disconnect事件的列表生成逻辑,发送完整用户列表,让客户端自己过滤掉自身:
io.on('connection', (socket) => { console.log(`User ${socket.id} connected`); socket.on("store_user", (data) => { // 简化connections存储,只存用户名 connections[socket.id] = data.userName; // 获取所有在线用户的完整列表 const allUsers = Object.values(connections); // 给当前用户发送过滤掉自己的列表 socket.emit('get_users', allUsers.filter(name => name !== data.userName)); // 给其他用户发送完整列表,由客户端自行过滤 socket.broadcast.emit('get_users', allUsers); }) socket.on("disconnect", () => { console.log(`User disconnected: ${socket.id}`); delete connections[socket.id]; const allUsers = Object.values(connections); // 给所有用户发送完整列表,客户端自行过滤 io.emit('get_users', allUsers); }) });
客户端修改
将socket实例移到组件内部,添加事件清理逻辑,并在客户端过滤自身:
import { useEffect, useState, useRef } from 'react'; import { io } from 'socket.io-client'; const serverURL = 'http://localhost:8000/'; const Chat = () => { const sessionUserName = sessionStorage.getItem('userName'); const [users, setUsers] = useState([]); // 用useRef保存socket实例,避免重复创建 const socket = useRef(io.connect(serverURL)).current; useEffect(() => { const handleDisconnect = () => { console.log('Disconnected'); }; const handleGetUsers = (data) => { // 客户端过滤掉当前登录用户 setUsers(data.filter(name => name !== sessionUserName)); }; socket.on('disconnect', handleDisconnect); socket.on('get_users', handleGetUsers); socket.emit('store_user', { userName: sessionUserName }); // 组件卸载时移除事件监听,避免内存泄漏 return () => { socket.off('disconnect', handleDisconnect); socket.off('get_users', handleGetUsers); }; }, [sessionUserName, socket]); return ( <> <button onClick={() => console.log(users)}>Show users</button> <ul> {users.map((user) => ( <li key={user}>{user}</li> ))} </ul> </> ); }; export default Chat;
内容的提问来源于stack exchange,提问作者Moshe Az
相关产品推荐
相关产品推荐

