Web Socket的基本使用

Web Socket与HTTP一样,都是基于TCP的网络协议,二者最大的区别是Web Socket是全双工,HTTP是半双工。二者的其他特性网上有很多介绍,这里不再多说,本文主要介绍如何使用Web Socket,以及心跳检测和重连机制。

基本用法

前端部分:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>客户端</title>
</head>
<body>
这是一个客户端
<button id="btn">发送数据给服务器</button>
</body>
<script>
    let btn=document.getElementById('btn');
    //创建websocket对象
    let ws=new WebSocket('ws://localhost:9595');//Web Socket是一种协议,因此应该用ws而不是http

    //监听事件
    //连接建立成功时
    ws.addEventListener('open',e=>{
      console.log('建立了ws连接');
      console.log(e);
    });

    //收到服务器的信息时
    ws.addEventListener('message',e=>{
      console.log(e);
    });

    //连接关闭时
    ws.addEventListener('close',e=>{
      console.log('close',e);
    });

    //发生错误,连接无法存续时
    ws.addEventListener('error',err=>{
      console.log('err',err);
    });

    btn.addEventListener('click',function () {
      //点击按钮发送数据给服务器
      //数据是字符串、ArrayBuffer或Blob中的一种
      ws.send('前端数据');
    })
</script>
</html>

服务端(需要自己安装nodejs-websocket模块):

const ws=require('nodejs-websocket');

ws.createServer(connect=>{
  console.log('创建了新的连接');
  //客户端发送来的消息时
  connect.on('text',data=>{
    console.log('text',data);
    //数据是字符串、ArrayBuffer或Blob中的一种
    connect.send('服务端的数据');
  });
  //连接断开时
  connect.on('close',e=>{
    console.log('close',e);
  });
  //连接发生错误时
  connect.on('error',e=>{
    console.log('error',e);
  })
}).listen(9595);//这个是端口号

在浏览器的开发者工具中可以查看具体的数据

心跳检测和重连机制

为什么要心跳检测?上面的这种写法,在正常情况下或许没有问题,如果服务器很久都没有响应呢,除非服务器关闭了连接,否则前端是不会触发error或者close事件的;同时,在客户端网络断开的情况下,前端也不会触发error或者close事件,这时候用户等了半天也不知道是自己的网络问题。
为了解决上面的问题,以前端作为主动方,定时发送ping消息,后端回复pong消息,用于检测网络和前后端连接问题。前端在一定时间内没有收到后端pong的消息,说明连接出现了异常,前端主动执行重连逻辑,直到重连成功或在重连一定次数后停止重连并告知用户网络异常。因为websocket链接必须是前端主动请求建立连接,因此重连肯定是给前端来做,所以判断重连逻辑都是写在前端。下面就来实现一下简单的心跳检测和重连。

先改造一下后端:

const ws=require('nodejs-websocket');

ws.createServer(connect=>{
  console.log('创建了新的连接');
  //客户端发送来的消息时
  connect.on('text',data=>{
    //如果是心跳检测消息时,直接回复pong
    if(data==='ping'){
      connect.send('pong');
    }else {
      console.log('正常的数据',data);
      connect.send('服务端的数据');
    }
  });
  //连接断开时
  connect.on('close',e=>{
    console.log('close',e);
  });
  //连接发生错误时
  connect.on('error',e=>{
    console.log('error',e);
  })
}).listen(9595);

再改造前端:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>客户端</title>
</head>
<body>
这是一个客户端
<button id="btn">发送数据给服务器</button>
</body>
<script>
    class MyWebSocket{
      timeout=5000;//超时时间(这个时间内没心跳就算超时)
      maxReconnectTimes=0;//重连的最大次数,0表示可以无限重连
      reconnectTimes=0;//已经重连的次数
      reconnectTime=5000;//每次重连的间隔时间
      heartBeatTime=5000;//每次心跳的间隔时间
      heartBeatTimer=null;//心跳定时器
      timeoutTimer=null;//超时定时器
      reconnectTimer=null;//每次重连的定时器
      isReconnecting=false;//是否正在重连
      isDone=false;//已经触发错误了
      url='';//连接地址
      socket=null;//websocket实例

      constructor(options) {
        this.url=options.url;
        this.timeout=options.timeout?options.timeout:this.timeout;
        this.maxReconnectTimes=options.maxReconnectTimes?options.maxReconnectTimes:this.maxReconnectTimes;
        this.reconnectTimes=options.reconnectTimes?options.reconnectTimes:this.reconnectTimes;
        this.reconnectTime=options.reconnectTime?options.reconnectTime:this.reconnectTime;
        this.heartBeatTime=options.heartBeatTime?options.heartBeatTime:this.heartBeatTime;
        this.errorFunc=options.errorFunc?options.errorFunc:this.errorFunc;
        this.connect();
      }

      //连接操作,参数为是否重连操作
      connect(){
        this.socket=new WebSocket(this.url);
        this.bindEvent();
      }

      //重连
      reconnect(){
        if(this.maxReconnectTimes&&this.reconnectTimes>=this.maxReconnectTimes){
          if(this.isDone){
            return;
          }
          //防止error和close触发触发两次错误回调
          this.isDone=true;
          //超过了重连次数,就执行错误回调不连了
          this.errorFunc();
        }else {
          //已经在重连了(连接失败时error和close事件都会触发)
          if(this.isReconnecting){
            return;
          }
          //重新连接
          this.isReconnecting=true;
          this.reconnectTimer=setTimeout(()=>{
            this.reconnectTimes++;
            console.log(`第${this.reconnectTimes}次重连`);
            this.connect();
            //可以进行下一次重连了
            this.isReconnecting=false;
          },this.reconnectTime);
        }
      }

      //绑定监听事件
      bindEvent(){
        let self=this;
        //连接建立成功时
        this.socket.onopen=function () {
          console.log('建立了ws连接');
          //在连接成功后启动定时器,开始心跳。前端发送一次ping,后端返回一次pong算一次心跳。
          self.heartbeat();
        }

        //收到服务器的信息时
        this.socket.onmessage=function (e) {
          console.log(e);
          //能收到消息就说明心跳没问题,可以开始下一次心跳
          self.heartbeat();
        }

        //连接关闭时
        this.socket.onclose=function (e) {
          console.log('close',e);
          self.reconnect();
        }

        //发生错误,连接无法存续时
        this.socket.onerror=function (err) {
          console.log('err',err);
          //发生错误时需要重连
          self.reconnect();
        }
      }

      //重置心跳
      resetHeartbeat(){
        clearTimeout(this.heartBeatTimer);
        clearTimeout(this.timeoutTimer);
      }

      //开始心跳
      heartbeat(){
        //有心跳就说明连接稳定,重置重连数据
        this.reconnectTimes=0;
        clearTimeout(this.reconnectTimer);
        this.isReconnecting=false;
        //每次心跳前先重置心跳
        this.resetHeartbeat();
        //heartBeatTime心跳一次
        this.heartBeatTimer=setTimeout(()=>{
          console.log('心跳');
          this.socket.send('ping');
          this.timeoutTimer=setTimeout(()=>{
            //超过超时时间没有检测到心跳回复就主动关闭连接
            //在调用close后,前端不会立即执行close事件回调,要等到下一次调用send方法时才会执行close事件的回调
            //但是后端会在调用close后立即执行close事件回调
            //try catch是无法捕获websocket的连接错误的,因此只能在close和error事件回调中执行重连操作
            console.log('主动关闭连接');
            this.socket.close();
            //对于客户端断网的情况,浏览器是不会抛出错误的,在网络恢复后,浏览器会自动重新连接,并且不会触发close或者error事件
            //这里手动调用reconnect的目的是在客户端断网的情况下,也能手动重连,在长时间网络断开的情况下可以提示用户,而不是一直等待网络恢复
            this.reconnect();
          },this.timeout);
        },this.heartBeatTime)
      }

      //错误处理函数
      errorFunc(){
        throw new Error('网络错误');
      }
    }

    let ws=new MyWebSocket({
      url:'ws://localhost:9595',
      maxReconnectTimes:5,//可以重连5次
    });
    let btn=document.getElementById('btn');
    btn.addEventListener('click',function () {
      //点击按钮发送数据给服务器
      //数据是字符串、ArrayBuffer或Blob中的一种
      ws.socket.send('前端数据');
    })
</script>
</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值