Tutorial

Starten von untergeordneten Prozessen in Node.js

Node.jsDevelopmentJavaScript

Der Autor hat den COVID-19 Relief Fund dazu ausgewählt, eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

Einführung

Wenn ein Benutzer ein einzelnes Node.js-Programm ausführt, wird es als einzelner Betriebssystem-(OS)-Prozess ausgeführt, der die Instanz des laufenden Programms darstellt. In diesem Prozess führt Node.js Programme auf einem einzigen Thread aus. Wie bereits in dieser Serie im Tutorial So schreiben Sie asynchronen Code in Node.js erwähnt können Operationen, die eine lange Zeit zur Ausführung in JavaScript benötigen, den Node.js-Thread blockieren und die Ausführung anderen Codes verzögern, da nur ein Thread in einem einzelnen Prozess ausgeführt werden kann. Eine Schlüsselstrategie zur Umgehung dieses Problems besteht darin, einen Kindprozess - oder einen durch einen anderen Prozess erstellten Prozess - bei lang laufenden Aufgaben zu starten. Wenn ein neuer Prozess gestartet wird, kann das Betriebssystem Multiprocessing-Techniken einsetzen, um sicherzustellen, dass der hauptsächliche Node.js-Prozess und der zusätzliche Kindprozess gleichzeitig ausgeführt werden.

Node.js enthält das child_process-Modul, das über Funktionen zur Erstellung neuer Prozesse verfügt. Neben der Bewältigung von langwierigen Aufgaben kann dieses Modul auch eine Schnittstelle mit dem OS bilden und Shell-Befehle ausführen. Systemadministratoren können Node.js zum Ausführen von Shell-Befehlen verwenden, um ihre Operationen als Node.js-Modul anstelle von Shell-Skripten zu strukturieren und zu pflegen.

In diesem Tutorial erstellen Sie Kindprozesse bei der Ausführung einer Reihe von Node.js-Beispielanwendungen. Sie erstellen Prozesse mit dem child_process-Modul durch Abrufen der Ergebnisse eines Kindprozesses über einen Puffer oder eine Zeichenfolge mit der exec()-Funktion und dann aus einem Datenstream mit der spawn()-Funktion. Abschließend verwenden fork() zur Erstellung eines Kindprozesses eines anderen Node.js-Programms, mit dem Sie während der Ausführung kommunizieren können. Um diese Konzepte zu illustrieren, erstellen Sie ein Programm zur Auflistung des Inhalts eines Verzeichnisses, ein Programm zur Auffindung von Dateien und einen Webserver mit mehreren Endpunkten.

Voraussetzungen

Schritt 1 — Erstellen eines Kindprozesses mit exec()

Entwickler erstellen üblicherweise Kindprozesse zur Ausführung von Befehlen in ihrem Betriebssystem, wenn sie die Ausgabe ihrer Node.js-Programme mit einer Shell manipulieren müssen, so wie zum Beispiel bei der Shell-Weiterleitung oder -Umleitung. Die exec()-Funktion in Node.js erstellt einen neuen Shell-Prozess und führt in dieser Shell einen Befehl aus. Die Ausgabe des Befehls wird in einem Puffer im Arbeitsspeicher aufbewahrt, den Sie über eine in exec() übergebene Callback-Funktion akzeptieren können.

Beginnen wir mit der Erstellung unserer ersten Kindprozesse in Node.js. Zuerst müssen wir unsere Codierungsumgebung einrichten, um die Skripte, die wir in diesem Tutorial erstellen, zu speichern. Erstellen Sie im Terminal einen Ordner namens child-processes:

  • mkdir child-processes

Geben Sie diesen Ordner im Terminal mit dem cd-Befehl ein:

  • cd child-processes

Erstellen Sie eine neue Datei namens listFiles.js und öffnen Sie die Datei in einem Texteditor. In diesem Tutorial verwenden wir nano, einen Terminaltexteditor.

  • nano listFiles.js

Wir schreiben ein Node.js-Modul, das die exec()-Funktion zur Ausführung des ls-Befehls verwendet. Der ls-Befehl listet die Dateien und Ordner in einem Verzeichnis. Dieses Programm nimmt die Ausgabe des ls-Befehls und zeigt sie dem Benutzer an.

Fügen Sie im Texteditor den folgenden Code hinzu:

~/child-processes/listFiles.js
const { exec } = require('child_process');

exec('ls -lh', (error, stdout, stderr) => {
  if (error) {
    console.error(`error: ${error.message}`);
    return;
  }

  if (stderr) {
    console.error(`stderr: ${stderr}`);
    return;
  }

  console.log(`stdout:\n${stdout}`);
});

Wir importieren zunächst den exec()-Befehl aus dem child_process-Modul unter Verwendung der JavaScript-Destrukturierung. Nach dem Import verwenden wir die exec()-Funktion. Das erste Argument ist der Befehl, den wir ausführen möchten. In diesem Fall ist es ls -lh, der alle Dateien und Ordner im aktuellen Verzeichnis im Langformat listet, mit einer Gesamtdateigröße in visuell lesbaren Einheiten am Anfang der Ausgabe.

Das zweite Argument ist eine Callback-Funktion mit drei Parametern: error, stdout und stderr. Wenn die Ausführung des Befehls fehlschlägt, erfasst error den entsprechenden Grund. Das kann vorkommen, wenn die Shell den Befehl, den Sie ausführen möchten, nicht finden kann. Wenn der Befehl erfolgreich ausgeführt wird, werden alle Daten, die er in den Standardausgabestream schreibt, in stdout erfasst, sowie alle Daten, die er an den Standardfehlerstream schreibt, in stderr erfasst.

Anmerkung: Es ist wichtig, den Unterschied zwischen error und stderr im Auge zu behalten. Wenn die Ausführung des Befehls selbst fehlschlägt, erfasst error den Fehler. Wenn der Befehl ausgeführt wird, aber eine Ausgabe in den Fehlerstream zurückgibt, wird dies von stderr erfasst. Die stabilsten Node.js-Programme verarbeiten alle möglichen Ausgaben für einen untergeordneten Prozess.

In unserer Callback-Funktion überprüfen wir zunächst, ob wir einen Fehler erhalten haben. Falls dem so ist, stellen wir die message des Fehlers (eine Eigenschaft des Error-Objekts) mit console.error() dar und beenden die Funktion mit return. Wir überprüfen dann, ob der Befehl eine Fehlermeldung und in dem Fall return ausgegeben hat. Wenn der Befehl erfolgreich ausgeführt wird, protokollieren wir seine Ausgabe in der Konsole mit console.log().

Führen wir diese Datei aus, um sie in Aktion zu sehen. Speichern und schließen Sie nano, indem Sie STRG+X drücken.

Zurück in Ihrem Terminal führen Sie nun Ihre Anwendung mit dem node-Befehl aus:

  • node listFiles.js

In Ihrem Terminal wird die folgende Ausgabe angezeigt:

Output
stdout: total 4.0K -rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js

In dieser wird der Inhalt des child-processes-Verzeichnisses in langer Form aufgelistet, zusammen mit der Größe des Inhalts am Anfang. Ihre Ergebnisse haben anstelle von sammy Ihren eigenen Benutzer und Ihre Gruppe. Das zeigt, dass das listFiles.js-Programm den Shell-Befehl ls -lh erfolgreich ausgeführt hat.

Schauen wir uns nun einen anderen Weg zur Ausführung gleichzeitiger Prozesse an. Das child_process-Modul von Node.js kann auch ausführbare Dateien mit der execFile()-Funktion ausführen. Der Hauptunterschied zwischen den Funktionen execFile() und exec() besteht darin, dass das erste Argument von execFile() nun ein Pfad zu einer ausführbaren Datei anstelle eines Befehls ist. Die Ausgabe der ausführbaren Datei wird in einem Puffer wie exec() gespeichert, auf den wir über eine Callback-Funktion mit error-, stdout- und stderr-Parametern zugreifen.

Anmerkung: Skripte in Windows wie .bat- und .cmd-Dateien können nicht mit execFile() ausgeführt werden, da die Funktion beim Ausführen der Datei keine Shell erstellt. Unter Unix, Linux und MacOS benötigen ausführbare Skripte nicht immer eine Shell, um ausgeführt zu werden. Windows-Rechner benötigen jedoch eine Shell, um Skripte auszuführen. Um Skriptdateien unter Windows auszuführen, verwenden Sie exec(), da es eine neue Shell erstellt. Alternativ können Sie spawn() verwenden. Sie verwenden es später in diesem Schritt.

Sie können jedoch .exe-Dateien in Windows erfolgreich mit execFile() ausführen. Die Beschränkung gilt nur für Skriptdateien, die eine Shell zur Ausführung benötigen.

Beginnen wir mit dem Hinzufügen eines ausführbaren Skripts für execFile(). Wir schreiben ein Bash-Skript, das das Node.js-Logo von der Website von Node.js herunterlädt und es mit Base64 kodiert, um seine Daten in eine Zeichenfolge von ASCII-Zeichen zu konvertieren.

Erstellen Sie eine neue Shell-Skriptdatei namens processNodejsImage.sh:

  • nano processNodejsImage.sh

Schreiben Sie nun ein Skript, um das Bild herunterzuladen und mit base64 zu konvertieren:

~/child-processes/processNodejsImage.sh
#!/bin/bash
curl -s https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg > nodejs-logo.svg
base64 nodejs-logo.svg

Die erste Anweisung ist eine Shebang-Anweisung. Sie wird in Unix, Linux und MacOS verwendet, wenn wir eine Shell zur Ausführung unseres Skripts spezifizieren möchten. Die zweite Anweisung ist ein curl-Befehl. Das cURL utility, dessen Befehl curl ist, ist ein Befehlszeilentool, das Daten zu und von einem Server übertragen kann. Wir verwenden cURL, um das Node.js-Logo von der Website herunterzuladen. Dann verwenden wir Umleitung, um die heruntergeladenen Daten in einer neuen Datei nodejs-logo.svg zu speichern. Die letzte Anweisung verwendet das base64-Dienstprogramm zur Codierung der mit cURL heruntergeladenen Datei nodejs-logo.svg. Das Skript gibt dann die codierte Zeichenfolge an die Konsole aus.

Speichern und schließen Sie, bevor Sie fortfahren.

Damit unser Node-Programm das bash-Skript ausführen kann, müssen wir es ausführbar machen. Führen Sie dazu Folgendes aus:

  • chmod u+x processNodejsImage.sh

Dadurch erhält Ihr aktueller Benutzer die Berechtigung, die Datei auszuführen.

Mit unserem vorhandenen Skript können wir ein neues Node.js-Modul schreiben, um es auszuführen. Dieses Skript verwendet execFile(), um das Skript in einem untergeordneten Prozess auszuführen, wobei etwaige Fehler abgefangen werden und jede Ausgabe in der Konsole angezeigt wird.

Erstellen Sie in Ihrem Terminal eine neue JavaScript-Datei namens getNodejsImage.js:

  • nano getNodejsImage.js

Geben Sie den folgenden Code im Texteditor ein:

~/child-processes/getNodejsImage.js
const { execFile } = require('child_process');

execFile(__dirname + '/processNodejsImage.sh', (error, stdout, stderr) => {
  if (error) {
    console.error(`error: ${error.message}`);
    return;
  }

  if (stderr) {
    console.error(`stderr: ${stderr}`);
    return;
  }

  console.log(`stdout:\n${stdout}`);
});

Wir verwenden JavaScript-Destrukturierung zum Importieren der execFile()-Funktion aus dem child_process-Modul. Dann verwenden wir diese Funktion, indem wir den Dateipfad als Vorname übergeben. __dirname enthält den Verzeichnispfad des Moduls, in dem es geschrieben ist. Node.js stellt die __dirname-Variable für ein Modul bereit, wenn das Modul ausgeführt wird. Durch die Verwendung von __dirname findet unser Skript immer die Datei processNodejsImage.sh unter verschiedenen Betriebssystemen,unabhängig davon, wo wir getNodejsImage.js ausführen. Beachten Sie, dass sich für unsere aktuelle Projekteinrichtung getNodejsImage.js und processNodejsImage.sh im gleichen Ordner befinden müssen.

Das zweite Argument ist ein Callback mit den Parametern error, stdout und stderr. Wie bei unserem vorherigen Beispiel, in dem exec() verwendet wurde, überprüfen wir jede mögliche Ausgabe der Skriptdatei und protokollieren sie in der Konsole.

Speichern Sie in Ihrem Texteditor diese Datei und verlassen Sie den Editor.

Verwenden Sie in Ihrem Terminal node, um das Modul auszuführen:

  • node getNodejsImage.js

Bei Ausführung dieses Skripts wird eine Ausgabe wie diese erstellt:

Output
stdout: PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDQyLjQgMjcwLjkiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjE4MC43IiB5MT0iODAuNyIge ...

Beachten Sie, dass wir die Ausgabe in diesem Artikel aufgrund ihrer großen Größe verkürzt haben.

Bevor der base64-Codierung des Bildes lädt processNodejsImage.sh es zunächst herunter. Sie können auch überprüfen, ob Sie das Bild heruntergeladen haben, indem Sie das aktuelle Verzeichnis inspizieren.

Führen Sie listFiles.js aus, um die aktualisierte Liste von Dateien in unserem Verzeichnis zu finden:

  • node listFiles.js

Das Skript zeigt Inhalte ähnlich wie folgende im Terminal an:

Output
stdout: total 20K -rw-rw-r-- 1 sammy sammy 316 Jul 27 17:56 getNodejsImage.js -rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js -rw-rw-r-- 1 sammy sammy 5.4K Jul 27 18:01 nodejs-logo.svg -rwxrw-r-- 1 sammy sammy 129 Jul 27 17:56 processNodejsImage.sh

Wir haben nun processNodejsImage.sh erfolgreich als Kindprozess in Node.js mit der execFile()-Funktion ausgeführt.

Die Funktionen exec() und execFile() können Befehle auf der Shell des Betriebssystems in einem Node.js-Kindprozess ausführen. Node.js bietet auch eine andere Methode mit ähnlicher Funktionalität, spawn(). Der Unterschied besteht darin, dass wir die Ausgabe der Shell-Befehle nicht auf einmal erhalten, sondern in Stücken über einen Stream. Im nächsten Abschnitt verwenden wir den spawn()-Befehl zur Erstellung eines untergeordneten Prozesses.

Schritt 2 — Erstellen eines Kindprozesses mit spawn()

Die spawn()-Funktion führt einen Befehl in einem Prozess aus. Diese Funktion gibt Daten über die Stream-API zurück. Um die Ausgabe des Kindprozesses zu erhalten, müssen wir daher auf Stream-Ereignisse lauschen.

Streams in Node.js sind Instanzen von Ereignis-Emittern. Wenn Sie mehr über das Lauschen auf Ereignisse und die Grundlagen der Interaktion mit Streams erfahren möchten, können Sie unseren Leitfaden zur Verwendung von Ereigniss-Emittern in Node.js lesen.

Es ist oft eine gute Idee, spawn() anstatt exec() oder execFile() zu wählen, wenn der Befehl, den Sie ausführen möchten, eine große Menge an Daten ausgeben kann. Bei einem Puffer, wie er von exec() und execFile() verwendet wird, werden alle verarbeiteten Daten im Arbeitsspeicher des Computers gespeichert. Bei großen Datenmengen kann hierbei die Systemleistung sinken. Bei einem Stream werden die Daten in kleinen Stücken verarbeitet und übertragen. Daher können Sie eine große Menge an Daten verarbeiten, ohne zu viel Arbeitsspeicher im selben Moment zu verwenden.

Sehen wir uns an, wie wir spawn() zur Erstellung eines Kindprozesses verwenden können. Wir schreiben ein neues Node.js-Modul, das einen Kindprozess erstellt, um den Befehl find auszuführen. Wir verwenden den Befehl find , um alle Dateien im aktuellen Verzeichnis aufzulisten.

Erstellen Sie eine neue Datei namens findFiles.js:

  • nano findFiles.js

Beginnen Sie in Ihrem Texteditor mit dem Aufruf des spawn()-Befehls:

~/child-processes/findFiles.js
const { spawn } = require('child_process');

const child = spawn('find', ['.']);

Wir haben zunächst die spawn()-Funktion aus dem child_process-Modul importiert. Dann haben wir die spawn()-Funktion aufgerufen, um einen Kindprozess zu erstellen, der den Befehl find ausführt. Wir halten den Verweis auf den Prozess in der child-Variable, die wir zum Lauschen seiner gestreamten Ereignisse verwenden.

Das erste Argument in spawn() ist der auszuführende Befehl, in diesem Fall find. Das zweite Argument ist ein Array, das die Argumente für den ausgeführten Befehl enthält. In diesem Fall weisen wir Node.js an, den Befehl find mit dem Argument ., auszuführen. Dadurch wird der Befehl veranlasst, alle Dateien im aktuellen Verzeichnis zu finden. Der äquivalente Befehl im Terminal ist find ..

Mit den Funktionen exec() und execFile() haben wir die Argumente zusammen mit dem Befehl in einer Zeichenfolge geschrieben. Bei spawn() müssen jedoch alle Argumente für Befehle in das Array eingegeben werden. Das liegt daran, dass spawn(), im Gegensatz zu exec() und execFile(), keine neue Shell erstellt, bevor ein Prozess ausgeführt wird. Um Befehle mit ihren Argumenten in einer einzelnen Zeichenfolge zu haben, ist es auch erforderlich, das Node.js eine neue Shell erstellt.

Wir fahren mit unserem Modul fort, indem wir Listener für die Ausgabe des Befehls hinzufügen. Fügen Sie die folgenden hervorgehobenen Zeilen hinzu:

~/child-processes/findFiles.js
const { spawn } = require('child_process');

const child = spawn('find', ['.']);

child.stdout.on('data', data => {
  console.log(`stdout:\n${data}`);
});

child.stderr.on('data', data => {
  console.error(`stderr: ${data}`);
});

Befehle können Daten entweder im stdout-Stream oder im stderr-Stream zurückgeben. Daher fügen Sie Listener für beide hinzu. Sie können Listener hinzufügen, indem Sie die on()-Methode der Objekte jedes Streams aufrufen. Das data-Ereignis aus den Streams gibt uns die Ausgabe des Befehls an diesen Stream. Wann immer wir Daten über einen Stream erhalten, protokollieren wir diese in der Konsole.

Dann lauschen wird auf zwei andere Ereignisse: das error-Ereignis, wenn der Befehl nicht ausgeführt oder unterbrochen wird, und das close-Ereignis, wenn der Befehl die Ausführung beendet hat und damit den Stream schließt.

Schließen Sie im Texteditor das Node.js-Modul ab, indem Sie die folgenden hervorgehobenen Zeilen schreiben:

~/child-processes/findFiles.js
const { spawn } = require('child_process');

const child = spawn('find', ['.']);

child.stdout.on('data', (data) => {
  console.log(`stdout:\n${data}`);
});

child.stderr.on('data', (data) => {
  console.error(`stderr: ${data}`);
});

child.on('error', (error) => {
  console.error(`error: ${error.message}`);
});

child.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
});

Für die error- und close-Ereignisse haben Sie einen Listener direkt in der child-Variable eingerichtet. Wenn beim Lauschen auf error-Ereignisse ein Fehler auftritt, stellt Node.js ein Error-Objekt bereit. In diesem Fall protokollieren Sie die message-Eigenschaft des Fehlers.

Beim Lauschen auf das close-Ereignis stellt Node.js den Exit-Code des Befehls bereit. Ein Exit-Code gibt an, ob der Befehl erfolgreich ausgeführt wurde oder nicht. Wenn ein Befehl ohne Fehler ausgeführt wird, gibt er den geringmöglichsten Wert für einen Exit-Code zurück: 0. Wenn er mit einem Fehler ausgeführt wird, wird ein Code ungleich Null zurückgegeben.

Das Modul ist komplett. Speichern und schließen Sie nano mit STRG+X.

Führen Sie nun den Code mit dem node-Befehl aus:

  • node findFiles.js

Nach der Ausführung finden Sie die folgende Ausgabe:

Output
stdout: . ./findFiles.js ./listFiles.js ./nodejs-logo.svg ./processNodejsImage.sh ./getNodejsImage.js child process exited with code 0

Wir finden eine Liste aller Dateien in unserem aktuellen Verzeichnis und den Exit-Code des Befehls, der 0 ist, da die Ausführung erfolgreich war. Unser aktuelles Verzeichnis hat nur eine kleine Anzahl von Dateien. Würden wir diesen Code in unserem Stammverzeichnis ausführen, würde unser Programm jede einzelne Datei in jedem zugänglichen Ordner für unseren Benutzer auflisten. Die Verwendung der spawn()-Funktion ist ideal, da sie eine potenziell so große Ausgabe hat, und weil ihre Streams nicht so viel Arbeitsspeicher benötigen wie ein großer Puffer.

Bisher haben wir Funktionen zur Erstellung von Kindprozessen verwendet, um externe Befehle in unserem Betriebssystem auszuführen. Node.js bietet auch die Möglichkeit, einen Kindprozess zu erstellen, der andere Node.js-Programme ausführt. Im nächsten Abschnitt verwenden wir die fork()-Funktion zum Erstellen eines Kindprozesses für ein Node.js-Modul.

Schritt 3 — Erstellen eines Kindprozesses mit fork()

Node.js bietet die fork()-Funktion, eine Variante von spawn(), um einen Kindprozess zu erstellen, der gleichzeitig ein Node.js-Prozess ist. Der Hauptvorteil der Verwendung von fork() zur Erstellung eines Node.js-Prozesses anstelle von spawn() oder exec() besteht darin, dass fork() die Kommunikation zwischen dem Eltern- und dem Kindprozess ermöglicht.

Mit fork() kann ein Elternprozess nicht nur Daten aus dem Kindprozess abrufen, sondern auch Nachrichten an den laufenden Kindprozess senden. Ebenso kann der Kindprozess Nachrichten an den Elternprozess senden.

Sehen wir uns ein Beispiel an, bei dem die Verwendung von fork() zur Erstellung eines neuen Node.js-Kindprozesses die Leistung unserer Anwendung verbessern kann. Node.js-Programme werden in einem einzelnen Prozess ausgeführt. Daher stoppen CPU-intensive Aufgaben wie das Iterieren über große Schleifen oder das Parsing großer JSON-Dateien die Ausführung von anderem JavaScript-Code. Für bestimmte Anwendungen ist dies keine tragfähige Option. Wenn ein Webserver blockiert ist, kann er keine neuen eingehenden Anfragen verarbeiten, bis der blockierende Code seine Ausführung abgeschlossen hat.

Wir sehen uns das in der Praxis an, indem wir einen Webserver mit zwei Endpunkten erstellen. Ein Endpunkt nimmt eine langsame Berechnung vor, die den Node.js-Prozess blockiert. Der andere Endpunkt gibt ein JSON-Objekt zurück, das hello sagt.

Erstellen Sie zunächst eine neue Datei namens httpServer.js, die den Code für unseren HTTP-Server haben wird:

  • nano httpServer.js

Wir beginnen mit der Einrichtung des HTTP-Servers. Dazu gehört das Importieren des http-Moduls, die Erstellung einer Request Listener-Funktion, die Erstellung eines Server-Objekts und das Lauschen auf Anfragen an das Server-Objekt. Wenn Sie mehr über die Erstellung von HTTP-Servern in Node.js erfahren möchten oder eine Auffrischung zum Thema wünschen, können Sie unseren Leitfaden zum Erstellen eines Web-Servers in Node.js mit dem HTTP-Modul lesen.

Geben Sie den folgenden Code in Ihren Texteditor ein, um einen HTTP-Server einzurichten:

~/child-processes/httpServer.js
const http = require('http');

const host = 'localhost';
const port = 8000;

const requestListener = function (req, res) {};

const server = http.createServer(requestListener);
server.listen(port, host, () => {
  console.log(`Server is running on http://${host}:${port}`);
});

Dieser Code erstellt einen HTTP-Server, der unter http://localhost:8000 ausgeführt wird. Er verwendet Template-Literale, um diese URL dynamisch zu generieren.

Als Nächstes schreiben wir eine absichtlich langsame Funktion, die in einer Schleife 5 Milliarden Mal zählt. Fügen Sie vor der Funktion requestListener() den folgenden Code hinzu:

~/child-processes/httpServer.js
...
const port = 8000;

const slowFunction = () => {
  let counter = 0;
  while (counter < 5000000000) {
    counter++;
  }

  return counter;
}

const requestListener = function (req, res) {};
...

Dieser verwendet die Syntax der Pfeilfunktion, um eine while-Schleife zu erstellen, die bis 5000000000 zählt.

Um dieses Modul abzuschließen, müssen wir der requestListener()-Funktion Code hinzufügen. Unsere Funktion ruft die slowFunction() auf dem Unterpfad auf und gibt eine kleine JSON-Nachricht für die andere zurück. Fügen Sie dem Modul folgenden Code hinzu:

~/child-processes/httpServer.js
...
const requestListener = function (req, res) {
  if (req.url === '/total') {
    let slowResult = slowFunction();
    let message = `{"totalCount":${slowResult}}`;

    console.log('Returning /total results');
    res.setHeader('Content-Type', 'application/json');
    res.writeHead(200);
    res.end(message);
  } else if (req.url === '/hello') {
    console.log('Returning /hello results');
    res.setHeader('Content-Type', 'application/json');
    res.writeHead(200);
    res.end(`{"message":"hello"}`);
  }
};
...

Wenn der Benutzer den Server am Unterpfad /total erreicht, führen wir slowFunction() aus. Wenn wir auf den Unterpfad /hello treffen, geben wir diese JSON-Nachricht zurück: {"message":"hello"}.

Speichern und schließen Sie die Datei, indem Sie STRG+X drücken.

Zum Testen führen Sie dieses Server-Modul mit node aus:

  • node httpServer.js

Wenn unser Server startet, zeigt die Konsole Folgendes an:

Output
Server is running on http://localhost:8000

Um die Leistung unseres Moduls zu testen, öffnen Sie nun zwei zusätzliche Terminals. Verwenden Sie im ersten Terminal den curl-Befehl, um eine Anfrage an den /total-Endpunkt zu stellen, von dem wir erwarten, dass er langsam ist:

  • curl http://localhost:8000/total

Verwenden Sie im anderen Terminal curl, um eine Anfrage an den /hello-Endpunkt wie folgt zu stellen:

  • curl http://localhost:8000/hello

Die erste Anfrage gibt folgende JSON zurück:

Output
{"totalCount":5000000000}

Die zweite Anfrage gibt diese JSON zurück:

Output
{"message":"hello"}

Die Anfrage an /hello wurde erst nach der Anfrage an /total abgeschlossen. Die slowFunction() hat die Ausführung allen anderen Codes blockiert, während sie noch in ihrer Schleife war. Sie können dies überprüfen, indem Sie die Ausgabe des Node.js-Servers anschauen, die in Ihrem ursprünglichen Terminal protokolliert worden ist:

Output
Returning /total results Returning /hello results

Um den blockierenden Code zu verarbeiten und gleichzeitig eingehende Anfragen zu akzeptieren, können wir den blockierenden Code mit fork() in einen Kindprozess verschieben. Wir verschieben den blockierenden Code in sein eigenes Modul. Der Node.js-Server erstellt dann einen Kindprozess, wenn jemand auf den /total-Endpunkt zugreift und lauscht auf Ergebnisse aus diesem Kindprozess.

Refaktorieren Sie den Server, indem Sie zunächst ein neues Modul namens getCount.js erstellen, das slowFunction(): enthält:

  • nano getCount.js

Geben Sie nun den Code für slowFunction() erneut ein:

~/child-processes/getCount.js
const slowFunction = () => {
  let counter = 0;
  while (counter < 5000000000) {
    counter++;
  }

  return counter;
}

Da dieses Modul ein mit fork() erstellter untergeordneter Prozess ist, können wir auch Code hinzufügen, um mit dem übergeordneten Prozess zu kommunizieren, wenn slowFunction() die Verarbeitung abgeschlossen hat. Fügen Sie den folgenden Code-Block hinzu, der eine Nachricht an den Elternprozess mit der JSON zur Rückgabe an den Benutzer sendet:

~/child-processes/getCount.js
const slowFunction = () => {
  let counter = 0;
  while (counter < 5000000000) {
    counter++;
  }

  return counter;
}

process.on('message', (message) => {
  if (message == 'START') {
    console.log('Child process received START message');
    let slowResult = slowFunction();
    let message = `{"totalCount":${slowResult}}`;
    process.send(message);
  }
});

Gehen wird diesen Code-Block durch. Die von fork() erstellten Nachrichten zwischen einem Eltern- und Kindprozess sind über das globale process-Objekt von Node.js zugänglich. Wir fügen der process-Variable einen Listener hinzu, um nach message-Ereignissen zu suchen. Sobald wir ein message-Ereignis erhalten, überprüfen wir, ob es das START-Ereignis ist. Unser Servercode sendet das START-Ereignis, wenn jemand auf den /total-Endpunkt zugreift. Nach dem Erhalt dieses Ereignisses führen wir slowFunction() aus und erstellen mit dem Ergebnis der Funktion eine JSON-Zeichenfolge. Wir verwenden process.send(), um eine Nachricht an den übergeordneten Prozess zu senden.

Speichern und schließen Sie getCount.js, indem Sie STRG+X in nano eingeben.

Wir ändern nun die Datei httpServer.js so, dass sie anstatt slowFunction() aufzurufen, einen Kindprozess erstellt, der getCount.js ausführt.

Öffnen Sie httpServer.js erneut mit nano:

  • nano httpServer.js

Importieren Sie zunächst die fork()-Funktion aus dem child_process-Modul:

~/child-processes/httpServer.js
const http = require('http');
const { fork } = require('child_process');
...

Als Nächstes entfernen wir die slowFunction() aus diesem Modul und ändern die requestListener()-Funktion, um einen Kindprozess zu erstellen. Ändern Sie den Code in Ihrer Datei, damit er so aussieht:

~/child-processes/httpServer.js
...
const port = 8000;

const requestListener = function (req, res) {
  if (req.url === '/total') {
    const child = fork(__dirname + '/getCount');

    child.on('message', (message) => {
      console.log('Returning /total results');
      res.setHeader('Content-Type', 'application/json');
      res.writeHead(200);
      res.end(message);
    });

    child.send('START');
  } else if (req.url === '/hello') {
    console.log('Returning /hello results');
    res.setHeader('Content-Type', 'application/json');
    res.writeHead(200);
    res.end(`{"message":"hello"}`);
  }
};
...

Wenn jemand zum Endpunkt /total geht, erstellen wir nun einen neuen Kindprozess mit fork(). Das Argument von fork() ist der Pfad zum Node.js-Modul. In diesem Fall ist es die getCount.js-Detei in unserem aktuellen Verzeichnis, die wir von __dirname erhalten. Der Verweis auf diesen Kindprozess wird in einer child-Variablen gespeichert.

Dann fügen wir dem child-Objekt einen Listener hinzu. Dieser Listener erfasst alle Nachrichten, die der Kindprozess uns gibt. In diesem Fall gibt getCount.js eine JSON-Zeichenfolge mit der Gesamtzahl zurück, die von der while-Schleife gezählt wurde. Wenn wir diese Nachricht erhalten, senden wir die JSON an den Benutzer.

Wir verwenden die send()-Funktion der child-Variablen, um die Nachricht zu übergeben. Dieses Programm sendet die Message START, mit der die Ausführung von slowFunction() im untergeordneten Prozess beginnt.

Speichern und schließen Sie nano, indem Sie STRG+X drücken.

Um die Verbesserung mit fork() auf HTTP-Server zu testen, beginnen Sie mit der Ausführung der httpServer.js-Datei mit node:

  • node httpServer.js

Wie zuvor gibt sie beim Starten die folgende Nachricht aus:

Output
Server is running on http://localhost:8000

Um den Server zu testen, benötigen wir so wie beim ersten Mal zwei weitere Terminals. Sie können diese erneut verwenden, wenn sie noch offen sind.

Verwenden Sie im ersten Terminal den curl-Befehl, um eine Anfrage an den /total-Endpunkt zu stellen, deren Berechnung eine Weile dauert:

  • curl http://localhost:8000/total

Verwenden Sie im anderen Terminal curl, um eine Anfrage an den /hello-Endpunkt zu stellen, der in kurzer Zeit reagiert:

  • curl http://localhost:8000/hello

Die erste Anfrage gibt folgende JSON zurück:

Output
{"totalCount":5000000000}

Die zweite Anfrage gibt diese JSON zurück:

Output
{"message":"hello"}

Anders als beim ersten Versuch wird die zweite Anfrage an /hello sofort ausgeführt. Sie können dies bestätigen, indem Sie die Protokolle überprüfen, die folgendermaßen aussehen:

Output
Child process received START message Returning /hello results Returning /total results

Diese Protokolle zeigen, dass die Anfrage für den /hello-Endpunkt nach Erstellung des untergeordneten Prozesses, aber vor Beendigung des untergeordneten Prozesses ausgeführt wurde.

Da wir den blockierenden Code in einem Kindprozess mit fork() verschoben haben, konnte der Server nach wie vor auf andere Anfragen reagieren und anderen JavaScript-Code ausführen. Aufgrund der Fähigkeit der fork()-Funktion, Nachrichten zu übergeben, können wir kontrollieren, wann ein Kindprozess eine Aktivität beginnt, und wir können Daten von einem Kindprozess an einen Elternprozess zurückgeben.

Zusammenfassung

In diesem Artikel haben Sie verschiedene Funktionen zur Erstellung eines Kindprozesses in Node.js verwendet. Sie haben zunächst Kindprozesse mit exec() erstellt, um Shell-Befehle aus Node.js-Code auszuführen. Sie haben dann eine ausführbare Datei mit der execFile()-Funktion ausgeführt. Sie haben sich die Funktion spawn() angeschaut, die auch Befehle ausführen kann, Daten jedoch über einen Stream zurückgibt und anders als exec() und execFile() keine Shell startet. Abschließend haben Sie die Funktion fork() verwendet, um eine zweigleisige Kommunikation zwischen dem Eltern- und Kindprozess zu ermöglichen.

Um mehr über das child_process-Modul zu erfahren, können Sie die Node.js-Dokumentation lesen. Wenn Sie Node.js weiter lernen möchten, können Sie zur Codieren in Node-Reihe zurückkehren oder Programmierprojekte und -einstellungen auf unserer Node-Themenseite durchsuchen.

Creative Commons License