搜索
首页web前端js教程深入了解Node.js和Electron是如何做进程通信的

深入了解Node.js和Electron是如何做进程通信的

Jul 26, 2021 am 10:11 AM
electronnode.js进程通信

本篇文章给大家探究一下Node.js 和 Electron 的进程通信原理,介绍一下electron 如何做进程通信、nodejs 的 child_process 和 cluster 如何做进程通信,了解进程通信的本质。

深入了解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(&#39;hello world\n&#39;);
  })
  
  server.listen(8000);
  
  process.on(&#39;message&#39;, (msg) => {
    if (msg === &#39;shutdown&#39;) {
       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 是开发中频繁遇到的需求,希望这篇文章能够帮大家梳理清楚从操作系统层到不同语言和运行时的封装层次的脉络。

原文地址:https://juejin.cn/post/6988484297485189127

作者:zxg_神说要有光

更多编程相关知识,请访问:编程视频!!

以上是深入了解Node.js和Electron是如何做进程通信的的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:掘金--zxg_神说要有光。如有侵权,请联系admin@php.cn删除
Python vs. JavaScript:开发人员的比较分析Python vs. JavaScript:开发人员的比较分析May 09, 2025 am 12:22 AM

Python和JavaScript的主要区别在于类型系统和应用场景。1.Python使用动态类型,适合科学计算和数据分析。2.JavaScript采用弱类型,广泛用于前端和全栈开发。两者在异步编程和性能优化上各有优势,选择时应根据项目需求决定。

Python vs. JavaScript:选择合适的工具Python vs. JavaScript:选择合适的工具May 08, 2025 am 12:10 AM

选择Python还是JavaScript取决于项目类型:1)数据科学和自动化任务选择Python;2)前端和全栈开发选择JavaScript。Python因其在数据处理和自动化方面的强大库而备受青睐,而JavaScript则因其在网页交互和全栈开发中的优势而不可或缺。

Python和JavaScript:了解每个的优势Python和JavaScript:了解每个的优势May 06, 2025 am 12:15 AM

Python和JavaScript各有优势,选择取决于项目需求和个人偏好。1.Python易学,语法简洁,适用于数据科学和后端开发,但执行速度较慢。2.JavaScript在前端开发中无处不在,异步编程能力强,Node.js使其适用于全栈开发,但语法可能复杂且易出错。

JavaScript的核心:它是在C还是C上构建的?JavaScript的核心:它是在C还是C上构建的?May 05, 2025 am 12:07 AM

javascriptisnotbuiltoncorc; saninterpretedlanguagethatrunsonenginesoftenwritteninc.1)javascriptwasdesignedAsalightweight,解释edganguageforwebbrowsers.2)Enginesevolvedfromsimpleterterterpretpreterterterpretertestojitcompilerers,典型地提示。

JavaScript应用程序:从前端到后端JavaScript应用程序:从前端到后端May 04, 2025 am 12:12 AM

JavaScript可用于前端和后端开发。前端通过DOM操作增强用户体验,后端通过Node.js处理服务器任务。1.前端示例:改变网页文本内容。2.后端示例:创建Node.js服务器。

Python vs. JavaScript:您应该学到哪种语言?Python vs. JavaScript:您应该学到哪种语言?May 03, 2025 am 12:10 AM

选择Python还是JavaScript应基于职业发展、学习曲线和生态系统:1)职业发展:Python适合数据科学和后端开发,JavaScript适合前端和全栈开发。2)学习曲线:Python语法简洁,适合初学者;JavaScript语法灵活。3)生态系统:Python有丰富的科学计算库,JavaScript有强大的前端框架。

JavaScript框架:为现代网络开发提供动力JavaScript框架:为现代网络开发提供动力May 02, 2025 am 12:04 AM

JavaScript框架的强大之处在于简化开发、提升用户体验和应用性能。选择框架时应考虑:1.项目规模和复杂度,2.团队经验,3.生态系统和社区支持。

JavaScript,C和浏览器之间的关系JavaScript,C和浏览器之间的关系May 01, 2025 am 12:06 AM

引言我知道你可能会觉得奇怪,JavaScript、C 和浏览器之间到底有什么关系?它们之间看似毫无关联,但实际上,它们在现代网络开发中扮演着非常重要的角色。今天我们就来深入探讨一下这三者之间的紧密联系。通过这篇文章,你将了解到JavaScript如何在浏览器中运行,C 在浏览器引擎中的作用,以及它们如何共同推动网页的渲染和交互。JavaScript与浏览器的关系我们都知道,JavaScript是前端开发的核心语言,它直接在浏览器中运行,让网页变得生动有趣。你是否曾经想过,为什么JavaScr

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器