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);//这个是端口号

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


QQ截图20230129191959.png

心跳检测和重连机制

为什么要心跳检测?上面的这种写法,在正常情况下或许没有问题,如果服务器很久都没有响应呢,除非服务器关闭了连接,否则前端是不会触发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>

参考文献

《JavaScript高级程序设计第四版》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容