<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
electron 開發時會遇到一對多的情況,在進行 websocket 通訊時,如果接收到伺服器端多個指令時,而這個指令剛好需要佔用執行緒,這個時候整個介面就會失去響應,那麼我們就可以使用執行緒來解決這個問題.
npm create vite@latest electron-worker
執行完後修改 package.json 如下:
{ "name": "electron-worker", "private": true, "version": "0.0.0", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "dependencies": {}, "devDependencies": { "@vitejs/plugin-vue": "^3.2.0", "vite": "^3.2.0", "vue": "^3.2.41", "electron": "19.1.4", "electron-builder": "^23.3.3" } }
建立 mainEntry.js 作為 electron 的入口檔案,啟動一個視窗
// src/main/mainEntry.js import { app, BrowserWindow } from "electron"; process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true"; let mainWindow; app.whenReady().then(() => { let config = { webPreferences: { nodeIntegration: true, webSecurity: false, allowRunningInsecureContent: true, contextIsolation: false, webviewTag: true, spellcheck: false, disableHtmlFullscreenWindowResize: true, }, }; mainWindow = new BrowserWindow(config); mainWindow.webContents.openDevTools({ mode: "undocked" }); mainWindow.loadURL(process.argv[2]); });
編寫 vite 外掛,在伺服器啟動後載入 electron 入口檔案
// plugins/devPlugin.js export const devPlugin = () => { return { name: "dev-plugin", configureServer(server) { require("esbuild").buildSync({ entryPoints: ["./src/main/mainEntry.js"], bundle: true, platform: "node", outfile: "./dist/mainEntry.js", external: ["electron"], }); server.httpServer.once("listening", () => { let { spawn } = require("child_process"); let electronProcess = spawn(require("electron").toString(), ["./dist/mainEntry.js", `http://127.0.0.1:${server.config.server.port}/`], { cwd: process.cwd(), stdio: "inherit", }); electronProcess.on("close", () => { server.close(); process.exit(); }); }); }, }; };
使用外掛
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { devPlugin } from "./plugins/devPlugin"; export default defineConfig({ plugins: [devPlugin(), vue()], })
將 vue 專案檔案放入和 main 同級, 結構如下所示
└─src ├─main │ mainEntry.js └─renderer │ App.vue │ main.js ├─assets └─components
修改 index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" rel="external nofollow" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite + Vue</title> </head> <body> <div id="app"></div> <script type="module" src="/src/renderer/main.js"></script> </body> </html>
現在執行 npm run dev
就可以執行專案了
var WebSocketServer = require('ws').Server; var wss = new WebSocketServer({port: 8181}); wss.on('connection', function (ws) { console.log('有使用者端連線'); ws.send("連線成功") ws.on('message', function (jsonStr) { console.log(jsonStr.toString()); }); });
準備 Socket 物件
export default class Socket { websocket wsUrl constructor(wsUrl) { this.wsUrl = wsUrl } init() { if (this.websocket) return this.websocket const socket = this.websocket = new WebSocket(this.wsUrl) // WebSocket 接收伺服器端資料 socket.onmessage = (e) => { console.log("接收伺服器端訊息:", e.data) } // WebSocket 斷開連線後觸發 socket.onclose = (e) => {} // WebSocket 連線成功 socket.onopen = () => { console.log("連線成功") } // WebSocket 連線異常 socket.onerror = (e) => {} } }
連線 Socket
<script setup> import Socket from './socket' const socket = new Socket("ws://localhost:8181") function register() { socket.init() } </script> <template> <div> <button @click="register">註冊</button> </div> </template> <style scoped> </style>
點選註冊後顯示如下:
一般為了確保服務一直連線,需要使用者端定時給服務傳送心跳
export default class Socket { // ... heartbeatCount // 心跳次數 heartbeatTimer // 心跳定時器 heartbeatInterval = 1000 * 20 // 心跳傳送頻率(2秒一次) // ... sendHeartBeat() { this.heartbeatCount = 0 if (this.heartbeatTimer) clearInterval(this.heartbeatTimer) this.heartbeatTimer = setInterval(() => { this.websocket.send("傳送心跳") }, this.heartbeatInterval) } }
App.vue
function sendHeartBeat() { socket.sendHeartBeat() }
<button @click="sendHeartBeat">傳送心跳</button>
可以看到我們在伺服器端紀錄檔裡看到有持續心跳紀錄檔
因為是定時器傳送,當伺服器端掉線後定時器卻還在繼續傳送,現在我們來優化這個
// 斷開連線 onclose() { console.log("已斷開連線") this.websocket = null // 清除心跳定時器 if (this.heartbeatTimer) clearInterval(this.heartbeatTimer) }
在 socket 斷開後進行呼叫
// WebSocket 斷開連線後觸發 socket.onclose = (e) => { this.onclose() }
websocket 斷開有可能是使用者端網路問題,所以我們需要進行嘗試重連
export default class Socket { // ... socketOpen // 是否連線 isReconnect = true // 是否可以重新連線 reconnectCountMax = 3 // 最大重新次數 reconnectTimer // 重連定時器 reconnectCurrent = 0 // 重連次數 reconnectInterval // 1000 * 3 // 重連頻率(3秒一次) // ... // 斷開連線 onclose() { console.log("已斷開連線") this.websocket = null // 清除心跳定時器 if (this.heartbeatTimer) clearInterval(this.heartbeatTimer) // 需要重新連線 if (this.isReconnect) { this.reconnectTimer = setTimeout(() => { if (this.reconnectCurrent >= this.reconnectCountMax) { console.log("超過重連次數,重連失敗") clearTimeout(this.reconnectTimer) } else { this.reconnectCurrent += 1 this.reconnect() } }, this.reconnectInterval) } } // 重新連線 reconnect() { console.log("重新連線", this.reconnectCurrent) if (this.websocket && this.socketOpen) { this.websocket.close() } this.init() } }
我們每三秒一次進行嘗試重新連線,如果重連三次還未連線,那我們認為無法重新連線
export enum PostMessageType { ON_OPEN = 'open', // websocket開啟 ON_ERROR = 'error', // websocket異常 ON_CLOSE = 'close', // websocket關閉 ON_MESSAGE = 'message', // websocket接收訊息 RECONNECT = 'reconnect', // websocket重新連線 HEARTBEAT = 'heartbeat', // websocket傳送心跳 OFF = 'off', // websocket主動關閉 REGISTER = 'register', // websocket註冊成功 }
class Socket { wsUrl: string // 服務地址 websocket: WebSocket | null = null // websocket物件 socketOpen: boolean = false // socket是否開啟 heartbeatTimer: any // 心跳定時器 heartbeatCount: number = 0 // 心跳次數 heartbeatInterval: number = 1000 * 20 // 心跳傳送頻率(2秒一次) isReconnect: boolean = true // 是否可以重新連線 reconnectCountMax: number = 3 // 最大重新次數 reconnectCurrent: number = 0 // 已發起重連次數 reconnectTimer: any // 重連timer reconnectInterval: number = 1000 * 3 // 重連頻率(3秒一次) constructor(url: string) { this.wsUrl = url } // socket 初始化 init() { if (this.websocket) return this.websocket const socket = this.websocket = new WebSocket(this.wsUrl) // WebSocket 接收伺服器端資料 socket.onmessage = (e) => { this.receive(e.data) } // WebSocket 斷開連線後觸發 socket.onclose = (e) => { this.postMessage(PostMessageType.ON_CLOSE, e) this.onclose() } // WebSocket 連線成功 socket.onopen = () => { this.onopen() } // WebSocket 連線異常 socket.onerror = (e) => { this.postMessage(PostMessageType.ON_ERROR, e) } } // 連線成功後的回撥 onopen() { this.socketOpen = true this.isReconnect = true this.reconnectCurrent = 1 this.heartbeatCount = 0 this.postMessage(PostMessageType.ON_OPEN) } /** * 訊息處理器 * @param type * @param data */ postMessage(type: PostMessageType, data?: any) {} /** * 斷開連線 */ onclose() { this.websocket = null this.socketOpen = false // 清除心跳定時器 clearInterval(this.heartbeatTimer) // 需要重新連線 if (this.isReconnect) { this.reconnectTimer = setTimeout(() => { if (this.reconnectCurrent >= this.reconnectCountMax) { clearTimeout(this.reconnectTimer) } else { this.reconnectCurrent += 1 this.reconnect() } }, this.reconnectInterval) } } /** * 重新連線 */ reconnect() { this.postMessage(PostMessageType.RECONNECT, this.reconnectCurrent) if (this.websocket && this.socketOpen) { this.websocket.close() } this.init() } /** * 給伺服器端傳送訊息 * @param data * @param callback */ send(data: any, callback?: () => void) { const ws = this.websocket if (!ws) { this.init() setTimeout(() => { this.send(data, callback) }, 1000) return } switch (ws.readyState) { case ws.OPEN: ws.send(data) if (callback) { callback() } break case ws.CONNECTING: // 未開啟,則等待1s後重新呼叫 setTimeout(() => { this.send(data, callback) }, 1000) break default: this.init() setTimeout(() => { this.send(data, callback) }, 1000) } } receive(data: any) { this.postMessage(PostMessageType.ON_MESSAGE, data) } /** * 傳送心跳 * @param data 心跳資料 */ sendHeartBeat(data: any) { this.heartbeatCount = 0 if (this.heartbeatTimer) clearInterval(this.heartbeatTimer) this.heartbeatTimer = setInterval(() => { this.send(data, () => { this.heartbeatCount += 1 this.postMessage(PostMessageType.HEARTBEAT, { heartBeatData: data, heartbeatCount: this.heartbeatCount }) }) }, this.heartbeatInterval) } /** * 主動關閉websocket連線 * 關閉後 websocket 關閉監聽可以監聽到,所以無需去額外處理 */ close() { this.isReconnect = false this.postMessage(PostMessageType.OFF, "主動斷開websocket連線") this.websocket && this.websocket.close() } }
上面是基礎的 websocket ,具體使用需要結合業務進行繼承使用
export default class SelfSocket extends Socket { registerData: any // 註冊資料 heartBeatData: any // 心跳資料 constructor(url: string) { super(url); } initSocket(registerData: any, heartBeatData: any) { this.registerData = registerData this.heartBeatData = heartBeatData super.init() } onopen() { this.register() super.onopen(); } /** * websocket 註冊訊息,註冊成功後進行心跳傳送 */ register() { this.send(this.registerData, () => { this.sendHeartBeat(this.heartBeatData) this.postMessage(PostMessageType.REGISTER, this.registerData) }) } send(data: any, callback?: () => void) { // 資料加密 const str = _encrypt(data) super.send(str, callback); } receive(data: any) { this.postMessage(PostMessageType.ON_MESSAGE, _decode(data)) } postMessage(type: PostMessageType, e?: any) {} }
我們公司 websocket 連線需要註冊後進行心跳傳送,且在接收和傳送資料時都進行了加密和解密,簡單的可以使用 base64 進行
Web Worker 使用可以參考阮一峰老師的文章,這裡就不做過多介紹
建立一個 websocketWorker.js
const URL = "ws://localhost:8181" import Socket from "./socket"; const ws = new Socket(URL) self.addEventListener('message', (e) => { const { type, data } = e.data switch (type) { case "init": ws.init(); break case "message": ws.send(data) break case "close": ws.close() break default: console.error("傳送websocket命令有誤") break } })
<script setup> import Worker from './websocketWorker?worker' const worker = new Worker() worker.onmessage = function (e) { console.log(e.data) } function register() { worker.postMessage({ type: 'init' }) } function close() { worker.postMessage({ type: 'close' }) } </script> <template> <div> <button @click="register">註冊</button> <button @click="close">關閉服務</button> </div> </template>
vite 使用 worker 可以檢視 worker選項
如果是 webpack 可以檢視 worker-loader
module.exports = { chainWebpack: config => { config.module .rule('worker') .test(/.worker.js$/) .use('worker-loader') .loader('worker-loader') .options({ inline: 'no-fallback', }) .end() config.module.rule('js').exclude.add(/.worker.js$/) } }
這裡是我的設定
以上就是Web Worker執行緒解決方案electron踩坑記錄的詳細內容,更多關於Web Worker執行緒electron的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45