websocket实现心跳检测&短线重连

4,850 阅读5分钟

前言:

websocket用途很广呀,尤其是一些实时性的需求,比如天气预报,股票实时数据,聊天室啥的,所以今天就把基础知识先讲一下

为什么需要websocket

答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。

举例来说: 我想获取最新的股票信息,但是只能客户端主动向服务端发起请求,并不能服务端主动向客户端推送消息

所以websocket它诞生的目的就是在浏览器和服务器之间建立一个不受限的双向通信的通道

websocket的特点

  1. 建立在TCP协议之上,服务端的实现比较容易
  2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
  3. 可以发送文本,也可以发送二进制数据。
  4. 没有同源限制,客户端可以与任意服务器通信。
  5. 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

image.png

websocket应用

聊天室,自动回复聊天机器人,股票,天气等实时数据推送

websocket基础知识

事件

事件用法
open链接建立时触发
message客户端接受服务端数据是触发
error通信发生错误时触发
close链接关闭时触发

方法

方法用法
ws.send()使用链接发送数据
ws.close()关闭链接

最小websocket运行环境

搭建koa环境

为什么要搭建koa环境?

有人可能要问了,为什么搭建koa环境呀,我就随便建立一个index.html写前端, index.js写后端不行么,当前是可以的,但是真正我们在项目中应用的时候,可能并没有只写index.htmlindex.js这么简单,我们还要考虑到很多因素,所以我们也是从实际项目的角度出发,来搭建这个环境,方便扩展

创建koa脚手架

首先全局安装generator

npm install koa-generator -g

然后生成我们的项目websocket-test

修改views中的index.jadeindex.html

修改app.js的内容

app.use(views('views', {
  root: __dirname + '/views',
  default: 'jade'
}));

app.use(views('views'));

服务端

创建utils/Ws.js 文件

const WebSocket = require('ws');

class Ws {
  constructor() {
    this.wss = WebSocket.Server;
    this.ws = null;
  }


  init = (server) => {
    this.wss = new WebSocket.Server({ server });
    this.wss.on('connection', (ws) => {
      this.ws = ws;
      ws.on('message', this.onMessage)
      ws.on('error', this.onError);
      ws.on('close', this.onClose);
    })
  }

  // 监听客户端消息
  onMessage = (msg) => {
    console.log('收到客户端消息' + msg);
    this.ws.send(222);
  }
  
  // webSocket 连接异常
  onError = () => { }

  // 客户端关闭 webSocket
  onClose = () => { }
}

module.exports = Ws;

客户端

views/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  1111
</body>
<script>
  const ws = new WebSocket('ws://localhost:3000');
  ws.onopen = function(){
    console.log('链接成功');
    ws.send(1111);
  }

  ws.onmessage = function(event){
    console.log('接收到服务端消息'+event.data);
  }
</script>
</html>

上面实现了websocket使用以后,接下来我们来讲讲心跳机制

心跳机制是什么?

心跳机制其实只要看词就能大概了解,就是类似一个轮询的机制,必要时向对方询问情况的一种操作。

为什么需要心跳机制?

在使用原生 Websocket 的时候,如果设备网络断开,不会立刻触发 Websocket 的任何事件,前端也就无法得知当前连接是否已经断开。这个时候如果调用 Websocket.send 方法,浏览器才会发现链接断开了,便会立刻或者一定时间后(不同浏览器或者浏览器版本可能表现不同)触发 onclose 函数。

后端 Websocket 服务也可能出现异常,造成连接断开,这时前端也并没有收到断开通知,因此需要前端定时发送心跳消息 ping,后端收到 ping 类型的消息,立马返回消息,告知前端连接正常。如果一定时间没收到消息,就说明连接不正常,前端便会执行重连。

所以现在就有以下两个问题

    1. 如果前端没有发送消息,如果网络断开了,前端是不知道的,没法收到后端发过来的消息
    1. 网络断开后,后端websocket服务出现异常,但是没办法通知到前端

为了解决以上两个问题,以前端作为主动方,定时发送 ping 消息,用于检测网络和前后端连接问题。一旦发现异常,前端持续执行重连逻辑,直到重连成功。

  • 1、建立连接
  • 2、发送消息
  • 3、接收消息
  • 4、关闭连接
  • 5、检测心跳
  • 6、重新连接

image.png

客户端代码

class Socket {
  /**
   * @description: 初始化实例属性,保存参数
   * 
   */
  constructor(options) {
      // 要连接的后端地址
      this.url = options.url;
      // 接收到信息后执行的方法
      this.callback = options.received;
      this.name = options.name || 'default';
      this.ws = null;
      // 当前状态
      this.status = null;
      this.pingInterval = null;
      // 心跳检测频率
      this.timeout = 3000;
      this.isHeart = options.isHeart;
      this.isReconnection = options.isReconnection;
  }
  connect(data) {
      this.ws = new WebSocket(this.url);
      // 建立连接
      this.ws.onopen = (e) => {
          this.status = 'open';
          console.log("连接成功", e)
          if(this.isHeart) {
              // 心跳
              this.heartCheck()
          }
          // 给后台发送数据
        if (data !== undefined) {
          return this.ws.send(JSON.stringify({type: 'init'}))
        }
      }
      // 接受服务器返回的信息
      this.ws.onmessage = (e) => {
          if(typeof this.callback === 'function'){
              return this.callback(e.data)
          }else{
              console.log('参数的类型必须为函数')
          }
      }
      // 关闭连接
      this.ws.onclose = (e) => {
          console.log('onclose',e)
          this.closeSocket(e)
      }
      // 报错
      this.onerror = (e) => {
          console.log('onerror',e)
          this.closeSocket(e)
      }
  }
  sendMsg(data) {
      let msg = JSON.stringify(data)
      return this.ws.send(msg)
  }
  resetHeart() {
      clearInterval(this.pingInterval)
      return this
  }
  heartCheck() {
      this.pingInterval = setInterval(() => {
          if(this.ws.readyState === 1) {
              this.ws.send(JSON.stringify({type: 'ping'}))
          }
      },this.timeout)
  }
  closeSocket(e) {
      this.resetHeart()
      if(this.status !== 'close') {
          console.log('断开,重连', e)
          if(this.isReconnection){
              // 重连
              this.connect()
          }
      }else{
          console.log('手动关闭了', e)
      }
  }
  close() {
      this.status = 'close'
      this.resetHeart()
      return this.ws.close();
  }
}

客户端html代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
<button onclick="onSend()">发送</button>
<button onclick="onDisconnect()">断开</button>
</body>
<script src="./javascripts/socket.js"></script>
<script>
   // 初始化
   const ws = new Socket({
        url: 'ws://localhost:3000',// 阮一峰老师教程链接
        name: '',			// name
        isHeart:true,			// 是否心跳
        isReconnection:false,		// 是否断开重连
        received: function(data){
        	// 监听服务器返回信息
            console.log("received",data)
        }
    });
    // 建立连接
    let data = {
        type: 'init'
    }
    ws.connect(data);
    
    function onSend(){
      // 发送消息
      let sendData = {
        type: 'sendMsg'
      }
      ws.sendMsg(sendData)
    }

    function onDisconnect(){
      // 手动关闭
      ws.close()
    }
</script>
</html>

总结

  1. 此篇文章我们讲了为什么需要websocketwebsocket的特点,事件和方法
  2. 然后使用服务端使用ws库用最小环境运行了websocket
  3. 然后讲了websocket的痛点就是在断网的时候,无法感知,也无法在联网的时候进行重连,于是我们封装了心跳机制

感谢大家阅读本文章,创作不易

参考