WebSocket心跳重连机制实现与跨浏览器差异分析
本文深入探讨WebSocket长连接中因网络波动或后端异常导致的断连问题,详细解析前端主动发送Ping/Pong心跳的检测逻辑与重连策略。通过对比Chrome与Firefox在断网触发onclose时的行为差异,提出添加重连锁的优化方案,并封装了npm包websocket-heartbeat-js以简化开发流程。
心跳重连缘由
提示:文章最下方有仓库地址
WebSocket作为前后端交互的长连接协议,常因网络波动或后端服务异常导致连接意外失效,且双方往往无法即时感知。为确保连接的持续性与稳定性,引入心跳重连机制显得尤为必要。
在使用原生WebSocket时,若设备网络突然断开,浏览器并不会立即触发任何WebSocket事件,前端因此无法获知连接状态。此时若调用ws.send()方法,浏览器才会检测到链接断开,并在极短时间内(具体表现因浏览器及版本而异)触发onclose回调。
此外,后端WebSocket服务若发生异常导致连接断开,前端同样收不到断开通知。为此,前端需定时发送心跳消息(Ping),后端收到后应立即返回响应消息(Pong)以确认连接正常。若前端在指定时间内未收到Pong,则判定连接异常并执行重连逻辑。
针对上述两种情况,解决方案是由前端作为主动方,定时发送Ping消息以检测网络状态及前后端连接质量。一旦检测到异常,前端将持续执行重连逻辑,直至重连成功。
如何实现
在初始化WebSocket实例时,我们需要绑定相关事件监听器:
var ws = new WebSocket(url);
ws.onclose = function () {
//something
};
ws.onerror = function () {
//something
};
ws.onopen = function () {
//something
};
ws.onmessage = function (event) {
//something
}若希望保持WebSocket连接不断,通常会在onclose或onerror事件中绑定重连方法。
ws.onclose = function () {
reconnect();
};
ws.onerror = function () {
reconnect();
};在正常断连场景下,触发onclose方法后,我们即可执行重连逻辑。
针对断网情况的心跳重连,实现思路是定时发送消息以触发ws.send()。若网络已断开,浏览器会触发onclose事件,进而启动重连流程。
简单实现示例如下:
var heartCheck = {
timeout: 60000,//60ms
timeoutObj: null,
reset: function(){
clearTimeout(this.timeoutObj);
this.start();
},
start: function(){
this.timeoutObj = setTimeout(function(){
ws.send("HeartBeat");
}, this.timeout)
}
}
ws.onopen = function () {
heartCheck.start();
};ws.onmessage = function (event) {
heartCheck.reset();
}如上代码所示,heartCheck对象的reset和start方法主要用于控制心跳检测的定时逻辑。
什么条件下执行心跳:
当onopen事件触发,即连接成功后,我们调用start方法开始计时。若在设定的时间间隔内,通过onmessage接收到后端消息,则重置倒计时。
若距离上次从后端获取消息超过60秒,则执行心跳检测以判断是否断连。该检测时间可根据实际业务需求自行设定。
判断前端websocket断开(断网但不限于断网的情况):
当心跳检测执行send方法后,若当前WebSocket处于断开状态(如断网),发送超时后浏览器会自动触发onclose方法,从而立即执行重连(因为onclose中绑定了重连事件)。若网络持续断开,重连逻辑会每隔2秒(代码中设定的时间)执行一次,直到网络恢复正常并连接成功。
至此,前端主动断开WebSocket的心跳检测机制实现完毕。之所以称为“前端主动断开”,是因为当前场景主要通过前端调用ws.send()来检测并触发onclose,后续将讨论后端断开的情况。
在测试WebSocket超时时间时,我发现了新的问题。
1. 在Chrome浏览器中,若心跳检测(即WebSocket实例执行send)后15秒内未将消息发送至接收端,onclose便会执行。这表明Chrome的超时时间为15秒。
2. 打开Firefox浏览器后发现,断网7秒后直接执行onclose。这说明在Firefox中,无需心跳检测即可自动触发onclose。
3. 同一代码下,reconnect方法在Chrome中执行了一次,而在Firefox中执行了两次。这是因为我们在多处(代码逻辑处和WebSocket事件处)绑定了reconnect()函数。
因此,为确保安全,建议给reconnect()方法添加一个锁机制,保证每次只执行一次重连。
综上所述,不同浏览器具有不同的底层机制。无论浏览器自身是否在断网时执行onclose,加上心跳重连机制后,均能保证onclose的正常触发。这主要归功于Socket本身具备底层心跳机制,当消息发送不出去时,会等待一段时间尝试重新连接,若超时则触发onclose。
判断后端断开:
若后端因可控原因断开WebSocket连接,通常会下发断连通知,从而触发前端WebSocket的onclose方法,我们据此执行重连。
若因异常导致连接断开,前端通常无法直接感知。因此,若前端发送心跳后,后端既未返回心跳响应消息,前端也未收到其他任何消息,则可判定后端发生异常断开。
特别重要的一点:发送心跳到后端后,后端必须返回消息,否则超过60秒后前端会判定后端主动断开。改造后的代码如下:
var heartCheck = {
timeout: 60000,//60ms
timeoutObj: null,
serverTimeoutObj: null,
reset: function(){
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
this.start();
},
start: function(){
var self = this;
this.timeoutObj = setTimeout(function(){
ws.send("HeartBeat");
self.serverTimeoutObj = setTimeout(function(){
ws.close();//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
}, self.timeout)
}, this.timeout)
},
}
ws.onopen = function () {
heartCheck.start();
};
ws.onmessage = function (event) {
heartCheck.reset();
}ws.onclose = function () {
reconnect();
};
ws.onerror = function () {
reconnect();
};PS:
由于当前方式在连接失败或断连时会持续重连,若存在两个设备同时登录并踢掉另一端的情况,务必发送一种“踢下线”的消息类型。前端接收到该类型消息后,经逻辑判断应停止执行reconnect,否则会导致相互挤下线的死循环。
结语
由于断连等原因可能导致发送的数据未成功发出,为保证数据不丢失,可实现消息回执机制。例如:A向B发送消息id=1,B返回收到id=1的确认消息;若A未收到回执,则可再次发送消息id=1。
由上文可知,我们采用了前端发送Ping、后端返回Pong的心跳方式。另一种方式是后端主动发送心跳,前端判断是否超时。由于WebSocket连接必须由前端主动请求建立,因此重连逻辑必然由前端负责,判断重连的代码也写在前端。
上述第二种方式由服务端发送心跳,前端接收,这种方式可节省部分带宽。因为前端发送心跳时,后端需返回响应,涉及Ping/Pong两次数据交互;而后端发送心跳时,前端无需回应,仅需单向传输。但这带来一个问题:前端需与后端约定心跳间隔。例如后端设置10秒发送一次心跳,前端需设置安全值(如12秒未收到下一个心跳则重连)。这种方式调节时间不够灵活,需双方共同确定时间约定,且后端逻辑较为复杂。若采用前端发送Ping、后端返回Pong的方式,间隔时间仅需前端控制。此外,我的代码将接收到的任何后端信息均视为连接正常并重置心跳时间,从而节约了部分请求次数。使用此方式后端较为轻松,只需在onmessage中编写如下代码:
if(msg=='heartbeat') socket.send(anything);封装了一个npm包,欢迎使用
https://github.com/zimv/websocket-heartbeat-js
https://www.npmjs.com/package/websocket-heartbeat-js
以上就是websocket心跳重连实现探索(npm:websocket-heartbeat-js)的详细内容,更多关于websocket心跳重连的资料请关注本站其它相关文章!
D3.js 处理大规模数据点的性能优化策略
2026-11-05 15:37:25
JS实现网页摄像头实时扫码:结合getUserMedia与jsQR教程
2026-11-05 15:27:28
D3.js v5 迁移到 v7 的 API 变更与注意事项
2026-11-05 11:57:17
使用 animate.js 实现多元素协同的序列动画
2026-11-04 22:27:53
JS拖拽排序时怎么添加平滑动画效果?FLIP技术实战
2026-11-04 10:12:22
