WebWorker线程解决方案electron踩坑记录
初始化项目
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"
}
}
编写入口文件和 electron 插件
创建 mainEntry.js 作为 electron 的入口文件,启动一个窗口
// class="lazy" data-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: ["./class="lazy" data-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 同级, 结构如下所示
└─class="lazy" data-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" class="lazy" data-src="/class="lazy" data-src/renderer/main.js"></script>
</body>
</html>
现在执行 npm run dev
就可以运行项目了
websocket
websocket 服务
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());
});
});
连接 websocket 服务
准备 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)
}
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()
}
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)
}
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)
}
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();
}
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 进行
Worker
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的资料请关注编程网其它相关文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341