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

Socket.io向房间发送play_game事件时触发Operation timed out错误求助

Socket.io向房间发送play_game事件时触发Operation timed out错误求助

看起来你在做异步Socket.io版井字棋时遇到了挺头疼的超时问题,我帮你捋捋代码里可能的问题点,以及对应的解决思路:

先看服务器端的核心问题

1. 重复启动HTTP服务器导致连接混乱

你的代码里同时启动了两个独立的HTTP服务器:

server.listen(3000, () => console.log("server listening at port 3000"));
app.listen(4000, () => console.log("server listening at port 4000"));

Socket.io是绑定在server(由http.createServer(app)创建)上的,而app.listen(4000)又启动了另一个不关联Socket.io的服务器,这会导致客户端如果连错端口,就会出现无法通信的超时问题。直接删掉app.listen(4000)这行,只保留server.listen(3000)即可。

2. play_game事件的超时与回调逻辑错误

你用了io.timeout(20000).in(roomname).emit(...),但这里有两个关键问题:

  • Socket.io的timeout()机制是等待客户端发送确认,但你的客户端代码里没有给play_game事件添加确认回调,服务器会一直等不到响应,必然触发超时。
  • 重试逻辑里,你在调用重试后又手动attemptCount++,导致计数混乱;而且不管重试是否完成,你都直接调用了callback({status: "ok"}),让客户端误以为发送成功,实际可能还在重试。

修正方案:

  • 客户端给play_game事件添加确认回调:
    socket.on("play_game", (payload, callback) => {
      setXIsNext(payload.X);
      if (payload.squares) setSquares(payload.squares);
      callback(); // 给服务器发送确认信号
    });
    
  • 服务器端修正重试与回调逻辑:
    const retrySendMessage = (attemptCount) => {
      io.timeout(5000) // 先缩短超时时间,避免不必要的长等待
        .in(roomname)
        .emit("play_game", { squares, X, statement: "emitter to other" }, (err, responses) => {
          if (err) {
            console.log(`第${attemptCount + 1}次发送失败:`, err);
            if (attemptCount < 4) { // 最多重试4次,总计5次尝试
              setTimeout(() => retrySendMessage(attemptCount + 1), 1000); // 加延迟避免频繁发送
            } else {
              callback({ status: "not ok" }); // 所有重试失败后通知客户端
            }
          } else {
            console.log("play_game事件发送成功");
            callback({ status: "ok" }); // 成功后再通知客户端
          }
        });
    };
    
    注意:要把原来try块里直接调用的callback({status: "ok"})删掉,只保留emit回调里的成功/失败通知。

3. 房间存在性检查的准确性问题

你用socket.adapter.rooms.has(roomname)检查房间,但Socket.io的空房间会被自动销毁,用io.in(roomname).allSockets()获取房间内的socket列表会更准确:

// 注意要给外层函数加async
socket.on("move", async (payload, callback) => {
  const { squares, X, roomname } = payload;
  try {
    const socketsInRoom = await io.in(roomname).allSockets();
    if (socketsInRoom.size > 0) {
      retrySendMessage(0);
    } else {
      console.log(`错误:房间"${roomname}"不存在或无成员`);
      callback({ status: "not ok", message: "房间不存在" });
    }
  } catch (error) {
    console.log(error);
    callback({ status: "not ok" });
  }
});

客户端的小细节问题

1. roomnameRef的取值错误

你在handleClick里打印roomnameRef.current.value,但roomnameRef是用useRef(secondPlayer ? rivalEmail : userEmail)创建的,它的current是字符串,不是DOM元素,没有value属性,直接用roomnameRef.current即可,否则可能导致发送的房间名不正确,服务器找不到房间进而超时。

2. 按钮禁用逻辑缺失

你的disabled函数是空的,应该根据回合状态禁用按钮,避免无效的事件发送:

const disabled = (index) => {
  // 非自己回合时,所有按钮禁用
  if ((piece === "X" && !xIsNext) || (piece === "O" && xIsNext)) {
    return true;
  }
  // 已有棋子的按钮禁用
  return squares[index] !== null;
};

额外排查建议

  • 确认客户端连接的Socket.io端口是3000,和服务器的server.listen(3000)一致;
  • 在服务器的connection事件里打印socket.rooms,确认玩家正确加入了目标房间;
  • 用浏览器开发者工具的Network -> WS面板,查看Socket.io的连接状态,有没有断开或错误帧。

备注:内容来源于stack exchange,提问作者The ReBel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.21 15:14:40