websocket的基础使用,心跳机制,断线重连

前言

websoket出现的原因: 传统的http请求只能是由前端向后台发送一个请求,然后后台把结果返回给前端,前端再进行展示。这里就暴露了一个问题,就是通信只能由前端发起,而后台无法主动与前端通信。而websoket的出现就是为了解决这个问题,让前端可以主动联系后台,后台也可以主动联系前台。
应用场景: 相信大家都知道websoket的应用场景主要是用于即时通讯,比如QQ、微信即时通讯软件,同时在一些实时监控,需要即时暴露问题的地方也需要用到websoket,比如大屏可视化,需要及时的展现商品的成交数量。另外还有就是后台需要某个条件才能触发任务,在触发了任务时需要主动的告诉前端。

主流的技术

1.原生websocket
2.socket.io(推荐)

目前在websocket中应用比较多的就是这两个技术,一个是原生的websoket,另外一个socket.io是在原生websoket的基础上进行了封装了一层后的技术,让程序员更加的好用。

如何进行技术的选择:个人推荐最好还是使用ocket.io这个库, 原因就是更好用。但是很大的原因还是需要取决于后台的选择,如果后台选择了原生websocket,那么前端也需要使用原生websocket。如果后台对socket.io熟悉,那么前端也需要选择 socket.io,这是最好不过的,会让你们的开发更加的顺畅。

基础使用

1.websoket

// WebSocket构造函数,创建WebSocket对象
let ws = new WebSocket('ws://localhost:8888')

// 连接成功后的回调函数
ws.onopen = function (params) {
    
    
  console.log('客户端连接成功')
  // 向服务器发送消息
  ws.send('hello')
};

// 从服务器接受到信息时的回调函数
ws.onmessage = function (e) {
    
    
  console.log('收到服务器响应', e.data)
};

// 连接关闭后的回调函数
ws.onclose = function(evt) {
    
    
  console.log("关闭客户端连接");
};

// 连接失败后的回调函数
ws.onerror = function (evt) {
    
    
  console.log("连接失败了");
};


// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,这样服务端会抛异常。
window.onbeforeunload = function() {
    
    
    ws.close();
}  

如以上代码,基本上已经涵盖了原生ws的所有用法,包括原生ws的事件:open,Message,Error,Close四大事件,两个基本方法:send方法(用于发送数据),close方法(用于关闭连接)

解释:
open事件:websoket连接成功时会执行该方法(也叫做回调函数)
message事件:websoket在收到消息时会执行该方法,例如后台send一个数据给前端,前端收到后就会执行message方法。
error事件:websoket连接错误时会执行该方法,一般是用于处理ws错误的情况
close事件:websoket连接关闭后会执行该回调函数,一般用于处理错误或善后
send方法:websoket发送消息的方法
colse方法:主动关闭ws连接的方法

参考链接:websoket前端基本使用:https://blog.csdn.net/qq_17627195/article/details/128559926

2.socket.io

这里soket.io分为前端和后端,前端使用只需要使用socket.io-client这个库就ok了,如下图官网
image.png
引入:

import io from 'socket.io-client';

使用:


<template></template>
<script>
import io from 'socket.io-client';
export default {
    
    
  data(){
    
    
    return{
    
    
      sockets:null,
    }
  }
  mounted(){
    
    
    // 建立socket连接
    this.sockets = io('ws://localhost:8888')
    //监听
    this.sockets.on('connect', () => {
    
    
      //监听连接是否成功
      this.sockets.emit("news","这是发送事件的内容");
      console.log('connect');
    });
    this.sockets.on('disconnect', (reason) => {
    
    
      //断开连接时触发此事件
      console.log(reason);
    });
    this.sockets.on("connect_error", () => {
    
    
		  // 连接错误时触发该事件
       console.log('connect_error');
		});
    this.sockets.on('XXXX', (data) => {
    
    
      //监听后端返回事件,XXXX是与后端约定的字段
      console.log('XXXX',data);
    });
  }destroyed() {
    
    
    //断开连接,需要时再执行
    this.sockets.close()
  },
}
</script>


这里列出了socket.io的主要事件和方法(还有一些没有列出,需要的可以到官网查询)
主要事件有connect,disconnect,connect_error事件以及很重要的自定义消息事件。方法有on,emit,close方法
connect事件:socket.io连接成功后会执行该回调
disconnect事件:socket.io断开连接后会执行该回调
connect_error事件:socket.io连接错误时会执行该回调
自定义消息事件xxxx:socket.io在收到后台发送的xxxx事件后会执行xxxx的事件。这里的自定义消息事件,一定是前后端都需要一致的。比如后台向前端发送了(emit)事件A,内容为’aaaa’,则前端通过监听事件A,则能拿到后台的消息,一致的是事件A的名字。
on方法:监听各种事件
emit方法:用于发送消息
close方法:用于关闭socket.io的连接

参考链接:
1.官网:https://socket.io/docs/v4/client-socket-instance/
2.socket.io前端基本使用:https://blog.csdn.net/dragon_zjl/article/details/124793808

心跳机制和断线重连

心跳机制和断线重连方法出现的原因: 这里想说明的是websoket的基本使用其实是不难的,最主要还是需要去处理实际业务中会出现的问题,如果仅会基础使用的话,你将会面临以下几个问题:1.服务端挂了,半小时后服务重启成功了,但是发现websoket依然没有连接上来。 2.用户断网,断网之后又有网了,发现websoket在断网后断开了连接,但是在用户有网时却没有连接上来 3.在使用了nginx之后发现:websoket会自动断线,且连接不上来。

心跳机制简述:就是客户端每隔一段时间向服务端发送一个特有的心跳消息,每次服务端收到消息后只需将消息返回,此时,若二者还保持连接,则客户端就会收到消息,若没收到,则说明连接断开,此时,客户端就要主动重连,完成一个周期

断线重连简述:就是在用户断线或者服务端掉线之后,每隔一段时间重新new一个websoket实例的过程(new的过程就是连接的过程)。

实现代码(基础版-node后端):

const WebSocket = require("ws")
const wss = new WebSocket.Server({
    
     port: 3000 })

wss.on("connection", (ws) => {
    
    
  console.log("有人连接进来了")
  // ws.on("message", (data) => {
    
    
  //   ws.send(data + "+九月九日忆山东兄弟")
  // })
  ws.on("error", () => {
    
    
    console.log("连接错误")
  })
  ws.on("close", () => {
    
    
    console.log("有人断开连接了")
  })
  ws.on("message", (e) => {
    
    
    console.log(e)
    const data = JSON.parse(e)
    console.log(data)
    switch (data.ModeCode) {
    
    
      case "message":
        console.log("收到消息" + data.msg)
        ws.send(e)
        break
      case "heart_beat":
        console.log(`收到心跳${
      
      data.msg}`)
        ws.send(e)
        break
    }
  })
})

实现代码(基础版-前端):

<template>
  <div id="app">
    <nav>
      <router-link to="/">Home</router-link>
      |
      <router-link to="/about">About</router-link>
      <div>
        <!-- {
    
    {
    
     wsdata }} -->
      </div>
    </nav>
    <router-view />
  </div>
</template>

<script>
const ModeCode = {
    
    
  // websocket消息类型
  MSG: 'message', // 普通消息
  HEART_BEAT: 'heart_beat' // 心跳
};

export default {
    
    
  data() {
    
    
    return {
    
    
      ws: null,
      webSocketState: false, // webSocket的连接状态
      heartBeat: {
    
    
        // 心跳连接的时间设置
        time: 5 * 1000, // 心跳时间间隔
        timeout: 3 * 1000, // timeout:心跳超时间隔
        reconnect: 10 * 1000 // 断线重连时间
      },
      reconnectTimer: null // 断线重连时间器
    };
  },
  created() {
    
    
    this.connectWebSocket();
  },
  methods: {
    
    
    /*
     * 心跳初始函数
     * @param time:心跳时间间隔
     */
    startHeartBeat(time) {
    
    
      setTimeout(() => {
    
    
        this.ws.send(
          JSON.stringify({
    
    
            ModeCode: ModeCode.HEART_BEAT,
            msg: new Date()
          })
        );
        this.waitingServer();
      }, time);
    },
    // 延时等待服务端响应,通过webSocketState判断是否连线成功
    waitingServer() {
    
    
      this.webSocketState = false;
      setTimeout(() => {
    
    
        if (this.webSocketState) {
    
    
          this.startHeartBeat(this.heartBeat.time);
          return;
        }
        console.log('心跳无响应,已断线');
        try {
    
    
          this.ws.close();
        } catch (e) {
    
    
          console.log('连接已关闭,无需关闭');
        }
        this.reconnectWebSocket();
      }, this.heartBeat.timeout);
    },
    // 重连操作
    reconnectWebSocket() {
    
    
      this.reconnectTimer = setTimeout(() => {
    
    
        this.reconnectWs();
      }, this.heartBeat.reconnect);
    },
    connectWebSocket() {
    
    
      this.ws = new WebSocket('ws://localhost:3000');
      this.init();
    },
    init() {
    
    
      this.ws.addEventListener('open', () => {
    
    
        // eslint-disable-next-line spaced-comment
        this.webSocketState = true; //socket状态设置为连接,做为后面的断线重连的拦截器
        // eslint-disable-next-line no-unused-expressions
        this.heartBeat && this.heartBeat.time ? this.startHeartBeat(this.heartBeat.time) : ''; // 是否启动心跳机制
        console.log('开启');
      });
      this.ws.addEventListener('message', (e) => {
    
    
        const data = JSON.parse(e.data);
        switch (data.ModeCode) {
    
    
          case ModeCode.MSG: // 普通消息
            console.log('收到消息' + data.msg);
            break;
          case ModeCode.HEART_BEAT: // 心跳
            this.webSocketState = true;
            console.log('收到心跳响应' + data.msg);
            break;
        }
      });
      this.ws.addEventListener('close', (e) => {
    
    
        this.webSocketState = false; // socket状态设置为断线
        console.log('断开了连接');
      });
      this.ws.addEventListener('error', (e) => {
    
    
        this.webSocketState = false; // socket状态设置为断线
        this.reconnectWebSocket(); // 重连
        console.log('连接发生了错误');
      });
    },
    reconnectWs() {
    
    
      if (!this.ws) {
    
    
        // 第一次执行,初始化
        this.connectWebSocket();
      }
      if (this.ws && this.reconnectTimer) {
    
    
        // 防止多个websocket同时执行
        clearTimeout(this.reconnectTimer);
        this.ws.reconnectTimer = null;
        this.connectWebSocket();
      }
    }
  }
};
</script>

<style lang="less">
#app {
    
    
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

nav {
    
    
  padding: 30px;

  a {
    
    
    font-weight: bold;
    color: #2c3e50;

    &.router-link-exact-active {
    
    
      color: #42b983;
    }
  }
}
</style>

主要的代码逻辑:

1.在声明周期钩子函数created中进行连接ws,主要是去new出一个ws实例,以及为该实例注册事件。
2.在连接ws成功后会执行open的回调函数,在该回调函数中开启心跳,即定时向后端发送数据,如代码中,向后台每5秒发送一个心跳,并同时等待服务端的响应(waitingServer函数)。在waitingServer函数中将连接状态设置为false(假设没连接),如果此时后台是连接正常的,则会触发message的回调函数,将连接状态设置为true。而且waitingServer函数将会在3秒后进行检测连接的状态。如果3s后的状态正常,则继续发送心跳。但是如果不成功,那么就去关闭本次websoket连接,并触发重连操作。
image.png
3.在重连操作中,则是每隔10s进行一次初始化ws的操作。但是在初始化ws时,由于后台服务掉线/用户没网会触发ws的’error’事件,该事件的回调中又去触发重连操作,重连操作中又是去触发初始化ws的操作,所以形成了一次次的重连操作,直到连接上后台服务为止。
image.png
image.png

最后是模拟重连的截图

a.后台服务掉线
image.png
image.png
b.用户断网
image.png
image.png

最后

websocket心跳机制和断线重连的理解可能需要花费更多的时间,重点是需要自己去跑通代码并看其中的逻辑,我这里放的代码是完全没有抽取过的,属于新手比较容易看得懂的代码。如果追求更好的代码封装,可以看下下面这篇文章:
参考链接:心跳机制和断线重连:https://juejin.cn/post/6945057379834675230

这里放出本次demo的地址,可以下载下来自己去看。
demo地址:
node后台:https://github.com/rui-rui-an/wsnodeserver
前端:https://github.com/rui-rui-an/wswebcode

猜你喜欢

转载自blog.csdn.net/weixin_43239880/article/details/130818481