Web Socket 及 心跳重连

Web Socket

  • Web Socket(套接字)的目标是通过一个长时连接实现与服务器全双工、双向的通信
  • Web Socket 得到所有游览器支持
  • Web Socket使用自定义协议,所以URL方案发生了变化
    • 不能再使用 http:// 或 https://(不安全的连接), 而要使用 ws:// 和 wss://(安全的连接)
    • 默认端口也是 80 和 433

使用自定义协议的特点

  • 客户端与服务器之间可以发送非常少的数据,不会对 HTTP 造成任何负担
  • 定义协议的时间比定义 Javascript API 要长

API

Web Socket 构造函数

  • 要创建一个新的 Web Socket,就要实例化一个 WebSocket 对象并传入提供连接的 URL:
let socket = new WebSocket("wss://echo.websocket.org");

必须给 WebSocket 构造函数传入一个绝对 URL
同源策略不适用于 Web Socket, 因此可以打开任意站点的连接

webSocket.readyState

  • 游览器会在初始化 WebSocket 对象之后立即创建连接
  • 于 XHR 类似, WebSocket 也有一个 readyState 属性表示当前状态
CONNECTING:值为0,表示正在连接。
OPEN:值为1,表示连接成功,可以通信了。
CLOSING:值为2,表示连接正在关闭。
CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
  • 示例
switch (socket.readyState) {
  case WebSocket.CONNECTING:
    // do something
    break;
  case WebSocket.OPEN:
    // do something
    break;
  case WebSocket.CLOSING:
    // do something
    break;
  case WebSocket.CLOSED:
    // do something
    break;
  default:
    // this never happens
    break;
}

方法

close()

  • 任何时候都可以调用 close() 方法关闭 Web Socket 连接:
    • 调用之后,readyState 立即变为2(连接正在关闭)
    • 并会再关闭之后变为3(连接已经关闭)
websocket.close();

send()

  • 要向服务器发送数据,使用 send() 方法并传入一个 字符串或者二进制数据(blob 对象或 Arraybuffer 对象)
let socket = new WebSocket("wss://echo.websocket.org");

const stringData = "Hello World";
const arraybufferData = Uint8Array.from(['f', 'o', 'o']);
const blobData = new Blob(['f', 'o', 'o']);

socket.send(stringData);
socket.send(arraybufferData);
socket.send(blobData);

事件

onmessage()

  • 服务器向客户端发送消息时,WebSocket 对象上触发 message 事件
  • 这个 message 事件于其他消息协议类型,可以通过 event.data 属性访问到有效载荷
socket.onmessage = function(event) {
    let data = event.data;
    // 对数据执行某些操作
}
  • 与通过 send() 方法发送的数据类似,event.data 返回的数据也可能是 ArrayBuffer 或 Blob
  • 这由 webSocket 对象的 binaryType 属性决定,该属性可能是"blob"或"arraybuffer"

onopen()

  • 在连接成功建立时触发
socket.onopen = function() {
    // doSomething
}

onerror()

  • 在发生错误时触发,连接无法存续
socket.onerror = function() {
    // doSomething
}

onclose()

  • 在连接关闭时触发
socket.onclose = function(event) {
    // event.wasClean 布尔值,表示连接是否干净的关闭
    // event.code 来自服务器的数值状态码
    // event.reason 字符串,包含服务器发来的消息
}

心跳重连

  • 在使用Web Socket的过程中,有时候会遇到网络断开的情况,但是在网络断开的时候服务器端并没有触发onclose的事件。
  • 这样会导致:服务器会继续向客户端发送多余的链接,并且这些数据还会丢失。
  • 所以就需要一种机制来检测客户端和服务端是否处于正常的链接状态。
  • 因此就有了Web Socket的心跳重连技术。

心跳重连机制

  • 心跳机制是每隔一段时间会向服务器发送一个数据包,告诉服务器自己还活着,同时客户端会确认服务器端是否还活着,如果还活着的话,就会回传一个数据包给客户端来确定服务器端也还活着,否则的话,有可能是网络断开连接了。需要重连~~~

心跳包

  • 像心跳一样每隔固定的时间发一次,来告诉服务器,我还活着。

实例

初始化 Web Socket

  • 先调用createWebSocket函数创建 Web Socket
function createWebSocket() {
  try {
    socket = new WebSocket(socketUrl);
    init();
  } catch(e) {
    console.log('catch');
    reconnect(socketUrl);
  }
}

调用init方法,该方法内把一些监听事件封装如下:

  • 当网络断开的时候,会先调用 onerror,onclose 事件可以监听到,会调用 reconnect 方法进行重连操作。
  • 正常的情况下,是先调用 onopen 方法的,当接收到数据时,会被 onmessage 事件监听到。
function init() {
  socket.onclose = function () {
    console.log('链接关闭');
    reconnect(socketUrl);
  };
  socket.onerror = function() {
    console.log('发生异常了');
    reconnect(socketUrl);
  };
  socket.onopen = function () {
    //心跳检测重置
    heartCheck.start();
  };
  socket.onmessage = function (event) {
    //拿到任何消息都说明当前连接是正常的
    console.log('接收到消息');
    //心跳检测重置
    heartCheck.start();
  }
}

重连操作 reconnect

  • 如果网络断开的话,会执行 reconnect 方法,使用了一个定时器,4秒后会重新创建一个新的websocket链接,重新调用createWebSocket函数,
    重新会执行及发送数据给服务器端。
let lockReconnect = false; //避免重复连接
function reconnect(url) {
  if(lockReconnect) return;
  lockReconnect = true;
  //没连接上会一直重连,设置延迟避免请求过多
  tt && clearTimeout(tt);
  tt = setTimeout(() => {
    createWebSocket(url);
    lockReconnect = false;
  }, 4000);
}

实现心跳检测

const heartCheck = {
  timeout: 3000,
  timeoutObj: null, // 倒计时
  serverTimeoutObj: null, // 定时器
  start: function(){
    console.log('start');

    // 关闭定时器
    this.timeoutObj && clearTimeout(this.timeoutObj);
    this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);

    this.timeoutObj = setTimeout(() => {
      //这里发送一个心跳,后端收到后,返回一个心跳消息,
      //onmessage拿到返回的心跳就说明连接正常
      socket.send("123456789");

      this.serverTimeoutObj = setTimeout(() => {
        console.log(socket);
        socket.close();
      }, this.timeout);

    }, this.timeout)
  }
}

实现心跳检测的思路

  • 正常情况下,服务端向客户端推送数据时会一直重置心跳检测方法(onmessage事件触发),服务端也能收到回应知道客户端处于正常连接
  • 但是当网络连接断开或者服务器并无数据需要推送时(onmessage未事件触发),服务端和客户端都不知道对方是否处于正常的链接状态
  • 这时倒计时结束后,心跳检测方法会向客户端发送一个心跳包(发送的数据可以自行约定)
  • 当没有断网的情况下,服务端收到心跳包(指定的时间)知道客户端正常连接,这时再向客户端推送一条消息; 客户端就会重置心跳检测方法(onmessage事件触发)
  • 当断网的情况下,服务端未收到心跳包(指定的时间),服务端就知道客户端未处于正常连接状态,可以断开连接避免浪费性能和丢包;客户端也将会一直尝试重连,在网络正常的情况时重新建立 Web Socket 连接
posted @ 2021-02-28 21:15  懒惰ing  阅读(770)  评论(0编辑  收藏  举报