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

React组件无法接收Socket.io房间事件问题求助

排查玩家A无法接收Socket.io房间事件的问题

以下是针对你的场景的具体排查方向和修复建议:

1. 确认玩家A的Socket已成功加入目标房间

服务器端GAME_CREATE_ROOM事件中,先验证玩家A的Socket是否真的加入了对应房间:

// 服务器端修改GAME_CREATE_ROOM事件,添加日志
socket.on(PokeBattleSocketEvents.GAME_CREATE_ROOM, async (data) => {
    const game = await Game.findByPk(data.gameId);
    if (!game) {
        console.error('Game not found for ID:', data.gameId);
        return;
    }
    const roomId = game.dataValues.id;
    socket.join(roomId);
    // 打印日志确认加入情况
    console.log(`Socket ${socket.id} joined room: ${roomId}`);
    // 额外发送测试事件,验证A是否能收到房间消息
    socket.to(roomId).emit(PokeBattleSocketEvents.TEST_EVENT, { msg: 'Joined room successfully' });
});

同时在玩家A的Lobby组件中监听TEST_EVENT,看是否能收到这条测试消息,直接验证房间通信是否正常。

2. 修改服务器事件发送方式,排除范围问题

当前服务器用socket.to(data.roomId).emit(...),只会给房间内除当前Socket(玩家B)之外的成员发消息。如果玩家A的Socket不在房间里,自然收不到。可以先改成全局广播到房间的方式测试:

socket.on(PokeBattleSocketEvents.GAME_PLAYER_JOINING, async (data) => {
    socket.join(data.roomId);
    console.log("dummy log", data.roomId);
    // 改用io.to()给房间内所有成员发事件(包括B)
    app.io.to(data.roomId).emit(PokeBattleSocketEvents.GAME_PLAYER_JOINED, {
        'player': data.userId,
        'gameId': data.roomId
    });
});

如果这样玩家A能收到消息,说明之前的socket.to()本身没问题,但玩家A的Socket确实不在房间内,回到第一步排查加入逻辑。

3. 确保React组件的事件监听时机正确

即使把监听放到useEffect里,也要确保:

  • 监听是在玩家A的Socket已经加入房间之后才注册的
  • 组件渲染时不会重复注册或丢失监听

优化Lobby组件的监听逻辑:

export function Lobby() {
    const {socket} = useContext(SocketContext);
    const [player2, setPlayer2] = useState<string | null>(null);

    useEffect(() => {
        // 注册监听前先移除旧的,避免重复绑定
        socket.off(PokeBattleSocketEvents.GAME_PLAYER_JOINED);
        
        const handlePlayerJoined = (data: {player: string, gameId: string}) => {
            console.log("player joined", data);
            toast.custom((t) => <Toast t={t} msg={`Player ${data.player} joined your lobby !`} />);
            setPlayer2(data.player);
        };

        socket.on(PokeBattleSocketEvents.GAME_PLAYER_JOINED, handlePlayerJoined);
        console.log('GAME_PLAYER_JOINED listener registered');

        return () => {
            socket.off(PokeBattleSocketEvents.GAME_PLAYER_JOINED, handlePlayerJoined);
        };
    }, [socket]);

    return (<></>);
}

打开浏览器控制台,确认GAME_PLAYER_JOINED listener registered日志在玩家A进入Lobby时打印,说明监听已正确注册。

4. 排查Socket重连导致的房间丢失问题

如果玩家A的Socket连接断开后重连,新的Socket实例不会自动加入之前的房间。在SocketProvider中添加重连处理:

export const SocketProvider = ({children}) => {
    const socket = io(import.meta.env.VITE_API_ENDPOINT);

    useEffect(() => {
        // 处理重连逻辑
        const handleReconnect = () => {
            // 从本地存储获取玩家A当前的游戏ID,重新加入房间
            const currentGameId = localStorage.getItem('currentGameId');
            if (currentGameId) {
                socket.emit(PokeBattleSocketEvents.GAME_CREATE_ROOM, { gameId: currentGameId });
            }
        };

        socket.on('reconnect', handleReconnect);

        // 其他原有监听...
        socket.on(PokeBattleSocketEvents.TEST_EVENT, (data: object) => {
            console.log(data);
        });

        return () => {
            socket.off('reconnect', handleReconnect);
            socket.off(PokeBattleSocketEvents.TEST_EVENT);
            socket.off(PokeBattleSocketEvents.GAME_CREATE_ROOM);
        };
    }, []);

    // 其他原有逻辑...
};

同时在玩家A创建游戏成功后,把gameId存入localStorage:

// 玩家A创建游戏的逻辑中添加
const createGame = async () => {
    const newGame = await gameCreate(...);
    localStorage.setItem('currentGameId', newGame.id);
    socket.emit(PokeBattleSocketEvents.GAME_CREATE_ROOM, { gameId: newGame.id });
    navigate('/game/lobby/' + newGame.id);
};

5. 验证事件名称的一致性

彻底检查前后端PokeBattleSocketEvents中GAME_PLAYER_JOINED的定义,确保拼写、大小写完全一致。比如前端枚举:

export enum PokeBattleSocketEvents {
    GAME_PLAYER_JOINED = 'GAME_PLAYER_JOINED',
    // 其他事件...
}

后端必须使用完全相同的字符串,不能有任何差异。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 19:56:02