封装一个websocket,支持断网重连、心跳检测,拿来开箱即用

封装一个websocket,支持断网重连、心跳检测

代码封装

编写 WebSocketClient.js

import { EventDispatcher } from './dispatcher'

export class WebSocketClient extends EventDispatcher {
  constructor(url) {
    console.log(url, 'urlurl')
    super()
    this.url = url
  }

  // #socket实例
  socket = null
  // #重连次数
  reconnectAttempts = 0
  // #最大重连数
  maxReconnectAttempts = 5
  // #重连间隔
  reconnectInterval = 10000 // 10 seconds
  // #发送心跳数据间隔
  heartbeatInterval = 1000 * 30
  // #计时器id
  heartbeatTimer = undefined
  // #彻底终止ws
  stopWs = false

  // >生命周期钩子
  onopen(callBack) {
    this.addEventListener('open', callBack)
  }

  onmessage(callBack) {
    this.addEventListener('message', callBack)
  }

  onclose(callBack) {
    this.addEventListener('close', callBack)
  }

  onerror(callBack) {
    this.addEventListener('error', callBack)
  }

  // >消息发送
  send(message) {
    if (this.socket && this.socket.readyState === WebSocket.OPEN) {
      this.socket.send(message)
    } else {
      console.error('[WebSocket] 未连接')
    }
  }

  // !初始化连接
  connect() {
    if (this.reconnectAttempts === 0) {
      this.log('WebSocket', `初始化连接中...          ${this.url}`)
    }
    if (this.socket && this.socket.readyState === WebSocket.OPEN) {
      return
    }
    this.socket = new WebSocket(this.url)

    // !websocket连接成功
    this.socket.onopen = (event) => {
      this.stopWs = false
      // 重置重连尝试成功连接
      this.reconnectAttempts = 0
      // 在连接成功时停止当前的心跳检测并重新启动
      this.startHeartbeat()
      this.log('WebSocket', `连接成功,等待服务端数据推送[onopen]...     ${this.url}`)
      this.dispatchEvent('open', event)
    }

    this.socket.onmessage = (event) => {
      this.dispatchEvent('message', event)
      this.startHeartbeat()
    }

    this.socket.onclose = (event) => {
      if (this.reconnectAttempts === 0) {
        this.log('WebSocket', `连接断开[onclose]...    ${this.url}`)
      }
      if (!this.stopWs) {
        this.handleReconnect()
      }
      this.dispatchEvent('close', event)
    }

    this.socket.onerror = (event) => {
      if (this.reconnectAttempts === 0) {
        this.log('WebSocket', `连接异常[onerror]...    ${this.url}`)
      }
      this.closeHeartbeat()
      this.dispatchEvent('error', event)
    }
  }

  // > 断网重连逻辑
  handleReconnect() {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++
      this.log(
        'WebSocket',
        `尝试重连... (${this.reconnectAttempts}/${this.maxReconnectAttempts})       ${this.url}`
      )
      setTimeout(() => {
        this.connect()
      }, this.reconnectInterval)
    } else {
      this.closeHeartbeat()
      this.log('WebSocket', `最大重连失败,终止重连: ${this.url}`)
    }
  }

  // >关闭连接
  close() {
    if (this.socket) {
      this.stopWs = true
      this.socket.close()
      this.socket = null
      this.removeEventListener('open')
      this.removeEventListener('message')
      this.removeEventListener('close')
      this.removeEventListener('error')
    }
    this.closeHeartbeat()
  }

  // >开始心跳检测 -> 定时发送心跳消息
  startHeartbeat() {
    if (this.stopWs) return
    if (this.heartbeatTimer) {
      this.closeHeartbeat()
    }
    this.heartbeatTimer = setInterval(() => {
      if (this.socket) {
        this.socket.send(JSON.stringify({ type: 'heartBeat', data: {} }))
        this.log('WebSocket', '送心跳数据...')
      } else {
        console.error('[WebSocket] 未连接')
      }
    }, this.heartbeatInterval)
  }

  // >关闭心跳
  closeHeartbeat() {
    clearInterval(this.heartbeatTimer)
    this.heartbeatTimer = undefined
  }
}

引用的 dispatcher.js 源码

import { Log } from './log'

export class EventDispatcher extends Log {
  constructor() {
    super()
    this.listeners = {}
  }

  addEventListener(type, listener) {
    if (!this.listeners[type]) {
      this.listeners[type] = []
    }
    if (this.listeners[type].indexOf(listener) === -1) {
      this.listeners[type].push(listener)
    }
  }

  removeEventListener(type) {
    this.listeners[type] = []
  }

  dispatchEvent(type, data) {
    const listenerArray = this.listeners[type] || []
    if (listenerArray.length === 0) return
    listenerArray.forEach((listener) => {
      listener.call(this, data)
    })
  }
}

上面还用到了一个 log.js ,用于美化控制台打印的,这个文件在其他地方也通用

export class Log {
  static console = true

  log(title, text) {
    if (!Log.console) return
    const color = '#09c'
    console.log(
      `%c ${title} %c ${text} %c`,
      `background:${color};border:1px solid ${color}; padding: 1px; border-radius: 2px 0 0 2px; color: #fff;`,
      `border:1px solid ${color}; padding: 1px; border-radius: 0 2px 2px 0; color: ${color};`,
      'background:transparent'
    )
  }

  closeConsole() {
    Log.console = false
  }
}

至此一个 WebSocket 就封装好了

使用方法

首先使用node编写一个后端服务,用于 WebSocket 连接

需要安装一下 ws

npm install ws
const WebSocket = require("ws");

const wss = new WebSocket.Server({port: 3200});

console.log("服务运行在http://localhost:3200/");

wss.on("connection", (ws) => {
    console.log("[服务器]:连接成功");
    ws.send(`[websocket云端]您已经连接云端!等待数据推送~`);

    ws.on("message", (res) => {
        ws.send(`[websocket云端]收到消息:${res.toString()}`);
    });

    ws.on("close", () => {
        console.log("[服务器]:连接已关闭~");
    });
});

然后我这里编写了一个简单的demo页面

<template>
  <div>
    <el-button type="primary" @click="connection">创建连接</el-button>
    <el-button type="danger" @click="close">关闭连接</el-button>

    <el-input v-model="message" placeholder="placeholder"></el-input>
    <el-button type="primary" @click="send">发送消息</el-button>

    <ul>
      <li v-for="(item, index) in messageList" :key="index">{{ item }}</li>
    </ul>
  </div>
</template>

<script>
import { WebSocketClient } from '@/utils/WebSocketClient'

export default {
  data() {
    return {
      message: '',
      messageList: [],
      ws: null,
    }
  },
  methods: {
    connection() {
      if (this.ws) {
        this.close()
      }
      this.ws = new WebSocketClient('ws://localhost:3200')
      this.setupWebSocketListeners()
      this.ws.connect()
    },
    close() {
      if (this.ws) {
        this.ws.close()
        this.ws = null
      }
    },
    send() {
      if (this.ws) {
        this.ws.send(this.message)
      }
    },
    setupWebSocketListeners() {
      this.ws.onmessage((msg) => {
        this.ws.log('WebSocketClient', msg.data)
        this.messageList.push(msg.data)
      })
      this.ws.onopen(() => {
        this.ws.log('WebSocketClient', '连接已打开')
      })
      this.ws.onclose(() => {
        this.ws.log('WebSocketClient', '连接已关闭')
      })
      this.ws.onerror((error) => {
        this.ws.log('WebSocketClient', '连接错误')
        console.error(error)
      })
    },
  },
  mounted() {
    this.connection()
  },
}
</script>

初次连接

image-20240531100922169

消息发送

image-20240531100944165

关闭连接后,消息就无法发送了

image-20240531101029443

再次连接

image-20240531101057517

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Centos给普通用户添加sudo命令权限

打开sudoers文件 sudo visudo 修改sudoers文件 找到root ALL(ALL) ALL这一行&#xff0c;即如下图标出红线的一行 在此行下新增如下内容: lbs为用给予sudo执行权限的用户名 # 执行sudo命令&#xff0c;需要输入命令 lbs ALL(ALL) ALL 或 # 执行sudo命令&#xff0c;…

大规模 Transformer 模型 8 比特矩阵乘

本文基于 Hugging Face Transformers、Accelerate 以及 bitsandbytes库。 Transformers&#xff1a;Hugging Face 提供的一个开源库&#xff0c;包含了多种预训练的 Transformer 模型&#xff0c;方便用户进行各种 NLP 任务。Accelerate&#xff1a;Hugging Face 开发的一个库…

python多种方式 保留小数点位数(附Demo)

目录 前言1. 字符串格式2. round函数3. Decimal模块4. numpy库5. Demo 前言 在Python中&#xff0c;保留小数点后特定位数可以通过多种方式实现 以下是几种常见的方法&#xff0c;并附上相应的代码示例&#xff1a; 使用字符串格式化&#xff08;String Formatting&#xff…

根据模板和git commit自动生成日·周·月·季报

GitHub - qiaotaizi/dailyreport: 日报生成器 GitHub - yurencloud/daily: 程序员专用的日报、周报、月报、季报自动生成器&#xff01; config.json: { "Author": "gitname", "Exclude": ["update:", "add:", "…

操作系统之PV操作——生产者与消费者问题

目录 前言 问题描述 PV操作的应用 含义 需要的准备 生产者消费者的具体过程 结束语 前言 今天是坚持写博客的第14天&#xff0c;也是第二周的最后一天&#xff0c;非常高兴自己可以坚持两周&#xff0c;大概不算三分钟热度吧&#xff0c;也希望可以继续努力&#xff0…

04.k8s的附加组件

4.k8s的附加组件 4.1 dns服务 安装dns服务 1:下载dns_docker镜像包 wget http://192.168.12.201/docker_image/docker_k8s_dns.tar.gz2:导入dns_docker镜像包(所有节点或者node2节点) 3:修改skydns-rc.yaml&#xff0c;指定13的机器&#xff0c;该功能可加可不加 spec:node…

element-plus中在表格校验输入的值

element-plus中在表格校验输入的值 效果&#xff1a; 注意事项&#xff1a;需要在表单套一个表格的字段 代码&#xff1a; <el-form :model"tableFrom" ref"tableDataRef" :rules"rules" style"margin: 0px !important;">&…

Facebook的隐私保护挑战:用户数据安全的新时代

在全球范围内&#xff0c;Facebook已经成为了不可忽视的社交媒体巨头&#xff0c;它连接着超过20亿的活跃用户。然而&#xff0c;随着其影响力的不断扩大&#xff0c;关于用户隐私和数据安全的问题也愈加引人关注。本文将深入探讨Facebook面临的隐私保护挑战&#xff0c;以及它…

关于OpenFlow协议的运行机制和实践分析(SDN)

目录 OpenFlow运行机制 1 OpenFlow信道建立 1.1 OpenFlow消息类型 1.2 信道建立过程解析 2 OpenFlow消息处理 2.1 OpenFlow流表下发与初始流表 2.2 OpenFlow报文上送控制器 2.3 控制器回应OpenFlow报文 3 OpenFlow交换机转发 3.1 单播报文转发流程 OpenFlow的实践分析…

解锁 JavaScript ES6:函数与对象的高级扩展功能

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;JavaScript 精粹 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; ES5、ES6介绍 文章目录 ES6函数扩展1 默认参数1.1 之前写法1.2 ES6 写法1.3 注意点 2 …

springboot kafka 提高拉取数量

文章目录 背景问题复现解决问题原理分析fetch.min.bytesfetch.max.wait.ms源码分析ReplicaManager#fetchMessages 背景 开发过程中&#xff0c;使用kafka批量消费&#xff0c;发现拉取数量一直为1&#xff0c;如何提高批量拉取数量&#xff0c;记录下踩坑记录。 问题复现 ka…

【设计模式】结构型-门面模式

前言 在软件开发中&#xff0c;设计模式是解决特定问题的经验总结&#xff0c;为开发者提供了一种可复用的解决方案。其中&#xff0c;门面模式&#xff08;Facade Pattern&#xff09;是一种结构型模式&#xff0c;旨在为复杂系统提供简化的接口&#xff0c;使客户端与系统之…

GPT-4o(OpenAI最新推出的大模型)

简介&#xff1a;最近&#xff0c;GPT-4o横空出世。对GPT-4o这一人工智能技术进行评价&#xff0c;包括版本间的对比分析、GPT-4o的技术能力以及个人感受等。 方向一&#xff1a;对比分析 GPT-4o&#xff08;OpenAI最新推出的大模型&#xff09;与GPT-4之间的主要区别体现在响应…

微服务学习Day8

文章目录 Sentinel雪崩问题服务保护框架Sentinel配置 限流规则快速入门流控模式流控效果热点参数限流 隔离和降级FeignClient整合Sentinel线程隔离&#xff08;舱壁模式&#xff09;熔断降级 授权规则及规则持久化授权规则自定义异常结果持久化 Sentinel 雪崩问题 服务保护框架…

SpringBoot打war包并配置外部Tomcat运行

简介 由于其他原因&#xff0c;我们需要使用SpringBoot打成war包放在外部的Tomcat中运行,本文就以一个案例来说明从SpringBoot打war包到Tomcat配置并运行的全流程经过 环境 SpringBoot 2.6.15 Tomcat 8.5.100 JDK 1.8.0_281 Windows 正文 一、SpringBoot配置打war包 第一步&a…

怎么通过互联网远程控制电脑?

远程访问又称为网络远程控制&#xff0c;它使用户能够通过互联网连接两台设备以解决问题。进行控制的电脑称为控制端&#xff0c;被控制的电脑则称为被控端。在远程访问过程中&#xff0c;控制端电脑掌握整个连接的操作。远程控制软件会捕获被控端电脑的操作&#xff0c;并在主…

JS数组怎么去重?| JavaScript中数组去重的14种方法

目录 一、利用for循环嵌套去重 二、利用splice() for循环嵌套&#xff08;ES5中最常用&#xff09; 三、利用indexOf() for循环去重 四、利用sort() for循环去重 五、利用includes() for循环去重&#xff08;ES7&#xff09; 六、利用Object键值对及其hasOwnProperty…

521源码网-免费网络教程-Cloudflare使用加速解析-优化大陆访问速度

Cloudfalre 加速解析是由 心有网络 向中国大陆用户提供的公共优化服务 接入服务节点: cf.13d7s.sit 接入使用方式类似于其它CDN的CNAME接入&#xff0c;可以为中国大陆用户访问Cloudflare网络节点大幅度加速&#xff0c;累计节点130 如何接入使用 Cloudflare 加速解析&#…

LabVIEW中进行步进电机的位置控制

在LabVIEW中进行步进电机的位置控制&#xff0c;通常涉及以下几个关键步骤&#xff1a;设置硬件、配置通信、编写控制算法和实施反馈控制。以下是一个详细的介绍。 硬件设置 步进电机&#xff1a;选择合适的步进电机&#xff0c;根据负载和应用需求选择适当的步数和转矩。 驱…

Kafka broker的新增和剔除(服役与退役)

说明&#xff1a;集群现有broker:node1,node2,node3三个,broker.id分别为0&#xff0c;1&#xff0c;2 已有两个topic&#xff1a;products、cities 1、退役&#xff08;Kafka集群中减少一个服务器broker2&#xff09; 退役后要保证剩下的服务器数量大于等于备份数&#xff0c…