如何判断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); } });
服务端处理
- 新增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('通知已接收'); });
- 修改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
相关产品推荐
相关产品推荐

