首页
/ 30分钟上手Electron WebSocket客户端:从0到1实现实时消息推送

30分钟上手Electron WebSocket客户端:从0到1实现实时消息推送

2026-02-05 04:16:32作者:郁楠烈Hubert

你是否遇到过桌面应用需要实时数据更新却不知如何实现?是否在寻找一种简单可靠的方式让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"进行测试,这是一个简单的回声服务,会将收到的消息原样返回。

测试步骤:

  1. 确保输入框中显示"wss://echo.websocket.events"
  2. 点击"连接"按钮
  3. 等待连接状态变为"已连接"
  4. 在消息输入框中输入任意文本
  5. 点击"发送"按钮或按Enter键
  6. 你将看到发送的消息和服务器返回的回声消息

常见问题与解决方案

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连接安全,防止常见攻击和数据泄露。

登录后查看全文
热门项目推荐
相关项目推荐