Streams in Node.js können kompliziert sein, aber es lohnt sich, sich die Zeit zu nehmen, sie zu verstehen.

Die zentralen Thesen

  • Streams in Node.js sind ein grundlegendes Werkzeug für die Datenverarbeitung und -übertragung und eignen sich daher ideal für Echtzeit- und ereignisgesteuerte Anwendungen.
  • Um einen beschreibbaren Stream in Node.js zu erstellen, können Sie die Funktion createWriteStream() des fs-Moduls verwenden, die Daten an einen bestimmten Ort schreibt.
  • Lesbar, beschreibbar, duplex und transformieren sind die vier Arten von Streams in Node.js, jeder mit seinem eigenen Anwendungsfall und seiner eigenen Funktionalität.

Ein Stream ist ein grundlegendes Programmierwerkzeug, das den Datenfluss verwaltet. Im Kern stellt ein Stream typischerweise die sequentielle Übertragung von Bytes von einem Punkt zu einem anderen dar. Die offizielle Dokumentation von Node.js definiert einen Stream als abstrakte Schnittstelle, die Sie zum Arbeiten mit Daten verwenden können.

Die Übertragung von Daten auf einem Computer oder über ein Netzwerk ist eine ideale Verwendung eines Streams.

instagram viewer

Streams in Node.js

Streams haben eine wesentliche Rolle für den Erfolg von Node.js gespielt. Sie eignen sich ideal für Echtzeit-Datenverarbeitung und ereignisgesteuerte Anwendungen, zwei herausragende Merkmale der Node.js-Laufzeitumgebung.

Um einen neuen Stream in Node.js zu erstellen, müssen Sie die Stream-API verwenden, die ausschließlich mit Strings und funktioniert Node.js-Pufferdaten. Node.js verfügt über vier Arten von Streams: beschreibbar, lesbar, Duplex und Transformation.

So erstellen und verwenden Sie einen beschreibbaren Stream

Mit einem beschreibbaren Stream können Sie Daten an einen bestimmten Ort schreiben oder senden. Das fs-Modul (Dateisystem) verfügt über eine WriteStream-Klasse, mit der Sie einen neuen Stream erstellen können fs.createWriteStream() Funktion. Diese Funktion akzeptiert den Pfad zu der Datei, in die Sie Daten schreiben möchten, sowie ein optionales Array von Optionen.

const {createWriteStream} = require("fs");

(() => {
const file = "myFile.txt";
const myWriteStream = createWriteStream(file);
let x = 0;
const writeNumber = 10000;

const writeData = () => {
while (x < writeNumber) {
const chunk = Buffer.from(`${x}, `, "utf-8");
if (x writeNumber - 1) return myWriteStream.end(chunk);
if (!myWriteStream.write(chunk)) break;
x++
}
};

writeData();
})();

Dieser Code importiert die createWriteStream() Funktion, die die anonyme Pfeilfunktion Anschließend wird daraus ein Stream erstellt, der Daten in myFile.txt schreibt. Die anonyme Funktion enthält eine innere Funktion namens writeData() das schreibt Daten.

Der createWriteStream() Die Funktion arbeitet mit einem Puffer, um eine Sammlung von Zahlen (0–9.999) in die Zieldatei zu schreiben. Wenn Sie jedoch das obige Skript ausführen, wird im selben Verzeichnis eine Datei erstellt, die die folgenden Daten enthält:

Die aktuelle Zahlensammlung endet bei 2.915, sie hätte aber auch Zahlen bis 9.999 umfassen sollen. Diese Diskrepanz tritt auf, weil jeder WriteStream einen Puffer verwendet, der jeweils eine feste Datenmenge speichert. Um zu erfahren, was dieser Standardwert ist, müssen Sie die konsultieren Hochwassermarke Möglichkeit.

console.log("The highWaterMark value is: " +
myWriteStream.writableHighWaterMark + " bytes.");

Das Hinzufügen der obigen Codezeile zur anonymen Funktion führt zu der folgenden Ausgabe im Terminal:

Die Terminalausgabe zeigt die Standardeinstellung Hochwassermarke Der Wert (der anpassbar ist) beträgt 16.384 Bytes. Das bedeutet, dass Sie jeweils nur weniger als 16.384 Byte Daten in diesem Puffer speichern können. Bis zur Zahl 2.915 (plus alle Kommas und Leerzeichen) stellt dies die maximale Datenmenge dar, die der Puffer gleichzeitig speichern kann.

Die Lösung für den Pufferfehler besteht darin, ein Stream-Ereignis zu verwenden. Ein Stream trifft in verschiedenen Phasen des Datenübertragungsprozesses auf verschiedene Ereignisse. Der Abfluss Event ist für diese Situation die geeignete Option.

Im writeData() Funktion oben, der Aufruf der write() von WriteStream Die Funktion gibt „true“ zurück, wenn der Datenblock (oder der interne Puffer) unter dem liegt Hochwassermarke Wert. Dies zeigt an, dass die Anwendung mehr Daten an den Stream senden kann. Sobald jedoch die schreiben() Wenn die Funktion „false“ zurückgibt, wird die Schleife unterbrochen, da Sie den Puffer entleeren müssen.

myWriteStream.on('drain', () => {
console.log("a drain has occurred...");
writeData();
});

Einfügen der Abfluss Wenn Sie den obigen Ereigniscode in die anonyme Funktion einfügen, wird die Datei geleert Der Puffer von WriteStream wenn es voll ist. Dann erinnert es sich an die writeData() Methode, damit weiterhin Daten geschrieben werden können. Das Ausführen der aktualisierten Anwendung erzeugt die folgende Ausgabe:

Sie sollten beachten, dass die Anwendung das entleeren musste WriteStream-Puffer dreimal während seiner Ausführung. Auch die Textdatei erfuhr einige Änderungen:

So erstellen und verwenden Sie einen lesbaren Stream

Um Daten zu lesen, erstellen Sie zunächst einen lesbaren Stream mithilfe von fs.createReadStream() Funktion.

const {createReadStream} = require("fs");

(() => {
const file = "myFile.txt";
const myReadStream = createReadStream(file);

myReadStream.on("open", () => {
console.log(`The read stream has successfully opened ${file}.`);
});

myReadStream.on("data", chunk => {
console.log("The file contains the following data: " + chunk.toString());
});

myReadStream.on("close", () => {
console.log("The file has been successfully closed.");
});
})();

Das obige Skript verwendet die createReadStream() Methode, um auf die Datei zuzugreifen, die der vorherige Code erstellt hat: myFile.txt. Der createReadStream() Die Funktion akzeptiert einen Dateipfad (der in Form einer Zeichenfolge, eines Puffers oder einer URL vorliegen kann) und mehrere optionale Optionen als Argumente.

In der anonymen Funktion gibt es mehrere wichtige Stream-Ereignisse. Es gibt jedoch keine Anzeichen dafür Abfluss Ereignis. Dies liegt daran, dass ein lesbarer Stream Daten nur puffert, wenn Sie den aufrufen stream.push (Chunk) Funktion oder verwenden Sie die lesbar Ereignis.

Der offen Das Ereignis wird ausgelöst, wenn fs die Datei öffnet, aus der Sie lesen möchten. Wenn Sie das anbringen Daten Wenn Sie ein Ereignis in einen implizit kontinuierlichen Stream umwandeln, führt es dazu, dass der Stream in den Fließmodus übergeht. Dadurch können Daten weitergeleitet werden, sobald sie verfügbar sind. Das Ausführen der obigen Anwendung erzeugt die folgende Ausgabe:

So erstellen und verwenden Sie einen Duplex-Stream

Ein Duplex-Stream implementiert sowohl die beschreibbare als auch die lesbare Stream-Schnittstelle, sodass Sie in einen solchen Stream lesen und schreiben können. Ein Beispiel ist ein TCP-Socket, dessen Erstellung auf dem Netzmodul basiert.

Eine einfache Möglichkeit, die Eigenschaften eines Duplex-Streams zu demonstrieren, besteht darin, einen TCP-Server und -Client zu erstellen, der Daten überträgt.

Die server.js-Datei

const net = require('net');
const port = 5000;
const host = '127.0.0.1';

const server = net.createServer();

server.on('connection', (socket)=> {
console.log('Connection established from client.');

socket.on('data', (data) => {
console.log(data.toString());
});

socket.write("Hi client, I am server " + server.address().address);

socket.on('close', ()=> {
console.log('the socket is closed')
});
});

server.listen(port, host, () => {
console.log('TCP server is running on port: ' + port);
});

Die client.js-Datei

const net = require('net');
const client = new net.Socket();
const port = 5000;
const host = '127.0.0.1';

client.connect(port, host, ()=> {
console.log("connected to server!");
client.write("Hi, I'm client " + client.address().address);
});

client.on('data', (data) => {
console.log(data.toString());
client.write("Goodbye");
client.end();
});

client.on('end', () => {
console.log('disconnected from server.');
});

Sie werden feststellen, dass sowohl die Server- als auch die Client-Skripte einen lesbaren und beschreibbaren Stream für die Kommunikation (Übertragung und Empfang von Daten) verwenden. Natürlich wird zuerst die Serveranwendung ausgeführt und beginnt, auf Verbindungen zu warten. Sobald Sie den Client starten, verbindet er sich mit dem Server die TCP-Portnummer.

Nach dem Verbindungsaufbau initiiert der Client die Datenübertragung, indem er mit seinem auf den Server schreibt WriteStream. Der Server protokolliert die empfangenen Daten am Terminal und schreibt dann Daten mithilfe seines WriteStream. Abschließend protokolliert der Client die empfangenen Daten, schreibt zusätzliche Daten und trennt dann die Verbindung zum Server. Der Server bleibt für die Verbindung anderer Clients geöffnet.

So erstellen und verwenden Sie einen Transformationsstream

Transformationsströme sind Duplexströme, bei denen die Ausgabe mit der Eingabe zusammenhängt, sich jedoch von ihr unterscheidet. Node.js verfügt über zwei Arten von Transform-Streams: Zlib- und Crypto-Streams. Ein Zlib-Stream kann eine Textdatei komprimieren und nach der Dateiübertragung wieder dekomprimieren.

Die compressFile.js-Anwendung

const zlib = require('zlib');
const { createReadStream, createWriteStream } = require('fs');

(() => {
const source = createReadStream('myFile.txt');
const destination = createWriteStream('myFile.txt.gz');

source.pipe(zlib.createGzip()).pipe(destination);
})();

Dieses einfache Skript nimmt die ursprüngliche Textdatei, komprimiert sie und speichert sie im aktuellen Verzeichnis. Dank der lesbaren Streams ist dies ein einfacher Vorgang Rohr() Methode. Stream-Pipelines machen die Verwendung von Puffern überflüssig und leiten Daten direkt von einem Stream an einen anderen weiter.

Bevor die Daten jedoch den beschreibbaren Stream im Skript erreichen, erfolgt ein kleiner Umweg über die Methode createGzip() der zlib. Diese Methode komprimiert die Datei und gibt ein neues Gzip-Objekt zurück, das der Schreibstream dann empfängt.

Die decompressFile.js-Anwendung

const zlib = require('zlib'); 
const { createReadStream, createWriteStream } = require('fs');
 
(() => {
const source = createReadStream('myFile.txt.gz');
const destination = createWriteStream('myFile2.txt');

source.pipe(zlib.createUnzip()).pipe(destination);
})();

Das obige Skript nimmt die komprimierte Datei und dekomprimiert sie. Wenn Sie das neue öffnen myFile2.txt Datei, werden Sie sehen, dass sie dieselben Daten wie die Originaldatei enthält:

Warum sind Streams wichtig?

Streams steigern die Effizienz der Datenübertragung. Lese- und beschreibbare Streams dienen als Grundlage für die Kommunikation zwischen Clients und Servern sowie die Komprimierung und Übertragung großer Dateien.

Streams verbessern auch die Leistung von Programmiersprachen. Ohne Streams wird der Datenübertragungsprozess komplexer, erfordert mehr manuelle Eingaben von Entwicklern und führt zu mehr Fehlern und Leistungsproblemen.