ws.onmessage = function (event) {
heartCheck.reset();
如上代码,heartCheck 的 reset和start方法主要用来控制心跳的定时。
什么条件下执行心跳:
当onopen也就是连接成功后,我们便开始start计时,如果在定时时间范围内,onmessage获取到了后端的消息,我们就重置倒计时,
距离上次从后端获取到消息超过60秒之后,执行心跳检测,看是不是断连了,这个检测时间可以自己根据自身情况设定。
判断前端websocket断开(断网但不限于断网的情况):
当心跳检测执行send方法之后,如果当前websocket是断开状态(或者说断网了),发送超时之后,浏览器的websocket会自动触发onclose方法,重连就会立刻执行(onclose方法体绑定了重连事件),如果当前一直是断网状态,重连会2秒(时间是自己代码设置的)执行一次直到网络正常后连接成功。
如此一来,判断前端断开websocket的心跳检测就实现了。为什么说是前端主动断开,因为当前这种情况主要是通过前端websocket.send来检测并触发的onclose,后面说后端断开的情况。
我本想测试websocket超时时间,又发现了一些新的问题
1. 在chrome中,如果心跳检测 也就是websocket实例执行send之后,15秒内没发送到另一接收端,onclose便会执行。那么超时时间是15秒。
2. 我又打开了Firefox ,Firefox在断网7秒之后,直接执行onclose。说明在Firefox中不需要心跳检测便能自动onclose。
3. 同一代码, reconnect方法 在chrome 执行了一次,Firefox执行了两次。当然我们在几处地方(代码逻辑处和websocket事件处)绑定了reconnect(),
所以保险起见,我们还是给reconnect()方法加上一个锁,保证只执行一次
目前来看不同的浏览器,有不同的机制,无论浏览器websocket自身会不会在断网情况下执行onclose,加上心跳重连后,已经能保证onclose的正常触发。 其实这是由于socket本身就有底层的心跳,socket消息发送不出去的时候,会等待一定时间看是否能在这个时间之内再次连接上,如果超时便会触发onclose。
判断后端断开:
如果后端因为一些情况断开了ws,是可控情况下的话,会下发一个断连的通知,这样会触发前端weboscket的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();
因为目前我们这种方式会一直重连如果没连接上或者断连的话,如果有两个设备同时登陆并且会踢另一端下线,一定要发送一个踢下线的消息类型,这边接收到这种类型的消息,逻辑判断后就不再执行reconnect,否则会出现一只相互挤下线的死循环。
由于断开等原因可能会导致发送的数据没有发送出去,要保证数据不丢失的话,可以做消息回执,也就是a给b发送消息id=1,b返回收到id=1的消息,如果没有回执a可以再次发送消息id=1。
由上文可以看到,我们使用了前端发送ping,后端返回pong的这样一种心跳的方式。也有一种方式是后端主动发送心跳,前端判断是否超时。因为ws链接必须是前端主动请求建立连接,因此重连肯定是给前端来做,所以判断重连逻辑都是写在前端。
上面所说第二种方式是让服务端发送心跳,前端来接收,这样的方式会多节约一点带宽,因为如果是前端发送心跳,后端需要返回心跳,也就是ping pong的过程会有两次数据传递。 而后端来发送心跳的话,就只需要发送ping,前端不需要回应。但是这样造成了一个问题。前端需要和后端约定好心跳间隔,比如后端设置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
Introduction
The websocket-heartbeat-js is base on WebSocket of browser javascript, whose main purpose is to ensure web client and server connection, and it has a mechanism of heartbeat detection and automatic reconnection. When client device has network outage or server error which causes websocket to disconnect, the program will automatically reconnect until reconnecting is successful again.
When we use the native websocket, if network disconnects, any event function not be executed. So front-end program doesn't know that websocket was disconnected. But if program is now executing ***WebSocket.send()***, browser must discover that message signal is failed, so the onclose function will execute.
Back-end websocket service is likely to happen error, when websocket disconnected that front-end not notice message received. So need to send ping message by timeout. Server return pong message to client when server received ping message. Because received pong message, client know connection normal. If client not received pong message, it is connection abnormal, client will reconnect.
In summary, for solve above two problems. Client should initiative send ping message for check connect status.
1.close websocket connection
If websocket need to disconnect, client must execute ***WebsocketHeartbeatJs.close()***. If server wants to disconnect, it should send a close message to client. When client received close message that it to execute ***WebsocketHeartbeatJs.close()***.
Example:
websocketHeartbeatJs.onmessage = (e) => {
if(e.data == 'close') websocketHeartbeatJs.close();
2.ping & pong
Server should to return pong message when the client sends a ping message. Pong message can be of any value. websocket-heartbeat-js will not handle pong message, instead it will only reset heartbeat after receiving any message, as receiving any message means that the connection is normal.
npm install websocket-heartbeat-js
import WebsocketHeartbeatJs from 'websocket-heartbeat-js';
let websocketHeartbeatJs = new WebsocketHeartbeatJs({
url: 'ws://xxxxxxx'
websocketHeartbeatJs.onopen = function () {
console.log('connect success');
websocketHeartbeatJs.send('hello server');
websocketHeartbeatJs.onmessage = function (e) {
console.log(`onmessage: ${e.data}`);
websocketHeartbeatJs.onreconnect = function () {
console.log('reconnecting...');
<script src="./node_modules/websocket-heartbeat-js/dist/index.js"></script>
let websocketHeartbeatJs = new window.WebsocketHeartbeatJs({
url: 'ws://xxxxxxx'
This websocketHeartbeatJs.ws is native Websocket instance. If you need more native Websocket features, operate the websocketHeartbeatJs.ws.
websocketHeartbeatJs.ws == WebSocket(websocketHeartbeatJs.opts.url);
Attributerequiredtypedefaultdescription
string
websocket service address
pingTimeout
false
number
15000
A heartbeat is sent every 15 seconds. If any backend message is received, the timer will reset
pongTimeout
false
number
10000
After the Ping message is sent, the connection will be disconnected without receiving the backend message within 10 seconds
reconnectTimeout
false
number
The interval of reconnection
pingMsg
false
string
"heartbeat"
Ping message value
repeatLimit
false
number
The trial times of reconnection。default: unlimited
const options = {
url: 'ws://xxxx',
pingTimeout: 15000,
pongTimeout: 10000,
reconnectTimeout: 2000,
pingMsg: "heartbeat"
let websocketHeartbeatJs = new WebsocketHeartbeatJs(options);
Send the message to the back-end service
websocketHeartbeatJs.send('hello server');
The front end manually disconnects the websocket connection. This method does not trigger reconnection.
websocketHeartbeatJs.onclose = () => {
console.log('connect close');
websocketHeartbeatJs.onerror = () => {
console.log('connect onerror');
websocketHeartbeatJs.onopen = () => {
console.log('open success');
websocketHeartbeatJs.onmessage = (e) => {
console.log('msg:', e.data);
websocketHeartbeatJs.onreconnect = (e) => {
console.log('reconnecting...');
demo show
初探和实现websocket心跳重连
RFC 6455 - The WebSocket Protocol https://tools.ietf.org/html/rfc6455#section-5.5
The Ping frame contains an opcode of 0x9.
A Ping frame MAY include "Application data".
Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in
response, unless it already received a Close frame. It SHOULD
respond with Pong frame as soon as is practical. Pong frames are
discussed in Section 5.5.3.
An endpoint MAY send a Ping frame any time after the connection is
established and before the connection is closed.
NOTE: A Ping frame may serve either as a keepalive or as a means to
verify that the remote endpoint is still responsive.
The Pong frame contains an opcode of 0xA.
Section 5.5.2 details requirements that apply to both Ping and Pong
frames.
A Pong frame sent in response to a Ping frame must have identical
"Application data" as found in the message body of the Ping frame
being replied to.
If an endpoint receives a Ping frame and has not yet sent Pong
frame(s) in response to previous Ping frame(s), the endpoint MAY
elect to send a Pong frame for only the most recently processed Ping
frame.
Writing WebSocket servers - Web APIs | MDN https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#Pings_and_Pongs_The_Heartbeat_of_WebSockets
Pings and Pongs: The Heartbeat of WebSockets
At any point after the handshake, either the client or the server can choose to send a ping to the other party. When the ping is received, the recipient must send back a pong as soon as possible. You can use this to make sure that the client is still connected, for example.
A ping or pong is just a regular frame, but it's a control frame. Pings have an opcode of 0x9
, and pongs have an opcode of 0xA
. When you get a ping, send back a pong with the exact same Payload Data as the ping (for pings and pongs, the max payload length is 125). You might also get a pong without ever sending a ping; ignore this if it happens.
If you have gotten more than one ping before you get the chance to send a pong, you only send one pong.
Closing the connection
To close a connection either the client or server can send a control frame with data containing a specified control sequence to begin the closing handshake (detailed in Section 5.5.1). Upon receiving such a frame, the other peer sends a Close frame in response. The first peer then closes the connection. Any further data received after closing of connection is then discarded.
Sending and receiving heartbeat messages — django-websocket-redis 0.5.2 documentation https://django-websocket-redis.readthedocs.io/en/latest/heartbeats.html
Sending and receiving heartbeat messages
The Websocket protocol implements so called PING/PONG messages to keep Websockets alive, even behind proxies, firewalls and load-balancers. The server sends a PING message to the client through the Websocket, which then replies with PONG. If the client does not reply, the server closes the connection.
The client part
Unfortunately, the Websocket protocol does not provide a similar method for the client, to find out if it is still connected to the server. This can happen, if the connection simply disappears without further notification. In order to have the client recognize this, some Javascript code has to be added to the client code responsible for the Websocket:
var ws = new WebSocket('ws://www.example.com/ws/foobar?subscribe-broadcast');
var heartbeat_msg = '--heartbeat--', heartbeat_interval = null, missed_heartbeats = 0;
function on_open() {
// ...
// other code which has to be executed after the client
// connected successfully through the websocket
// ...
if (heartbeat_interval === null) {
missed_heartbeats = 0;
heartbeat_interval = setInterval(function() {
try {
missed_heartbeats++;
if (missed_heartbeats >= 3)
throw new Error("Too many missed heartbeats.");
ws.send(heartbeat_msg);
} catch(e) {
clearInterval(heartbeat_interval);
heartbeat_interval = null;
console.warn("Closing connection. Reason: " + e.message);
ws.close();
}, 5000);
The heartbeat message, here --heartbeat--
can be any magic string which does not interfere with your remaining logic. The best way to achieve this, is to check for that magic string inside the receive function, just before further processing the message:
function on_message(evt) {
if (evt.data === heartbeat_msg) {
// reset the counter for missed heartbeats
missed_heartbeats = 0;
return;
// ...
// code to further process the received message
// ...
The server part
The main loop of the Websocket server is idle for a maximum of 4 seconds, even if there is nothing to do. After that time interval has elapsed, this loop optionally sends a magic string to the client. This can be configured using the special setting:
WS4REDIS_HEARTBEAT = '--heartbeat--'
The purpose of this setting is twofold. During processing, the server ignores incoming messages containing this magic string. Additionally the Websocket server sends a message with that magic string to the client, about every four seconds. The above client code awaits these messages, at least every five seconds, and if too many were not received, it closes the connection and tries to reestablish it.
By default the setting WS4REDIS_HEARTBEAT
is None
, which means that heartbeat messages are neither expected nor sent.