이 글에서는 Node.js와 Electron 간의 프로세스 통신 원리를 살펴보고, Electron이 어떻게 통신을 처리하는지, nodejs의 child_process와 클러스터가 어떻게 통신을 처리하는지 소개하고, 프로세스 통신의 본질을 이해합니다.
프런트엔드가 프로세스 통신을 이해해야 하는 이유:
프론트엔드 필드는 더 이상 브라우저에서 실행되는 페이지 작성뿐만 아니라 전자, nodejs 등도 담당하며 두 기술 모두 마스터링이 필요합니다. 프로세스 커뮤니케이션.
nodejs는 브라우저와 달리 프로세스 및 스레드 관련 API를 포함하여 운영 체제 기능을 캡슐화하는 많은 API를 확장합니다. 프로세스 API를 학습하려면 프로세스 간 통신 메커니즘을 배워야 합니다.
electron은 chromium과 nodejs를 기반으로 하는 데스크톱 개발 솔루션입니다. 그 아키텍처는 주요 프로세스이며 여러 렌더링 프로세스도 필요합니다. 전자의 프로세스 통신 메커니즘을 배워야 합니다. [추천 학습: "nodejs Tutorial"]
이 글에서는 프로세스 커뮤니케이션에 대해 더 자세히 살펴보겠습니다.
이 기사에서는 다음 지식 포인트를 설명합니다.
우리가 작성한 코드는 하드웨어 리소스를 더 잘 활용하기 위해 운영 체제에서 실행되어야 하며, 운영 체제는 여러 프로그램의 동시성을 지원하고 하드웨어 자원의 사용, 할당 단위는 프로세스이고, 이 프로세스는 프로그램의 실행 프로세스입니다. 예를 들어, 프로그램이 어떤 단계를 실행했는지, 어떤 하드웨어 리소스가 적용되었는지, 어떤 포트가 사용되었는지 등을 기록합니다.
프로세스에는 실행될 코드, 코드에 의해 작동되는 데이터, 프로세스 제어 블록 PCB(Processing Control Block)가 포함됩니다. 왜냐하면 프로그램은 데이터 세트에 대한 코드의 실행 프로세스이고 프로세스의 상태이기 때문입니다. 실행 프로세스와 요청된 리소스는 데이터 구조(PCB)에 기록되어야 합니다. 따라서 프로세스는 코드, 데이터, PCB로 구성됩니다.
pcb는 pid, 실행된 코드 주소, 프로세스 상태(차단, 실행 중, 준비 등)는 물론 통신에 사용되는 세마포어, 파이프, 메시지 큐와 같은 데이터 구조를 기록합니다.
코드 생성부터 지속적인 실행, 하드웨어 리소스(메모리, 하드디스크 파일, 네트워크 등) 적용까지 프로세스가 중간에 차단될 수 있으며, 실행 후 결국 프로세스가 소멸됩니다. 이것이 프로세스의 생명주기입니다.
프로세스는 요청된 리소스에만 독점적입니다. 각 프로세스는 자신의 리소스에만 액세스할 수 있습니다. 그러면 프로세스는 어떻게 서로 통신합니까?
프로세스마다 사용 가능한 메모리가 다르기 때문에 중간 매체를 통해 통신해야 합니다.
Semaphore
숫자로 표시되고 PCB의 속성에 배치되는 간단한 표시인 경우 이를 Semaphore
라고 합니다. 신호량을 통해 구현됩니다. 信号量
,比如锁的实现就可以通过信号量。
这种信号量的思想我们写前端代码也经常用,比如实现节流的时候,也要加一个标记变量。
管道
但是信号量不能传递具体的数据啊,传递具体数据还得用别的方式。比如我们可以通过读写文件的方式来通信,这就是管道
,如果是在内存中的文件,叫做匿名管道,没有文件名,如果是真实的硬盘的文件,是有文件名的,叫做命名管道。
文件需要先打开,然后再读和写,之后再关闭,这也是管道的特点。管道是基于文件的思想封装的,之所以叫管道,是因为只能一个进程读、一个进程写,是单向的(半双工)。而且还需要目标进程同步的消费数据,不然就会阻塞住。
这种管道的方式实现起来很简单,就是一个文件读写,但是只能用在两个进程之间通信,只能同步的通信。其实管道的同步通信也挺常见的,就是 stream 的 pipe 方法。
消息队列
管道实现简单,但是同步的通信比较受限制,那如果想做成异步通信呢?加个队列做缓冲(buffer)不就行了,这就是消息队列
Pipeline
하지만 세마포어는 특정 데이터를 전송할 수 없으며 특정 데이터를 전송하려면 다른 방법을 사용해야 합니다. 예를 들어 파일을 읽고 쓰는 방식으로 통신할 수 있습니다. 이것이파이프라인
입니다. 메모리에 있는 파일이면 익명 파이프라고 하며 파일 이름이 없습니다. 실제 하드디스크에는 Named Pipe라는 파일이 있습니다. 🎜🎜파일을 먼저 열고 읽고 쓴 다음 다시 닫아야 하는 것도 파이프라인의 특징입니다. 파이프는 파일이라는 개념을 기반으로 캡슐화됩니다. 단방향(반이중)이므로 하나의 프로세스에서만 읽고 쓸 수 있기 때문에 파이프라고 합니다. 또한 대상 프로세스도 데이터를 동기식으로 소비해야 합니다. 그렇지 않으면 차단됩니다. 🎜🎜이 파이프라인 방법은 구현이 매우 간단하지만 파일 읽기 및 쓰기이지만 두 프로세스 간의 통신에만 사용할 수 있으며 동기식으로만 통신할 수 있습니다. 실제로 파이프의 동기식 통신도 매우 일반적이며 이는 스트림의 파이프 방식입니다. 🎜🎜🎜🎜Message Queue🎜🎜🎜🎜파이프라인 구현은 간단하지만 동기 통신은 상대적으로 제한적입니다. 큐를 버퍼로 추가하면 됩니다. 이것이 메시지 큐
입니다. 🎜🎜메시지 큐도 두 프로세스 간의 통신이지만 파일 기반 아이디어를 기반으로 하지 않지만 단방향이지만 어느 정도 비동기 특성을 가지며 많은 메시지를 넣어 모두 소비할 수 있습니다. 한 번. 🎜🎜🎜🎜공유 메모리🎜🎜🎜파이프라인과 메시지 큐가 두 프로세스 사이에 있으면 어떻게 될까요?
우리는 共享内存
라는 여러 프로세스에서 작동할 수 있는 메모리를 적용하여 이러한 방식으로 통신할 수 있습니다. 각 프로세스는 이 메모리에 데이터를 읽고 쓸 수 있으며 이는 상대적으로 효율적입니다.
공유 메모리는 효율적이고 여러 프로세스 간의 통신에 사용할 수 있지만 여러 프로세스가 읽고 쓸 수 있기 때문에 모두 좋은 것은 아니므로 혼동되기 쉽습니다. 제어할 프로세스의 세마포어(표시 변수)입니다.
공유 메모리는 중간 매체를 거치지 않고 여러 프로세스 간 통신에 적합하므로 더 효율적이지만 사용하기가 더 복잡합니다.
위에서 언급한 방법은 로컬 프로세스 통신을 위한 거의 모든 방법입니다. 로컬을 추가하는 이유는 무엇입니까?
프로세스 통신은 ipc(Inter-Process Communication)입니다. 두 프로세스는 한 컴퓨터에 속할 수도 있고 네트워크에 있는 다른 컴퓨터의 프로세스일 수도 있으므로 두 가지 처리 방법이 있습니다. 통신 :
로컬 프로시저 호출 LPC(로컬 프로시저 호출), 원격 프로시저 호출 RPC(원격 프로시저 호출).
로컬 프로시저 호출은 위에서 언급한 세마포어, 파이프, 메시지 대기열 및 공유 메모리의 통신 방법입니다. 그러나 네트워크에 있는 경우 실제로는 다음과 같은 네트워크 프로토콜을 통해 통신해야 합니다. http, 웹소켓.
그러므로 누군가 ipc를 언급한다면 로컬과 원격이라는 두 가지 방식으로 논의할 수 있는 프로세스 통신에 대해 이야기하는 것입니다.
원격은 네트워크 프로토콜을 기반으로 캡슐화되는 반면, 로컬은 세마포어, 파이프, 메시지 큐 및 공유 메모리(electron 및 nodejs와 같은)를 기반으로 캡슐화됩니다. 이에 대해서는 다음에 설명하겠습니다.
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 외에도 원격 메소드 호출 RMI(원격 메소드 호출) API도 제공합니다.
사실 이는 메시지를 더욱 캡슐화한 것입니다. 즉, 전달된 메시지에 따라 다른 메소드를 호출하는 것입니다. 형식적으로는 이 프로세스의 메소드를 호출하는 것과 같지만 실제로는 메시지를 전송하여 수행됩니다. 다른 프로세스로, ipcMain, ipcRenderer 형식은 본질적으로 동일합니다.
예를 들어 렌더링 프로세스에서 원격을 사용하여 기본 프로세스에서만 사용할 수 있는 BrowserWindow API를 직접 호출할 수 있습니다.
const { BrowserWindow } = require('electron').remote; let win = new BrowserWindow({ width: 800, height: 600 }); win.loadURL('https://github.com');
요컨대 전자의 상위-하위 프로세스 통신 방식은 메시지 큐 캡슐화를 기반으로 합니다. 하나는 ipcMain과 ipcRenderer의 API를 통해 사용되는 이벤트 방식입니다. 메소드 호출(rmi) 역시 하단의 메시지를 기반으로 하지만 원격 메소드를 실행하지만 로컬 메소드를 실행하는 것처럼 보입니다.
nodejs는 child_process와 Cluster라는 두 가지 모듈을 사용하여 프로세스 생성을 위한 API를 제공합니다. 분명히 하나는 상위-하위 프로세스의 생성 및 통신을 위한 것이고, 다른 하나는 다중 프로세스를 위한 것입니다.
child_process
child_process는 다양한 프로세스를 생성하는 데 사용되는 generate, exec, execFile 및 fork API를 제공합니다.
spawn, exec
셸을 통해 명령을 실행하려면 다음을 사용하세요. 생성 또는 실행. 일반적으로 명령을 실행하려면 반환 값이 필요하기 때문에 두 API는 값을 반환하는 방식이 다릅니다.
spawn은 데이터 이벤트를 통해 검색된 스트림을 반환하고 exec는 사용하기 더 간단하지만 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의 네 가지 API에 대한 간략한 요약:
셸 명령을 실행하려면 generate와 exec를 사용하여 스트림을 반환하고 exec는 버퍼에 추가로 캡슐화됩니다. exec가 때때로 maxBuffer를 설정해야 한다는 점을 제외하면 차이가 없습니다.
실행 파일을 실행하려면 execFile을 사용하세요.
js 파일을 실행하려면 포크를 사용하세요.
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 是开发中频繁遇到的需求,希望这篇文章能够帮大家梳理清楚从操作系统层到不同语言和运行时的封装层次的脉络。
原文地址:https://juejin.cn/post/6988484297485189127
作者:zxg_神说要有光
更多编程相关知识,请访问:编程视频!!
위 내용은 Node.js와 Electron이 프로세스 간 통신하는 방법에 대해 자세히 알아보세요.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!