WebSocket教程

7,095 阅读6分钟

1.什么是WebSocket?

WebSocket 是一种网络通信协议,很多高级功能都需要它。初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处? 答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。 举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。 这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。

​ 它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

img

其他特点包括:

(1)建立在 TCP 协议之上,服务器端的实现比较容易。

(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

(3)数据格式比较轻量,性能开销小,通信高效。

(4)可以发送文本,也可以发送二进制数据。

(5)没有同源限制,客户端可以与任意服务器通信。

(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

img

2.WebSocket简单示例

var ws = new WebSocket("ws://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
 }

3.WebSocket常用API介绍

  • webSocket构造函数

    let ws = new WebSocket('ws://localhost:8080');
    

    执行上面语句之后,客户端就会与服务器进行连接。

  • webSocket的状态(readyState)

    readyState属性返回实例对象的当前状态,总共有四种状态。

    CONNECTING:值为0, 正在连接
    OPEN:值为1,连接成功
    CLOSING:值为2,连接正在关闭
    CLOSED:值为3,连接已经关闭
    
  • websocket.onopen(连接成功之后的回调函数)

    实例对象的onopen属性,用于指定连接成功后的回调函数。

    ws.onopen = function () {
      ws.send('Hello Server!');
    }
    

    如果要指定多个回调函数,可以使用addEventListener方法。

    ws.addEventListener('open', function (event) {
      ws.send('Hello Server!');
    });
    
  • websocket.onclose(关闭之后调用的方法)

    实例对象的onclose属性,用于指定连接关闭后的回调函数。

    ws.onclose = function(event) {
     console.log('onclose')
    }
    

    如果要指定多个回调函数,可以使用addEventListener方法。

    ws.addEventListener("close", function(event) {
     console.log('onclose')
    });
    
  • websocket.onmessage(接受到服务器数据之后的回调函数)

    ws.onmessage = function(event) {
      // 获取数据event.data
      var data = event.data;
      // 处理数据
    };
    
  • websocket.send(向服务器端发送数据)

    ws.send('your message')
    
  • websocket.onerror(报错时调用的方法)

    ws.onerror = function(event) {
     // handle error event
    };
    

4.WebSocket的心跳重连

websocket是前后端交互的长连接,前后端也都可能因为一些情况导致连接失效并且相互之间没有反馈提醒。因此为了保证连接的可持续性和稳定性,websocket心跳重连就应运而生。在使用原生websocket的时候,如果设备网络断开,不会立刻触发websocket的任何事件,前端也就无法得知当前连接是否已经断开。这个时候如果调用websocket.send方法,浏览器才会发现链接断开了,便会立刻或者一定短时间后(不同浏览器或者浏览器版本可能表现不同)触发onclose函数。后端websocket服务也可能出现异常,造成连接断开,这时前端也并没有收到断开通知,因此需要前端定时发送心跳消息ping,后端收到ping类型的消息,立马返回pong消息,告知前端连接正常。如果一定时间没收到pong消息,就说明连接不正常,前端便会执行重连。为了解决以上两个问题,以前端作为主动方,定时发送ping消息,用于检测网络和前后端连接问题。一旦发现异常,前端持续执行重连逻辑,直到重连成功。

一般的心跳检测的函数:

// 心跳检测, 每隔一段时间检测连接状态,如果处于连接中,就向server端主动发送消息,来重置server端与客户端的最大连接时间,如果已经断开了,发起重连。
let heartCheck = {
    // 心跳,比server端设置的连接时间稍微小一点,在接近断开的情况下以通信的方式去重置连接时间。
    timeout: 100000,
    serverTimeoutObj: null,
    reset: function() {
      clearTimeout(this.serverTimeoutObj)
      return this
    },
    start: function() {
      this.serverTimeoutObj = window.setInterval(() => {
        if (websocket.readyState === 1) {
          websocket.send('ping')
        } else {
          window.clearTimeout(this.serverTimeoutObj)
          // 处理逻辑:重连或者其他
        }
      }, this.timeout)
    }
  }

5.WebSocket完整的代码实例

function newWebSocket(option) {
  console.log('new webSocket.....')
  let websocket = null
  // 判断当前环境是否支持websocket
  if (window.WebSocket) {
    if (!websocket) {
      websocket = new WebSocket('你的请求地址')
    }
  } else {
    console.log('not support websocket')
  }
  // 连接成功建立的回调方法
  websocket.onopen = function(e) {
    // 成功建立连接后,重置心跳检测
    heartCheck.reset().start()
    console.log('connected successfully')
  }
  // 连接发生错误,连接错误时会继续尝试发起连接
  websocket.onerror = function() {
    console.log(`onerror`)
    newWebSocket()
  }
  // 接受到消息的回调方法
  websocket.onmessage = function(e) {
    console.log('onmessage', e.data)
    var message = e.data
    if (message) {
      // 执行接收到消息的操作
      if (option != undefined) {
        // 执行传入对象的方法,传出消息
        option.onmessage(message)
      }
    }
  }

  // 接受到服务端关闭连接时的回调方法
  websocket.onclose = function() {
    console.log('onclose')
  }
  // 监听窗口事件,当窗口关闭时,主动断开websocket连接,防止连接没断开就关闭窗口,server端报错
  window.onbeforeunload = () => {
    return websocket.close()
  }

  // 心跳检测, 每隔一段时间检测连接状态,如果处于连接中,就向server端主动发送消息,来重置server端与客户端的最大连接时间,如果已经断开了,发起重连。
  var heartCheck = {
    // 心跳,比server端设置的连接时间稍微小一点,在接近断开的情况下以通信的方式去重置连接时间。
    timeout: 100000,
    serverTimeoutObj: null,
    reset: function() {
      clearTimeout(this.serverTimeoutObj)
      return this
    },
    start: function() {
      this.serverTimeoutObj = window.setInterval(() => {
        if (websocket.readyState === 1) {
          websocket.send('ping')
        } else {
          console.log('websocket stop', websocket.readyState)
          window.clearTimeout(this.serverTimeoutObj)
          newWebSocket(option)
        }
      }, this.timeout)
    }
  }
  return websocket
}