Home > Article > Web Front-end > Learn more about how Node.js and Electron communicate between processes
This article will explore the process communication principle between Node.js and Electron, introduce how electron does process communication, how nodejs's child_process and cluster do process communication, and understand the essence of process communication.
Why the front-end needs to understand process communication:
The front-end field is no longer just about writing pages that run in the browser, but also needs to know electron, nodejs, etc., and both technologies require mastery of process communication.
Nodejs is a runtime for js. Unlike browsers, it extends many APIs that encapsulate operating system capabilities, including process and thread-related APIs. Learning process APIs requires learning the interactions between processes. communication mechanism.
Electron is a desktop development solution based on chromium and nodejs. Its architecture is a main process and multiple rendering processes. Communication is also required between these two processes. You need to learn the process communication mechanism of electron. [Recommended study: "nodejs Tutorial"]
In this article, we will take a deeper look at process communication.
This article will explain the following knowledge points:
The code we have written must run on the operating system. In order to better utilize hardware resources, the operating system supports the concurrency of multiple programs and the allocation of hardware resources. The unit of allocation is Process, this process is the execution process of the program. For example, record which step the program has executed, which hardware resources have been applied for, which ports are occupied, etc.
The process includes the code to be executed, the data operated by the code, and the process control block PCB (Processing Control Block), because the program is the execution process of the code on the data set, and the status of the execution process and the resources applied for Need to be recorded in a data structure (PCB). So the process consists of code, data, PCB.
pcb records the pid, the executed code address, the status of the process (blocked, running, ready, etc.) as well as the semaphores, pipes, and message queues used for communication. and other data structures.
The process may be blocked from creation to continuous code execution to application for hardware resources (memory, hard disk files, network, etc.), and the process will eventually be destroyed after execution. . This is the life cycle of a process.
The process has exclusive access to the requested resources. Each process can only access its own resources. How do processes communicate with each other?
Because the available memory is different, different processes must communicate through an intermediate medium.
Semaphore
If it is a simple mark, represented by a number and placed in an attribute of the PCB, this is called Semaphore
, for example, locks can be implemented through semaphores.
This semaphore idea is often used when writing front-end code. For example, when implementing throttling, a mark variable must also be added.
Pipeline
But the semaphore cannot transfer specific data. You have to use other methods to transfer specific data. For example, we can communicate by reading and writing files, which is pipeline
. If it is a file in memory, it is called an anonymous pipe and has no file name. If it is a file on a real hard disk, it has a file name. , called named pipes.
The file needs to be opened first, then read and written, and then closed. This is also a characteristic of pipelines. Pipes are encapsulated based on the idea of files. They are called pipes because they can only be read by one process and written by one process. They are one-way (half-duplex). Moreover, the target process also needs to consume data synchronously, otherwise it will be blocked.
This pipe method is very simple to implement. It is a file reading and writing, but it can only be used to communicate between two processes and can only communicate synchronously. In fact, synchronous communication of pipes is also quite common, which is the pipe method of stream.
Message Queue
Pipeline implementation is simple, but synchronous communication is relatively limited. What if you want to make asynchronous communication? Just add a queue as a buffer. This is Message Queue
.
The message queue is also a communication between two processes, but it is not based on the file-based idea. Although it is also one-way, it has a certain asynchronous nature and can put a lot of messages and consume them all at once.
Shared memory
Pipelines and message queues are between two processes. What if there are multiple processes?
We can communicate in this way by applying for a memory that can be operated by multiple processes, called shared memory
. Each process can read and write data to this memory, which is relatively efficient.
Although shared memory is highly efficient and can be used for communication between multiple processes, it is not all good because multiple processes can read and write, so it is easy to get confused. You have to control the order yourself, such as through The semaphore (mark variable) of the process is controlled.
Shared memory is suitable for communication between multiple processes without going through an intermediate medium, so it is more efficient, but it is also more complicated to use.
The above mentioned are almost all the ways for local process communication. Why add a local one?
Process communication is ipc (Inter-Process Communication). The two processes may be on the same computer, or they may be on different networks. Computer process, so process communication methods are divided into two types:
Local procedure call LPC (local procedure call), remote procedure call RPC (remote procedure call).
Local procedure calls are the communication methods of semaphores, pipes, message queues, and shared memory we mentioned above. But if it is on the network, it must communicate through the network protocol. This is actually what we use There are many, such as http and websocket.
So, when someone mentions ipc, they are talking about process communication, which can be discussed in two ways: local and remote.
Remote ones are encapsulated based on network protocols, while local ones are encapsulated based on semaphores, pipes, message queues, and shared memory, such as electron and nodejs, which we will discuss next.
electron will first start the main process, then create a rendering process through BrowserWindow, and load the html page for rendering. The communication between the two processes is through the ipc API provided by electron.
ipcMain, ipcRenderer
The main process listens to events through the on method of ipcMain
import { ipcMain } from 'electron'; ipcMain.on('异步事件', (event, arg) => { event.sender.send('异步事件返回', 'yyy'); })
The rendering process passes ipcRenderer The on method listens to events and sends messages through send
import { ipcRenderer } from 'electron'; ipcRender.on('异步事件返回', function (event, arg) { const message = `异步消息: ${arg}` }) ipcRenderer.send('异步事件', 'xxx')
The api is relatively simple to use. This is an event-form api that is encapsulated by the c layer and then exposed to js.
We can think about what mechanism it is based on?
Obviously there is a certain degree of asynchronous nature, and it is communication between parent and child processes, so it is implemented in the form of a message queue.
remote
In addition to the API in the event form, electron also provides the API in the form of remote method invocation rmi (remote method invoke).
In fact, it is a further encapsulation of the message, that is, calling different methods according to the passed message. In form, it is just like calling the method of this process, but in fact, it is done by sending the message to another process. The form is essentially the same as ipcMain and ipcRenderer.
For example, in the rendering process, the BrowserWindow api that is only available in the main process is directly called through remote.
const { BrowserWindow } = require('electron').remote; let win = new BrowserWindow({ width: 800, height: 600 }); win.loadURL('https://github.com');
To summarize, the parent-child process communication method of electron is based on message queue encapsulation. There are two encapsulation forms. One is the event method, which is used through the api of ipcMain and ipcRenderer, and the other is further It is encapsulated into calls of different methods (rmi). The bottom layer is also based on messages. It executes remote methods but looks like executing local methods.
nodejs provides an API for creating processes, with two modules: child_process and cluster. Obviously, one is for the creation and communication of parent-child processes, and the other is for multiple processes.
child_process
child_process provides spawn, exec, execFile, and fork APIs, which are used for the creation of different processes:
spawn, exec
If you want to execute commands through the shell, use spawn or exec. Because generally executing commands requires a return value, the two APIs are different in the way they return values.
Spawn returns a stream, which is retrieved through the data event. exec is further divided into buffers, which is simpler to use, but may exceed 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); });
In fact, exec is encapsulated based on spwan and can be used in simple scenarios. Sometimes maxBuffer needs to be set.
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
In addition to executing commands, if you want to execute an executable file, use the execFile api:
const { execFile } = require('child_process'); const child = execFile('node', ['--version'], (error, stdout, stderr) => { if (error) { throw error; } console.log(stdout); });
fork
And if you want to execute js, then use fork:
const { fork } = require('child_process'); const xxxProcess = fork('./xxx.js'); xxxProcess.send('111111'); xxxProcess.on('message', sum => { res.end('22222'); });
Summary
A brief summary of the four APIs of child_process:
If you want to execute a shell command, use spawn and exec. spawn returns a stream, and exec further encapsulates it into a buffer. Except that exec sometimes needs to set maxBuffer, there is no difference.
If you want to execute an executable file, use execFile.
If you want to execute the js file, use 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 是开发中频繁遇到的需求,希望这篇文章能够帮大家梳理清楚从操作系统层到不同语言和运行时的封装层次的脉络。
原文地址:https://juejin.cn/post/6988484297485189127
作者:zxg_神说要有光
更多编程相关知识,请访问:编程视频!!
The above is the detailed content of Learn more about how Node.js and Electron communicate between processes. For more information, please follow other related articles on the PHP Chinese website!