站長資訊網
        最全最豐富的資訊網站

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

        本篇文章給大家探究一下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 不再是父子進程了,而是

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 久久国产乱子精品免费女| 国产91精品一区二区麻豆亚洲| 国产精品污WWW在线观看| 国产精品自产拍在线18禁| 国产精品99久久99久久久| 亚洲人成电影网站国产精品| 青青草国产精品久久久久| 精品久久久久久无码专区不卡| 人妻偷人精品成人AV| 国产精品无码免费播放| 国产一成人精品福利网站| 精品福利一区二区三区免费视频| 亚洲麻豆精品国偷自产在线91 | 亚洲国产精品无码成人片久久| 国产精品一级AV在线播放| 国产欧美久久久精品| 久久ww精品w免费人成| 亚洲一区无码精品色| 国产L精品国产亚洲区久久| 国产精品成人观看视频国产| 国产最新进精品视频| 久久久久久九九99精品| 亚洲高清国产拍精品26U| 亚洲国产人成精品| 欧美日韩精品一区二区三区不卡| 国产精品九九九| 国产精品女人呻吟在线观看| 国产av无码专区亚洲国产精品| 欧美亚洲国产精品第一页| 国产亚洲精品xxx| 国产精品久久久久久久久免费| 6一12呦女精品| 99久久精品国产麻豆| 538国产精品一区二区在线| 国产精品分类视频分类一区| 99久久精品国内| 九九热这里只有在线精品视 | 999久久久无码国产精品| 国产精品视频一区二区三区四| 国语自产少妇精品视频蜜桃| 国产一精品一AV一免费|