
Electron 如何用 MessagePort 在主进程与渲染进程间传输消息与可转移对象【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron在 Electron 应用里当你需要把MessagePort交给对方进程、让两个进程之间走一条独立的通信信道甚至实现“一次请求、多次回复”的回包流时会遇到一个硬性限制常规的 IPC 方法send和invoke无法携带MessagePort只有postMessage系列方法可以传输MessagePort。本文基于 MessagePorts in Electron 教程与 MessagePortMain、MessageChannelMain、ipcRenderer、WebContents 的 API 文档给出从建信道、发端口到接收验证的完整操作路径。先弄清哪些方法能传输 MessagePortMessagePort是 Web 特性它类似window.postMessage但工作在独立信道上。MessagePort总是成对创建一对相连的端口称为 channel信道。在渲染进程中MessagePort类与 Web 上的行为完全一致。主进程不是网页没有 Blink 集成因此没有MessagePort或MessageChannel类。Electron 为此新增了两个主进程类MessagePortMain主进程侧的MessagePort等价物使用 Node.js 的EventEmitter事件系统所以监听消息要用port.on(message, ...)而不是port.onmessage ...MessageChannelMain唯一作用是创建一对相连的MessagePortMain通过channel.port1和channel.port2属性访问。端口传递方向与到达形式发送方法发送方端口在接收方的形式ipcRenderer.postMessage(channel, message, [transfer])渲染进程主进程中通过事件的event.ports属性获得MessagePortMainwebContents.postMessage(channel, message, [transfer])主进程渲染进程中为原生 DOMMessagePort对象同样通过event.ports访问两个注意点常规的send、invoke等方法不能用来传输MessagePort只能用postMessage方法MessagePortMain不从electron模块导出它只能作为其他方法如MessageChannelMain的返回值获得。路径一渲染进程把端口交给主进程渲染进程创建信道用ipcRenderer.postMessage把其中一端端口发给主进程// renderer.js (Renderer Process) // MessagePorts are created in pairs. A connected pair of message ports is // called a channel. const channel new MessageChannel() // The only difference between port1 and port2 is in how you use them. Messages // sent to port1 will be received by port2 and vice-versa. const port1 channel.port1 const port2 channel.port2 // Its OK to send a message on the channel before the other end has registered // a listener. Messages will be queued until a listener is registered. port2.postMessage({ answer: 42 }) // Here we send the other end of the channel, port1, to the main process. ipcRenderer.postMessage(port, null, [port1])主进程在 IPC 事件里取到端口。文档示例中到达的event.data是{ answer: 42 }文档示例值下同// main.js (Main Process) // In the main process, we receive the port. ipcMain.on(port, (event) { // When we receive a MessagePort in the main process, it becomes a // MessagePortMain. const port event.ports[0] // MessagePortMain uses the Node.js-style events API, rather than the // web-style events API. So .on(message, ...) instead of .onmessage ... port.on(message, (event) { // data is { answer: 42 } const data event.data }) // MessagePortMain queues messages until the .start() method has been called. port.start() })这里有两个容易踩坑的点主进程收到的是MessagePortMain事件风格是 Node.js 的EventEmitterport.on(message, ...)不是 Web 风格的onmessage赋值。MessagePortMain在调用port.start()之前会把消息排队不调start()消息不会派发。反过来在另一端注册监听器之前就可以先发消息消息会排队等待。postMessage的transfer参数只用于转移端口ipcRenderer.postMessage的transfer类型是MessagePort[]webContents.postMessage的是MessagePortMain[]。消息体本身走结构化克隆可以是任意可序列化对象——文档的 worker 示例中明确提到事件数据可以是任意可序列化对象并且事件里还能携带其他MessagePort。路径二主进程把端口发给渲染进程主进程用MessageChannelMain建信道用webContents.postMessage把一端发过去// Main process const { BrowserWindow, MessageChannelMain } require(electron) const w new BrowserWindow() const { port1, port2 } new MessageChannelMain() w.webContents.postMessage(port, null, [port2]) port1.postMessage({ some: message })// Renderer process const { ipcRenderer } require(electron) ipcRenderer.on(port, (e) { // e.ports is a list of ports sent along with this message e.ports[0].onmessage (messageEvent) { console.log(messageEvent.data) } })端口到达渲染进程后就是原生 DOMMessagePort可以直接用 Web 风格的onmessage接收。主进程保留的port1用MessagePortMain.postMessage(message, [transfer])发消息该方法同样支持转移对象的所有权。回包流一次请求、多次回复内置 IPC 只支持两种模式即发即忘send和请求-响应invoke。用MessageChannel可以实现“回包流”一次请求对应一串回复。渲染进程每次请求新建一个信道信道很轻量每次请求建一个开销很小把一端发给主进程自己留另一端接收回复和结束信号// renderer.js (Renderer Process) const makeStreamingRequest (element, callback) { // MessageChannels are lightweight--its cheap to create a new one for each // request. const { port1, port2 } new MessageChannel() // We send one end of the port to the main process ... ipcRenderer.postMessage( give-me-a-stream, { element, count: 10 }, [port2] ) // ... and we hang on to the other end. port1.onmessage (event) { callback(event.data) } port1.onclose () { console.log(stream ended) } } makeStreamingRequest(42, (data) { console.log(got response data:, data) }) // We will see got response data: 42 10 times.主进程端从event.ports取到回复端口把结果逐条postMessage出去发完后close()// main.js (Main Process) ipcMain.on(give-me-a-stream, (event, msg) { // The renderer has sent us a MessagePort that it wants us to send our // response over. const [replyPort] event.ports // Here we send the messages synchronously, but we could just as easily store // the port somewhere and send messages asynchronously. for (let i 0; i msg.count; i) { replyPort.postMessage(msg.element) } // We close the port when were done to indicate to the other end that we // wont be sending any more messages. replyPort.close() })按文档示例运行结果应看到 10 次got response data: 42流结束时打印stream ended。close()不是严格必需的——如果显式关闭端口最终会被垃圾回收同样会触发对端的close事件显式关闭只是明确告知对端不再发送。结果验证验证方式就是观察各端控制台输出以上均为文档示例给出的预期输出基础路径主进程在port.on(message, ...)回调里读到data为{ answer: 42 }主→渲染路径渲染进程onmessage回调里console.log(messageEvent.data)打印主进程发来的对象回包流10 行got response data: 42加一次stream ended信道断开close事件在信道另一端关闭或被垃圾回收而隐式关闭时触发。这是 Electron 在MessagePort上添加的、Web 上不存在的事件渲染进程里可以用port.onclose或port.addEventListener(close, ...)监听主进程里用port.on(close, ...)。限制与注意事项只有postMessage系列方法ipcRenderer.postMessage、webContents.postMessage以及event.senderFrame.postMessage能传输MessagePortsend、invoke不行。worker 示例里主进程之所以用mainFrame.ipc.on监听、用senderFrame.postMessage回复就是因为ipcMain.handle的回复路径无法转移MessagePort。主进程侧的MessagePortMain必须调用start()后才派发排队的消息渲染进程侧的 DOMMessagePort则是注册监听后直接接收。MessagePortMain不从electron模块导出只能从MessageChannelMain等 API 获得。Electron 的内置类不能在你的代码中被继承见 FAQ。端口可以被垃圾回收而隐式关闭两端都会收到close事件。教程中还给出了几个与本文同一机制的进阶用例可按需阅读主进程作为中继让两个渲染进程互发消息、用隐藏窗口充当 worker 进程直接通信、以及开启 context isolation 时把端口送入页面主世界MessagePorts in Electron。【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考