websocket重连优化

# 背景

在实例化websocket后,我们会定义好各个回调事件需要执行的函数

var ws = new WebSocket(url);
ws.onclose = function () {
    //连接关闭
};
ws.onerror = function () {
    //连接报错
};
ws.onopen = function () {
    //连接成功
};
ws.onmessage = function (event) {
    //收到数据
}
1
2
3
4
5
6
7
8
9
10
11
12
13

在弱网环境下,发送消息无法抵达接收端;或断网到浏览器约定时限等一些异常情况都会触发onclose和onerror,

所以理论上,我们只要在onclose和onerror时,重新创建长连接就可以。

优化方向

  • 重连实现;
  • 加入心跳优化;
  • 同时触发多次重连优化;
  • 加入断线重连次数;

# 重连实现

根据上面的简单思路,代码如下:

var ws = new WebSocket(url);
ws.onclose = function () {
  reconnect()
};
ws.onerror = function () {
  reconnect()
};
function reconnect(url) {
    setTimeout(function () {     //没连接上会一直重连,设置延迟避免请求过多
      var ws = new WebSocket(url);
      ws.onclose = function () {
        reconnect()
      };
      ws.onerror = function () {
        reconnect()
      };
    }, 2000);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

稍微抽取再丰富下,createWebSocket来创建websocket实例,initEventHandle来绑定各回调函数:

var ws = new WebSocket(url);
ws.onclose = function () {
  reconnect()
};
ws.onerror = function () {
  reconnect()
};

// 重连
function reconnect(url) {
    setTimeout(function () {     //没连接上会一直重连,设置延迟避免请求过多
      createWebSocket(url);
    }, 2000);
}

// 实例websocket
function createWebSocket(url) {
    try {
        if ('WebSocket' in window) {
            ws = new WebSocket(url);
        } else if ('MozWebSocket' in window) {
            ws = new MozWebSocket(url);
        } else {
       		socket = new window.SockJS(newUrl);
        } else {
            _alert("当前浏览器不支持websocket协议,建议使用现代浏览器",3000)
        }
        initEventHandle();
    } catch (e) {
        reconnect(url);
    }
}

// 初始化事件函数
function initEventHandle() {
    ws.onclose = function () {
        reconnect(wsUrl);
    };
    ws.onerror = function (err) {
        reconnect(wsUrl);
    };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 加入心跳优化

弱网、断连所导致重连都是被动的,而在一般的websocket连接中都是存在心跳机制的,客户端和服务端约定一个特定的心跳消息用于检测链路是否通信正常。 我们通过心跳机制,在客户端来检测链路的正常,在约定时间间隔内收不到心跳或者其他任何通信消息时,客户端进行主动重连。所以下面优化的,我们需要加一个心跳检测的方法

var heartCheck = {
    timeout: heartBeatTime*1000,  //  心跳检测时长
    timeoutObj: null, // 定时变量
    reset: function () { // 重置定时
        clearTimeout(this.timeoutObj);
        return this;
    },
    start: function () { // 开启定时
        var self = this;
        this.timeoutObj = setTimeout(function () {
            ws.close(); // 心跳时间内收不到消息,主动触发连接关闭,开始重连
        },this.timeout)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

心跳检测对象,定义了

  • timeout:心跳超时时间
  • timeoutObj:定时器变量
  • reset:重置心跳
  • start:开启心跳 当连接成功时,开启心跳;在收到消息时,重置心跳并开启下一轮检测,所以我们只需要在onopen和onmessage中加入心跳检测就行
// 初始化事件函数
function initEventHandle() {
    ws.onclose = function () {
        reconnect(wsUrl);
    };
    ws.onerror = function (err) {
        reconnect(wsUrl);
    };
    ws.onopen = function () {
        heartCheck.reset().start();      //心跳检测重置
    };
    ws.onmessage = function (msg) {    //如果获取到消息,心跳检测重置
        heartCheck.reset().start();    //拿到任何消息都说明当前连接是正常的
        handleMsg(msg)
    };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 同时触发多次重连优化

在实际使用过程中,发现有些浏览器,reconnect重连会多次触发,所以需要给重连加一把锁,当一个重连正在执行的时候,无法再触发一次重连

function reconnect(url) {
    if (reconnect.lockReconnect) return;
    reconnect.lockReconnect = true;
    setTimeout(function () {     //没连接上会一直重连,设置延迟避免请求过多
        createWebSocket(url);
        reconnect.lockReconnect = false;
    }, 2000);
}
1
2
3
4
5
6
7
8

如果是同个浏览器中多个页面共用一个连接来进行通信,那么就需要使用浏览器缓存/数据路(localStorage/indexedDB)去加这把锁。

# 加入断线重连次数

参考一下其他的设置;

# 其他

加入断线重连次数;

//1.创建websocket客户端
var wsServer = 'ws://ip/';
var limitConnect = 3;  // 断线重连次数
var timeConnect =0;
webSocketInit(wsServer);

//socket初始化
function webSocketInit(service){
  var ws = new WebSocket(service);
  ws.onopen = function () {
    console.log("已连接TCP服务器");
  };
  ws.onmessage = function (msg) {
    console.log(msg);
  };
  ws.onclose = function () {
    console.log('服务器已经断开');
    reconnect(service);
  };
  ws.onerror = function (err) {
    //console.log("服务器报错:");
    reconnect(service);
  };

  // 重连
  function reconnect(service) {
    // lockReconnect加锁,防止onclose、onerror两次重连
    if(limitConnect>0){
      if(localStorage.getItem('lockReconnect')!=true){
        localStorage.setItem("lockReconnect",1);
        limitConnect --;
        timeConnect ++;
        console.log("第"+timeConnect+"次重连");
        // 进行重连
        setTimeout(function(){
          webSocketInit(service);
          localStorage.removeItem("lockReconnect");
        },2000);
      }
    }else{
      console.log("TCP连接已超时");
    }
  }

  // 心跳 * 回应
  setInterval(function(){
    websocket.send('');
  }, 1000*100);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

以上便是手动实现websocket重连的解决方案,还有一种方式是使用一个叫做reconnecting websocket的库:

只要把:

var ws = new WebSocket('ws://....');
1

替换成:

var ws = new ReconnectingWebSocket('ws://....');
1

这样就好了。

ReconnectingWebSocketa同样支持onopen、onmessage、onclose这些事件。

上次更新: 2022/04/15, 05:41:29
×