websocket具体实践

news2024/10/6 10:27:14

websocket具体实践

参考:
如何使用websocket
WebSocket客户端连接不上和掉线的问题以及解决方案

    继6月份对websocket一顿了解之后,我们的项目也要上websocket了,虽然这部分不是我做,但是借此机会,我也想要尝试一下:假如是我来写这个模块,我会写成什么样呢?
抱着这样的想法,在11月份开始着手查了一些资料,打算完整地写一份前端的socket实例,一开始完全没有头绪,网上查了查大家写的都很简略的样子,达不到想要的效果,后来写小程序的时候想到可以借鉴小程序里的websocket封装,然后终于确定了类的api和基本功能。
    修改过几次后,实现的功能和流程如下:(当然我自己写的这一份只是在自己的网站里做了一些简单测试,里面肯定还有很多bug待解决的)

  • 1.0 第一版:引入scoketio客户端依赖做的,已经全面否决
  • 1.1 第二版:使用函数实现,实际使用感乱七八糟的。
  • 1.2 第三版:借鉴小程序的类封装,定义好了简单的success/fail/onError API 实现
  • 1.3 第四版:增加多场景(1、时效性强不需要重连机制,连接失败立马更换轮询场景。2、保持长连接的可重连场景),增加心跳、断线重连
  • 1.4 第五版:断线后不会发送close事件,心跳事件仍然照常发送,故取消断线重连的机制,但是检测定时器仍然保留。
    疑问:
    1. 心跳事件如果只有客户端发送保活,后端收到后不响应,会不会有什么问题?
    • 基本不会有问题,保活是为了确保正确的连接到俩端,出于服务器性能考虑可以这样做。
    1. 如果断线后没有检测到连接断开,会不会重连时多个客户端连接到同一个后端上去了,这样有办法检测吗?
    • 后端可以检测连接的websocket session,把之前的取消掉,否则可能会出现服务端发送了消息但是客户端接受不到的情况,
编写中遇到的问题:
  1. 重连需要注意把之前的连接关闭,否则就会出现内存泄漏的问题,如下图一下开了好多个连接,后端也收到好多个连接

  2. websocket和MQ消息队列有什么不同,为什么不采用MQ.
  • MQ没办法精准定位到用户,如果有多个用户需要消息,往消息队列中取消息不知道哪个才是自己的。
  • 前端获取MQ信息不像后端那么好操作易上手,而ws是html支持的API

前端代码实现如下:
后端是使用express-ws简单地建立了一个本地服务器,代码不贴了吧,网上老多了。

const READY_STATE = {
  CONNECTING: 'CONNECTING',
  OPEN: 'OPEN',
  CLOSING: 'CLOSING',
  CLOSED: 'CLOSED',
  0: 'CONNECTING',
  1: 'OPEN',
  2: 'CLOSING',
  3: 'CLOSED',
};

/**
 * eg: new Socket('/connect',{success:(ws)=>{}, fail:(e)=>{switch(e.code){}}, onMessage:(res)=>{} })
 * onclose 如果允许重连则重连,不允许则直接回调fail
 * onerror 仅在后台发出警示,回调中不开放这个回调,一切在fail函数中处理
 * @param url 需要连接的ws链接,仅 /+pathname host拼接
 * @param success 连接成功后调用
 * @param fail 连接失败 除了以下code,其他为未知bug
 *             code=1 -> 不支持websocket
 *             code=2 -> 不允许重连-连接失败、允许重连-重连三次后也还是连接失败
 *             code=3 -> 身份验证未通过
 *             code=4 -> 未收到心跳事件主动断开连接
 *             code=5 -> (暂时不处理) 因为客户端断线 / 服务端主动断开等原因导致连接关闭后的回调
 * @param onMessage 收到消息的回调函数。
 * @param allowReConnect 允许连接失败后再重新连接而不立刻返回失败结果。(比如结果页就不需要重连,只需要第一次连接的结果,否则重连几次后,支付结果都出来了)
 * 连接成功后将发送身份验证,每次连接发送数据需要带上本次会话的发送时间戳(不然前端无法确定本次回复是哪次消息)
 * ? 如果意外在同一个页面同时创建了多个链接,前端无法检测出来,因为对象之间互相不联系,需要后端查询是否有相同用户链接,主动断开。
 */
class Socket {
  constructor(connectUrl = '', options = {}) {
    this.initData(connectUrl, options);
    this.connect();
  }

  initData = (connectUrl, options) => {
    this.connectUrl = connectUrl;
    this.props = options;

    // socket连接事件
    this.socketOpen = false;
    this.socketConnecting = false;
    this.socketMsgQueue = []; // 待发送、发送后未收到回复 的消息队列

    this.maxSocketConnectCount = 3; // 最多断线重连3次
    this.socketConnectCount = 0; // 断线重连次数
    this.socketConnectTimer = null; // 断线重连定时器

    // 心跳事件
    this.heartBeatFailCount = 0; // 心跳连接失败次数
    this.heartBeatTimer = null; // 心跳事件定时器
    this.heartBeatEventCb = null; // 心跳事件回调函数,一旦回应则清空,未回应则重连或关闭连接。
  };

  /**
   * 建立连接
   * @param {string} from reconnet | undefined  默认为初始化连接,reconnet为连接失败重连
   */
  connect = (from = 'connect') => {
    try {
      if (!window.WebSocket) {
        this.options.fail({ code: 1, error: '不支持' });
      }

      const ws = new WebSocket('ws://localhost:8100' + this.connectUrl);
      this.socketConnecting = true;

      // 注册ws相关事件
      ws.onopen = () => {
        console.log('success');
        this.ws = ws;
        this.socketOpen = true;
        this.socketConnecting = false;
        this.cancelReConnect();
        this.ping();
      };

      ws.onerror = (e) => {
        this.socketOpen = false;
        this.socketConnecting = false;
        console.error('ERROR:' + from + '-连接失败');
      };

      ws.onclose = () => {
        console.error('CLOSE:' + from + '-连接失败');
        this.socketOpen = false;
        this.cancelHeartBeat();
        if(this.socketConnecting){
          ws.close();
          this.socketConnecting = false;
        }
        if (this.props.allowReConnect) {
          this.reConnect();
        } else {
          this.props.fail?.({ code: 2, error: '连接失败' });
        }
      };
    } catch (error) {
      this.options.fail({ code: 1000, error });
    }
  };

  /** 取消重连 */
  cancelReConnect = () => {
    if (this.socketConnectTimer) {
      clearTimeout(this.socketConnectTimer);
      this.socketConnectTimer = null;
      this.socketConnectCount = 0;
    }
  };

  /** 重连 */
  reConnect = () => {
    if (this.socketConnectCount < this.maxSocketConnectCount) {
      this.socketConnectCount = this.socketConnectCount + 1;
      this.socketConnectTimer = setTimeout(()=>{
        this.connect();
      },1000)
    } else {
      this.socketConnectCount = 0;
      clearTimeout(this.socketConnectTimer);
      this.socketConnectTimer = null;
    }
  };

  onmessage = (e, cb) => {
    if (e.data === 'pong') {
      // 清空当前的心跳回调事件
      clearTimeout(this.heartBeatEventCb);
      this.heartBeatEventCb = null;
    }

    console.log('from server: ' + e.data);
    cb(e);
  };

  // 关闭心跳事件
  cancelHeartBeat = () => {
    clearInterval(this.heartBeatTimer);
    this.heartBeatTimer = null;
  };

  // 注册心跳事件
  ping = () => {
    if (this.heartBeatTimer) {
      this.cancelHeartBeat();
    }
    this.heartBeatTimer = setInterval(() => {
      this.ws.send('ping');
      // 向心跳事件列表push一个心跳事件,30秒发送一次,setTimeOut 10s后没有收到Pong消息则重连或关闭连接。
      this.heartBeatEventCb = setTimeout(()=>{
        this.ws.close();
        this.props.fail({code:4,error:'未收到心跳事件'})
      },1 * 10 * 1000)
    }, 1 * 30 * 1000);
  };
}

export default Socket;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1446434.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[C/C++] -- CMake使用

CMake&#xff08;Cross-platform Make&#xff09;是一个开源的跨平台构建工具&#xff0c;用于自动生成用于不同操作系统和编译器的构建脚本。它可以简化项目的构建过程&#xff0c;使得开发人员能够更方便地管理代码、依赖项和构建设置。 CMake 使用一个名为 CMakeLists.tx…

【Java程序设计】【C00266】基于Springboot的超市进存销管理系统(有论文)

【Java程序设计】【C00266】基于Springboot的超市进存销管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的超市进销存系统 本系统分为登录注册模块、管理员功能模块以及员工功能模块。 登录注册模块&#…

Solidworks:平面工程图练习

把草图变成工程图&#xff0c;遇到第一个问题是线宽需要用鼠标选中后再设置线宽和颜色。我觉得应该有一个自动设置现款的功能&#xff0c;不知道有没有&#xff0c;我找了半天也没找到。 另一个问题是&#xff0c;作业代号字体上下颠倒了&#xff0c;不知道这是啥意思。 第三个…

鸿蒙开发理论之页面和自定义组件生命周期

1、自定义组件和页面的关系 页面&#xff1a;即应用的UI页面。可以由一个或者多个自定义组件组成&#xff0c;Entry装饰的自定义组件为页面的入口组件&#xff0c;即页面的根节点&#xff0c;一个页面有且仅能有一个Entry。只有被Entry装饰的组件才可以调用页面的生命周期。自…

从零开始学howtoheap:理解fastbins的​unsorted bin攻击

how2heap是由shellphish团队制作的堆利用教程&#xff0c;介绍了多种堆利用技术&#xff0c;后续系列实验我们就通过这个教程来学习。环境可参见从零开始配置pwn环境&#xff1a;从零开始配置pwn环境&#xff1a;从零开始配置pwn环境&#xff1a;优化pwn虚拟机配置支持libc等指…

开源版发卡小程序源码,云盘发卡微信小程序源码带PC端

一款发卡小程序。带PC端 系统微信小程序前端采用nuiapp 后端采用think PHP6 PC前端采用vue开发 使用HBuilderX工具打开&#xff0c;运行到微信小程序工具&#xff0c;系统会自动打包微信小程序代码 修改文件common/request/request.js 改成你的后端网址 微信小程序端完全…

【原创 附源码】Flutter安卓及iOS海外登录--Google登录最详细流程

最近接触了几个海外登录的平台&#xff0c;踩了很多坑&#xff0c;也总结了很多东西&#xff0c;决定记录下来给路过的兄弟坐个参考&#xff0c;也留着以后留着回顾。更新时间为2024年2月8日&#xff0c;后续集成方式可能会有变动&#xff0c;所以目前的集成流程仅供参考&#…

vscode预览github上的markdown效果

需要安装的插件 Github Markdown Preview Markdown Checkboxes Markdown Emoji Markdown footnotes Markdown Preview Github Styling Markdown Preview Mermaid Support Markdown yaml Preamble 操作步骤 ①ctrlshiftv会弹出预览页面 ②点击Split Up ③把这个拖过去…

Linux第49步_移植ST公司的linux内核第1步_获取linux源码

已知ST公司的linux源码路径&#xff1a; /home/zgq/linux/atk-mp1/stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24/sources/arm-ostl-linux-gnueabi/linux-stm32mp-5.4.31-r0 1、创建“my_linux”目录 打开第1个终端 输入“ls回车” 输入“cd linux/回车”&#xff0c;切换…

svg基础(九)滤镜-feMorphology(形态学)

feMorphology:形态学滤镜 用来侵蚀或扩张输入的图像。它在增肥或瘦身效果方面特别有用。适合用来创建轮廓和边界。 1 用法 <feMorphology operator"" radius""/>2 属性 inoperator -dilate膨胀,erode侵蚀radius- 3 示例 <svg width"50…

【易学】周易入门 ③ ( 玄学五术 - 山医命相卜 | 天命无常 唯有德者居之 | 预测学模型 | 五行学说 | 五行相生 | 五行相克 )

文章目录 一、玄学五术 - 山医命相卜二、天命无常 唯有德者居之三、预测学模型四、五行学说1、五行相生2、五行相克 一、玄学五术 - 山医命相卜 玄学五术 : 山 : 修行 " 肉体 " 和 " 精神 " , 以寻求 身心超脱 ; 肉体修行 - 拳法 : 太极拳 , 五禽戏 , 易筋…

Git分支和迭代流程

Git分支 feature分支&#xff1a;功能分支 dev分支&#xff1a;开发分支 test分支&#xff1a;测试分支 master分支&#xff1a;生产环境分支 hotfix分支&#xff1a;bug修复分支。从master拉取&#xff0c;修复并测试完成merge回master和dev。 某些团队可能还会有 reale…

【技巧】Allegro实用技巧之模块复用

需求分析&#xff1a;使用Allegro软件进行PCB Layout设计时&#xff0c;当电路图中有很多路相同的模块&#xff0c;使用模块复用的的操作方法&#xff0c;可以显著提高工作效率&#xff0c;同时也可以使PCB布局在整体上显得美观。下面来讲述这个方法。 具体方法及说明&#xf…

linux优化空间完全卸载mysql——centos7.9

文章目录 ⭐前言⭐linux命令使用&#x1f496; 基础命令&#x1f496; 内存优化&#x1f496; 完全删除mysql ⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享 linux优化空间&完全卸载mysql——centos7.9。 linux内存分配 在Linux中&#xff0c;内存分配是…

AcWing 802. 区间和 离散化

文章目录 题目链接题目描述解题思路代码实现总结 题目链接 链接: AcWing 802. 区间和 题目描述 解题思路 离散化是一种常用的技巧&#xff0c;它能够将原始的连续数值转换为一组离散的值&#xff0c;从而简化问题的处理。在这段代码中&#xff0c;离散化的过程主要分为三个步…

分布式文件系统 SpringBoot+FastDFS+Vue.js

分布式文件系统 SpringBootFastDFSVue.js 一、分布式文件系统1.1.文件系统1.2.什么是分布式文件系统1.3.分布式文件系统的出现1.3.主流的分布式文件系统1.4.分布式文件服务提供商1.4.1.阿里OSS1.4.2.七牛云存储1.4.3.百度云存储 二、fastDFS2.1.fastDSF介绍2.2.为什么要使用fas…

2024年最新指南:如何订阅Midjourney(详尽步骤解析)

前言&#xff1a; Midjourney是一个基于人工智能的图像生成工具&#xff0c;它使用高级算法来创建独特和复杂的图像。这个工具能够根据用户输入的文字描述生成对应的图片。Midjourney的特点在于它能够处理非常抽象或者具体的描述&#xff0c;生成高质量、富有创意的视觉内容。M…

vue3-内置组件-Suspense

Suspense (实验性功能) <Suspense> 是一项实验性功能。它不一定会最终成为稳定功能&#xff0c;并且在稳定之前相关 API 也可能会发生变化。 <Suspense> 是一个内置组件&#xff0c;用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌…

ChatGPT高效提问—prompt实践(生成VBA)

ChatGPT高效提问—prompt实践&#xff08;生成VBA&#xff09; 2. 生成VBA函数操作Excel ​ 当前Excel表格数据无背景颜色&#xff0c;区分不明显。假如我们想美化数据展示效果&#xff0c;把标题行设置为浅蓝色&#xff0c;其余奇数行设置为橙色&#xff0c;该怎么操作呢&am…

一、DataX简介

DataX简介 一、什么是DataX二、DataX设计三、支持的数据源四、框架设计五、运行原理六、DataX和Sqoop对比 一、什么是DataX DataX是阿里巴巴开源的一个异构数据源离线同步工具&#xff0c;致力于实现包括关系型数据库&#xff08;MySQL、Oracle等&#xff09;、HDFS、Hive、OD…