中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

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

發布時間:2021-07-27 10:55:42 來源:億速云 閱讀:394 作者:chen 欄目:web開發

這篇文章主要講解了“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是怎么做進程通信的這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

重庆市| 阜宁县| 娱乐| 呈贡县| 鄂托克旗| 汉源县| 丰宁| 美姑县| 布尔津县| 泾川县| 改则县| 犍为县| 白朗县| 荔浦县| 绥化市| 阿拉尔市| 都匀市| 来凤县| 翁牛特旗| 静海县| 蓬溪县| 元江| 彭泽县| 嘉义市| 蒙阴县| 宝兴县| 叶城县| 顺义区| 朔州市| 大安市| 靖西县| 黄冈市| 延川县| 广汉市| 玉林市| 郸城县| 偃师市| 乌兰察布市| 重庆市| 花垣县| 阿拉善右旗|