vue2使用webSocket双向通讯

基于webSocket实现双向通信,使用webworker保持心跳。

由于浏览器的资源管理策略会暂停或限制某些资源的消耗,导致前端心跳包任务时效,后端接收不到webSocket心跳主动断开,因此需要使用webworker保持心跳

  1. 引入webworker

    npm install worker-loader -D
  2. vue.config配置webworker
    module.exports = {
     chainWebpack: config => {
            // web worker配置
            config.module
                .rule('worker')
                .test(/\.worker\.js$/)
                .use('worker-loader')
                .loader('worker-loader')
                .options({
                    inline: 'fallback',
                    filename: 'workerName.[hash].worker.js'
                })
                .end();
            // 解决worker 热更新
            config.module.rule('js').exclude.add(/\.worker\.js$/);
        }
    }
  3. 新增websocket.js创建websocket单例
    const token = null;
    const WsUrl = null;
    import store from '@/store'
    import { WS_CODE_ENUM, TASK_TYPE_ENUM } from 'config/enum';
    import { startNetworkListener, stopNetworkListener, startHeartBeat, stopHeartBeat, openReconnect, clearRetry } from './wsUtils'
    
    /**
     * WebSocket对象实例
     */
    class WebSocketUtil {
         constructor () {
            /**
             * ws对象,全局共用同一个对象
             */
            this.socket = null
            /**
             * WS是否重连标识
             * 0:非重连,1:重连,
             * ws建立连接参数
             */
            this.reconnect = 0
            /**
             * 前后端心跳服务端响应次数
             * ws连接建立时重置
             * 第一次收到服务端响应,车辆未连接且非ws重连时,打开车辆连接弹窗
             * 接收到后端心跳响应累加
             */
            this.pongNum = 0
            /**
             * 前后端心跳后端未响应次数
             * 前端发送心跳时加1
             * 后端有响应时清空
             * 车辆连接成功后, 未响应次数≥2,说明后端两次未响应,自动开启前后端重连
             */
            this.heartBeatRsp = 0
            /**
             * 前后端ws重连尝试次数,最多三次,
             * 重连累加,重连结束重置为0
             * 三次均重连失败,断开ws连接,清空单车诊断业务缓存,跳转到车辆连接页面
             */
            this.retryTime = 0
            /**
             * 前后端重连任务,重试最多持续10秒,若超过10秒,则按照重试失败处理,
             * 第一次开启重连时,开启任务,
             * 重连成功、10秒未连接成功关闭任务,
             * 重连失败时,若用户处于车辆连接页面,toast提示; 若用户处于单车诊断页面则跳转回车辆连接页面;若用于处于非单车诊断toast提示
             */
            this.retryTimer = 0
            /**
             * 重连任务toast
             * 重连开始开启
             * 重连结束关闭、重置
             */
            this.reconnectMsg = null
            /**
             * 重连任务全局遮罩
             * 重连开始开启
             * 重连结束关闭、重置
             */
            this.reconnectLoading = null
            /**
             * 页面超时任务
             * 最后一次下发或最后一次上报开始时间点
             * 11分钟内无任务下发、任务上报判定页面超时
             * 浏览器刷新、车辆连接成功后开启
             * ws断开连接、重连过程中关闭
             * 页面超时关闭ws,跳转单车连接页面
             */
            this.pageTimer = null
            /**
             * token续期任务,每5分钟一次,
             * 浏览器刷新、车辆连接成功后开启
             * ws断开连接、重连过程中关闭
             */
            this.tokenPolling = null
            /**
             * 开启一个独立线程,处理心跳包任务
             * setInterval是基于当前页面的定时任务,如果浏览器切换窗口/隐藏时会停止任务,这时后端接收不到前端发送的心跳包,会触发断开ws
             * 使用webWorker线程,可以突破浏览器默认机制
             * 启动心跳后,开启独立线程,发送心跳包
             * 心跳关闭时关闭独立线程
             */
            this.worker = null
        }
        
            /**
         * 建立WS连接
         * @param {*} reconnect 是否重连,0:非重连,1:重连
         * @param {*} vin 车辆VIN码
         * @param {*} userName 用户信息
         * @param {*} needAuth 车机授权
         * @description 每次建立ws连接,重置服务端响应次数
         */
        connect (reconnect = 0, vin, userName, needAuth) {
            this.socket = new WebSocket(`${WsUrl}/${vin}/${userName}/${reconnect}/${needAuth}`, getToken());
            this.reconnect = reconnect
            this.pongNum = 0 // 服务端响应次数
            this.socket.onopen = this.onOpen.bind(this);
            this.socket.onmessage = this.onMessage.bind(this);
            this.socket.onerror = this.onError.bind(this);
            this.socket.onclose = this.onClose.bind(this);
        }
        
        /**
         * 开启WebSocket
         * 启动心跳任务
         * 启动网络监听
         */
        onOpen () {
            // 启动心跳
            startHeartBeat()
            // 启动网络监听
            startNetworkListener()
        }
        /**
         * WebSocket响应业务处理
         * 1:服务端响应次数累加
         * 2:第一次收到服务端响应,根据是否重连,响应不通的车辆连接业务
         * 3:服务端未响应次数重置
         * 4:车辆已连接且处于重连过程时,关闭重连业务,提示重连成功
         */
        onMessage ({ data }) {
            // 服务端响应次数累加
            this.pongNum++
            // 收到服务端第一次信息
            if (this.pongNum === 1) {
                // 非重连,第一次收到服务端信息,开启车辆连接弹窗
                if (this.reconnect === 0) // TODO 业务操作
                // 重连继续心跳计时
                else TODO 业务操作
            }
            // 心跳响应无需处理
            if (data === 'pong') {
                // 服务端未响应次数重置
                this.heartBeatRsp = 0
                // 车辆已连接,存在重连
                if (store.getters.connected && this.retryTime > 0) {
                    clearRetry()
                    Message.success('重连成功')
                }
                return
            }
            // 业务操作
        }
    
            /**
         * WebSocket关闭处理
         * 1:关闭网络监听
         * 2:前后端重连业务
         * 3:连接过程中ws断开异常处理
         * 4: 退出业务
         */
        onClose (event) {
            console.log('WebSocket关闭:', event.code, event.reason);
            // 关闭网络监听
            stopNetworkListener()
            // 服务端未响应次数 ≥ 2,需要重连
            if (this.heartBeatRsp >= 2) return openReconnect()
            this.disconnect()
        }
    
        /**
         * 断开ws连接
         * 关闭心跳包、清空tokne续期任务、关闭页面超时
         */
        clearWs () {
            this.socket?.close();
            this.socket = null;
            stopHeartBeat()
        }
    
        /**
         * 退出单车诊断业务
         * @param {boolean} clearAll 是否清空所有state数据
         * 1: 车辆已连接,记录断开连接时间,用于车辆连接页面-车辆连接按钮退出后5秒不能连接判断
         * 2:断开ws连接,
         * 3: 关闭心跳包、清空tokne续期任务、关闭页面超时判定
         * 4:清空重连loading、toast提示、关闭重连超时任务
         * 5: 调用退出实时模式接口,通知后端退出实施模式
         * 6:清空单车诊断相关浏览器缓存
         */
        disconnect (clearAll = false) {
            console.log('WebSocket断开连接')
            // 记录断开连接日期
            if (store.getters.connected) storage.set('WS_LAST_CLOSE_TIME', dayjs().unix())
            this.clearWs()
            clearRetry()
            Message.closeAll();
            // 重置信息
            store.commit('RESET_STATE')
        }
    }
        // 懒汉模式
    const LazySingleton = (function () {
        let _instance = null
        return function () {
            return _instance || (_instance = new WebSocketUtil())
        }
    })()
    
    const websocket = new LazySingleton()
    export default websocket

  4. websocket工具类wsUtils.js
    import websocket from '@/diagnostic/websocket'
    import store from '@/store'
    import { Message, Loading } from 'element-ui';
    import WsWorker from './ws.worker.js'
    /**
     * ws通信建立成功开启心跳包任务;
     * 每5秒发送一次心跳包;
     * 每次发送心跳包累加服务端未响应次数【heartBeatRsp】;
     * 服务端未响应次数【heartBeatRsp】 ≥ 2,判定服务端响应超,开启前后端重连;
     */
    export const startHeartBeat = () => {
        websocket.socket && websocket.socket.readyState === WebSocket.OPEN &&     websocket.socket.send('ping');
        websocket.worker = new WsWorker()
        websocket.worker.postMessage({ type: 'start' })
        websocket.worker.onmessage = (e) => {
            const { type } = e.data
            if (type === 'send') {
                // 发送心跳ping
                sendPing()
            }
        }
    }
    
    const sendPing = () => {
        // 服务端未响应次数累加
        websocket.heartBeatRsp++
        // 车辆已连接,服务端响应次数≥2,鉴定为服务端响应超时,断开ws连接,开启重连
        if (store.getters.connected && websocket.heartBeatRsp >= 2) websocket.clearWs()
        websocket.socket && websocket.socket.readyState === WebSocket.OPEN && websocket.socket.send('ping');
    }
    
    /**
     * ws断开连接,关闭心跳包任务,
     * 清空tokne续期任务,关闭页面超时判定
     */
    export const stopHeartBeat = () => {
        // 关闭心跳
        websocket.worker?.postMessage({ type: 'stop' })
        // 清空轮询
        clearInterval(websocket.tokenPolling)
        websocket.tokenPolling = null
        // 关闭页面超时判定
        clearTimeout(websocket.pageTimeout)
        websocket.pageTimeout = null
        // 关闭心跳包独立线程
        websocket.worker?.terminate()
    }
    /**
     * 开启重连
     * 每次重连需要间隔三秒
     * 三次重连失败,toast提示,退出单车诊断业务
     */
    export const openReconnect = async () => {
        switch (websocket.retryTime) {
        case 0:
            retryConnect()
            break;
        case 1:
        case 2:
            await sleep(3000)
            retryConnect()
            break;
        default:
            Message.error('当前您的网络不稳定,车辆连接已断开,请重新进行连接')
            websocket.disconnect()
            break;
        }
    }
    export const sleep = (ms) => {
        return new Promise((resolve) => setTimeout(resolve, ms))
    }
    /**
     * 前后端重连,重连次数累加
     * 1:开启重连全屏loading、toast提示
     * 2:关闭旧连接
     * 3:第一次重连开启重连超时任务
     * 4:开始重连
     */
    export const retryConnect = () => {
        // 重连次数累加
        websocket.retryTime++
        // 开启全屏loading
        websocket.reconnectLoading = Loading.service({ fullscreen: true });
        websocket.reconnectMsg?.close()
        websocket.reconnectMsg = Message.warning({
            message: `当前您的网络环境不稳定,正在进行第${websocket.retryTime}次重连,请等待`,
            duration: 0
        })
        // 关闭旧连接
        websocket.clearWs()
        // 第一次重连开启重连超时任务
        if (websocket.retryTime === 1) startRetryTimer()
        // 开启重连
        websocket.connect(1)
    }
    
    /**
     * 重连超时任务
     * 重连三次时间不能超过10秒,超过10秒按照重连失败处理
     * 1:开启重连超时任务前,如果存在重连超时任务,先关闭
     * 2:开启超时重连任务,时间10秒
     * 3:10秒后,服务端未响应次数不等于0 判定重连超时,退出单车诊断业务
     */
    const startRetryTimer = () => {
        // 1:开启重连超时任务前,如果存在重连超时任务,先关闭
        stopRetryTimer()
        // 2:暂停车云心跳计时
        store.commit('connect/STOP_CONNECT_TIMER')
        // 3:开启超时重连任务,时间10秒
        websocket.retryTimer = setTimeout(() => {
            // 4:10秒后,服务端未响应次数不等于0 判定重连超时,退出单车诊断业务
            if (websocket.retryTime != 0) {
                Message.error('当前您的网络不稳定,车辆连接已断开,请重新进行连接')
                websocket.disconnect()
            }
        }, 1000 * 10)
    }
    
    /**
     * 关闭重连超时任务
     */
    const stopRetryTimer = () => {
        if (websocket.retryTimer) {
            clearTimeout(websocket.retryTimer)
            websocket.retryTimer = null
        }
    }
    /**
     * 关闭重连流程
     * 清空重连全屏loading、toast提示
     * 重置重连次数、关闭重连超时任务、重置服务端未响应次数
     */
    export const clearRetry = () => {
        websocket.reconnectLoading?.close()
        websocket.reconnectLoading = null
        websocket.reconnectMsg?.close()
        websocket.reconnectMsg = null
        websocket.retryTime = 0
        websocket.heartBeatRsp = 0
        stopRetryTimer()
    }
    
    /**
     * 监听网络连接状态,
     * ws建立通信后开启
     */
    export const startNetworkListener = () => {
        window.addEventListener('offline', offline)
    }
    
    /**
     * ws连接断开后,停止网络监听
     */
    export const stopNetworkListener = () => {
        window.removeEventListener('offline', offline)
    }
    /**
     * 监听网络连接状态
     * 监听到网络中断:主动断开当前ws连接;网络中断后onclose事件会失效,需要主动提前断开ws
     * 判断车辆是否已连接,如果车辆已连接需要开启前后端三次重连
     */
    const offline = (e) => {
        // 网络中断
        if (e.type === 'offline') {
            // 车辆已连接
            if (store.getters.connected) websocket.heartBeatRsp = 2 // 后端未响应次数≥2开启重连
            // 主动断开当前ws连接
            websocket.clearWs()
        }
    }
    

  5. 创建webWorker进程用来处理ws心跳ws.worker.js
    /**
     * 前后端心跳包任务,ws连接建立后每5秒发送一次,由前端主动发起,后端响应
     * ws断开心跳任务清空
     */
    let heartBeatTimer = null
    onmessage = (e) => {
        const { type } = e.data;
        if (type === 'start') {
            heartBeatTimer = setInterval(() => {
                console.log('WebSocket is sending heartbeat');
                postMessage({ type: 'send' });
            }, 1000 * 5);
        }
        if (type === 'stop') {
            // 清除定时器
            clearInterval(heartBeatTimer)
            heartBeatTimer = null
            console.log('心跳包任务停止成功')
        }
    }
    

  6. 在组件中使用
    import websocket from '@/websocket'
    
    export default {
        mounted () {
            websocket.connect()
        }
    }

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

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

相关文章

【Ubuntu】Ubuntu的安装和配置

下载ubuntu镜像 https://releases.ubuntu.com/22.04.4/ubuntu-22.04.4-desktop-amd64.iso 一、Ubuntu安装 1.新建虚拟机 1.1按照它的提示创建用户;后面一直下一步就好 2.启动Ubuntu虚拟机 2.1设置为中文键盘 2.2默认即可;若是有低需求也可以选择最小…

Coursera上Golang专项课程3:Concurrency in Go 学习笔记(完结)

Concurrency in Go 本文是 Concurrency in Go 这门课的学习笔记,如有侵权,请联系删除。 文章目录 Concurrency in GoMODULE 1: Why Use Concurrency?Learning Objectives M1.1.1 - Parallel ExecutionM1.1.2 - Von Neumann BottleneckM1.1.3 - Power W…

Python基础(六)之数值类型元组

Python基础(六)之数值类型元组 1、简介 元组: 在Python中是内置的数据结构之一,是一个不可变的序列,切可以是任何类型数据。元组的元素放在()小括号内。一般我们希望数据不改变的时候使用 不可变与可变的…

Day69:WEB攻防-Java安全JWT攻防Swagger自动化算法签名密匙Druid泄漏

目录 Java安全-Druid监控-未授权访问&信息泄漏 黑盒发现 白盒发现 攻击点 Java安全-Swagger接口-导入&联动批量测试 黑盒发现 白盒发现 自动化发包测试 自动化漏洞测试 Java安全-JWT令牌-空算法&未签名&密匙提取 识别 JWT 方式一:人工识…

web渗透测试漏洞复现:Elasticsearch未授权漏洞复现

web渗透测试漏洞复现 Elasticsearch未授权漏洞复现Elasticsearch简介Elasticsearch复现Elasticsearch漏洞修复和加固措施 Elasticsearch未授权漏洞复现 Elasticsearch简介 Elasticsearch 是一款 Java 编写的企业级搜索服务,它以分布式多用户能力和全文搜索引擎为特…

使用jenkins-pipeline进行利用项目文件自动化部署到k8s上

Discard old builds:丢弃旧的构建,目的是管理存储空间、提升性能以及保持环境整洁 Do not allow concurrent builds: 禁止并发构建是指同一时间内只允许一个构建任务执行,避免多个构建同时运行可能带来的问题 Do not allow the pipeline to resume if the controller resta…

RPC学习笔记一

什么是RPC RPC(Remote Procedure Call,远程过程调用)是一种用于实现分布式系统中不同计算机或进程之间进行通信和调用的技术和模式。 在传统的过程调用中,当一个程序需要调用另一个程序的函数或方法时,通常是在同一台…

ChatGPT4的Dalle-3 生成电影海报及升级教程

引言 首先DALL E3首先需要升级为ChatGPT4才能使用,接下来从以下几个方面进行介绍: 一、ChatGPT4中的DALL E3 的电影海报二、ChatGPT4下的DALL E3的实例三、ChatGPT4的升级教程 一、ChatGPT4中的DALL E3 的电影海报 DALLE 3可以直接在画面中识别和生成…

【Qt图形界面引擎(一)】:第一个Qt程序

跨平台图形界面引擎,接口简单,易上手,一定程度简化内存。 Qt发展史 1991年由Qt Company开发的跨平台C图形用户界面应用程序开发框架2008年,Qt Company科技被诺基亚公司收购,Qt也因此成为诺基亚旗下的编程语言工具2012…

【vue elementUI】修改el-dropdown样式

实现效果如下&#xff1a; 代码如下&#xff1a; <el-dropdown trigger"click" command"handleCommand" active-text-color"#606266"><span class"product-card">{{getCategoryName(categoryId)}}</span><el-dro…

一文解决内网传外网sftp没跑满带宽问题

随着企业网络的日益复杂&#xff0c;内部网络与外部网络之间的文件传输需求不断增长。然而&#xff0c;标准的SFTP协议在跨网络传输时常常无法充分运用可用带宽&#xff0c;导致传输效率不尽人意。本文旨在探讨影响内网至外网SFTP传输效率的因素&#xff0c;并结合一种高效的解…

Uibot (RPA设计软件)财务会计Web应用自动化(批量开票机器人)

Uibot (RPA设计软件&#xff09;Mage AI智能识别&#xff08;发票识别&#xff09;———机器人的小项目友友们可以参考小北的课前材料五博客~ (本博客中会有部分课程ppt截屏,如有侵权请及请及时与小北我取得联系~&#xff09; 紧接着小北的前两篇博客&#xff0c;友友们我们…

Vue+SpringBoot打造数据可视化的智慧河南大屏

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 数据模块 A4.2 数据模块 B4.3 数据模块 C4.4 数据模块 D4.5 数据模块 E 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的数据可视化的智慧河南大屏&#xff0c;包含了GDP、…

深入解析:在 Node.js 中删除文件的正确姿势

引言 在 Node.js 中处理文件尤其是移除文件&#xff0c;对于维护高效应用程序至关重要。储存和秩序当道的今天&#xff0c;删除不必要或冗余的文件能力显得尤为关键。本文深入探讨你会想要使用这个强大功能的时刻和原因&#xff0c;并通过各种案例展示了这个概念&#xff0c;同…

基于Matlab的视频人面检测识别,Matalb实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

HTML静态网页成品作业(HTML+CSS)——个人介绍网页(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…

sqllab第四十七关通关笔记

知识点&#xff1a; 错误注入字符型注入order by 位置注入 报错注入时间盲注 通过测试发现是一个字符型注入 构造payload:sort2 报错了 构造payload:sort2 不提示报错了 说明这里存在字符型注入 尝试进行单引号绕过 构造payload:sort2and 11 …

解锁数据可视化新境界:山海鲸可视化免费编辑与组件探索

作为一名长期使用山海鲸可视化的资深用户&#xff0c;我在数据可视化看板的制作过程中&#xff0c;深刻感受到了这款软件带来的便捷与高效。今天&#xff0c;我想与大家分享一些我在使用山海鲸可视化制作数据可视化看板时的经验&#xff0c;给对这款产品同样感兴趣的朋友同行一…

Mammoth——从docx文档提取html

1. Mammoth——提取word文档 Github: GitHub - mwilliamson/mammoth.js: Convert Word documents (.docx files) to HTML NPM: mammoth - npm CDN: https://cdn.jsdelivr.net/npm/mammoth1.4.8/mammoth.browser.min.js * 优缺点&#xff1a; 缺点&#xff1a;只能转换.docx文…

docker小白第十三天-compose容器编排

docker-compose容器编排 Docker-Compose是Docker官方的开源项目&#xff0c;负责实现对Docker容器集群的快速编排。Compose是Docker公司推出的一个工具软件&#xff0c;可以管理多个Docker容器组成一个应用。你需要定义一个YAML格式的配置文件docker-compose.yml&#xff0c;写…