基于Pinia的WebSocket管理与优化实践(实现心跳重连机制,异步发送)

WebSocket作为一种全双工通信协议,允许服务器和客户端之间建立持久的连接,提供了比传统HTTP请求更为高效的数据交换方式。本文将探讨如何使用Pinia状态管理库在Vue应用中优雅地管理和优化WebSocket连接,以实现稳定、高效的实时数据传输。

环境与依赖

  • 环境:Vue.js项目
  • 依赖:Pinia (pnpm install pinia) 和 vant (pnpm install vant)

项目结构与初始化

在项目中,我们创建了一个名为useWebsocketStore的Pinia store,专门用于管理WebSocket相关的状态和行为。该store包含了一系列关键的状态变量和动作,旨在简化WebSocket的连接、消息处理、心跳机制以及重连策略。

WebSocket Store设计

核心状态

  • socket: WebSocket实例。
  • ConnectSuccess: 连接成功标志。
  • messageHandlers: 存储注册的消息处理器数组。
  • deviceIP: 设备的IP地址。
  • reconnectNum: 重连次数。
  • heartbeatMessage: 心跳消息内容。
  • strWs: 发送消息的字符串形式。
  • sendNum: 发送消息计数。
  • messageNum: 接收消息计数。
  • message: 最近接收的消息内容。
    动作详解
  • connectWs: 初始化WebSocket连接,包括建立连接、监听事件、启动心跳,并提供Promise接口确保异步操作的完成。
  • registerMessageHandler & unregisterMessageHandler:允许外部组件注册或注销消息处理器,实现了插拔式的事件处理机制。
  • handleMessageFromWebSocket: 处理从WebSocket接收到的消息,更新内部状态并调用所有注册的消息处理器。
  • handleClose: WebSocket关闭时的处理逻辑,包括重连尝试、状态重置及错误通知。
  • **`sendMessage: 发送消息至WebSocket,包含基本的发送逻辑。
  • sendMessageWithRetry: 异步发送消息,自动处理连接重试,增强了消息发送的健壮性。
  • **sendHeartbeat: 发送心跳消息,维护连接活性。
  • **startHeartbeat&stopHeartbeat: 控制心跳定时器的启动与停止,确保资源的有效利用。

为什么需要心跳机制

心跳机制(Heartbeat Mechanism)主要用于保持网络连接的活跃状态,尤其是在TCP/IP连接中,如HTTP长轮询、WebSocket、TCP长连接等场景中。它通过周期性地发送小量数据(通常称为心跳包)来确认连接的双方仍然存活,进而避免因网络层的超时或中间代理(如负载均衡器、防火墙)的会话超时而导致的连接意外断开。

useWebsocketStore.js

import { defineStore } from 'pinia'

import { showToast } from 'vant'

export const useWebsocketStore = defineStore('websocket', {
  state: () => ({
    socket: null,
    ConnectSuccess: false,
    // 新增一个数组用于存储注册的处理器
    messageHandlers: [],
    deviceIP: null,
    reconnectNum: 0,
    heartbeatMessage: JSON.stringify({ cmd: '1111' }), // 心跳消息内容
    strWs: null,
    sendNum: 0,
    messageNum: 0,
    message: 0
  }),
  getters: {},
  actions: {
    async connectWs(deviceIP) {
      if (!this.socket && !this.ConnectSuccess) {
        console.log('connectWs', deviceIP)
        this.deviceIP = deviceIP
        this.socket = new WebSocket(deviceIP)
        // this.socket.onopen = this.handleOpen
        this.socket.onmessage = this.handleMessageFromWebSocket
        this.socket.onclose = this.handleClose
        // this.socket.onerror = this.handleError

        return new Promise((resolve, reject) => {
          this.socket.onopen = (event) => {
            console.log('WebSocket 连接已建立', event)
            this.ConnectSuccess = true
            this.startHeartbeat()
            resolve()
          }
          this.socket.onerror = (event) => {
            console.error('WebSocket 连接错误', event)
            this.ConnectSuccess = false
            reject(event)
          }
        })
      }
    },
    // 新增方法用于注册消息处理器
    registerMessageHandler(handler) {
      this.messageHandlers.push(handler)
    },
    // 新增方法用于移除消息处理器
    unregisterMessageHandler(handler) {
      this.messageHandlers = this.messageHandlers.filter((h) => h !== handler)
    },
    //
    handleMessageFromWebSocket(event) {
      const message = JSON.parse(event.data)
      console.log('消息', message)
      this.message = message.cmd
      this.messageNum += 1
      // 调用所有注册的处理器
      // console.log(message)
      this.messageHandlers.forEach((handler) => handler(message))
    },
    handleOpen(event) {
      console.log('WebSocket 连接已建立', event)
      if (event.target.readyState === 1) {
        this.ConnectSuccess = true
      }
    },
    handleClose(event) {
      console.error('WebSocket 关闭:', event)
      this.socket = null
      this.ConnectSuccess = false
      this.sendNum = 0
      this.messageNum = 0
      this.stopHeartbeat()
      // 添加自动重连逻辑
      this.reconnectWs()
        .then(() => {
          this.ConnectSuccess = true
          showToast('WebSocket 重连成功')
        })
        .catch((error) => {
          this.ConnectSuccess = false
          showToast('最终重连失败', error)
        })
    },
    handleError(error) {
      console.error('WebSocket 错误:', error)

      this.ConnectSuccess = false
    },
    async reconnectWs() {
      return new Promise(async (resolve, reject) => {
        this.reconnectNum++
        try {
          console.log(`尝试重新连接... (${this.reconnectNum})`)
          await this.connectWs(this.deviceIP)
          if (this.socket && this.ConnectSuccess) {
            resolve() // 连接成功,停止重试
          } else {
            throw new Error('连接失败')
          }
        } catch (error) {
          this.ConnectSuccess = false
          console.log(`重连WebSocket失败,稍后重试... (${this.reconnectNum})`)
          reject()
        }
      })
    },

    sendMessage(message) {
      // 发送消息逻辑...
      if (this.socket.readyState === WebSocket.OPEN) {
        // showToast('确定发送', message)
        this.strWs = JSON.parse(message).cmd
        this.sendNum += 1
        this.socket.send(message)
      } else {
        // 如果在此之后仍然无法发送,可以选择抛出错误或继续等待(根据需求)
        showToast('WebSocket 尚未连接')
        console.error('即使重试后,WebSocket仍无法发送消息')
      }
    },
    // 在actions中添加一个新的异步发送方法
    async sendMessageWithRetry(message) {
      if (!this.ConnectSuccess) {
        // 如果连接未建立,尝试重新连接
        try {
          await this.reconnectWs()
        } catch (error) {
          console.error('尝试重新连接WebSocket失败', error)
          throw error
        }
      }
      this.sendMessage(message)
    },

    // 发送心跳消息的方法
    sendHeartbeat() {
      this.sendMessageWithRetry(this.heartbeatMessage)
    },
    // 启动心跳定时器
    startHeartbeat() {
      this.heartbeatInterval = setInterval(() => {
        this.sendHeartbeat()
      }, 5000) // 每隔3秒发送一次心跳
    },

    // 如果需要在适当的时候清理心跳定时器,比如在断开连接时
    stopHeartbeat() {
      if (this.heartbeatInterval) {
        clearInterval(this.heartbeatInterval)
        this.heartbeatInterval = null
      }
    }
  }
})

.vue 文件中使用

useWebsocket.connectWs(deviceIP) 连接socket 只需要连接一次,可以放在main.js 里面初始化的时候连接

<script setup>
import {  onMounted, onUnmounted,  } from 'vue'

import { useWebsocketStore } from '@/stores/useWebsocketStore'
const useWebsocket = useWebsocketStore()

const deviceIP='ws:192.168.89'
const handleMessage= (message)=>{
  // 外部处理消息逻辑
}
const init = () => {
  useWebsocket.connectWs(deviceIP) // 
  useWebsocket.registerMessageHandler(handleMessage)
}
init()

onUnmounted(() => {
  // 组件卸载时移除消息处理器
  useWebsocket.unregisterMessageHandler(handleMessage)
 
})

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

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

相关文章

大厂面试官问我:MySQL宕机重启了,怎么知道哪些事务是需要回滚的哪些是需要提交的?【后端八股文九:Mysql事务八股文合集】

本文为【Mysql事务八股文合集】初版&#xff0c;后续还会进行优化更新&#xff0c;欢迎大家关注交流~ 大家第一眼看到这个标题&#xff0c;不知道心中是否有答案了&#xff1f;在面试当中&#xff0c;面试官经常对项目亮点进行深挖&#xff0c;来考察你对这个项目亮点的理解以及…

2024/7/6 英语每日一段

More than half of late-teens are specifically calling for more youth work that offers “fun”, with older teenagers particularly hankering for more jollity, according to a study carried out by the National Youth Agency. One in 10 said they have zero option…

vite+vue3整合less教程

1、安装依赖 pnpm install -D less less-loader2、定义全局css变量文件 src/assets/css/global.less :root {--public_background_font_Color: red;--publicHouver_background_Color: #fff;--header_background_Color: #fff;--menu_background: #fff; }3、引入less src/main.…

罗剑锋的C++实战笔记学习(二):容器、算法库、多线程

4、容器 1&#xff09;、容器的通用特性 所有容器都具有的一个基本特性&#xff1a;它保存元素采用的是值&#xff08;value&#xff09;语义&#xff0c;也就是说&#xff0c;容器里存储的是元素的拷贝、副本&#xff0c;而不是引用 容器操作元素的很大一块成本就是值的拷贝…

重大更新来袭!!《植物大战僵尸杂交版V2.1+修改器+融合版》

大家好&#xff01;每个软件更新总是令人兴奋不已。前段时间介绍的《植物大战僵尸》系列以其独特的策略玩法和丰富的植物角色&#xff0c;赢得了很多玩家的喜爱。而在今天&#xff0c;这款经典游戏全网最新版本——《植物大战僵尸&#xff1a;杂交版V2.1》正式推出&#xff0c;…

【Mindspore进阶】实战ResNet50图像分类

ResNet50图像分类 图像分类是最基础的计算机视觉应用&#xff0c;属于有监督学习类别&#xff0c;如给定一张图像(猫、狗、飞机、汽车等等)&#xff0c;判断图像所属的类别。本章将介绍使用ResNet50网络对CIFAR-10数据集进行分类。 ResNet网络介绍 ResNet50网络是2015年由微…

vue require引入静态文件报错

如果是通过向后端发送请求&#xff0c;动态的获取对应的文件数据流很容易做到文件的显示和加载。现在研究&#xff0c;一些不存放在后端而直接存放在vue前端项目中的静态媒体文件如何加载。 通常情况下&#xff0c;vue项目的图片jpg&#xff0c;png等都可以直接在/ass…

量化机器人:金融市场的智能助手

引言 想象一下&#xff0c;在繁忙的金融市场中&#xff0c;有一位不知疲倦、冷静客观的“超级交易员”&#xff0c;它能够迅速分析海量数据&#xff0c;精准捕捉交易机会&#xff0c;并自动完成买卖操作。这位“超级交易员”不是人类&#xff0c;而是我们今天要聊的主角——量…

Qt5.9.9 关于界面拖动导致QModbusRTU(QModbusTCP没有测试过)离线的问题

问题锁定 参考网友的思路&#xff1a; Qt5.9 Modbus request timeout 0x5异常解决 网友认为是Qt的bug&#xff0c; 我也认同&#xff1b;网友认为可以更新模块&#xff0c; 我也认同&#xff0c; 我也编译了Qt5.15.0的code并成功安装到Qt5.9.9中进行使用&#xff0c;界面拖…

从CPU的视角看C++的构造函数和this指针

从汇编角度&#xff0c;清晰的去看构造函数和this指针到底是个什么东西呢&#xff1f;也许可以解决你的一点小疑问 首先写一个很简单的代码demo&#xff1a; class A{ public:int a;A(){;}void seta(int _a){a_a;}A* getA(){return this;} };int fun1(int px){return px; }in…

全新桌面编辑器

目录 前言 一、链接 ONLYOFFICE 8.1版本 官网下载链接&#xff1a; ONLYOFFICE 在线工具&#xff1a; 下载版本推荐&#xff1a; 二、使用体验 1. 界面设计&#xff1a; 2. 文档编辑功能&#xff1a; 3. 电子表格功能&#xff1a; 4. 演示文稿功能&#xff1a; 5.PDF编…

python-开关灯(赛氪OJ)

[题目描述] 假设有 N 盏灯&#xff08;N 为不大于 5000 的正整数&#xff09;&#xff0c;从 1 到到 N 按顺序依次编号&#xff0c;初始时全部处于开启状态&#xff1b;第一个人&#xff08; 1 号&#xff09;将灯全部关闭&#xff0c;第二个人&#xff08; 2 号&#xff09;将…

nginx修改网站默认根目录及发布(linux、centos、ubuntu)openEuler软件源repo站点

目录 安装nginx配置nginx其它权限配置 安装nginx dnf install -y nginx配置nginx whereis nginxcd /etc/nginx llcd conf.d touch vhost.conf vim vhost.conf 命令模式下输入:set nu或:set number可以显示行号 复制如下内容&#xff1a; server {listen 80;server_name…

基于java+springboot+vue实现的流浪动物管理系统(文末源码+Lw)277

摘 要 在如今社会上&#xff0c;关于信息上面的处理&#xff0c;没有任何一个企业或者个人会忽视&#xff0c;如何让信息急速传递&#xff0c;并且归档储存查询&#xff0c;采用之前的纸张记录模式已经不符合当前使用要求了。所以&#xff0c;对流浪动物信息管理的提升&…

玩转Easysearch语法

Elasticsearch 是一个基于Apache Lucene的开源分布式搜索和分析引擎&#xff0c;广泛应用于全文搜索、结构化搜索、分析等多种场景。 Easysearch 作为Elasticsearch 的国产化替代方案&#xff0c;不仅保持了与原生Elasticsearch 的高度兼容性&#xff0c;还在功能、性能、稳定性…

Spring框架Mvc(2)

1.传递数组 代码示例 结果 2.集合参数存储并进行存储类似集合类 代码示例 postman进行测试 &#xff0c;测试结果 3.用Json来对其进行数据的传递 &#xff08;1&#xff09;Json是一个经常使用的用来表示对象的字符串 &#xff08;2&#xff09;Json字符串在字符串和对象…

Mysql数据库索引、事务相关知识

索引 索引是一种特殊的文件&#xff0c;包含着对数据表里所有记录的引用指针。可以对表中的一列或多列创建索引&#xff0c; 并指定索引的类型&#xff0c;各类索引有各自的数据结构实现 查看索引 show index from 表名;创建索引对于非主键、非唯一约束、非外键的字段&#…

JAVA ArrayList应用案例

一案例要求&#xff1a; 二代码&#xff1a; package 重修;import java.util.ArrayList; import java.util.Random; import java.util.Scanner;public class first {public static void main(String[] args) {ArrayList<String>arrayListnew ArrayList<>();array…

ctfshow-web入门-文件包含(web87)巧用 php://filter 流绕过死亡函数的三种方法

目录 方法1&#xff1a;php://filter 流的 base64-decode 方法 方法2&#xff1a;通过 rot13 编码实现绕过 方法3&#xff1a;通过 strip_tags 函数去除 XML 标签 除了替换&#xff0c;新增 file_put_contents 函数&#xff0c;将会往 $file 里写入 <?php die(大佬别秀了…

微软与OpenAI/谷歌与三星的AI交易受欧盟重点关注

近日&#xff0c;欧盟委员会主管竞争事务的副主席玛格丽特维斯塔格(Margrethe Vestager)在一次演讲中透露&#xff0c;欧盟反垄断监管机构将就微软与OpenAI的合作&#xff0c;以及谷歌与三星达成的AI协议寻求更多第三方意见。这意味着微软与 OpenAI、谷歌与三星的 AI 交易及合作…