溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Node.js和Electron是怎么做進(jìn)程通信的

發(fā)布時(shí)間:2021-07-27 10:55:42 來源:億速云 閱讀:391 作者:chen 欄目:web開發(fā)

這篇文章主要講解了“Node.js和Electron是怎么做進(jìn)程通信的”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Node.js和Electron是怎么做進(jìn)程通信的”吧!

為什么前端要了解進(jìn)程通信:

前端領(lǐng)域已經(jīng)不是單純寫在瀏覽器里跑的頁(yè)面就可以了,還要會(huì) electron、nodejs 等,而這倆技術(shù)都需要掌握進(jìn)程通信。

nodejs 是 js 的一個(gè)運(yùn)行時(shí),和瀏覽器不同,它擴(kuò)展了很多封裝操作系統(tǒng)能力的 api,其中就包括進(jìn)程、線程相關(guān) api,而學(xué)習(xí)進(jìn)程 api 就要學(xué)習(xí)進(jìn)程之間的通信機(jī)制。

electron 是基于 chromium 和 nodejs 的桌面端開發(fā)方案,它的架構(gòu)是一個(gè)主進(jìn)程,多個(gè)渲染進(jìn)程,這兩種進(jìn)程之間也需要通信,要學(xué)習(xí) electron 的進(jìn)程通信機(jī)制?!就扑]學(xué)習(xí):《nodejs 教程》】

這篇文章我們就來深入了解一下進(jìn)程通信。

本文會(huì)講解以下知識(shí)點(diǎn):

  • 進(jìn)程是什么

  • 本地進(jìn)程通信的四種方式

  • ipc、lpc、rpc 都是什么

  • electron 如何做進(jìn)程通信

  • nodejs 的 child_process 和 cluster 如何做進(jìn)程通信

  • 進(jìn)程通信的本質(zhì)

進(jìn)程

我們寫完的代碼要在操作系統(tǒng)之上跑,操作系統(tǒng)為了更好的利用硬件資源,支持了多個(gè)程序的并發(fā)和硬件資源的分配,分配的單位就是進(jìn)程,這個(gè)進(jìn)程就是程序的執(zhí)行過程。比如記錄程序執(zhí)行到哪一步了,申請(qǐng)了哪些硬件資源、占用了什么端口等。

進(jìn)程包括要執(zhí)行的代碼、代碼操作的數(shù)據(jù),以及進(jìn)程控制塊 PCB(Processing Control Block),因?yàn)槌绦蚓褪谴a在數(shù)據(jù)集上的執(zhí)行過程,而執(zhí)行過程的狀態(tài)和申請(qǐng)的資源需要記錄在一個(gè)數(shù)據(jù)結(jié)構(gòu)(PCB)里。所以進(jìn)程由代碼、數(shù)據(jù)、PCB 組成。

Node.js和Electron是怎么做進(jìn)程通信的

pcb 中記錄著 pid、執(zhí)行到的代碼地址、進(jìn)程的狀態(tài)(阻塞、運(yùn)行、就緒等)以及用于通信的信號(hào)量、管道、消息隊(duì)列等數(shù)據(jù)結(jié)構(gòu)。

Node.js和Electron是怎么做進(jìn)程通信的

進(jìn)程從創(chuàng)建到代碼不斷的執(zhí)行,到申請(qǐng)硬件資源(內(nèi)存、硬盤文件、網(wǎng)絡(luò)等),中間還可能會(huì)阻塞,最終執(zhí)行完會(huì)銷毀進(jìn)程。這是一個(gè)進(jìn)程的生命周期。

進(jìn)程對(duì)申請(qǐng)來的資源是獨(dú)占式的,每個(gè)進(jìn)程都只能訪問自己的資源,那進(jìn)程之間怎么通信呢?

進(jìn)程通信

不同進(jìn)程之間因?yàn)榭捎玫膬?nèi)存不同,所以要通過一個(gè)中間介質(zhì)通信。

信號(hào)量

如果是簡(jiǎn)單的標(biāo)記,通過一個(gè)數(shù)字來表示,放在 PCB 的一個(gè)屬性里,這叫做信號(hào)量,比如鎖的實(shí)現(xiàn)就可以通過信號(hào)量。

這種信號(hào)量的思想我們寫前端代碼也經(jīng)常用,比如實(shí)現(xiàn)節(jié)流的時(shí)候,也要加一個(gè)標(biāo)記變量。

管道

但是信號(hào)量不能傳遞具體的數(shù)據(jù)啊,傳遞具體數(shù)據(jù)還得用別的方式。比如我們可以通過讀寫文件的方式來通信,這就是管道,如果是在內(nèi)存中的文件,叫做匿名管道,沒有文件名,如果是真實(shí)的硬盤的文件,是有文件名的,叫做命名管道。

文件需要先打開,然后再讀和寫,之后再關(guān)閉,這也是管道的特點(diǎn)。管道是基于文件的思想封裝的,之所以叫管道,是因?yàn)橹荒芤粋€(gè)進(jìn)程讀、一個(gè)進(jìn)程寫,是單向的(半雙工)。而且還需要目標(biāo)進(jìn)程同步的消費(fèi)數(shù)據(jù),不然就會(huì)阻塞住。

這種管道的方式實(shí)現(xiàn)起來很簡(jiǎn)單,就是一個(gè)文件讀寫,但是只能用在兩個(gè)進(jìn)程之間通信,只能同步的通信。其實(shí)管道的同步通信也挺常見的,就是 stream 的 pipe 方法。

消息隊(duì)列

管道實(shí)現(xiàn)簡(jiǎn)單,但是同步的通信比較受限制,那如果想做成異步通信呢?加個(gè)隊(duì)列做緩沖(buffer)不就行了,這就是消息隊(duì)列。

消息隊(duì)列也是兩個(gè)進(jìn)程之間的通信,但是不是基于文件那一套思路,雖然也是單向的,但是有了一定的異步性,可以放很多消息,之后一次性消費(fèi)。

共享內(nèi)存

管道、消息隊(duì)列都是兩個(gè)進(jìn)程之間的,如果多個(gè)進(jìn)程之間呢?

我們可以通過申請(qǐng)一段多進(jìn)程都可以操作的內(nèi)存,叫做共享內(nèi)存,用這種方式來通信。各進(jìn)程都可以向該內(nèi)存讀寫數(shù)據(jù),效率比較高。

共享內(nèi)存雖然效率高、也能用于多個(gè)進(jìn)程的通信,但也不全是好處,因?yàn)槎鄠€(gè)進(jìn)程都可以讀寫,那么就很容易亂,要自己控制順序,比如通過進(jìn)程的信號(hào)量(標(biāo)記變量)來控制。

共享內(nèi)存適用于多個(gè)進(jìn)程之間的通信,不需要通過中間介質(zhì),所以效率更高,但是使用起來也更復(fù)雜。

上面說的這些幾乎就是本地進(jìn)程通信的全部方式了,為什么要加個(gè)本地呢?

ipc、rpc、lpc

進(jìn)程通信就是 ipc(Inter-Process Communication),兩個(gè)進(jìn)程可能是一臺(tái)計(jì)算機(jī)的,也可能網(wǎng)絡(luò)上的不同計(jì)算機(jī)的進(jìn)程,所以進(jìn)程通信方式分為兩種:

本地過程調(diào)用 LPC(local procedure call)、遠(yuǎn)程過程調(diào)用 RPC(remote procedure call)。

本地過程調(diào)用就是我們上面說的信號(hào)量、管道、消息隊(duì)列、共享內(nèi)存的通信方式,但是如果是網(wǎng)絡(luò)上的,那就要通過網(wǎng)絡(luò)協(xié)議來通信了,這個(gè)其實(shí)我們用的比較多,比如 http、websocket。

所以,當(dāng)有人提到 ipc 時(shí)就是在說進(jìn)程通信,可以分為本地的和遠(yuǎn)程的兩種來討論。

遠(yuǎn)程的都是基于網(wǎng)絡(luò)協(xié)議封裝的,而本地的都是基于信號(hào)量、管道、消息隊(duì)列、共享內(nèi)存封裝出來的,比如我們接下來要探討的 electron 和 nodejs。

electron 進(jìn)程通信

electron 會(huì)先啟動(dòng)主進(jìn)程,然后通過 BrowserWindow 創(chuàng)建渲染進(jìn)程,加載 html 頁(yè)面實(shí)現(xiàn)渲染。這兩個(gè)進(jìn)程之間的通信是通過 electron 提供的 ipc 的 api。

ipcMain、ipcRenderer

主進(jìn)程里面通過 ipcMain 的 on 方法監(jiān)聽事件

import { ipcMain } from 'electron';

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

渲染進(jìn)程里面通過  ipcRenderer 的 on 方法監(jiān)聽事件,通過 send 發(fā)送消息

import { ipcRenderer } from 'electron';

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

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

api 使用比較簡(jiǎn)單,這是經(jīng)過 c++ 層的封裝,然后暴露給 js 的事件形式的 api。

我們可以想一下它是基于哪種機(jī)制實(shí)現(xiàn)的呢?

很明顯有一定的異步性,而且是父子進(jìn)程之間的通信,所以是消息隊(duì)列的方式實(shí)現(xiàn)的。

remote

除了事件形式的 api 外,electron 還提供了遠(yuǎn)程方法調(diào)用 rmi (remote method invoke)形式的 api。

其實(shí)就是對(duì)消息的進(jìn)一步封裝,也就是根據(jù)傳遞的消息,調(diào)用不同的方法,形式上就像調(diào)用本進(jìn)程的方法一樣,但其實(shí)是發(fā)消息到另一個(gè)進(jìn)程來做的,和 ipcMain、ipcRenderer 的形式本質(zhì)上一樣。

比如在渲染進(jìn)程里面,通過 remote 來直接調(diào)用主進(jìn)程才有的  BrowserWindow 的 api。

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

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

小結(jié)一下,electron 的父子進(jìn)程通信方式是基于消息隊(duì)列封裝的,封裝形式有兩種,一種是事件的方式,通過 ipcMain、ipcRenderer 的 api 使用,另一種則是進(jìn)一步封裝成了不同方法的調(diào)用(rmi),底層也是基于消息,執(zhí)行遠(yuǎn)程方法但是看上去像執(zhí)行本地方法一樣。

nodejs

nodejs 提供了創(chuàng)建進(jìn)程的 api,有兩個(gè)模塊: child_process 和 cluster。很明顯,一個(gè)是用于父子進(jìn)程的創(chuàng)建和通信,一個(gè)是用于多個(gè)進(jìn)程。

child_process

child_process 提供了 spawn、exec、execFile、fork 的 api,分別用于不同的進(jìn)程的創(chuàng)建:

spawn、exec

如果想通過 shell 執(zhí)行命令,那就用 spawn 或者 exec。因?yàn)橐话銏?zhí)行命令是需要返回值的,這倆 api 在返回值的方式上有所不同。

spawn 返回的是 stream,通過 data 事件來取,exec 進(jìn)一步分裝成了 buffer,使用起來簡(jiǎn)單一些,但是可能會(huì)超過 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);
});

其實(shí) exec 是基于 spwan 封裝出來的,簡(jiǎn)單場(chǎng)景可以用,有的時(shí)候要設(shè)置下 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

除了執(zhí)行命令外,如果要執(zhí)行可執(zhí)行文件就用 execFile 的 api:

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

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

fork

還有如果是想執(zhí)行 js ,那就用 fork:

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

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

小結(jié)

簡(jiǎn)單小結(jié)一下 child_process 的 4 個(gè) api:

如果想執(zhí)行 shell 命令,用 spawn 和 exec,spawn 返回一個(gè) stream,而 exec 進(jìn)一步封裝成了 buffer。除了 exec 有的時(shí)候需要設(shè)置下 maxBuffer,其他沒區(qū)別。

如果想執(zhí)行可執(zhí)行文件,用  execFile。

如果想執(zhí)行 js 文件,用 fork。

child_process 的進(jìn)程通信

說完了 api 我們來說下 child_process 創(chuàng)建的子進(jìn)程怎么和父進(jìn)程通信,也就是怎么做 ipc。

pipe

首先,支持了 pipe,很明顯是通過管道的機(jī)制封裝出來的,能同步的傳輸流的數(shù)據(jù)。

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

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

比如上面通過管道把一個(gè)進(jìn)程的輸出流傳輸?shù)搅肆硪粋€(gè)進(jìn)程的輸入流,和下面的 shell 命令效果一樣:

cat ./aaa.js | wc -l

message

spawn 支持 stdio 參數(shù),可以設(shè)置和父進(jìn)程的 stdin、stdout、stderr 的關(guān)系,比如指定 pipe 或者 null。還有第四個(gè)參數(shù),可以設(shè)置 ipc,這時(shí)候就是通過事件的方式傳遞消息了,很明顯,是基于消息隊(duì)列實(shí)現(xiàn)的。

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 創(chuàng)建的子進(jìn)程自帶了 ipc 的傳遞消息機(jī)制,可以直接用。

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

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

cluster

cluster 不再是父子進(jìn)程了,而是更多進(jìn)程,也提供了 fork 的 api。

比如 http server 會(huì)根據(jù) cpu 數(shù)啟動(dòng)多個(gè)進(jìn)程來處理請(qǐng)求。

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,用于多個(gè)進(jìn)程之間的消息傳遞,因?yàn)槎鄠€(gè)進(jìn)程其實(shí)也只是多個(gè)父子進(jìn)程的通信,子進(jìn)程之間不能直接通信,所以還是基于消息隊(duì)列實(shí)現(xiàn)的。

共享內(nèi)存

子進(jìn)程之間通信還得通過父進(jìn)程中轉(zhuǎn)一次,要多次讀寫消息隊(duì)列,效率太低了,就不能直接共享內(nèi)存么?

現(xiàn)在 nodejs 還是不支持的,可以通過第三方的包 shm-typed-array 來實(shí)現(xiàn),感興趣可以看一下。

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

總結(jié)

進(jìn)程包括代碼、數(shù)據(jù)和 PCB,是程序的一次執(zhí)行的過程,PCB 記錄著各種執(zhí)行過程中的信息,比如分配的資源、執(zhí)行到的地址、用于通信的數(shù)據(jù)結(jié)構(gòu)等。

進(jìn)程之間需要通信,可以通過信號(hào)量、管道、消息隊(duì)列、共享內(nèi)存的方式。

  • 信號(hào)量就是一個(gè)簡(jiǎn)單的數(shù)字的標(biāo)記,不能傳遞具體數(shù)據(jù)。

  • 管道是基于文件的思想,一個(gè)進(jìn)程寫另一個(gè)進(jìn)程讀,是同步的,適用于兩個(gè)進(jìn)程。

  • 消息隊(duì)列有一定的 buffer,可以異步處理消息,適用于兩個(gè)進(jìn)程。

  • 共享內(nèi)存是多個(gè)進(jìn)程直接操作同一段內(nèi)存,適用于多個(gè)進(jìn)程,但是需要控制訪問順序。

這四種是本地進(jìn)程的通信方式,而網(wǎng)絡(luò)進(jìn)程則基于網(wǎng)絡(luò)協(xié)議的方式也可以做進(jìn)程通信。

進(jìn)程通信叫做 ipc,本地的叫做 lpc,遠(yuǎn)程的叫 rpc。

其中,如果把消息再封裝一層成具體的方法調(diào)用,叫做 rmi,效果就像在本進(jìn)程執(zhí)行執(zhí)行另一個(gè)進(jìn)程的方法一樣。

electron 和 nodejs 都是基于上面的操作系統(tǒng)機(jī)制的封裝:

  • elctron 支持 ipcMain 和 ipcRenderer 的消息傳遞的方式,還支持了 remote 的 rmi 的方式。

  • nodejs 有 child_process 和 cluster 兩個(gè)模塊和進(jìn)程有關(guān),child_process 是父子進(jìn)程之間,cluster 是多個(gè)進(jìn)程:

    • child_process 提供了用于執(zhí)行 shell 命令的 spawn、exec,用于執(zhí)行可執(zhí)行文件的 execFile,用于執(zhí)行 js 的 fork。提供了 pipe 和 message 兩種 ipc 方式。

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

當(dāng)然,不管封裝形式是什么,都離不開操作系統(tǒng)提供的信號(hào)量、管道、消息隊(duì)列、共享內(nèi)存這四種機(jī)制。

ipc 是開發(fā)中頻繁遇到的需求,希望這篇文章能夠幫大家梳理清楚從操作系統(tǒng)層到不同語言和運(yùn)行時(shí)的封裝層次的脈絡(luò)。

感謝各位的閱讀,以上就是“Node.js和Electron是怎么做進(jìn)程通信的”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)Node.js和Electron是怎么做進(jìn)程通信的這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI