Maison >interface Web >js tutoriel >En savoir plus sur la façon dont Node.js et Electron communiquent entre les processus

En savoir plus sur la façon dont Node.js et Electron communiquent entre les processus

青灯夜游
青灯夜游avant
2021-07-26 10:11:545555parcourir

Cet article explorera le principe de communication de processus entre Node.js et Electron, présentera comment Electron traite la communication, comment le child_process et le cluster de nodejs effectuent la communication de processus et comprendra l'essence de la communication de processus.

En savoir plus sur la façon dont Node.js et Electron communiquent entre les processus

Pourquoi le front-end doit comprendre la communication des processus :

Le domaine du front-end ne consiste plus seulement à écrire des pages qui s'exécutent dans le navigateur, mais aussi à électronique, nodejs, etc., et les deux technologies nécessitent une maîtrise communication de processus.

nodejs est un environnement d'exécution de js. Contrairement aux navigateurs, il étend de nombreuses API qui encapsulent les capacités du système d'exploitation, y compris les API liées aux processus et aux threads. L'apprentissage des API de processus nécessite l'apprentissage du mécanisme de communication entre les processus.

electron est une solution de développement de bureau basée sur Chrome et Nodejs. Son architecture est un processus principal et plusieurs processus de rendu sont également nécessaires entre ces deux processus. Vous devez apprendre le mécanisme de communication des processus d'Electron. [Étude recommandée : "Tutoriel Nodejs"]

Dans cet article, nous examinerons de plus près la communication des processus.

Cet article expliquera les points de connaissances suivants :

  • Qu'est-ce qu'un processus
  • Quatre façons de communication de processus local
  • Que sont ipc, lpc, rpc
  • Comment l'électron fait-il la communication de processus
  • Comment faire child_process et cluster de nodejs Communication de processus
  • L'essence de la communication de processus

Processus

Le code que nous écrivons doit être exécuté sur le système d'exploitation Afin de mieux utiliser les ressources matérielles, le système d'exploitation prend en charge la concurrence de plusieurs programmes et. l'utilisation des ressources matérielles, l'unité d'allocation est le processus, et ce processus est le processus d'exécution du programme. Par exemple, enregistrez quelle étape le programme a exécutée, quelles ressources matérielles ont été demandées, quels ports sont occupés, etc.

Le processus comprend le code à exécuter, les données exploitées par le code et le bloc de contrôle de processus PCB (Processing Control Block), car le programme est le processus d'exécution du code sur l'ensemble de données et l'état du Le processus d'exécution et les ressources demandées doivent être enregistrés dans un Dans la structure de données (PCB). Le processus se compose donc de code, de données et de PCB.

En savoir plus sur la façon dont Node.js et Electron communiquent entre les processus

pcb enregistre le pid, l'adresse du code exécuté, l'état du processus (bloqué, en cours d'exécution, prêt, etc.), ainsi que les structures de données telles que les sémaphores, les canaux et les files d'attente de messages utilisées pour la communication.

En savoir plus sur la façon dont Node.js et Electron communiquent entre les processus

De la création à l'exécution continue du code, en passant par l'application des ressources matérielles (mémoire, fichiers du disque dur, réseau, etc.), le processus peut être bloqué au milieu, et le processus finira par être détruit après exécution. C'est le cycle de vie d'un processus.

Un processus est exclusif aux ressources demandées. Chaque processus ne peut accéder qu'à ses propres ressources. Alors, comment les processus communiquent-ils entre eux ?

Communication des processus

Étant donné que la mémoire disponible est différente entre les différents processus, ils doivent communiquer via un support intermédiaire.

Semaphore

S'il s'agit d'une simple marque, représentée par un numéro et placée dans un attribut du PCB, cela s'appelle 信号量 Par exemple, la mise en place d'un verrou peut utiliser un sémaphore.

Cette idée de sémaphore est souvent utilisée lors de l'écriture de code front-end. Par exemple, lors de l'implémentation de la limitation, une variable de marqueur doit également être ajoutée.

Pipeline

Mais le sémaphore ne peut pas transférer de données spécifiques. Vous devez utiliser d'autres méthodes pour transférer des données spécifiques. Par exemple, nous pouvons communiquer en lisant et en écrivant des fichiers. C'est 管道 S'il s'agit d'un fichier en mémoire, il s'appelle un tube anonyme et n'a pas de nom de fichier. S'il s'agit d'un fichier sur un vrai disque dur, il a un. nom de fichier et est appelé un canal nommé.

Le fichier doit d'abord être ouvert, puis lu et écrit, puis refermé. C'est également une caractéristique des pipelines. Les tuyaux sont encapsulés sur la base de l'idée de fichiers. Ils sont appelés tuyaux car ils ne peuvent être lus que par un seul processus et écrits par un seul processus. Ils sont unidirectionnels (semi-duplex). De plus, le processus cible doit également consommer des données de manière synchrone, sinon il sera bloqué.

Cette méthode de pipeline est très simple à mettre en œuvre. Il s'agit d'une lecture et d'une écriture de fichiers, mais elle ne peut être utilisée que pour communiquer entre deux processus et ne peut communiquer que de manière synchrone. En fait, la communication synchrone des tuyaux est également assez courante, ce qui est la méthode du flux par tuyaux.

Message Queue

La mise en œuvre du pipeline est simple, mais la communication synchrone est relativement limitée. Et si vous souhaitez établir une communication asynchrone ? Ajoutez simplement une file d'attente comme tampon. C'est 消息队列.

La file d'attente de messages est également une communication entre deux processus, mais elle n'est pas basée sur l'idée basée sur des fichiers. Bien qu'elle soit également unidirectionnelle, elle a une certaine nature asynchrone et peut mettre de nombreux messages et les consommer tous en même temps.

Mémoire partagée

Les pipelines et les files d'attente de messages se situent entre deux processus. Que se passe-t-il s'il existe plusieurs processus ?

Nous pouvons communiquer de cette manière en demandant un morceau de mémoire pouvant être exploité par plusieurs processus, appelé 共享内存. Chaque processus peut lire et écrire des données dans cette mémoire, ce qui est relativement efficace.

Bien que la mémoire partagée soit efficace et puisse être utilisée pour la communication entre plusieurs processus, tout n'est pas bon, car plusieurs processus peuvent lire et écrire, il est donc facile de gâcher l'ordre vous-même, par exemple. le sémaphore du processus (marque variable) à contrôler.

La mémoire partagée est adaptée à la communication entre plusieurs processus sans passer par un support intermédiaire, elle est donc plus efficace, mais elle est également plus compliquée à utiliser.

Les moyens mentionnés ci-dessus représentent presque tous les moyens de communication de processus locaux. Pourquoi en ajouter un local ?

ipc, rpc, lpc

La communication de processus est ipc (Inter-Process Communication). Les deux processus peuvent provenir d'un seul ordinateur, ou ils peuvent être des processus d'ordinateurs différents sur le réseau, il existe donc deux méthodes de traitement. communication. :

Appel de procédure locale LPC (appel de procédure locale), appel de procédure distante RPC (appel de procédure distante).

Les appels de procédure locale sont les méthodes de communication des sémaphores, des canaux, des files d'attente de messages et de la mémoire partagée que nous avons mentionnées ci-dessus. Mais s'il est sur le réseau, il doit communiquer via des protocoles réseau. En fait, nous l'utilisons plus souvent, comme. http, websocket.

Ainsi, quand quelqu'un mentionne ipc, il parle de communication de processus, qui peut être divisée en deux types : locale et distante.

Les distants sont encapsulés sur la base de protocoles réseau, tandis que les locaux sont encapsulés sur la base de sémaphores, de canaux, de files d'attente de messages et de mémoire partagée, comme Electron et Nodejs, dont nous parlerons ensuite.

communication du processus électronique

electron démarrera d'abord le processus principal, puis créera un processus de rendu via BrowserWindow, chargera la page html pour le rendu. La communication entre les deux processus se fait via l'API ipc fournie par Electron.

ipcMain, ipcRenderer

Dans le processus principal, les événements sont surveillés via la méthode on d'ipcMain

import { ipcMain } from 'electron';

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

Dans le processus de rendu, les événements sont surveillés via la méthode on d'ipcRenderer et les messages sont envoyés via send

import { ipcRenderer } from 'electron';

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

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

api est relativement simple à utiliser, c'est le processus. La couche C++ est encapsulée puis exposée à l'API de forme d'événement de JS.

On peut réfléchir à quel mécanisme il repose ?

Il existe évidemment un certain degré de nature asynchrone, et il s'agit d'une communication entre les processus parent et enfant, elle est donc implémentée sous la forme d'une file d'attente de messages.

remote

En plus des API basées sur des événements, Electron fournit également des API RMI (Remote Method Invocation) d'invocation de méthode à distance.

En fait, il s'agit d'une encapsulation supplémentaire du message, c'est-à-dire d'appeler différentes méthodes en fonction du message transmis. Dans la forme, c'est comme appeler la méthode de ce processus, mais en fait cela se fait en envoyant le message. à un autre processus, et ipcMain, ipcRenderer La forme est essentiellement la même.

Par exemple, dans le processus de rendu, utilisez Remote pour appeler directement l'API BrowserWindow qui n'est disponible que dans le processus principal.

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

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

Pour résumer, la méthode de communication du processus parent-enfant d'Electron est basée sur l'encapsulation de la file d'attente de messages. L'une est la méthode événementielle, qui est utilisée via l'API d'ipcMain et d'ipcRenderer. L'invocation de méthode (rmi) est également basée sur les messages en bas. Elle exécute des méthodes distantes mais ressemble à l'exécution de méthodes locales.

nodejs

nodejs fournit une API de création de processus, avec deux modules : child_process et cluster. Évidemment, l’un est destiné à la création et à la communication de processus parent-enfant, et l’autre à plusieurs processus.

child_process

child_process fournit des API spawn, exec, execFile et fork, qui sont utilisées pour créer différents processus :

spawn, exec

Si vous souhaitez exécuter des commandes via le shell, utilisez spawn ou exec. Étant donné que l'exécution de commandes nécessite généralement une valeur de retour, les deux API sont différentes dans la manière dont elles renvoient les valeurs.

spawn renvoie un flux, qui est récupéré via l'événement data, et exec est ensuite divisé en tampons, ce qui est plus simple à utiliser, mais peut dépasser 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);
});

En fait, exec est encapsulé sur la base de spwan et peut être utilisé dans des scénarios simples. Parfois, maxBuffer doit être défini.

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

En plus d'exécuter des commandes, si vous souhaitez exécuter un fichier exécutable, utilisez l'API execFile :

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

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

fork

Et si vous souhaitez exécuter js, utilisez fork :

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

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

Résumé

Un bref résumé des quatre API de child_process :

Si vous souhaitez exécuter des commandes shell, utilisez spawn et exec renvoie un flux, et exec est encapsulé dans un tampon. Sauf que exec doit parfois définir maxBuffer, il n'y a aucune différence.

Si vous souhaitez exécuter un fichier exécutable, utilisez execFile.

Si vous souhaitez exécuter le fichier js, utilisez 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(&#39;hello world\n&#39;);
  })
  
  server.listen(8000);
  
  process.on(&#39;message&#39;, (msg) => {
    if (msg === &#39;shutdown&#39;) {
       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_神说要有光

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer