WebSocket心跳重连机制实现与跨浏览器差异分析

作者:像素漫游

时间:2026-10-30

浏览:635

本文深入探讨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心跳重连的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章