Appearance
心跳检测与连接保持
本文档引用的文件
目录
引言
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,广泛用于实时数据推送场景。然而,长时间空闲的连接可能因网络中间设备(如 NAT、防火墙)超时而被断开,且客户端或服务端无法立即感知。为解决此问题,系统实现了基于定时心跳检测的连接保持机制。
本文档详细说明本项目中 WebSocket 心跳检测机制的实现原理,涵盖服务端与客户端的双向心跳设计、心跳间隔与超时配置、连接活跃状态管理、无效连接清理策略,以及网络异常下的重连逻辑。通过分析核心代码文件,帮助开发者理解如何保障 WebSocket 长连接的稳定性与可靠性。
心跳机制概述
本系统采用双向心跳机制,即客户端定时向服务端发送 ping 消息,服务端收到后立即响应 pong 消息。通过这一交互,双方可确认连接的活跃性。
- 客户端行为:每 60 秒发送一次
ping消息。 - 服务端行为:收到
ping后立即回复pong。 - 连接判定:若服务端在 WebSocket 连接的
onmessage回调中持续收到ping,则认为该连接正常;若连接关闭或出错,则通过onclose和onerror事件清理相关资源。
该机制有效防止了因网络空闲导致的连接中断,并能及时发现并处理异常连接。
Section sources
服务端心跳处理实现
服务端在 websocket.router.ts 文件中通过监听 onmessage 事件处理心跳消息。当收到客户端发送的纯文本 "ping" 消息时,服务端调用 socket.send("pong") 进行响应。
Diagram sources
此外,服务端在遍历所有客户端连接并广播消息时,会检查每个 WebSocket 对象的 readyState 是否为 WebSocket.OPEN。若状态异常,则从 socketMap 中移除该连接,并尝试关闭 socket,防止无效连接占用资源。
typescript
if (socket.readyState !== WebSocket.OPEN) {
socketMap.set(clientId, sockets.filter((item) => item !== socket));
clientIdTopicsMap.delete(clientId);
try {
socket.close();
} catch (_err) {
error(_err);
}
continue;
}此逻辑在 websocket.dao.ts 的 publish 函数和 websocket.router.ts 的 onmessage 处理中均有体现,确保了服务端连接状态的实时性与准确性。
Section sources
客户端心跳发送与响应
前端在 pc/src/compositions/websocket.ts 中实现了定时心跳发送逻辑。通过 socketPing 函数,使用 setTimeout 每 60 秒检查一次连接状态,并向服务端发送 ping 消息。
typescript
function socketPing() {
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send("ping");
}
setTimeout(socketPing, 60000); // 每60秒发送一次
}
socketPing();客户端的 onmessage 事件监听器会接收服务端返回的 "pong" 消息。虽然代码中未对 "pong" 做特殊处理(仅忽略),但成功接收 pong 表明连接畅通。若长时间未收到 pong,浏览器的 WebSocket 会触发 onclose 或 onerror 事件,进而触发重连流程。
该设计简洁高效,避免了复杂的超时计时器管理,依赖 WebSocket 原生事件机制进行异常检测。
Section sources
双向心跳与连接状态管理
本系统实现了逻辑上的双向心跳:
- 前向心跳:客户端 → 服务端 (
ping→pong) - 后向心跳:服务端 → 客户端 (通过
publish发送业务消息)
尽管服务端不主动发送 ping,但其在 publish 消息时会检查连接状态,相当于一种被动的心跳探测。若消息发送失败或连接状态非 OPEN,则立即清理该连接。
服务端使用三个全局 Map 管理连接状态:
| Map 名称 | 作用 | 数据结构 |
|---|---|---|
socketMap | 存储每个客户端 ID 对应的所有 WebSocket 连接 | Map<string, WebSocket[]> |
callbacksMap | 存储每个主题(topic)对应的回调函数列表 | Map<string, Function[]> |
clientIdTopicsMap | 记录每个客户端订阅的主题列表 | Map<string, string[]> |
当客户端连接断开时,服务端通过 onclose 和 onerror 事件从这三个 Map 中清除对应数据,实现资源的自动回收。
Diagram sources
心跳消息格式与通信协议
本系统的心跳消息采用纯文本格式,而非 JSON,以减少开销:
- 心跳请求(ping):
"ping"(字符串) - 心跳响应(pong):
"pong"(字符串)
对于业务消息,系统使用 JSON 格式进行通信,结构如下:
json
{
"action": "subscribe | unSubscribe | publish",
"data": { /* 业务数据 */ }
}例如,订阅主题的消息格式为:
json
{
"action": "subscribe",
"data": {
"topics": ["user_update"]
}
}这种设计将心跳消息与业务消息分离,心跳消息轻量高效,业务消息结构清晰,便于解析与扩展。
Section sources
网络波动与重连策略
当前端检测到连接关闭(onclose)或发生错误(onerror)时,会触发 reConnect() 函数进行自动重连。
重连策略如下:
- 若当前无任何订阅主题(
topicCallbackMap.size === 0),则不进行重连。 - 重连尝试次数通过
reConnectNum计数。 - 初始重连间隔为
reConnectNum * 200毫秒,最多尝试 10 次。 - 超过 10 次后,固定为 5 秒重试一次。
typescript
let reConnectNum = 0;
async function reConnect() {
if (topicCallbackMap.size === 0) {
return;
}
reConnectNum++;
let time = 200;
if (reConnectNum > 10) {
time = 5000;
} else {
time = reConnectNum * 200;
}
await new Promise((resolve) => setTimeout(resolve, time));
await connect();
}该策略采用指数退避的变体,避免在网络短暂波动时频繁重连,减轻服务端压力,同时保证最终能恢复连接。
Section sources
连接健康度判断与清理机制
系统通过以下方式判断连接健康度并清理无效连接:
服务端清理机制
- 发送时检查:在
publish消息时,遍历所有客户端连接,检查readyState。 - 事件驱动清理:通过
onclose和onerror事件,从socketMap和clientIdTopicsMap中删除失效连接。 - 资源回收:及时调用
socket.close()释放底层资源。
客户端清理机制
- 空闲关闭:客户端通过
closeSocketTimeout定时器,在无任何订阅主题时 10 分钟后自动关闭连接。 - 异常重连:连接异常时,清除当前
socket引用并尝试重连。
typescript
let closeSocketTimeout: NodeJS.Timeout | undefined = undefined;
// 每次操作后重置定时器
closeSocketTimeout = setTimeout(() => {
if (topicCallbackMap.size === 0) {
try {
socket?.close();
} catch (err) {
console.log(err);
} finally {
socket = undefined;
}
}
}, 600000); // 10分钟该机制避免了客户端长时间保持无用连接,提升了系统整体资源利用率。
Section sources
代码示例与使用方式
前端订阅主题
使用 useSubscribe 函数在 Vue 组件中响应式订阅主题:
typescript
import { useSubscribe } from '@/compositions/websocket';
export default {
setup() {
useSubscribe('user_update', (data) => {
console.log('收到用户更新:', data);
// 更新UI
});
}
}前端发布消息
调用 publish 函数向指定主题发送消息:
typescript
import { publish } from '@/compositions/websocket';
publish({
topic: 'user_update',
payload: { id: 1, name: '张三' }
});服务端接收并广播
服务端自动处理 publish 消息,并将数据广播给所有订阅该主题的客户端。
总结
本系统通过简洁高效的双向心跳机制,保障了 WebSocket 长连接的稳定性。客户端每 60 秒发送 ping,服务端即时响应 pong,结合连接状态检查与事件驱动的资源清理,实现了对无效连接的精准识别与回收。前端采用指数退避重连策略应对网络波动,后端通过全局 Map 结构高效管理连接与订阅关系。整体设计兼顾了性能、可靠性与资源利用率,为实时通信功能提供了坚实的基础。