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

socket.io服务端向客户端第二次emit事件客户端无法接收问题求助

问题根因

  • 核心错误是你把app.post('/player_connect')路由的定义写在了io.on('connection')的回调内部:express的路由是全局单例的,只有第一次有客户端连接时会注册成功该路由,后续所有新客户端连接时重复注册的路由都会被忽略,路由内部捕获到的socket变量永远是第一个连接的客户端实例,后续客户端触发该接口时,消息只会发给第一个连接的客户端,其他客户端都收不到。
  • 客户端useEffect监听socket事件没有添加清理函数,组件卸载/重渲染时会重复绑定事件监听器,可能导致消息处理异常。
  • 滥用await调用socket.emit:socket.emit是同步方法,不需要await修饰。

修复方案

服务端代码修改

首先把路由定义挪到io.connection外部,如果是全量通知所有在线玩家的场景直接用io.emit即可,需要定向给特定用户发消息可以自行维护socket连接池:

// 可选:全局socket连接池,需要定向发消息时可以使用
const connectedSockets = new Map()

io.on('connection',  async function (socket) {
    const socketId = socket.id;
    connectedSockets.set(socketId, socket)
    const mta = new Client("20.64.24.144", 22005, "*", "*");
    
    mta.resources.evokestats.getPlayerCount()
        .then((result) => {
            console.log("result", result);
            socket.emit("players-start", { players: result })
        })
        .catch((err) => {
            console.error(`Ooops! Something went wrong ${err}`);
        });

    // 连接断开时清理连接池
    socket.on('disconnect', () => {
        connectedSockets.delete(socketId)
    })
});

// 路由挪到connection回调外,全局仅注册一次
app.post('/player_connect', async function (req, res) {
    const ip = req.body[0];
    const player = await players.findOne({ ip: ip })
    const newPlayerCount = req.body[1]

    if (player) {
        // 给所有在线客户端广播消息
        io.emit("players", { players: newPlayerCount })
    } else {
        try {
            await players.create({ ip: ip, name: req.body[2] })
            io.emit("players", { players: newPlayerCount })
            io.emit("last_24_players", { players: 1 });
        } catch (error) {
            console.log("error", error)
        }
    }
    res.send("connected")
});

客户端代码修改

给useEffect添加清理函数,避免重复绑定事件:

useEffect(() => {
    const handlePlayersStart = (data) => {
        setNowPlayers(data.players)
    }
    const handlePlayers = (data) => {
        console.log("players", data)
        setNowPlayers(data.players)
    }

    socket.on("players-start", handlePlayersStart);
    socket.on("players", handlePlayers);

    // 组件卸载时移除事件监听
    return () => {
        socket.off("players-start", handlePlayersStart)
        socket.off("players", handlePlayers)
    }
}, [])

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.19 19:36:04