基于webSocket實(shí)現(xiàn)雙向通信,使用webworker保持心跳。
由于瀏覽器的資源管理策略會(huì)暫?;蛳拗颇承┵Y源的消耗,導(dǎo)致前端心跳包任務(wù)時(shí)效,后端接收不到webSocket心跳主動(dòng)斷開,因此需要使用webworker保持心跳
文章來源:http://www.zghlxwxcb.cn/news/detail-855461.html
-
引入webworker文章來源地址http://www.zghlxwxcb.cn/news/detail-855461.html
npm install worker-loader -D
- 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$/); } }
- 新增websocket.js創(chuàng)建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對象實(shí)例 */ class WebSocketUtil { constructor () { /** * ws對象,全局共用同一個(gè)對象 */ this.socket = null /** * WS是否重連標(biāo)識(shí) * 0:非重連,1:重連, * ws建立連接參數(shù) */ this.reconnect = 0 /** * 前后端心跳服務(wù)端響應(yīng)次數(shù) * ws連接建立時(shí)重置 * 第一次收到服務(wù)端響應(yīng),車輛未連接且非ws重連時(shí),打開車輛連接彈窗 * 接收到后端心跳響應(yīng)累加 */ this.pongNum = 0 /** * 前后端心跳后端未響應(yīng)次數(shù) * 前端發(fā)送心跳時(shí)加1 * 后端有響應(yīng)時(shí)清空 * 車輛連接成功后, 未響應(yīng)次數(shù)≥2,說明后端兩次未響應(yīng),自動(dòng)開啟前后端重連 */ this.heartBeatRsp = 0 /** * 前后端ws重連嘗試次數(shù),最多三次, * 重連累加,重連結(jié)束重置為0 * 三次均重連失敗,斷開ws連接,清空單車診斷業(yè)務(wù)緩存,跳轉(zhuǎn)到車輛連接頁面 */ this.retryTime = 0 /** * 前后端重連任務(wù),重試最多持續(xù)10秒,若超過10秒,則按照重試失敗處理, * 第一次開啟重連時(shí),開啟任務(wù), * 重連成功、10秒未連接成功關(guān)閉任務(wù), * 重連失敗時(shí),若用戶處于車輛連接頁面,toast提示; 若用戶處于單車診斷頁面則跳轉(zhuǎn)回車輛連接頁面;若用于處于非單車診斷toast提示 */ this.retryTimer = 0 /** * 重連任務(wù)toast * 重連開始開啟 * 重連結(jié)束關(guān)閉、重置 */ this.reconnectMsg = null /** * 重連任務(wù)全局遮罩 * 重連開始開啟 * 重連結(jié)束關(guān)閉、重置 */ this.reconnectLoading = null /** * 頁面超時(shí)任務(wù) * 最后一次下發(fā)或最后一次上報(bào)開始時(shí)間點(diǎn) * 11分鐘內(nèi)無任務(wù)下發(fā)、任務(wù)上報(bào)判定頁面超時(shí) * 瀏覽器刷新、車輛連接成功后開啟 * ws斷開連接、重連過程中關(guān)閉 * 頁面超時(shí)關(guān)閉ws,跳轉(zhuǎn)單車連接頁面 */ this.pageTimer = null /** * token續(xù)期任務(wù),每5分鐘一次, * 瀏覽器刷新、車輛連接成功后開啟 * ws斷開連接、重連過程中關(guān)閉 */ this.tokenPolling = null /** * 開啟一個(gè)獨(dú)立線程,處理心跳包任務(wù) * setInterval是基于當(dāng)前頁面的定時(shí)任務(wù),如果瀏覽器切換窗口/隱藏時(shí)會(huì)停止任務(wù),這時(shí)后端接收不到前端發(fā)送的心跳包,會(huì)觸發(fā)斷開ws * 使用webWorker線程,可以突破瀏覽器默認(rèn)機(jī)制 * 啟動(dòng)心跳后,開啟獨(dú)立線程,發(fā)送心跳包 * 心跳關(guān)閉時(shí)關(guān)閉獨(dú)立線程 */ this.worker = null } /** * 建立WS連接 * @param {*} reconnect 是否重連,0:非重連,1:重連 * @param {*} vin 車輛VIN碼 * @param {*} userName 用戶信息 * @param {*} needAuth 車機(jī)授權(quán) * @description 每次建立ws連接,重置服務(wù)端響應(yīng)次數(shù) */ connect (reconnect = 0, vin, userName, needAuth) { this.socket = new WebSocket(`${WsUrl}/${vin}/${userName}/${reconnect}/${needAuth}`, getToken()); this.reconnect = reconnect this.pongNum = 0 // 服務(wù)端響應(yīng)次數(shù) 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 * 啟動(dòng)心跳任務(wù) * 啟動(dòng)網(wǎng)絡(luò)監(jiān)聽 */ onOpen () { // 啟動(dòng)心跳 startHeartBeat() // 啟動(dòng)網(wǎng)絡(luò)監(jiān)聽 startNetworkListener() } /** * WebSocket響應(yīng)業(yè)務(wù)處理 * 1:服務(wù)端響應(yīng)次數(shù)累加 * 2:第一次收到服務(wù)端響應(yīng),根據(jù)是否重連,響應(yīng)不通的車輛連接業(yè)務(wù) * 3:服務(wù)端未響應(yīng)次數(shù)重置 * 4:車輛已連接且處于重連過程時(shí),關(guān)閉重連業(yè)務(wù),提示重連成功 */ onMessage ({ data }) { // 服務(wù)端響應(yīng)次數(shù)累加 this.pongNum++ // 收到服務(wù)端第一次信息 if (this.pongNum === 1) { // 非重連,第一次收到服務(wù)端信息,開啟車輛連接彈窗 if (this.reconnect === 0) // TODO 業(yè)務(wù)操作 // 重連繼續(xù)心跳計(jì)時(shí) else TODO 業(yè)務(wù)操作 } // 心跳響應(yīng)無需處理 if (data === 'pong') { // 服務(wù)端未響應(yīng)次數(shù)重置 this.heartBeatRsp = 0 // 車輛已連接,存在重連 if (store.getters.connected && this.retryTime > 0) { clearRetry() Message.success('重連成功') } return } // 業(yè)務(wù)操作 } /** * WebSocket關(guān)閉處理 * 1:關(guān)閉網(wǎng)絡(luò)監(jiān)聽 * 2:前后端重連業(yè)務(wù) * 3:連接過程中ws斷開異常處理 * 4: 退出業(yè)務(wù) */ onClose (event) { console.log('WebSocket關(guān)閉:', event.code, event.reason); // 關(guān)閉網(wǎng)絡(luò)監(jiān)聽 stopNetworkListener() // 服務(wù)端未響應(yīng)次數(shù) ≥ 2,需要重連 if (this.heartBeatRsp >= 2) return openReconnect() this.disconnect() } /** * 斷開ws連接 * 關(guān)閉心跳包、清空tokne續(xù)期任務(wù)、關(guān)閉頁面超時(shí) */ clearWs () { this.socket?.close(); this.socket = null; stopHeartBeat() } /** * 退出單車診斷業(yè)務(wù) * @param {boolean} clearAll 是否清空所有state數(shù)據(jù) * 1: 車輛已連接,記錄斷開連接時(shí)間,用于車輛連接頁面-車輛連接按鈕退出后5秒不能連接判斷 * 2:斷開ws連接, * 3: 關(guān)閉心跳包、清空tokne續(xù)期任務(wù)、關(guān)閉頁面超時(shí)判定 * 4:清空重連loading、toast提示、關(guān)閉重連超時(shí)任務(wù) * 5: 調(diào)用退出實(shí)時(shí)模式接口,通知后端退出實(shí)施模式 * 6:清空單車診斷相關(guān)瀏覽器緩存 */ 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
- 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通信建立成功開啟心跳包任務(wù); * 每5秒發(fā)送一次心跳包; * 每次發(fā)送心跳包累加服務(wù)端未響應(yīng)次數(shù)【heartBeatRsp】; * 服務(wù)端未響應(yīng)次數(shù)【heartBeatRsp】 ≥ 2,判定服務(wù)端響應(yīng)超,開啟前后端重連; */ 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') { // 發(fā)送心跳ping sendPing() } } } const sendPing = () => { // 服務(wù)端未響應(yīng)次數(shù)累加 websocket.heartBeatRsp++ // 車輛已連接,服務(wù)端響應(yīng)次數(shù)≥2,鑒定為服務(wù)端響應(yīng)超時(shí),斷開ws連接,開啟重連 if (store.getters.connected && websocket.heartBeatRsp >= 2) websocket.clearWs() websocket.socket && websocket.socket.readyState === WebSocket.OPEN && websocket.socket.send('ping'); } /** * ws斷開連接,關(guān)閉心跳包任務(wù), * 清空tokne續(xù)期任務(wù),關(guān)閉頁面超時(shí)判定 */ export const stopHeartBeat = () => { // 關(guān)閉心跳 websocket.worker?.postMessage({ type: 'stop' }) // 清空輪詢 clearInterval(websocket.tokenPolling) websocket.tokenPolling = null // 關(guān)閉頁面超時(shí)判定 clearTimeout(websocket.pageTimeout) websocket.pageTimeout = null // 關(guān)閉心跳包獨(dú)立線程 websocket.worker?.terminate() } /** * 開啟重連 * 每次重連需要間隔三秒 * 三次重連失敗,toast提示,退出單車診斷業(yè)務(wù) */ export const openReconnect = async () => { switch (websocket.retryTime) { case 0: retryConnect() break; case 1: case 2: await sleep(3000) retryConnect() break; default: Message.error('當(dāng)前您的網(wǎng)絡(luò)不穩(wěn)定,車輛連接已斷開,請重新進(jìn)行連接') websocket.disconnect() break; } } export const sleep = (ms) => { return new Promise((resolve) => setTimeout(resolve, ms)) } /** * 前后端重連,重連次數(shù)累加 * 1:開啟重連全屏loading、toast提示 * 2:關(guān)閉舊連接 * 3:第一次重連開啟重連超時(shí)任務(wù) * 4:開始重連 */ export const retryConnect = () => { // 重連次數(shù)累加 websocket.retryTime++ // 開啟全屏loading websocket.reconnectLoading = Loading.service({ fullscreen: true }); websocket.reconnectMsg?.close() websocket.reconnectMsg = Message.warning({ message: `當(dāng)前您的網(wǎng)絡(luò)環(huán)境不穩(wěn)定,正在進(jìn)行第${websocket.retryTime}次重連,請等待`, duration: 0 }) // 關(guān)閉舊連接 websocket.clearWs() // 第一次重連開啟重連超時(shí)任務(wù) if (websocket.retryTime === 1) startRetryTimer() // 開啟重連 websocket.connect(1) } /** * 重連超時(shí)任務(wù) * 重連三次時(shí)間不能超過10秒,超過10秒按照重連失敗處理 * 1:開啟重連超時(shí)任務(wù)前,如果存在重連超時(shí)任務(wù),先關(guān)閉 * 2:開啟超時(shí)重連任務(wù),時(shí)間10秒 * 3:10秒后,服務(wù)端未響應(yīng)次數(shù)不等于0 判定重連超時(shí),退出單車診斷業(yè)務(wù) */ const startRetryTimer = () => { // 1:開啟重連超時(shí)任務(wù)前,如果存在重連超時(shí)任務(wù),先關(guān)閉 stopRetryTimer() // 2:暫停車云心跳計(jì)時(shí) store.commit('connect/STOP_CONNECT_TIMER') // 3:開啟超時(shí)重連任務(wù),時(shí)間10秒 websocket.retryTimer = setTimeout(() => { // 4:10秒后,服務(wù)端未響應(yīng)次數(shù)不等于0 判定重連超時(shí),退出單車診斷業(yè)務(wù) if (websocket.retryTime != 0) { Message.error('當(dāng)前您的網(wǎng)絡(luò)不穩(wěn)定,車輛連接已斷開,請重新進(jìn)行連接') websocket.disconnect() } }, 1000 * 10) } /** * 關(guān)閉重連超時(shí)任務(wù) */ const stopRetryTimer = () => { if (websocket.retryTimer) { clearTimeout(websocket.retryTimer) websocket.retryTimer = null } } /** * 關(guān)閉重連流程 * 清空重連全屏loading、toast提示 * 重置重連次數(shù)、關(guān)閉重連超時(shí)任務(wù)、重置服務(wù)端未響應(yīng)次數(shù) */ export const clearRetry = () => { websocket.reconnectLoading?.close() websocket.reconnectLoading = null websocket.reconnectMsg?.close() websocket.reconnectMsg = null websocket.retryTime = 0 websocket.heartBeatRsp = 0 stopRetryTimer() } /** * 監(jiān)聽網(wǎng)絡(luò)連接狀態(tài), * ws建立通信后開啟 */ export const startNetworkListener = () => { window.addEventListener('offline', offline) } /** * ws連接斷開后,停止網(wǎng)絡(luò)監(jiān)聽 */ export const stopNetworkListener = () => { window.removeEventListener('offline', offline) } /** * 監(jiān)聽網(wǎng)絡(luò)連接狀態(tài) * 監(jiān)聽到網(wǎng)絡(luò)中斷:主動(dòng)斷開當(dāng)前ws連接;網(wǎng)絡(luò)中斷后onclose事件會(huì)失效,需要主動(dòng)提前斷開ws * 判斷車輛是否已連接,如果車輛已連接需要開啟前后端三次重連 */ const offline = (e) => { // 網(wǎng)絡(luò)中斷 if (e.type === 'offline') { // 車輛已連接 if (store.getters.connected) websocket.heartBeatRsp = 2 // 后端未響應(yīng)次數(shù)≥2開啟重連 // 主動(dòng)斷開當(dāng)前ws連接 websocket.clearWs() } }
- 創(chuàng)建webWorker進(jìn)程用來處理ws心跳ws.worker.js
/** * 前后端心跳包任務(wù),ws連接建立后每5秒發(fā)送一次,由前端主動(dòng)發(fā)起,后端響應(yīng) * ws斷開心跳任務(wù)清空 */ 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') { // 清除定時(shí)器 clearInterval(heartBeatTimer) heartBeatTimer = null console.log('心跳包任務(wù)停止成功') } }
- 在組件中使用
import websocket from '@/websocket' export default { mounted () { websocket.connect() } }
到了這里,關(guān)于vue2使用webSocket雙向通訊的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!