Electron 多端通信桥 MessageChannelMain和 MessagePortMain 坑点汇集

文章讲述了Electron中MessageChannelMain的作用,它是主进程与DOMMessageChannel的等价物,用于在进程间创建消息通道。重点讲解了如何在渲染进程、webview和工具进程中正确使用MessageChannelMain,以及为何Websocket是处理端对端通信的完美解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 注意:该通信方式 Electron 版本要≥22.0.0,低于该版本还没有此功能

简介

  1. MessageChannelMain 是 DOM MessageChannel 对象的主进程等价对象。 它的特有功能是创建一对已连接的 MessagePortMain 对象。

    1. Electron 本身为了灵活追加 on("message") 机制,就说明该 MessageChannelMain 已经被创建了,而 Web 开发中,是没有这种权限自由开进程,然后再把 port 塞过去的,所以消息不会丢失,但是 Electron 这种操作非常多,所以,要先写好 port.on("message"),再启动 port.start()避免消息丢失,这是和 MessageChannel 一个大区别

    2. MessagePortMain 是 DOM MessagePort 对象的主进程等价对象。 它的行为类似于DOM版本,不同的是它使用 Node.js EventEmitter 事件系统,而不是 DOM EventTarget 系统。 这意味着你应该用 port.on('message', ...) 来监听事件, 来代替 port.onmessage = ... 或 port.addEventListener('message', ...)

    3. 针对 b 能把你坑哭,尤其你不知道 MessageChannel 是啥的人,在了解了之后,第一反应就是 MessageChannel 的案例,结果不运行,是不是很尴尬?这里特别提醒

    4. MessageChannel 是什么,请参考HTML5 API 多端通信桥 MessageChannel 技术_森叶的博客-优快云博客 

    5. Electron 官方文档

助记解释

  1. MessageChannelMain 可以理解为一个独立的协程队列,提供的两个 port 之间互为对方的管道,port2 发送消息的队列会提取出来发给我 port2,同理 port2 的生产的消息也会发给我 port1,因此你在创建了MessageChannelMain 之后呢就可以开始生产消息了,但是你没 port1.start()时,port1.on("message",() => {}) 是不会被消费的。

  2. 上面这个结论就是如果你发多了消息,如果没有及时释放,应该都会存在这个队列里,如果一直不打开 port.start(),理论上内存会一直上涨,不打开 port.start()也是一个 bug 了,但是有可能,比如创建了之后,消费者因为什么原因没启动起来,只有生产者再发,就导致了内存溢出。

  3. MessageChannel 是 HTML5 API 的产物,只适合 Web 环境下的互相通信,不适合 Electon 进程级别的通信,所以 Electron 就搞了一个 MessageChannelMain ,这个可以在任意进程中来去自如。

问题

  1. 主进程创建了这个通信桥如何分发给其他各种进程?

    1. 渲染进程和工具进程(utility-process)

  2. 其他进程如何接收 port?

    1. 渲染进程、webview、utility-process(工具进程)

    2. 补充:MessageChannelMain 可以在渲染进程中进行传递,通过 ipcRender.send函数发送,但是 webview.send('channel', port); 是行不通的,提示 port 为复杂对象,在renderer 和 webview 之间无法实现拷贝

  3. 其他进程如何通过 port 收发信息?

渲染进程直接和 webview 标签的 preload.js 通信(该方案行不通,会只能在渲染进程和工具进程间随意传递

深度传递时,要注意 webview 加载完毕后,再发过去,不然可能导致没收到的尴尬问题

webview.send('channel', port); 是行不通的,提示 port 为复杂对象,在renderer 和 webview 之间无法实现拷贝;

完美解决方案:

利用主进程启动 Websocket 服务,直连所有渲染进程和 webview,实现端对端的消息互联互通,具体细节,请看我另外一篇文档

// 在主进程中
const { MessageChannelMain } = require('electron');

const channel = new MessageChannelMain();

// 这里意味着可以做一个定时轮询数据库操作 等到 webview 完成加载后再发过去
senderWebContents.once('did-finish-load', () => {
  senderWebContents.postMessage('channel', null, [channel.port1]);
});

containerWebContents.once('did-finish-load', () => {
  containerWebContents.postMessage('channel', null, [channel.port2]);
});

// 在sender渲染进程中
const { ipcRenderer } = require('electron');

ipcRenderer.on('channel', (event, ports) => {
  const port = ports[0];
  port.postMessage('Hello from sender!');
  port.on("message", (e) => {
    console.log("sender renderer receive message:", e.data);
  })
  //下面这个绝不能少
  port.start();
});

// 在container渲染进程中
const { ipcRenderer } = require('electron');

ipcRenderer.on('channel', (event, ports) => {
  const port = ports[0];
  const webview = document.querySelector('webview');
  webview.send('channel', port);
});

// 在webview的preload.js中
const { ipcRenderer } = require('electron');

ipcRenderer.on('channel', (event, port) => {
  port.on('message', (event) => {
    console.log(event.data);  // 打印 "Hello from sender!"
  });
  // 下面这个绝不能少
  port.start()
});

 utility-process 和 MessageChannelMain

Electron 工具进程utilityProcess 使用中遇到的坑点解决方案_森叶的博客-优快云博客

### Web端与Electron客户端之间的通信方法 在 Electron 应用程序中,Web 端(即渲染进程)与主进程(Main Process)之间的通信主要依赖于 IPC(Inter-Process Communication)。以下是几种常见的通信方式及其示例: --- #### 1. 使用 `ipcRenderer` `ipcMain` 这是最常用的进程间通信方式之一。通过 `ipcRenderer` 发送消息到主进程,并由 `ipcMain` 接收并处理这些消息。 ##### 示例代码 ```javascript // 渲染进程 (Web 页面) const { ipcRenderer } = require('electron'); // 向主进程发送消息 ipcRenderer.send('message-to-main', 'Hello from renderer!'); // 监听来自主进程的消息 ipcRenderer.on('response-from-main', (event, arg) => { console.log(arg); // 输出: Hello from main process! }); ``` ```javascript // 主进程 (Main.js) const { ipcMain } = require('electron'); // 监听来自渲染进程的消息 ipcMain.on('message-to-main', (event, arg) => { console.log(arg); // 输出: Hello from renderer! // 响应给渲染进程 event.reply('response-from-main', 'Hello from main process!'); }); ``` 这种通信方式基于事件驱动模型[^1],适用于需要实时交互的场景。 --- #### 2. 使用上下文隔离的方式进行通信 当使用 Vue 或 React 等现代前端框架时,推荐采用上下文隔离的方式来实现进程间的通信。这种方式不需要直接引入 `ipcRenderer`,而是利用预加载脚本(Preload Script)来接主进程渲染进程。 ##### 示例代码 ```javascript // Preload 脚本 (preload.js) const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('api', { sendMessage: (channel, data) => ipcRenderer.send(channel, data), onMessage: (channel, func) => ipcRenderer.on(channel, (_, args) => func(args)) }); ``` ```javascript // 渲染进程 (Vue/React 组件) window.api.sendMessage('message-to-main', 'Hello from isolated renderer'); window.api.onMessage('response-from-main', (data) => { console.log(data); // 输出: Hello from main process! }); ``` 这种方法的优在于它不会污染全局命名空间,适合现代化开发环境[^2]。 --- #### 3. 使用 `postMessage` 进行跨窗口通信 如果需要在一个 Electron 应用的不同窗口之间传递数据,则可以使用标准浏览器 API —— `postMessage` 来完成这一操作。 ##### 示例代码 ```javascript // 窗口 A 中向窗口 B 发送消息 let winB = new BrowserWindow({ webPreferences: {} }); winB.webContents.postMessage('message-to-window-b', 'Hello Window B!', '*'); ``` ```javascript // 窗口 B 中接收消息 window.addEventListener('message', (event) => { if (event.data === 'Hello Window B!') { console.log(event.data); } }); ``` 此方法特别适合多窗口架构的应用程序。 --- #### 4. 结合 HTTP 请求作为备用方案 对于某些复杂场景,也可以考虑让 Web 端通过 HTTP 协议调用本地服务接口的形式间接与主进程通信。例如,在主进程中启动一个小型 Express Server 并监听特定路径请求。 ##### 示例代码 ```javascript // Main.js (启动 HTTP 服务器) const express = require('express'); const app = express(); app.get('/send-message', (req, res) => { const message = req.query.message; mainWindow.webContents.send('http-response', message); res.sendStatus(200); }); app.listen(8080, () => { console.log('HTTP server is running...'); }); ``` ```javascript // Renderer.js (发起 AJAX 请求) fetch('http://localhost:8080/send-message?message=Hello%20from%20renderer') .then(() => console.log('Message sent via HTTP')); ``` 尽管这不是原生 IPC 的一部分,但在特殊情况下可能非常有用。 --- ### 总结 以上介绍了多种 Web 端与 Electron 客户端之间的通信手段,具体选择取决于实际需求以及项目的技术栈特。无论是传统的 `ipcRenderer/ipcMain` 方式还是更先进的上下文隔离技术,都能满足大多数应用场景的需求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

森叶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值