30分钟上手Electron WebSocket客户端:从0到1实现实时消息推送
你是否遇到过桌面应用需要实时数据更新却不知如何实现?是否在寻找一种简单可靠的方式让Electron应用与后端服务保持长连接?本文将带你从零开始构建一个完整的Electron WebSocket客户端,解决实时通信难题,让你的桌面应用秒变"实时响应专家"。
读完本文,你将能够:
- 理解Electron应用中主进程与渲染进程的通信机制
- 掌握WebSocket(网络套接字)客户端的核心实现
- 学会使用preload脚本安全暴露Node.js API
- 构建带有实时消息展示的用户界面
- 解决Electron环境下常见的WebSocket连接问题
项目准备与环境搭建
首先需要准备Electron开发环境。本教程基于electron-quick-start项目,这是一个最简化的Electron应用模板,包含了基本的项目结构和必要配置。
1. 克隆项目代码
git clone https://gitcode.com/gh_mirrors/el/electron-quick-start
cd electron-quick-start
2. 安装依赖包
项目基础依赖已在package.json中定义,执行以下命令安装:
npm install
查看package.json文件,我们可以看到项目使用Electron 38.2.2版本,这是当前较新的稳定版,提供了良好的WebSocket支持。
3. 项目结构解析
electron-quick-start项目的核心文件结构如下:
electron-quick-start/
├── main.js # 主进程代码
├── preload.js # 预加载脚本
├── renderer.js # 渲染进程代码
├── index.html # 应用界面
├── styles.css # 样式文件
└── package.json # 项目配置
其中,main.js是应用入口文件,负责创建窗口和控制应用生命周期;preload.js作为主进程与渲染进程之间的桥梁,用于安全地暴露API;renderer.js则运行在渲染进程中,处理页面交互逻辑。
WebSocket客户端实现方案
在Electron应用中实现WebSocket客户端有多种方案,我们将采用"主进程管理连接+渲染进程处理UI"的架构,这种方式既保证了连接的稳定性,又能安全地更新用户界面。
架构设计
以下是Electron WebSocket客户端的架构图:
graph TD
A[后端WebSocket服务] <--> B[Electron主进程WebSocket客户端]
B <--> C[IPC通信]
C <--> D[preload.js]
D <--> E[渲染进程]
E <--> F[UI界面]
这种架构的优势在于:
- 主进程管理WebSocket连接更稳定,不受页面刷新影响
- 通过IPC(进程间通信)实现安全的数据传递
- 渲染进程专注于UI更新,符合职责分离原则
核心代码实现
1. 安装WebSocket依赖
我们使用ws库来实现WebSocket客户端功能,这是一个轻量高效的Node.js WebSocket库。执行以下命令安装:
npm install ws --save
2. 主进程WebSocket实现
修改main.js文件,添加WebSocket客户端逻辑。我们需要创建一个WebSocket服务类,负责管理连接状态和消息处理:
// 在文件顶部引入WebSocket模块
const WebSocket = require('ws');
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('node:path');
// WebSocket客户端类
class WsClient {
constructor() {
this.ws = null;
this.isConnected = false;
this.reconnectInterval = 5000; // 重连间隔5秒
}
// 连接WebSocket服务器
connect(url) {
// 关闭已有连接
if (this.ws) {
this.ws.close();
}
this.ws = new WebSocket(url);
// 连接成功回调
this.ws.on('open', () => {
console.log('WebSocket连接已建立');
this.isConnected = true;
// 通知渲染进程连接状态
mainWindow.webContents.send('ws-status', 'connected');
});
// 接收消息回调
this.ws.on('message', (data) => {
console.log('收到消息:', data.toString());
// 将消息发送到渲染进程
mainWindow.webContents.send('ws-message', data.toString());
});
// 连接关闭回调
this.ws.on('close', () => {
console.log('WebSocket连接已关闭');
this.isConnected = false;
mainWindow.webContents.send('ws-status', 'disconnected');
// 自动重连
setTimeout(() => this.connect(url), this.reconnectInterval);
});
// 错误处理
this.ws.on('error', (error) => {
console.error('WebSocket错误:', error);
mainWindow.webContents.send('ws-error', error.message);
});
}
// 发送消息
sendMessage(message) {
if (this.isConnected && this.ws) {
this.ws.send(message);
return true;
}
return false;
}
}
// 创建WebSocket实例
const wsClient = new WsClient();
// 保留原有的createWindow函数
function createWindow () {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
// 保持原有配置
}
});
mainWindow.loadFile('index.html');
// 暴露mainWindow供WsClient使用
global.mainWindow = mainWindow;
return mainWindow;
}
// 添加IPC监听器,处理来自渲染进程的消息
ipcMain.on('ws-connect', (event, url) => {
wsClient.connect(url);
});
ipcMain.on('ws-send', (event, message) => {
const result = wsClient.sendMessage(message);
event.reply('ws-send-result', result);
});
// 保留原有的应用生命周期代码...
3. 预加载脚本配置
修改preload.js文件,使用contextBridge安全地将IPC API暴露给渲染进程:
const { contextBridge, ipcRenderer } = require('electron');
// 使用contextBridge暴露安全的API
contextBridge.exposeInMainWorld('electronWs', {
// 连接WebSocket服务器
connect: (url) => ipcRenderer.send('ws-connect', url),
// 发送消息
sendMessage: (message) => ipcRenderer.send('ws-send', message),
// 接收连接状态
onStatusChange: (callback) => ipcRenderer.on('ws-status', (event, status) => callback(status)),
// 接收消息
onMessage: (callback) => ipcRenderer.on('ws-message', (event, message) => callback(message)),
// 接收错误信息
onError: (callback) => ipcRenderer.on('ws-error', (event, error) => callback(error)),
// 移除监听器
removeListeners: () => {
ipcRenderer.removeAllListeners('ws-status');
ipcRenderer.removeAllListeners('ws-message');
ipcRenderer.removeAllListeners('ws-error');
}
});
// 保留原有的DOMContentLoaded事件处理...
4. 渲染进程实现
修改renderer.js文件,实现WebSocket客户端的UI交互逻辑:
// 等待DOM加载完成
document.addEventListener('DOMContentLoaded', () => {
// 获取DOM元素
const connectBtn = document.getElementById('connect-btn');
const disconnectBtn = document.getElementById('disconnect-btn');
const sendBtn = document.getElementById('send-btn');
const urlInput = document.getElementById('ws-url');
const messageInput = document.getElementById('message-input');
const statusElement = document.getElementById('connection-status');
const messagesList = document.getElementById('messages-list');
// 默认WebSocket服务器地址
urlInput.value = 'wss://echo.websocket.events';
// 连接状态显示
const updateStatus = (status) => {
statusElement.textContent = `连接状态: ${status}`;
statusElement.className = `status-${status}`;
// 更新按钮状态
connectBtn.disabled = status === 'connected';
disconnectBtn.disabled = status !== 'connected';
sendBtn.disabled = status !== 'connected';
};
// 添加消息到列表
const addMessage = (text, isOutgoing = false) => {
const li = document.createElement('li');
li.className = isOutgoing ? 'message outgoing' : 'message incoming';
li.textContent = text;
messagesList.appendChild(li);
// 滚动到底部
messagesList.scrollTop = messagesList.scrollHeight;
};
// 连接WebSocket服务器
connectBtn.addEventListener('click', () => {
const url = urlInput.value;
window.electronWs.connect(url);
addMessage(`正在连接到: ${url}`);
});
// 发送消息
sendBtn.addEventListener('click', () => {
const message = messageInput.value.trim();
if (message) {
window.electronWs.sendMessage(message);
addMessage(`我: ${message}`, true);
messageInput.value = '';
}
});
// 按Enter键发送消息
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendBtn.click();
}
});
// 监听连接状态变化
window.electronWs.onStatusChange((status) => {
updateStatus(status);
addMessage(`连接状态变为: ${status}`);
});
// 监听收到的消息
window.electronWs.onMessage((message) => {
addMessage(`服务器: ${message}`);
});
// 监听错误信息
window.electronWs.onError((error) => {
addMessage(`错误: ${error}`, false);
});
// 初始状态
updateStatus('disconnected');
});
5. 更新用户界面
修改index.html文件,添加WebSocket客户端的UI界面:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<link href="./styles.css" rel="stylesheet">
<title>Electron WebSocket客户端</title>
</head>
<body>
<h1>Electron WebSocket客户端</h1>
<div class="ws-container">
<div class="connection-panel">
<input type="text" id="ws-url" placeholder="WebSocket服务器地址">
<button id="connect-btn">连接</button>
<button id="disconnect-btn" disabled>断开连接</button>
<div id="connection-status" class="status-disconnected">连接状态: 未连接</div>
</div>
<div class="messages-panel">
<ul id="messages-list"></ul>
</div>
<div class="input-panel">
<input type="text" id="message-input" placeholder="输入消息...">
<button id="send-btn" disabled>发送</button>
</div>
</div>
<script src="./renderer.js"></script>
</body>
</html>
6. 添加样式美化界面
修改styles.css文件,添加WebSocket客户端界面样式:
/* 基础样式 */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
/* WebSocket客户端容器 */
.ws-container {
max-width: 800px;
margin: 0 auto;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
/* 连接面板 */
.connection-panel {
padding: 15px;
background-color: #f8f9fa;
border-bottom: 1px solid #eee;
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
#ws-url {
flex-grow: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
min-width: 200px;
}
button {
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
}
#connect-btn {
background-color: #28a745;
color: white;
}
#disconnect-btn {
background-color: #dc3545;
color: white;
opacity: 0.7;
}
#disconnect-btn:enabled {
opacity: 1;
}
#connection-status {
margin-left: auto;
padding: 5px 10px;
border-radius: 4px;
font-size: 0.9em;
}
/* 状态样式 */
.status-connected {
background-color: #d4edda;
color: #155724;
}
.status-disconnected {
background-color: #f8d7da;
color: #721c24;
}
/* 消息面板 */
.messages-panel {
height: 400px;
overflow-y: auto;
padding: 15px;
}
#messages-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.message {
max-width: 70%;
padding: 10px 15px;
border-radius: 18px;
word-break: break-word;
}
.incoming {
background-color: #e9ecef;
align-self: flex-start;
}
.outgoing {
background-color: #007bff;
color: white;
align-self: flex-end;
}
/* 输入面板 */
.input-panel {
padding: 15px;
background-color: #f8f9fa;
border-top: 1px solid #eee;
display: flex;
gap: 10px;
}
#message-input {
flex-grow: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 20px;
}
#send-btn {
background-color: #007bff;
color: white;
border-radius: 20px;
padding: 10px 20px;
}
#send-btn:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
功能测试与运行
启动应用
完成以上代码修改后,执行以下命令启动Electron应用:
npm start
应用启动后,你将看到一个包含WebSocket连接界面的窗口,界面分为三个部分:连接控制区、消息显示区和消息输入区。
测试WebSocket连接
我们可以使用公开的WebSocket测试服务"wss://echo.websocket.events"进行测试,这是一个简单的回声服务,会将收到的消息原样返回。
测试步骤:
- 确保输入框中显示"wss://echo.websocket.events"
- 点击"连接"按钮
- 等待连接状态变为"已连接"
- 在消息输入框中输入任意文本
- 点击"发送"按钮或按Enter键
- 你将看到发送的消息和服务器返回的回声消息
常见问题与解决方案
1. 连接失败问题
如果遇到WebSocket连接失败,首先检查以下几点:
- 确认服务器地址是否正确
- 检查网络连接是否正常
- 验证服务器是否支持WebSocket协议
- 对于自签名证书的HTTPS服务,可能需要在main.js中添加证书验证跳过代码:
// 在创建BrowserWindow之前添加
app.commandLine.appendSwitch('ignore-certificate-errors');
2. 消息发送失败
消息发送失败通常有以下原因:
- 连接尚未建立完成
- 连接已断开但自动重连尚未完成
- 消息格式不符合服务器要求
解决方法是在发送前检查连接状态,并添加错误处理机制。
3. 跨域问题
Electron应用中出现跨域问题较少见,但如果遇到,可以在main.js的webPreferences中添加:
webPreferences: {
webSecurity: false,
// 其他配置...
}
总结与扩展
通过本文的学习,你已经掌握了在Electron应用中实现WebSocket客户端的核心技术,包括:
- 使用
ws库在主进程创建WebSocket连接 - 通过IPC在主进程与渲染进程间传递消息
- 使用preload脚本安全暴露API
- 设计并实现实时消息界面
这个基础实现可以进一步扩展,添加更多高级功能:
- 消息历史记录保存
- 连接状态图标指示
- 消息发送状态反馈
- 多服务器连接管理
- 断线重连与消息缓存
希望本文能帮助你解决Electron应用的实时通信需求,让你的桌面应用具备专业级的实时响应能力。如果觉得本文有用,请点赞收藏,关注作者获取更多Electron开发技巧!
下期预告:《Electron WebSocket安全最佳实践》—— 教你如何保护WebSocket连接安全,防止常见攻击和数据泄露。
Kimi-K2.5Kimi K2.5 是一款开源的原生多模态智能体模型,它在 Kimi-K2-Base 的基础上,通过对约 15 万亿混合视觉和文本 tokens 进行持续预训练构建而成。该模型将视觉与语言理解、高级智能体能力、即时模式与思考模式,以及对话式与智能体范式无缝融合。Python00- QQwen3-Coder-Next2026年2月4日,正式发布的Qwen3-Coder-Next,一款专为编码智能体和本地开发场景设计的开源语言模型。Python00
xw-cli实现国产算力大模型零门槛部署,一键跑通 Qwen、GLM-4.7、Minimax-2.1、DeepSeek-OCR 等模型Go06
PaddleOCR-VL-1.5PaddleOCR-VL-1.5 是 PaddleOCR-VL 的新一代进阶模型,在 OmniDocBench v1.5 上实现了 94.5% 的全新 state-of-the-art 准确率。 为了严格评估模型在真实物理畸变下的鲁棒性——包括扫描伪影、倾斜、扭曲、屏幕拍摄和光照变化——我们提出了 Real5-OmniDocBench 基准测试集。实验结果表明,该增强模型在新构建的基准测试集上达到了 SOTA 性能。此外,我们通过整合印章识别和文本检测识别(text spotting)任务扩展了模型的能力,同时保持 0.9B 的超紧凑 VLM 规模,具备高效率特性。Python00
KuiklyUI基于KMP技术的高性能、全平台开发框架,具备统一代码库、极致易用性和动态灵活性。 Provide a high-performance, full-platform development framework with unified codebase, ultimate ease of use, and dynamic flexibility. 注意:本仓库为Github仓库镜像,PR或Issue请移步至Github发起,感谢支持!Kotlin08
VLOOKVLOOK™ 是优雅好用的 Typora/Markdown 主题包和增强插件。 VLOOK™ is an elegant and practical THEME PACKAGE × ENHANCEMENT PLUGIN for Typora/Markdown.Less00