Node.js と Electron がプロセス間でどのように通信するかについて詳しく学ぶ

青灯夜游
リリース: 2021-07-27 09:22:24
転載
5284 人が閲覧しました

この記事では、Node.js と Electron 間のプロセス通信の原理を探り、Electron がプロセス通信を行う方法、nodejs の child_process とクラスターがプロセス通信を行う方法を紹介し、プロセス通信の本質を理解します。

Node.js と Electron がプロセス間でどのように通信するかについて詳しく学ぶ

フロントエンドがプロセス通信を理解する必要がある理由:

フロントエンドの分野は、もはやブラウザーで実行されるページを作成するだけではありません。ただし、Electron、nodejs などの知識も必要であり、どちらのテクノロジもプロセス通信を習得する必要があります。

Nodejs は、js のランタイムです。ブラウザとは異なり、プロセスやスレッド関連の API など、オペレーティング システムの機能をカプセル化する多くの API を拡張します。プロセス API を学習するには、プロセス間の相互作用を学習する必要があります。通信メカニズム。

Electron は chromium と Nodejs をベースとしたデスクトップ開発ソリューションです。そのアーキテクチャはメインプロセスと複数のレンダリングプロセスで構成されています。これら 2 つのプロセス間でも通信が必要です。Electron のプロセス通信メカニズムを学ぶ必要があります。 [推奨される学習: "nodejs チュートリアル "]

この記事では、プロセス通信について詳しく見ていきます。

この記事では、次の知識ポイントについて説明します:

  • プロセスとは
  • ローカル プロセス通信の 4 つの方法
  • ipc、lpc、 rpc とは何ですか
  • electron がプロセス通信を行う方法
  • Nodejs の child_process とクラスターでプロセス通信を行う方法
  • プロセス通信の本質

プロセス

#私たちが作成したコードはオペレーティング システム上で実行する必要があります。ハードウェア リソースをより有効に活用するために、オペレーティング システムは複数のプログラムの同時実行とハードウェア リソースの割り当てをサポートしています。割り当ての単位はプロセスであり、このプロセスがプログラムの実行プロセスになります。たとえば、プログラムが実行したステップ、どのハードウェア リソースが適用されたか、どのポートが占有されているかなどを記録します。

プログラムはデータセット上のコードの実行プロセスであるため、プロセスには、実行されるコード、コードによって操作されるデータ、およびプロセス制御ブロックPCB (Processing Control Block)が含まれます。実行プロセスのステータスと適用されたリソースをデータ構造 (PCB) に記録する必要があります。したがって、プロセスはコード、データ、PCB で構成されます。

Node.js と Electron がプロセス間でどのように通信するかについて詳しく学ぶ

pcb は、pid、実行されたコード アドレス、プロセスのステータス (ブロック、実行中、準備完了など)、およびセマフォ、パイプ、メッセージを記録します。通信に使用されるキューやその他のデータ構造。

Node.js と Electron がプロセス間でどのように通信するかについて詳しく学ぶ

プロセスは、ハードウェア リソース (メモリ、ハードディスク ファイル、ネットワークなど) の作成からアプリケーションへの継続的なコード実行までブロックされる可能性があり、最終的にプロセスは実行後は破棄されます。これがプロセスのライフサイクルです。

プロセスは、要求されたリソースに排他的にアクセスできます。各プロセスは、自分のリソースのみにアクセスできます。プロセスはどのように相互に通信しますか?

プロセス通信

利用可能なメモリはプロセスごとに異なるため、プロセスは中間媒体を介して通信する必要があります。

セマフォ

数値で表され、PCB の属性に配置される単純なマークの場合、これは ## と呼ばれます。 #Semaphore、たとえば、ロックはセマフォを通じて実装できます。

このセマフォのアイデアは、フロントエンド コードを記述するときによく使用されます。たとえば、スロットリングを実装する場合は、マーク変数も追加する必要があります。

パイプライン

ただし、セマフォでは特定のデータを転送できないため、特定のデータを転送するには他の方法を使用する必要があります。たとえば、

パイプラインというファイルを読み書きすることで通信できますが、メモリ内のファイルの場合は匿名パイプと呼ばれ、ファイル名はありません。ハードディスクには、名前付きパイプと呼ばれるファイル名が付いています。

ファイルは最初に開いてから読み取りと書き込みを行ってから閉じる必要がありますが、これはパイプラインの特性でもあります。パイプはファイルの概念に基づいてカプセル化されており、1 つのプロセスでのみ読み取り、1 つのプロセスで書き込むことができるため、パイプと呼ばれます。一方向 (半二重) です。さらに、ターゲット プロセスはデータを同期的に消費する必要もあります。そうしないとブロックされてしまいます。

このパイプ メソッドは実装が非常に簡単で、ファイルの読み取りと書き込みを行いますが、2 つのプロセス間の通信にのみ使用でき、同期通信のみが可能です。実際、ストリームのパイプメソッドであるパイプの同期通信も非常に一般的です。

Message Queue

パイプラインの実装はシンプルですが、同期通信は比較的制限されています。非同期通信を行いたい場合はどうすればよいでしょうか?バッファとしてキューを追加するだけです。これは

Message Queue です。

メッセージ キューも 2 つのプロセス間の通信ですが、ファイルベースの考え方に基づいたものではなく、やはり一方向ですが、ある種の非同期的な性質があり、大量のメッセージを入れることができます。そしてそれらを一度にすべて消費します。 ############共有メモリ#########

パイプラインとメッセージ キューは 2 つのプロセス間にあります。複数のプロセスがある場合はどうなりますか?

共有メモリと呼ばれる、複数のプロセスで操作できるメモリを申請することで、この方法で通信できます。各プロセスはこのメモリに対してデータを読み書きできるため、比較的効率的です。

共有メモリは効率が高く、複数のプロセス間の通信に使用できますが、複数のプロセスが読み書きできるため混乱しやすく、良いことばかりではありません。順序を自分で制御する必要があります。などによりプロセスのセマフォ(マーク変数)を制御します。

共有メモリは、中間媒体を介さずに複数のプロセス間の通信に適しているため、より効率的ですが、使用方法も複雑になります。

上記は、ローカル プロセス通信のほぼすべての方法ですが、なぜローカル プロセスを追加するのでしょうか?

ipc、rpc、lpc

プロセス通信は ipc (プロセス間通信) です。2 つのプロセスは同じコンピュータ上に存在する場合もあれば、異なるコンピュータ上に存在する場合もあります。コンピュータプロセスの通信方式は、

ローカルプロシージャコールLPC(ローカルプロシージャコール)とリモートプロシージャコールRPC(リモートプロシージャコール)の2種類に分けられます。

ローカル プロシージャ コールは、上で説明したセマフォ、パイプ、メッセージ キュー、共有メモリの通信方法です。しかし、それがネットワーク上にある場合は、ネットワーク プロトコルを介して通信する必要があります。これが実際に使用されるものです。 http や websocket など、たくさんあります。

つまり、誰かが ipc について言及するとき、彼らはプロセス通信について話しているのですが、これはローカルとリモートの 2 つの方法で議論できます。

リモートのものはネットワーク プロトコルに基づいてカプセル化されますが、ローカルのものはセマフォ、パイプ、メッセージ キュー、次に説明する Electron や Nodejs などの共有メモリに基づいてカプセル化されます。

electron プロセス通信

electron は最初にメイン プロセスを開始し、次に BrowserWindow を通じてレンダリング プロセスを作成し、レンダリング用の HTML ページを読み込みます。 2 つのプロセス間の通信は、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 (リモート) の形式でも API を提供します。メソッドの呼び出し)。

実際には、これはメッセージをさらにカプセル化したもので、渡されたメッセージに応じてさまざまなメソッドを呼び出します。形式的には、このプロセスのメソッドを呼び出すのとまったく同じですが、実際には、メッセージを別のプロセスに送信することで実行され、その形式は基本的に ipcMain および ipcRenderer と同じです。

たとえば、レンダリング プロセスでは、メイン プロセスでのみ使用できる BrowserWindow API がリモート経由で直接呼び出されます。

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

let win = new BrowserWindow({ width: 800, height: 600 });
win.loadURL('https://github.com');
ログイン後にコピー

electron の親子プロセス通信方式はメッセージキューのカプセル化に基づいており、カプセル化形式は 2 つあり、1 つは ipcMain と ipcRenderer の API を介して使用されるイベントメソッド、もう 1 つはイベントメソッドです。もう 1 つはさらに詳細で、さまざまなメソッド (rmi) の呼び出しにカプセル化されています。最下層もメッセージに基づいており、リモート メソッドを実行しますが、ローカル メソッドを実行しているように見えます。

nodejs

nodejs は、child_process とクラスターの 2 つのモジュールを備えたプロセスを作成するための API を提供します。明らかに、1 つは親子プロセスの作成と通信用であり、もう 1 つは複数のプロセス用です。

child_process

child_process は、さまざまなプロセスの作成に使用される spawn、exec、execFile、および fork API を提供します。

spawn、exec

シェルを通じてコマンドを実行する場合は、spawn または exec を使用します。一般にコマンドの実行には戻り値が必要なため、2 つの 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');	
});
ログイン後にコピー
ログイン後にコピー

要約

次の 4 つの API の簡単な概要child_process:

シェル コマンドを実行する場合は、spawn と exec を使用します。spawn はストリームを返し、exec はそれをさらにバッファにカプセル化します。 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 是开发中频繁遇到的需求,希望这篇文章能够帮大家梳理清楚从操作系统层到不同语言和运行时的封装层次的脉络。

原文地址:https://juejin.cn/post/6988484297485189127

作者:zxg_神说要有光

更多编程相关知识,请访问:编程视频!!

以上がNode.js と Electron がプロセス間でどのように通信するかについて詳しく学ぶの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:juejin.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!