88 lines
2.7 KiB
Markdown
88 lines
2.7 KiB
Markdown
|
你提的这个问题很有代表性——如何区分 WebSocket 是“用户主动断开”还是“异常断线”,确实是一个经典难题。
|
|||
|
|
|||
|
✅ 目前主流的解决方案其实都是「绕过法」,原因是:
|
|||
|
🔒 浏览器并不会告诉你页面是“关闭了”还是“断网了”——WebSocket.onclose 都会触发。
|
|||
|
|
|||
|
所以目前更好的思路是:
|
|||
|
✅ 【方案一】手动标记断开意图(已介绍,业界最常用)
|
|||
|
js
|
|||
|
复制
|
|||
|
编辑
|
|||
|
let manuallyClosed = false
|
|||
|
|
|||
|
function closeWebSocket() {
|
|||
|
manuallyClosed = true
|
|||
|
ws.close()
|
|||
|
}
|
|||
|
|
|||
|
ws.onclose = () => {
|
|||
|
if (!manuallyClosed) {
|
|||
|
// 是异常断线
|
|||
|
reconnect()
|
|||
|
}
|
|||
|
}
|
|||
|
这种方式简单、可靠,你控制了连接断开的“意图”。
|
|||
|
|
|||
|
✅ 【方案二】结合 window.onbeforeunload 判断页面关闭
|
|||
|
你可以在用户关闭页面或刷新时做标记:
|
|||
|
|
|||
|
js
|
|||
|
复制
|
|||
|
编辑
|
|||
|
window.addEventListener("beforeunload", () => {
|
|||
|
manuallyClosed = true
|
|||
|
chatWs?.close()
|
|||
|
})
|
|||
|
这和第一种方案可以配合使用,适用于 Vue、React、原生项目等。
|
|||
|
|
|||
|
✅ 【方案三】心跳机制 + 超时判断(进阶)
|
|||
|
逻辑:
|
|||
|
前端每隔 N 秒发送一个 ping;
|
|||
|
|
|||
|
后端收到后回一个 pong;
|
|||
|
|
|||
|
如果前端 N 秒没收到 pong,说明服务挂了 → 手动调用重连;
|
|||
|
|
|||
|
如果连接是被主动 close() 的,不发送 ping。
|
|||
|
|
|||
|
优点:
|
|||
|
能更快判断“假连接”(服务端挂了但浏览器还没断);
|
|||
|
|
|||
|
能做“连接质量监测”。
|
|||
|
|
|||
|
这种方式通常配合方案一一起使用。
|
|||
|
|
|||
|
✅ 【方案四】统一封装 WebSocket 管理类(推荐)
|
|||
|
比如你封装一个 WebSocketManager,内部处理:
|
|||
|
|
|||
|
是否需要重连;
|
|||
|
|
|||
|
是否是用户主动关闭;
|
|||
|
|
|||
|
心跳检测;
|
|||
|
|
|||
|
最大重连次数限制;
|
|||
|
|
|||
|
事件监听。
|
|||
|
|
|||
|
封装后你只用关注:
|
|||
|
|
|||
|
js
|
|||
|
复制
|
|||
|
编辑
|
|||
|
WebSocketManager.connect()
|
|||
|
WebSocketManager.send("xxx")
|
|||
|
WebSocketManager.close()
|
|||
|
优势:避免在多个组件重复写 onclose/onerror 等逻辑。
|
|||
|
|
|||
|
✅ 总结对比
|
|||
|
方案 能力 可行性 说明
|
|||
|
✅ 手动标记断开意图 ✅ 区分主动 vs 异常 👍 极推荐 最直接可靠
|
|||
|
✅ onbeforeunload 页面标记 ✅ 判断刷新/关闭 👍 建议与上方案配合
|
|||
|
✅ 心跳机制 ✅ 检测假连接 👍 中大型项目推荐 适合服务端也配合开发
|
|||
|
✅ 封装统一管理器 ✅ 解耦复杂逻辑 👍 建议中后期项目使用 支持多频道、多状态
|
|||
|
|
|||
|
🧩 总结一句话:
|
|||
|
WebSocket 是单向的,不告诉你“为什么断了”,你只能自己判断。手动标记 + 心跳检测 + 页面事件,是目前最佳组合策略。
|
|||
|
|
|||
|
如你需要我给出一个封装好的 WebSocketManager 管理类(含标记断开、心跳、自动重连、事件订阅等),可以告诉我使用的框架(Vue/React/原生),我可以直接给你封装好一份。
|