我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Node.js和Electron是怎么做进程通信的

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Node.js和Electron是怎么做进程通信的

这篇文章主要讲解了“Node.js和Electron是怎么做进程通信的”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Node.js和Electron是怎么做进程通信的”吧!

为什么前端要了解进程通信:

前端领域已经不是单纯写在浏览器里跑的页面就可以了,还要会 electron、nodejs 等,而这俩技术都需要掌握进程通信。

nodejs 是 js 的一个运行时,和浏览器不同,它扩展了很多封装操作系统能力的 api,其中就包括进程、线程相关 api,而学习进程 api 就要学习进程之间的通信机制。

electron 是基于 chromium 和 nodejs 的桌面端开发方案,它的架构是一个主进程,多个渲染进程,这两种进程之间也需要通信,要学习 electron 的进程通信机制。【推荐学习:《nodejs 教程》】

这篇文章我们就来深入了解一下进程通信。

本文会讲解以下知识点:

  • 进程是什么

  • 本地进程通信的四种方式

  • ipc、lpc、rpc 都是什么

  • electron 如何做进程通信

  • nodejs 的 child_process 和 cluster 如何做进程通信

  • 进程通信的本质

进程

我们写完的代码要在操作系统之上跑,操作系统为了更好的利用硬件资源,支持了多个程序的并发和硬件资源的分配,分配的单位就是进程,这个进程就是程序的执行过程。比如记录程序执行到哪一步了,申请了哪些硬件资源、占用了什么端口等。

进程包括要执行的代码、代码操作的数据,以及进程控制块 PCB(Processing Control Block),因为程序就是代码在数据集上的执行过程,而执行过程的状态和申请的资源需要记录在一个数据结构(PCB)里。所以进程由代码、数据、PCB 组成。

Node.js和Electron是怎么做进程通信的

pcb 中记录着 pid、执行到的代码地址、进程的状态(阻塞、运行、就绪等)以及用于通信的信号量、管道、消息队列等数据结构。

Node.js和Electron是怎么做进程通信的

进程从创建到代码不断的执行,到申请硬件资源(内存、硬盘文件、网络等),中间还可能会阻塞,最终执行完会销毁进程。这是一个进程的生命周期。

进程对申请来的资源是独占式的,每个进程都只能访问自己的资源,那进程之间怎么通信呢?

进程通信

不同进程之间因为可用的内存不同,所以要通过一个中间介质通信。

信号量

如果是简单的标记,通过一个数字来表示,放在 PCB 的一个属性里,这叫做信号量,比如锁的实现就可以通过信号量。

这种信号量的思想我们写前端代码也经常用,比如实现节流的时候,也要加一个标记变量。

管道

但是信号量不能传递具体的数据啊,传递具体数据还得用别的方式。比如我们可以通过读写文件的方式来通信,这就是管道,如果是在内存中的文件,叫做匿名管道,没有文件名,如果是真实的硬盘的文件,是有文件名的,叫做命名管道。

文件需要先打开,然后再读和写,之后再关闭,这也是管道的特点。管道是基于文件的思想封装的,之所以叫管道,是因为只能一个进程读、一个进程写,是单向的(半双工)。而且还需要目标进程同步的消费数据,不然就会阻塞住。

这种管道的方式实现起来很简单,就是一个文件读写,但是只能用在两个进程之间通信,只能同步的通信。其实管道的同步通信也挺常见的,就是 stream 的 pipe 方法。

消息队列

管道实现简单,但是同步的通信比较受限制,那如果想做成异步通信呢?加个队列做缓冲(buffer)不就行了,这就是消息队列

消息队列也是两个进程之间的通信,但是不是基于文件那一套思路,虽然也是单向的,但是有了一定的异步性,可以放很多消息,之后一次性消费。

共享内存

管道、消息队列都是两个进程之间的,如果多个进程之间呢?

我们可以通过申请一段多进程都可以操作的内存,叫做共享内存,用这种方式来通信。各进程都可以向该内存读写数据,效率比较高。

共享内存虽然效率高、也能用于多个进程的通信,但也不全是好处,因为多个进程都可以读写,那么就很容易乱,要自己控制顺序,比如通过进程的信号量(标记变量)来控制。

共享内存适用于多个进程之间的通信,不需要通过中间介质,所以效率更高,但是使用起来也更复杂。

上面说的这些几乎就是本地进程通信的全部方式了,为什么要加个本地呢?

ipc、rpc、lpc

进程通信就是 ipc(Inter-Process Communication),两个进程可能是一台计算机的,也可能网络上的不同计算机的进程,所以进程通信方式分为两种:

本地过程调用 LPC(local procedure call)、远程过程调用 RPC(remote procedure call)。

本地过程调用就是我们上面说的信号量、管道、消息队列、共享内存的通信方式,但是如果是网络上的,那就要通过网络协议来通信了,这个其实我们用的比较多,比如 http、websocket。

所以,当有人提到 ipc 时就是在说进程通信,可以分为本地的和远程的两种来讨论。

远程的都是基于网络协议封装的,而本地的都是基于信号量、管道、消息队列、共享内存封装出来的,比如我们接下来要探讨的 electron 和 nodejs。

electron 进程通信

electron 会先启动主进程,然后通过 BrowserWindow 创建渲染进程,加载 html 页面实现渲染。这两个进程之间的通信是通过 electron 提供的 ipc 的 api。

ipcMain、ipcRenderer

主进程里面通过 ipcMain 的 on 方法监听事件

import { ipcMain } from 'electron';

ipcMain.on('异步事件', (event, arg) => {
  event.sender.send('异步事件返回', 'yyy');
})

渲染进程里面通过  ipcRenderer 的 on 方法监听事件,通过 send 发送消息

import { ipcRenderer } from 'electron';

ipcRender.on('异步事件返回', function (event, arg) {
  const message = `异步消息: ${arg}`
})

ipcRenderer.send('异步事件', 'xxx')

api 使用比较简单,这是经过 c++ 层的封装,然后暴露给 js 的事件形式的 api。

我们可以想一下它是基于哪种机制实现的呢?

很明显有一定的异步性,而且是父子进程之间的通信,所以是消息队列的方式实现的。

remote

除了事件形式的 api 外,electron 还提供了远程方法调用 rmi (remote method invoke)形式的 api。

其实就是对消息的进一步封装,也就是根据传递的消息,调用不同的方法,形式上就像调用本进程的方法一样,但其实是发消息到另一个进程来做的,和 ipcMain、ipcRenderer 的形式本质上一样。

比如在渲染进程里面,通过 remote 来直接调用主进程才有的  BrowserWindow 的 api。

const { BrowserWindow } = require('electron').remote;

let win = new BrowserWindow({ width: 800, height: 600 });
win.loadURL('https://github.com');

小结一下,electron 的父子进程通信方式是基于消息队列封装的,封装形式有两种,一种是事件的方式,通过 ipcMain、ipcRenderer 的 api 使用,另一种则是进一步封装成了不同方法的调用(rmi),底层也是基于消息,执行远程方法但是看上去像执行本地方法一样。

nodejs

nodejs 提供了创建进程的 api,有两个模块: child_process 和 cluster。很明显,一个是用于父子进程的创建和通信,一个是用于多个进程。

child_process

child_process 提供了 spawn、exec、execFile、fork 的 api,分别用于不同的进程的创建:

spawn、exec

如果想通过 shell 执行命令,那就用 spawn 或者 exec。因为一般执行命令是需要返回值的,这俩 api 在返回值的方式上有所不同。

spawn 返回的是 stream,通过 data 事件来取,exec 进一步分装成了 buffer,使用起来简单一些,但是可能会超过 maxBuffer。

const { spawn } = require('child_process'); 

var app = spawn('node','main.js' {env:{}});

app.stderr.on('data',function(data) {
  console.log('Error:',data);
});

app.stdout.on('data',function(data) {
  console.log(data);
});

其实 exec 是基于 spwan 封装出来的,简单场景可以用,有的时候要设置下 maxBuffer。

const { exec } = require('child_process'); 

exec('find . -type f', { maxBuffer: 1024*1024 }(err, stdout, stderr) => { 
    if (err) { 
        console.error(`exec error: ${err}`); return; 
    }   
    console.log(stdout); 
});

execFile

除了执行命令外,如果要执行可执行文件就用 execFile 的 api:

const { execFile } = require('child_process'); 

const child = execFile('node', ['--version'], (error, stdout, stderr) => { 
    if (error) { throw error; } 
    console.log(stdout); 
});

fork

还有如果是想执行 js ,那就用 fork:

const { fork } = require('child_process');	

const xxxProcess = fork('./xxx.js');	
xxxProcess.send('111111');	
xxxProcess.on('message', sum => {	
    res.end('22222');	
});

小结

简单小结一下 child_process 的 4 个 api:

如果想执行 shell 命令,用 spawn 和 exec,spawn 返回一个 stream,而 exec 进一步封装成了 buffer。除了 exec 有的时候需要设置下 maxBuffer,其他没区别。

如果想执行可执行文件,用  execFile。

如果想执行 js 文件,用 fork。

child_process 的进程通信

说完了 api 我们来说下 child_process 创建的子进程怎么和父进程通信,也就是怎么做 ipc。

pipe

首先,支持了 pipe,很明显是通过管道的机制封装出来的,能同步的传输流的数据。

const { spawn } = require('child_process'); 

const find = spawn('cat', ['./aaa.js']);
const wc = spawn('wc', ['-l']);  find.stdout.pipe(wc.stdin);

比如上面通过管道把一个进程的输出流传输到了另一个进程的输入流,和下面的 shell 命令效果一样:

cat ./aaa.js | wc -l

message

spawn 支持 stdio 参数,可以设置和父进程的 stdin、stdout、stderr 的关系,比如指定 pipe 或者 null。还有第四个参数,可以设置 ipc,这时候就是通过事件的方式传递消息了,很明显,是基于消息队列实现的。

const { spawn } = require('child_process');

const child = spawn('node', ['./child.js'], {
    stdio: ['pipe', 'pipe', 'pipe', 'ipc'] 
}); 
child.on('message', (m) => { 
    console.log(m); 
}); 
child.send('xxxx');

而 fork 的 api 创建的子进程自带了 ipc 的传递消息机制,可以直接用。

const { fork } = require('child_process');	

const xxxProcess = fork('./xxx.js');	
xxxProcess.send('111111');	
xxxProcess.on('message', sum => {	
    res.end('22222');	
});

cluster

cluster 不再是父子进程了,而是更多进程,也提供了 fork 的 api。

比如 http server 会根据 cpu 数启动多个进程来处理请求。

import cluster from 'cluster';
import http from 'http';
import { cpus } from 'os';
import process from 'process';

const numCPUs = cpus().length;

if (cluster.isPrimary) {
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
} else {
  const server = http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  })
  
  server.listen(8000);
  
  process.on('message', (msg) => {
    if (msg === 'shutdown') {
       server.close();
    }
  });
}

它同样支持了事件形式的 api,用于多个进程之间的消息传递,因为多个进程其实也只是多个父子进程的通信,子进程之间不能直接通信,所以还是基于消息队列实现的。

共享内存

子进程之间通信还得通过父进程中转一次,要多次读写消息队列,效率太低了,就不能直接共享内存么?

现在 nodejs 还是不支持的,可以通过第三方的包 shm-typed-array 来实现,感兴趣可以看一下。

https://www.npmjs.com/package/shm-typed-array

总结

进程包括代码、数据和 PCB,是程序的一次执行的过程,PCB 记录着各种执行过程中的信息,比如分配的资源、执行到的地址、用于通信的数据结构等。

进程之间需要通信,可以通过信号量、管道、消息队列、共享内存的方式。

  • 信号量就是一个简单的数字的标记,不能传递具体数据。

  • 管道是基于文件的思想,一个进程写另一个进程读,是同步的,适用于两个进程。

  • 消息队列有一定的 buffer,可以异步处理消息,适用于两个进程。

  • 共享内存是多个进程直接操作同一段内存,适用于多个进程,但是需要控制访问顺序。

这四种是本地进程的通信方式,而网络进程则基于网络协议的方式也可以做进程通信。

进程通信叫做 ipc,本地的叫做 lpc,远程的叫 rpc。

其中,如果把消息再封装一层成具体的方法调用,叫做 rmi,效果就像在本进程执行执行另一个进程的方法一样。

electron 和 nodejs 都是基于上面的操作系统机制的封装:

  • elctron 支持 ipcMain 和 ipcRenderer 的消息传递的方式,还支持了 remote 的 rmi 的方式。

  • nodejs 有 child_process 和 cluster 两个模块和进程有关,child_process 是父子进程之间,cluster 是多个进程:

    • child_process 提供了用于执行 shell 命令的 spawn、exec,用于执行可执行文件的 execFile,用于执行 js 的 fork。提供了 pipe 和 message 两种 ipc 方式。

    • cluster 也提供了 fork,提供了 message 的方式的通信。

当然,不管封装形式是什么,都离不开操作系统提供的信号量、管道、消息队列、共享内存这四种机制。

ipc 是开发中频繁遇到的需求,希望这篇文章能够帮大家梳理清楚从操作系统层到不同语言和运行时的封装层次的脉络。

感谢各位的阅读,以上就是“Node.js和Electron是怎么做进程通信的”的内容了,经过本文的学习后,相信大家对Node.js和Electron是怎么做进程通信的这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Node.js和Electron是怎么做进程通信的

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Linux进程通信的方法是什么

本篇内容介绍了“Linux进程通信的方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Linux进程通信具体方法1.管道管道分为有名管
2023-06-28

android多进程通信的方法是什么

Android中多进程通信的方法有以下几种:1. 使用Messenger:Messenger是一种轻量级的进程间通信(IPC)机制,使用Handler和Message进行通信。它可以在不同进程之间传递Message对象,实现进程间的数据交换
2023-10-10

python进程间的通信机制是什么

本文小编为大家详细介绍“python进程间的通信机制是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“python进程间的通信机制是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。进程间通信表示进程之间的数
2023-07-05

Linux进程间通信的方式是什么

本篇内容主要讲解“Linux进程间通信的方式是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Linux进程间通信的方式是什么”吧!·进程间通信:操作系统为系统提供的用于实现进程间通信的方式进
2023-06-29

Python进程间的通信方式是什么

这篇文章主要介绍“Python进程间的通信方式是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Python进程间的通信方式是什么”文章能帮助大家解决问题。什么是进程的通信这里举一个例子接介绍通信
2023-06-29

golang进程间通信的方法是什么

Golang中可以使用多种方式进行进程间通信,包括:1. Channel(通道):Golang的Channel是一种用于在协程之间进行通信和同步的机制。使用Channel可以在不同的goroutine之间发送和接收数据。通过创建一个Chan
2023-08-23

Linux系统进程间的通信方式和原理是什么

这篇文章给大家介绍Linux系统进程间的通信方式和原理是什么,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。进程的概念进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就创建了一个进程,在这个过程中,伴随着资
2023-06-28

常见的Linux进程通信方法是什么

这篇文章主要为大家分析了常见的Linux进程通信方法是什么的相关知识点,内容详细易懂,操作细节合理,具有一定参考价值。如果感兴趣的话,不妨跟着跟随小编一起来看看,下面跟着小编一起深入学习“常见的Linux进程通信方法是什么”的知识吧。进程虽
2023-06-28

Linux系统进程通信的主要方式是什么

Linux系统进程通信的主要方式是什么,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。概览进程间通信常见方式如下:管道FIFO消息队列信号量共享内存UNXI域套接
2023-06-28

通信工程师级别是怎么划分的

  通信工程师考试全称通信专业技术人员职业水平考试,通信专业技术人员职业水平评价,纳入全国专业技术人员职业资格证书制度统一规划,分初级、中级和高级三个级别层次。  通信工程师初级、中级职业水平采用考试的方式评价;通信工程师高级职业水平实行考试与评审相结合的方式评价,具体办法另行制定。参加通信专业技术人员初级、中级职业水
通信工程师级别是怎么划分的
2024-04-19

JavaEE的进程,线程是什么和怎么创建线程

本篇内容介绍了“JavaEE的进程,线程是什么和怎么创建线程”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、认识进程、线程 1.1什么是进
2023-06-29

Android中的多进程通信怎么利用继承Binder类实现

Android中的多进程通信怎么利用继承Binder类实现?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。服务端代码,BinderService.java:首先继
2023-05-31

Java中怎么使用wait和notify实现线程间的通信

这篇“Java中怎么使用wait和notify实现线程间的通信”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Java中怎么使
2023-06-30

编程热搜

目录