LinDaiDai

V1

2022/05/30阅读:35主题:凝夜紫

大概是全网最详细的Electron ipc 讲解(三)——定情信物传声筒port

希沃ENOW大前端

公司官网:CVTE(广州视源股份)

团队:CVTE旗下未来教育希沃软件平台中心enow团队

本文作者:

前言

本系列共有以下几个章节:

  • 主进程与渲染进程的两情相悦
  • 渲染进程与渲染进程的搭桥牵线
  • 定情信物传声筒port

您此次阅读的是第三章节:定情信物传声筒port。

注:以上所有文章都被归档到: https://github.com/LinDaiDai/niubility-coding-js 中 ,案例都上传至:https://github.com/LinDaiDai/electron-ipc-example ,欢迎 Star,感谢 Star。

大纲

在之前的文章中,我们主要介绍了 ipcMainipcRenderer 是如何实现主进程与渲染进程、以及渲染进程与渲染进程进行通信的。大家不难发现,之前介绍的方式都非常依赖主进程,特别是渲染进程之间的通信,每次都需要主进程这个中间人来传话,难道就没有什么更简单点的方式吗?

咦,这还真有一个,那就是利用 MessagePort ,通俗易懂的翻译过来:消息端口...(啪,就你英语好是吧)

大家别急,还不知道是啥玩意的话,让我来给大家介绍一下。用个小故事来简单举个例子哈:

渲染进程一(某先生)和渲染进程二(某女士)通过媒人介绍认识,由于男俊女美且三观相符,很快两人就看对眼坠入爱河了,但是奈何工作地点不在一起,不得不异地。这刚认识还处于暧昧期的俩年轻人咋能忍住不联系呢。于是互相交换了手机号加上了wx,相亲结束后,每日通过手机互相联系,增进感情,不日,便确定关系,订婚,结婚,买房,生娃......停停停,给我回来。

咳咳咳,故事呢,其实到 "每日通过手机互相联系" 就结束了哈,后面自行脑补。在上面这则小故事中,媒人就是主进程,渲染进程一和二在最初,会通过主进程进行一个交换 port 的过程,后续都通过 port 来进行通信,不再依赖主进程了。

乍一看,是不是觉得这种通信方式比前面介绍的那些靠谱多了,而且还不需要通过主进程中继的性能开销。

讲完了故事,聊了个大概,让我们来看看本篇文章大纲吧:

  • MessagePort 的基础用法
  • 主进程与渲染进程通信案例分析
  • 渲染进程与渲染进程案例预告
image.png
image.png

1. MessagePort 的基础用法

1.1 如何创建 MessagePort

首先还是得先来看看 MessagePort 的基础用法。 MessagePort 对象的创建依赖于 MessageChannel 类:

const channel = new MessageChannel();
const port1 = channel.port1
const port2 = channel.port2

// 或者简写为:
const { port1, port2 } = new MessageChannel();

实例化 MessageChannel 类之后,就产生了两个 portport1 和 port2 。这两个 port 就是 MessagePort 对象。它就是我们上面那则故事提到的,可以用于两个进程之间进行长期通信的关键所在。

举个小例子,假设现在:

  • 渲染进程一有了 port1
  • 渲染进程二有了 port2

那么现在这两个进程就可以通过 port.onmessageport.postMessage 来收发彼此间的消息了:

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

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

只要 port1port2 一直都存在,它们就可以进行持久通信,怎么样,是不是很 niubility

OKK,那么现在如果是在渲染进程一创建的这两个 port ,关键就是如何把 port2 给到另一个渲染进程二了。也就涉及到了 MessagePort 的传递。

image.png
image.png

1.2 ipcRenderer.postMessage()

说到 MessagePort 的传递就得谈到 ipcRenderer 对象的 postMessage 方法了。因为 MessagePort 对象就是依靠它来传递。 没错,此时应该有小伙伴可能想起来了,我们平常网页上的 window 对象也有一个 postMessage 方法,这两者之间其实挺像的,只不过呢,是在不同的通道上。

portMessage 它的参数如下:

ipcRenderer.postMessage(channle, message, [transfer])

  • channel String:事件名
  • message any:要传递的消息
  • transfer MessagePort[] (optional):0个或多个 MessagePort 对象。

前两个好理解,其实和 ipcRenderer 的其它方法差不多,事件,以及传递的消息。第三个参数有些特别,它是一个数组,其中可以传递 MessagePort 对象。这里需要注意,别看第三个参数标记的是 optional ,但其实它也是需要传递的,如果你不需要传 MessagePort 对象,那么就需要定义一个空数组,否则就会报错啦。

另外,在之前的文章中,我们还有用到 ipcRenderer 的其它方法: sendinvokesendSync ,这三种方法主进程都是可以给渲染进程传递返回结果的,比如:

//  render.js
const replyMessage = await ipcRenderer.invoke('render-invoke-to-main''我是渲染进程通过 invoke 发送的消息');
console.log('replyMessage', replyMessage); // "我是主进程返回的消息"

postMessage 的第二个参数也可以发送消息,那它是否也可以当成 send 或者 invoke 来用呢?这里我测试了一下,发现主进程那边不论是用 event.reply 还是用 event.returnValue 都不行,看来,官方还是希望我们遵循: ”什么样的API就做什么样的事” ,而 ipcRenderer.postMessage ,他的主要职责就是用来发送 MessagePort 的。

并且! ipcRenderer.postMessage 只能通过 ipcMain.on 来接收到, ipcRenderer.on 是接收不到的!

这样的话,看来如果我们要将某个 port 从一个渲染进程给到另一个渲染进程还是得依靠主进程了,需要它这个 媒人 来从中做媒。但问题不大,一旦这两人连接上了,就不再需要媒人了。

同时我们发现,通过这种方式我们也可以实现渲染进程与主进程之间的互相通信了,主进程在收到 port 的时候,如果不给其他人,自己用来和渲染进程通信也可以呀。

image.png
image.png

2. 主进程与渲染进程通信案例分析

好嘞,扯了这么多,让我们先写个小 demo,来看看通过 MessagePort 主进程与渲染进程是如何通信的吧。

和之前一样,让我们确定下要做什么事:

  • 在某个时机,渲染进程创建了两个 port 并将其中一个(名为 port1)发送给了主进程
  • 渲染进程这边的另一个 port2 绑定监听事件
  • 主进程接收到 port1 并将它保存下来,同时也绑定监听事件
  • 在另一个时机渲染进程通过 port2 给主进程发送消息

下面是 demo 的时序图:

image.png
image.png

第一步、调整目录结构

由于这个案例说的是渲染进程与主进程的通信,让我们基于之前的分支 example-3 再新建一个 example-4,同时删除我们不用的窗口2,此时目录结构变为:

image.png
image.png

第二步、渲染进程提供生成 MessagePort 并发送给主进程的能力

之前提到了,做的第一件事:

  • 在某个时机,渲染进程创建了两个 port 并将其中一个(名为 port1)发送给了主进程

这里的某个时机,我们就在页面上定义两个按钮吧:

  1. 点第一个按钮创建并发送 port
  2. 点第二个按钮给主进程发送消息
// window-one/index.html
<body>
    <h1>Window One</h1>
    <button onclick="sendPortToMain()">窗口1 postMessage 给主进程发送消息端口 port1</button>
    <button onclick="sendMessageToMain()">窗口1 通过 port2 给主进程发送消息</button>
    <script src="./renderer.js"></script>
</body>

对应的渲染进程的代码:

// window-one/render.js
const { ipcRenderer } = require('electron')

let portToMain

function sendPortToMain({
  // 1、创建一对 port
  const { port1, port2 } = new MessageChannel()
  // 2、给主进程传输消息端口 por1
  ipcRenderer.postMessage(
    'render-post-message-to-main',
    '我是渲染进程一通过 ipcRenderer.postMessage 发送过来的',
    [port1],
  )
  // 3、把 port2 赋值给 portToMain,方便其他模块获取
  portToMain = port2
  // 4、port2 绑定事件监听,之后主进程发送的消息都会在这里接收到
  portToMain.onmessage = (event) => {
    const data = event.data
    console.log('[Renderer receive]message', data)
  }
}

function sendMessageToMain({
  portToMain.postMessage('我是渲染进程一通过传声筒 port 发送过来的')
}

在上面代码中,点击第一个按钮执行 sendPortToMain 方法,其中创建了一堆 port ,并将其中一个通过 ipcRenderer.postMessage 发送给了主进程,同时设置监听。

点击第二个按钮,到时候会执行 sendMessageToMain 方法,就可以利用 port 进行通信了。

第三步、主进程提供接收 port 和设置监听的能力

在第二步中,渲染进程发送了 port 给主进程,那么主进程这边肯定要设置一个地方去接收,接收后同时也要保证它和渲染进程后续能持续通信。

那么只需要如下处理:

// main/ipc.js
const { ipcMain } = require('electron')

// 1、主进程监听一个事件,渲染进程想要发送 port 的话,就能在这里获取到
ipcMain.on('render-post-message-to-main', (event, params) => {
  console.log('[Main receive]render-post-message-to-main', params)

  // 2、获取到 port1
  const port1 = event.ports[0]

  // 3、需要调用一下 port1 的 start()
  port1.start()

  // 4、port1 绑定事件监听,之后渲染进程一发送的消息都会在这里接收到
  port1.on('message', (event) => {
    const data = event.data
    console.log('[Main receive]message', data)

    port1.postMessage('我是主进程通过 port 回复的消息')
  })
})

(记得将 main/ipc.js 在主进程中引用一下哦)

// main/index.js
const ipc = require('./ipc')

// ...其他代码

在上面的代码中,我们先用 ipcMain 保证能接收到渲染进程发送过来的 port ,再调用 port1.start() ,然后给它绑定 message 事件,之后渲染进程一发送过来消息都能接收到,也能通过 port1 给渲染进程一发。

第四步、效果演示

一切代码准备就绪,让我们启动项目来看看效果。

分别点击窗口中的第一个和第二个按钮,能够看到主进程和渲染进程的打印日志:

image.png
image.png

(终端里主进程的打印中文会乱码,还请理解…)

image.png
image.png

3. 渲染进程与渲染进程案例预告

在看完了上面的案例之后,相信你对于这种用 postMessage 进行窗口间通信的方式有了一些了解。实际的开发场景中,我们可能还会进行渲染进程与渲染进程间的通信,甚至是同一个进程内部之间的通信。在后面的文章中,我们会介绍如何通过 postMessage 来实现一个比较通用的 electron ipc 通信的库,你也可以先利用上面的知识自己尝试着看看可以如何去写,敬请期待哦。

后语

这篇文章就介绍到这里。通过这三个章节的介绍,相信你对 ipc 大致的通信都有所了解了吧。希望在实际的开发中能够帮助到你。

喜欢「霖呆呆」的小伙还希望可以关注霖呆呆的公众号 LinDaiDai

我会不定时的更新一些前端方面的知识内容以及自己的原创文章🎉

你的鼓励就是我持续创作的主要动力 😊.

分类:

前端

标签:

JavaScript

作者介绍

LinDaiDai
V1

CVTE希沃