WebSocket学习+心跳检测+断线重连
在 HTTP协议中,客户端必须先发起请求,服务器才能响应。这就像发邮件,你发一封,对方回一封。但如果需要开发一个即时聊天室、股票大盘或者实时视频监控的功能,让客户端每隔几秒钟问一次服务器“有新消息吗?”(轮询),既浪费带宽,延迟又高。WebSocket是解决这个问题的最优解。
一、 什么是 WebSocket?
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
- HTTP: 单向传输。只能由客户端发起,服务端响应。
- WebSocket: 双向传输。建立连接后,服务器和客户端可以随时向对方发送数据。
它的最大优点是:建立连接后,数据传输不仅实时,而且头部开销极小(不再需要像 HTTP 那样每次都带上一大堆 Header)。
二、 它是如何工作的?
WebSocket 并不是凭空建立的,它借用了 HTTP 来“搭桥”。
- 发起请求:客户端发送一个标准的 HTTP GET 请求,但在 Header 里带上
Upgrade: websocket,告诉服务器:“我想把协议升级为 WebSocket”。 - 协议切换:服务器如果支持,会返回
101 Switching Protocols状态码。 - 通道建立:此时,HTTP 连接中断,底层的 TCP 连接保持打开,双方开始使用 WebSocket 的二进制帧格式传输数据。
三、 生产环境中的痛点:连接“假死”
在本地开发(localhost)时,WebSocket 看起来非常稳定。但在真实的公网环境中,情况要复杂得多:
- 网络波动:手机进入电梯、WiFi 切换 4G,连接随时可能断开。
- 僵尸连接(Half-Open):有时候网线被拔掉,或者中间的路由器为了节省资源默默切断了连接,但客户端和服务器都不知道,双方还以为连接连着,结果发消息谁也收不到。
为了解决这个问题,我们需要引入心跳检测(Heartbeat)和断线重连机制。
心跳检测:定期向服务器发送Ping,接收服务器传回来的Pong,如果没有在规定时间内返回,就认为连接已经断开
四、 封装一个带有心跳检测和短线重连的 WebSocket 类
- 定期发送 Ping:证明我还活着。
- 倒计时等待 Pong:如果发了 Ping 之后 3 秒内没收到回复,判定连接已断,主动关闭。
- 自动重连:连接断开后,延迟几秒尝试重新连接。
class WebSocketManager {
constructor(url) {
this.url = url;
this.ws = null;
this.lockReconnect = false; // 避免重复重连
// 心跳配置
this.heartbeatConfig = {
pingTimeout: 5000, // 每 5秒发送一次心跳
pongTimeout: 3000, // 发送心跳后,3秒内没收到回复视为断线
pingMsg: 'ping',
};
this.pingTimer = null;
this.pongTimer = null;
}
connect() {
if (this.ws && this.ws.readyState === WebSocket.OPEN) return;
try {
this.ws = new WebSocket(this.url);
this.initEventHandle();
} catch (e) {
this.reconnect();
}
}
initEventHandle() {
this.ws.onopen = () => {
console.log('连接成功');
this.startHeartbeat(); // 开启心跳
};
this.ws.onmessage = (event) => {
// 收到任何消息(包括 pong),都说明连接正常,重置心跳
this.startHeartbeat();
if (event.data !== 'pong') {
console.log('收到业务数据:', event.data);
}
};
this.ws.onclose = () => {
console.log('连接断开');
this.stopHeartbeat();
this.reconnect(); // 尝试重连
};
}
reconnect() {
if (this.lockReconnect) return;
this.lockReconnect = true;
// 设置延迟,避免频繁请求炸服
setTimeout(() => {
this.connect();
this.lockReconnect = false;
}, 3000);
}
startHeartbeat() {
this.stopHeartbeat(); // 清除旧定时器
this.pingTimer = setTimeout(() => {
if(this.ws.readyState === WebSocket.OPEN) {
this.ws.send(this.heartbeatConfig.pingMsg); // 发送 ping
// 发送后开启倒计时,如果超时未响应则关闭连接
this.pongTimer = setTimeout(() => {
this.ws.close();
}, this.heartbeatConfig.pongTimeout);
}
}, this.heartbeatConfig.pingTimeout);
}
stopHeartbeat() {
clearTimeout(this.pingTimer);
clearTimeout(this.pongTimer);
}
}
// 使用
const ws = new WebSocketManager('ws://localhost:8080');
ws.connect();