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

如何判断Socket.io断开连接是否由beforeunload事件导致?

可靠区分页面卸载与其他原因断开的方案

核心思路

页面卸载时浏览器会限制常规网络请求的发送,因此需使用浏览器专门支持的、能在页面卸载后完成的请求方式,同时服务端配合标记机制,精准区分主动页面卸载和意外断开场景。

方案一:使用带keepalive的Fetch请求(推荐)

客户端在页面卸载时发送可靠的HTTP请求告知服务端,服务端记录该状态后,后续Socket断开时直接执行登出;若未收到该请求,则按原逻辑保留登录2分钟。

客户端代码

// 优先监听pagehide,在移动端和部分浏览器比beforeunload更可靠
window.addEventListener('pagehide', async () => {
  try {
    await fetch('/api/notify-page-unload', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ playerId: this.currentPlayerId }),
      keepalive: true, // 强制浏览器在页面卸载后完成请求
      credentials: 'include' // 需携带身份验证时启用
    });
  } catch (err) {
    console.error('发送页面卸载通知失败:', err);
  }
});

// 兼容旧浏览器,补充监听beforeunload
window.addEventListener('beforeunload', async () => {
  try {
    await fetch('/api/notify-page-unload', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ playerId: this.currentPlayerId }),
      keepalive: true,
      credentials: 'include'
    });
  } catch (err) {
    console.error('发送页面卸载通知失败:', err);
  }
});

服务端处理

  1. 新增HTTP接口处理页面卸载通知:
// 示例:Express框架下的接口
app.post('/api/notify-page-unload', (req, res) => {
  const { playerId } = req.body;
  // 用Map存储主动卸载标记,设置10秒过期避免误判
  activeUnloads.set(playerId, Date.now());
  setTimeout(() => activeUnloads.delete(playerId), 10000);
  res.status(200).send('通知已接收');
});
  1. 修改Socket断开事件的处理逻辑:
socket.on('disconnect', (reason) => {
  const player = getPlayerBySocketId(socket.id);
  if (!player) return;

  // 检查是否存在主动页面卸载标记
  if (activeUnloads.has(player.id)) {
    logout(player.id);
    activeUnloads.delete(player.id);
    console.log(`${player.profile.displayName} 因页面卸载断开连接,已登出`);
  } else {
    // 意外断开,保留登录2分钟后自动登出
    setTimeout(() => {
      if (isPlayerLoggedIn(player.id)) {
        logout(player.id);
        console.log(`${player.profile.displayName} 意外断开连接,超时后已登出`);
      }
    }, 2 * 60 * 1000);
  }
});

方案二:使用Navigator.sendBeacon

若无需复杂请求体,sendBeacon是更轻量的选择,同样支持页面卸载后发送:

客户端代码

window.addEventListener('pagehide', () => {
  const data = new FormData();
  data.append('playerId', this.currentPlayerId);
  navigator.sendBeacon('/api/notify-page-unload', data);
});

服务端解析FormData即可,后续逻辑与方案一一致。

关键注意事项

  • 不要依赖Socket.emit在页面卸载时发送消息:浏览器在页面卸载时会立即终止WebSocket连接,emit的消息大概率无法到达服务端。
  • 标记需设置过期时间:避免玩家主动卸载后,Socket断开事件延迟到达时标记仍存在,导致误判。
  • 多事件监听覆盖场景:同时监听pagehide和beforeunload,兼容更多浏览器和设备。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 07:03:26