vue2使用webSocket双向通讯

news2024/11/15 2:08:06

基于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()
        }
    }

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

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

相关文章

CSDN 停更通知

CSDN 不再更新,欢迎关注我的微信公众号,分享更多有趣的技术内容。 如果大家有任何疑问,或者感兴趣的话题,都可以通过微信公众号与我交流,相互学习,相互成长。

Trent电源设计那些事儿教学

本课程将深入探讨Trent电源设计的关键概念与技术。学生将学习功率电子器件和拓扑、电路保护、稳压技术以及EMI滤波等内容。通过理论和实践相结合的教学方式,帮助学员掌握Trent电源设计的原理与应用。 课程大小:12.5G 课程下载:https://down…

2024年发布jar到国外maven中央仓库最新教程

2024年发布jar到国外maven中央仓库最新教程 文章目录 1.国外sonatype仓库的版本1.1老OSSHR账号注册说明1.2新账号注册说明 2.新账号注册(必选)3.新账号登录创建Namespace3.1创建Namespace的名字的格式要求(必选)3.2发布一个静态网站(可选&…

科技云报道:第五次工业革命,中国AI企业如何打造新质生产力?

科技云报道原创。 人类历史的叙述与技术进步的影响深深交织在一起。 迄今为止,每一次工业革命都彻底改变了我们社会的轮廓,引入了机械化、大规模生产和数字化,并重新定义了人类生存的规范。 自2022年11月30日OpenAI发布ChatGPT以来&#x…

webpack5零基础入门-11处理html资源

1.目的 主要是为了自动引入打包后的js与css资源,避免手动引入 2.安装相关包 npm install --save-dev html-webpack-plugin 3.引入插件 const HtmlWebpackPlugin require(html-webpack-plugin); 4.添加插件(通过new方法调用) /**插件 *…

c语言,联合体

一.什么是联合体: 像结构体一样,联合体也是由一个或多个成员变量组成的这些成员变量可以是不同的类型,但编译器只给最大成员分配足够的内存,联合体体内的成员都是公用一块空间的,因此联合体也叫做共同体 二.联合体类…

聚类分析 | Matlab实现基于PCA+DBO+K-means的数据聚类可视化

聚类分析 | Matlab实现基于PCADBOK-means的数据聚类可视化 目录 聚类分析 | Matlab实现基于PCADBOK-means的数据聚类可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 PCA(主成分分析)、DBO(蜣螂优化算法)和K-means聚类…

【观察】紫光云:紫鸾5.0云平台“再升级”,为政企客户提供新质生产力

毫无疑问,数字化既是这个时代前进所趋,也是国家战略所指,更是所有企业在未来发展中已达成的高度共识。 在此过程中,千姿百态、复杂多样的应用场景,可以看做是遍布数字中国的“点”;百行百业、各种类型的行业…

阿里云发布 AI 编程助手 “通义灵码”——VSCode更强了 !!

文章目录 什么是 通义灵码(TONGYI Lingma) 快速体验“通义灵码” 什么是“通义灵码”(TONGYI Lingma) 通义灵码(TONGYI Lingma),是阿里云出品的一款基于通义大模型的智能编码辅助工具&#xff…

idea 开发serlvet班级通讯录管理系统idea开发mysql数据库web结构计算机java编程layUI框架开发

一、源码特点 idea开发 java servlet 班级通讯录管理系统是一套完善的web设计系统mysql数据库 系统采用serlvetdaobean mvc 模式开发,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。 servlet 班…

【linux】Debian访问Debian上的共享目录

要在Debian系统上访问共享目录,通常意味着要访问通过网络共享的文件夹,比如通过SMB/CIFS(Server Message Block/Common Internet File System)协议共享的Windows共享文件夹。以下是访问共享目录的步骤: 1. 安装必要的…

边缘计算+WEB端应用融合:AI行为识别智能监控系统搭建指南 -- 边缘设备图像识别及部署(二)

专栏目录 边缘计算WEB端应用融合:AI行为识别智能监控系统搭建指南 – 整体介绍(一) 边缘计算WEB端应用融合:AI行为识别智能监控系统搭建指南 -- 边缘图像识别及部署(二) 前言边缘图像识别与推流整体思路原始…

蓝桥杯刷题总结(Python组)

1、蛇形矩阵 解题思路:每次赋值后都对方向进行改变,一般上下左右就是(-1,0),(0,1),(1,0),(0&…

分布式事务的解决方案--Seata架构

一、Seata的XA模式 二、AT模式原理 三、TCC模式原理 四、MQ分布式事务 异步,非实时,实现最终的一致性。 四、分布式事务的解决方案

【MLLM+轻量多模态模型】24.02.Bunny-v1.0-2B-zh: 轻量级多模态语言模型 (效果一般)

24.02 北京人工智能研究院(BAAI)提出以数据为中心的轻量级多模态模型 arxiv论文:2402.Efficient Multimodal Learning from Data-centric Perspective 代码:https://github.com/BAAI-DCAI/Bunny 在线运行:https://wis…

CSS元素定位(学习笔记)

一、 z-index 1.1 作用 规定元素的堆叠顺序,取值越大,层级越往上 1.2 属性值 属性值为数字,可以取负值,不推荐 默认值:auto(跟父元素同一层级) 1.3 注意 必须配合定位(static除外)使用,默认情况下,后面的元…

openssl3.2 - exp - openssl speed test

文章目录 openssl3.2 - exp - openssl speed test概述笔记表面上能列出的算法集合没列出的算法, 有的也支持不支持的算法的例子直接提示算法不支持算法的属性找不到到底哪些算法才是可以测试的算法?那看看哪些算法是支持的?包含支持的算法的名称数组在算法失败的提示处, 将支…

【Redis】缓存穿透

问题发生背景:客户端请求的数据再缓存中和数据库中都不存在。 导致的问题:缓存永远不会生效,这些请求都会去请求数据库—导致数据库压力增大。 解决方案: 1.缓存空对象 在Redis中缓存空对象,告诉客户端数据库中没有该值…

Python快速导入id至json文件(2024.3.19)

Python实现id导入至json文件 2024.3.19 需求分析1、输入数据介绍1.1 三个.txt文件1.1.1 computers.txt(计算机)1.1.2 cameras.txt(摄像头)1.1.3 monitors.txt(显示器) 1.2 单个.xlsx文件 2、实现思路3、Pyt…

Day21:实现退出功能、开发账号设置、检查登录状态

实现退出功能 将登录凭证修改为失效状态。跳转至网站首页。 数据访问层 不用写了,已经有了updateStatus方法; 业务层 UserService public void logout(String ticket) {loginTicketMapper.updateStatus(ticket, 1);}Controller层 RequestMapping(p…