Electron实战之进程间通信

news2024/12/24 9:47:19

进程间通信(IPC)并非仅限于 Electron,而是源自甚至早于 Unix 诞生的概念。尽管“进程间通信”这个术语的确创造于何时并不清楚,但将数据传递给另一个程序或进程的理念可以追溯至 1964 年,当时 Douglas McIlroy 在 Unix 的第三版(1973 年)中描述了 Unix 管道的概念。

We should have some ways of coupling programs like garden hose--screw in another segment when it becomes when it becomes necessary to massage data in another way.

例如,我们可以通过使用管道操作符(|)将一个程序的输出传递到另一个程序。

# 列出当前目录下的所有.ts文件
ls | grep .ts

在 Unix 系统中,管道只是 IPC 的一种形式,还有许多其他形式,比如信号、消息队列、信号量和共享内存。

一、ipcMain 和 ipcRenderer

与 Chromium 相同,Electron 使用进程间通信(IPC)来在进程之间进行通信,在介绍 Electron 进程间通信前,我们必须先认识一下 Electron 的 2 个模块。

  • ipcMain 是一个仅在主进程中以异步方式工作的模块,用于与渲染进程交换消息。
  • ipcRenderer 是一个仅在渲染进程中以异步方式工作的模块,用于与主进程交换消息。

ipcMain 和 ipcRenderer 是 Electron 中负责通信的两个主要模块。它们继承自 NodeJS 的 EventEmitter 模块。在 EventEmitter 中允许我们向指定 channel 发送消息。channel 是一个字符串,在 Electron 中 ipcMain 和 ipcRenderer 使用它来发出和接收事件/数据。

// 接受消息
// EventEmitter: ipcMain / ipcRenderer
EventEmitter.on("string", function callback(event, messsage) {});


// 发送消息
// EventEmitter: win.webContents / ipcRenderer
EventEmitter.send("string", "mydata");

二、渲染进程 -> 主进程

大多数情况下的通信都是从渲染进程到主进程,渲染进程依赖 ipcRenderer 模块给主进程发送消息,官方提供了三个方法:

  • ipcRenderer.send(channel, …args)
  • ipcRenderer.invoke(channel, …args)
  • ipcRenderer.sendSync(channel, …args)

channel 表示的就是事件名(消息名称), args 是参数。需要注意的是参数将使用结构化克隆算法进行序列化,就像浏览器的 window.postMessage 一样,因此不会包含原型链。发送函数、Promise、Symbol、WeakMap 或 WeakSet 将会抛出异常。

2.1 ipcRenderer.send

渲染进程通过 ipcRenderer.send 发送消息:

// render.js
import { ipcRenderer } from 'electron';


function sendMessageToMain() {
  ipcRenderer.send('my_channel', 'my_data');
}

主进程通过 ipcMain.on 来接收消息:

// main.js
import { ipcMain } from 'electron';


ipcMain.on('my_channel', (event, message) => {
  console.log(`receive message from render: ${message}`) 
})

请注意,如果使用 send 来发送数据,如果你的主进程需要回复消息,那么需要使用 event.replay 来进行回复:

// main.js
import { ipcMain } from 'electron';


ipcMain.on('my_channel', (event, message) => {
  console.log(`receive message from render: ${message}`)
  event.reply('reply', 'main_data')
})

同时,渲染进程需要进行额外的监听:

// renderer.js
ipcRenderer.on('reply', (event, message) => { 
  console.log('replyMessage', message);
})

2.2  ipcRenderer.invoke

渲染进程通过 ipcRenderer.invoke 发送消息:

// render.js
import { ipcRenderer } from 'electron';


async function invokeMessageToMain() {
  const replyMessage = await ipcRenderer.invoke('my_channel', 'my_data');
  console.log('replyMessage', replyMessage);
}

主进程通过 ipcMain.handle 来接收消息:

// main.js
import { ipcMain } from 'electron';
ipcMain.handle('my_channel', async (event, message) => {
  console.log(`receive message from render: ${message}`);
  return 'replay';
});

注意,渲染进程通过 ipcRenderer.invoke 发送消息后,invoke 的返回值是一个 Promise 。主进程回复消息需要通过 return 的方式进行回复,而 ipcRenderer 只需要等到 Promise resolve 即可获取到返回的值。

2.3 ipcRender.sendSync

渲染进程通过 ipcRender.sendSync 来发送消息:

// render.js
import { ipcRenderer } from 'electron';


async function sendSyncMessageToMain() {
  const replyMessage = await ipcRenderer.sendSync('my_channel', 'my_data');
  console.log('replyMessage', replyMessage);
}

主进程通过 ipcMain.on 来接收消息:

// main.js
import { ipcMain } from 'electron';
ipcMain.on('my_channel', async (event, message) => {
  console.log(`receive message from render: ${message}`);
  event.returnValue = 'replay';
});

注意,渲染进程通过 ipcRenderer.sendSync 发送消息后,主进程回复消息需要通过 e.returnValue 的方式进行回复,如果 event.returnValue 不为 undefined 的话,渲染进程会等待 sendSync 的返回值才执行后面的代码。

2.4 小结

上面我们介绍了从渲染进程到主进程的几个通信方法,总结如下。

  • ipcRenderer.send: 这个方法是异步的,用于从渲染进程向主进程发送消息。它发送消息后不会等待主进程的响应,而是立即返回,适合在不需要等待主进程响应的情况下发送消息。
  • ipcRenderer.sendSync: 与 ipcRenderer.send 不同,这个方法是同步的,也是用于从渲染进程向主进程发送消息,但是它会等待主进程返回响应。它会阻塞当前进程,直到收到主进程的返回值或者超时。
  • ipcRenderer.invoke: 这个方法也是用于从渲染进程向主进程发送消息,但是它是一个异步的方法,可以方便地在渲染进程中等待主进程返回 Promise 结果。相对于 send 和 sendSync,它更适合处理异步操作,例如主进程返回 Promise 的情况。

三、主进程 -> 渲染进程

主进程向渲染进程发送消息一种方式是当渲染进程通过 ipcRenderer.send、ipcRenderer.sendSync、ipcRenderer.invoke 向主进程发送消息时,主进程通过 event.replay、event.returnValue、return … 的方式进行发送。这种方式是被动的,需要等待渲染进程先建立消息推送机制,主进程才能进行回复。

其实除了上面说的几种被动接收消息的模式进行推送外,还可以通过 webContents 模块进行消息通信。

3.1 ipcMain 和 webContents

主进程使用 ipcMain 模块来监听来自渲染进程的事件,通过 event.sender.send() 方法向渲染进程发送消息。

// 主进程
import { ipcMain, BrowserWindow } from 'electron';


ipcMain.on('messageFromMain', (event, arg) => {
  event.sender.send('messageToRenderer', 'Hello from Main!');
});

3.2 BrowserWindow.webContents.send

BrowserWindow.webContents.send 可以在主进程中直接使用 BrowserWindow 对象的 webContents.send() 方法向渲染进程发送消息。

// 主进程
import { BrowserWindow } from 'electron';


const mainWindow = new BrowserWindow();
mainWindow.loadFile('index.html');


// 在某个事件或条件下发送消息
mainWindow.webContents.send('messageToRenderer', 'Hello from Main!');

3.3 小结

不管是通过 event.sender.send() 还是 BrowserWindow.webContents.send 的方式,如果你只是单窗口的数据通信,那么本质上是没什么差异的。但是如果你想要发送一些数据到特定的窗口,那么你可以直接使用 BrowserWindow.webContents.send 这种方式。

四、渲染进程 -> 渲染进程

默认情况下,渲染进程和渲染进程之间是无法直接进行通信的。

image.png

虽然说无法直接通信,但是还是有一些“曲线救国”的方式。

4.1 利用主进程作为中间人

首先,需要在主进程注册一个事件监听程序,监听来自渲染进程的事件:

// main.js


// window 1
function createWindow1 () {
  window1 = new BrowserWindow({width: 800,height: 600})
  window1.loadURL('window1.html')
  window1.on('closed', function () {
     window1 = null
  })
  return window1
}


// window 2
function createWindow2 () {
  window2 = new BrowserWindow({width: 800, height: 600})
  window2.loadURL('window2.html')
  window2.on('closed', function () {
    window2 = null
  })
  return window2
}


app.on('ready', () => {
  createWindow1();
  createWindow2();
  ipcMain.on('win1-msg', (event, arg) => {
    // 这条消息来自 window 1
    console.log("name inside main process is: ", arg); 
    // 发送给 window 2 的消息.
    window2.webContents.send( 'forWin2', arg );
  });
})

然后,在 window2 窗口建立一个监听事件:

ipcRenderer.on('forWin2', function (event, arg){
  console.log(arg);
});

这样,window1 发送的 win1-msg 事件,就可以传输到 window2:

ipcRenderer.send('win1-msg', 'msg from win1');

4.2 使用 MessagePort

上面的传输方式虽然可以实现渲染进程之间的通信,但是非常依赖主进程,写起来也比较麻烦,那有什么不依赖于主进程的方式嘛?那当然也是有的,那就是 MessagePort。

MessagePort 并不是 Electron 提供的能力,而是基于 MDN 的 Web 标准 API,这意味着它可以在渲染进程直接创建。同时 Electron 提供了 node.js 侧的实现,所以它也能在主进程创建。

接下来,我们将通过一个示例来描述如何通过 MessagePort 来实现渲染进程之间的通信。

4.2.1 主进程中创建 MessagePort

import { BrowserWindow, app, MessageChannelMain } from 'electron';


app.whenReady().then(async () => {
  // 创建窗口
  const mainWindow = new BrowserWindow({
    show: false,
    webPreferences: {
      contextIsolation: false,
      preload: 'preloadMain.js'
    }
  })


  const secondaryWindow = new BrowserWindow({
    show: false,
    webPreferences: {
      contextIsolation: false,
      preload: 'preloadSecondary.js'
    }
  })


  // 建立通道
  const { port1, port2 } = new MessageChannelMain()


  // webContents准备就绪后,使用postMessage向每个webContents发送一个端口。
  mainWindow.once('ready-to-show', () => {
    mainWindow.webContents.postMessage('port', null, [port1])
  })


  secondaryWindow.once('ready-to-show', () => {
    secondaryWindow.webContents.postMessage('port', null, [port2])
  })
})

实例化 MessageChannel 类之后,就产生了两个 port: port1 和 port2。接下来只要让 渲染进程1 拿到 port1、渲染进程2 拿到 port2,那么现在这两个进程就可以通过 port.onmessage 和 port.postMessage 来收发彼此间的消息了。如下:

// mainWindow
port1.onmessage = (event) => {
  console.log('received result:', event.data)
};
port1.postMessage('我是渲染进程一发送的消息');


// secondaryWindow
port2.onmessage = (event) => {
  console.log('received result:', event.data)
};
port2.postMessage('我是渲染进程二发送的消息');

4.2.2 渲染进程中获取 port

有了上面的知识,我们最重要的任务就是需要获取主进程中创建的 port 对象,要做的是在你的预加载脚本(preload.js)中通过 IPC 接收 port,并设置相应的监听器。

// preloadMain.js
// preloadSecondary.js
const { ipcRenderer } = require('electron')


ipcRenderer.on('port', e => {
  // 接收到端口,使其全局可用。
  window.electronMessagePort = e.ports[0]


  window.electronMessagePort.onmessage = messageEvent => {
    // 处理消息
  }
})

4.3 消息通信

通过上面的一些操作后,就可以在应用程序的任何地方调用 postMessage 方法向另一个渲染进程发送消息。

// mainWindow renderer.js
// 在 renderer 的任何地方都可以调用 postMessage 向另一个进程发送消息
window.electronMessagePort.postMessage('ping')

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1452134.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

centos中docker操作+安装配置django+mysql5.7并使用simpleui美化管理后台

一、安装docker 确保系统是CentOS 7并且内核版本高于3.10,可以通过uname -r命令查看内核版本。 更新系统软件包到最新版本,可以使用命令yum update -y。 安装必要的软件包,包括yum-utils、device-mapper-persistent-data和lvm2。使用命令yum install -y yum-utils devic…

【51单片机】如何【手搓】定时器寄存器配置【低8位TL0(low)】和【高8位TH0(high)】

前言 大家好吖,欢迎来到 YY 滴单片机系列 ,热烈欢迎! 本章主要内容面向接触过单片机的老铁 本文是【【51单片机】从零开始手把手带你【查手册】配置定时器,并完成小项目(定时器&中断的应用)】博…

C++,stl,常用排序算法,常用拷贝和替换算法

目录 1.常用排序算法 sort random_shuffle merge reverse 2.常用拷贝和替换算法 copy replace replace_if swap 1.常用排序算法 sort 默认从小到大排序 #include<bits/stdc.h> using namespace std;int main() {vector<int> v;v.push_back(1);v.push_ba…

cpp杂项知识点(一)

大小端验证 代码如下&#xff1a; #include <iostream> #include <stdio.h> #include <memory> #include <string.h> #include <string>using namespace std;void hexdump(void *pSrc, int len ) {unsigned char *line;int i;int thisline;in…

Java的集合框架和泛型

文章目录 集合框架什么是集合框架类和接口总览 集合框架的重要性背后所涉及的数据结构以及算法什么是数据结构容器背后对应的数据结构什么是算法 包装类基本数据类型和对应的包装类装箱和拆箱自动装箱和自动拆箱 泛型什么是泛型引出泛型语法泛型类泛型的上界(没有下界)泛型方法…

Vue2学习第三天

Vue2 学习第三天 1. 计算属性 computed 计算属性实现 定义&#xff1a;要用的属性不存在&#xff0c;要通过已有属性计算得来。 原理&#xff1a;底层借助了Objcet.defineproperty方法提供的getter和setter。 get函数什么时候执行&#xff1f; 初次读取时会执行一次。当依赖…

知识图谱:py2neo将csv文件导入neo4j

文章目录 安装py2neo创建节点-连线关系图导入csv文件删除重复节点并连接边 安装py2neo 安装python中的neo4j操作库&#xff1a;pip install py2neo 安装py2neo后我们可以使用其中的函数对neo4j进行操作。 图数据库Neo4j中最重要的就是结点和边&#xff08;关系&#xff09;&a…

数字经济政策 | ZF工作报告-60个文本词频

根据各省政府工作报告&#xff0c;参考金灿阳(2022)和陶长琪(2022)&#xff0c;借助Python软件&#xff0c;统计数字经济相关的关键词词频&#xff0c;分别记为数字经济政策词频A、数字经济政策词频B A文献参考 B文献参考 年度趋势 一、数据介绍 数据名称&#xff1a; 政府工…

OpenAI首个文生视频模型亮相,你觉得咋样?

2月16日凌晨&#xff0c;OpenAI再次扔出一枚深水炸弹&#xff0c;发布了首个文生视频模型Sora。据介绍&#xff0c;Sora可以直接输出长达60秒的视频&#xff0c;并且包含高度细致的背景、复杂的多角度镜头&#xff0c;以及富有情感的多个角色。 目前官网上已经更新了48个视频d…

QGIS004:【08图层工具箱】-导出到电子表格、提取图层范围

摘要&#xff1a;QGIS图层工具箱常用工具有导出到电子表格、提取图层范围等选项&#xff0c;本文介绍各选项的基本操作。 实验数据&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1ZK4_ShrQ5BsbyWfJ6fVW4A?pwdpiap 提取码&#xff1a;piap 一、导出到电子表格 工具…

集团企业大数据应用:突破痛点,释放数据价值

在数字经济日益崛起的背景下&#xff0c;集团企业以其管理范围广泛、业务领域多元化和分支机构复杂化的特性&#xff0c;在市场竞争中扮演着重要角色。为了维持和提升这种竞争力&#xff0c;大数据应用成为了集团企业不可或缺的战略工具。然而&#xff0c;在实际应用中&#xf…

图表示学习 Graph Representation Learning chapter1 引言

图表示学习 Graph Representation Learning chapter1 引言 前言1.1图的定义1.1.1多关系图1.1.2特征信息 1.2机器学习在图中的应用1.2.1 节点分类1.2.2 关系预测1.2.3 聚类和组织检测1.2.4 图分类、回归、聚类 前言 虽然我并不研究图神经网络&#xff0c;但是我认为图高效的表示…

javascript+css+html购物车案例

javascript代码部分主要实现三部分功能 1、商品数量增加&#xff08;减少&#xff09;同时小计增加&#xff08;减少&#xff09; 这部分主要是通过for循环给增加&#xff08;减少&#xff09;按钮绑定点击事件 1&#xff09;点击后计数器自增&#xff08;自减&#xff09;…

【深度学习:开源数据注释】开源数据注释完整指南

【深度学习&#xff1a;Automated Data Annotation】开源数据注释完整指南 什么是开源数据标注工具&#xff1f;您会使用开源标签工具做什么&#xff1f;主要的开源数据标注工具有哪些&#xff1f;CVATMONAI LabelLabelMeRIL-ContourSefexa 使用开源注释工具的优点和缺点是什么…

Sora:将文本转化为视频的创新之旅

一.能力 我们正致力于让 AI 掌握理解和模拟物理世界动态的能力&#xff0c;旨在培养能够协助人们解决现实世界互动问题的模型。 介绍 Sora——我们开发的文本到视频转换模型。Sora 能够根据用户的输入提示&#xff0c;生成最长达一分钟的高质量视频内容。 目前&#xff0c;Sora…

Gitee入门之工具的安装

一、gitee是什么&#xff1f; Gitee&#xff08;码云&#xff09;是由开源中国社区在2013年推出的一个基于Git的代码托管平台&#xff0c;它提供中国本土化的代码托管服务。它旨在为个人、团队和企业提供稳定、高效、安全的云端软件开发协作平台&#xff0c;具备代码质量分析、…

揭开Markdown的秘籍:引用|代码块|超链接

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;Markdown指南、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️Markdown 引用1.1 &#x1f514;引用1.2 &#x1f514;嵌套引用1.3 &…

【Python---六大数据结构】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;Python &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; Python---六大数据结构 往期内容前言概述一下可变与不可变 Number四种不同的数值类型Number类型的创建i…

【Spring篇】Spring的创建与使用

目录 一 . 创建Spring项目 二 . Bean 对象存放到 Spring 三 . 从Spring中读到Bean 经过前⾯的学习我们已经知道了&#xff0c;Spring 就是⼀个包含了众多⼯具⽅法的 IoC 容器。既然是容器那么 它就具备两个最基本的功能&#xff1a; 将对象存储到容器&#xff08;Spring&am…

optee CA/TA flow

以 TEEC_InvokeCommand 为例 CA—normal world EL0 //imx-optee-client\libteec\src\tee_client_api.c TEEC_InvokeCommandioctl(session->ctx->fd, TEE_IOC_INVOKE, &buf_data)通过syscall陷入内核态driver linux driver—normal world EL1 tee_ioctl // drive…