Contents

Een inleiding tot het gebruik van streams in Node.js

Belangrijkste vaststellingen

Streams spelen een essentiële rol in Node.js omdat ze een efficiënte gegevensverwerking en -overdracht mogelijk maken en daardoor optimale ondersteuning bieden voor realtime en event-triggered toepassingen.

Door gebruik te maken van de mogelijkheden van de Node.js bestandssysteemmodule, kan de functie createWriteStream() gebruikt worden om een schrijfbare stream te maken die gegevens naar een bepaald gebied stuurt.

De Node.js-omgeving omvat een verscheidenheid aan streamtypen die tegemoet komen aan verschillende doeleinden en vereisten. Deze vier primaire categorieën zijn leesbare, beschrijfbare, duplex en transformeerbare streams. Elk type heeft een eigen functie en biedt specifieke mogelijkheden, zodat ontwikkelaars de meest geschikte optie kunnen kiezen voor hun specifieke applicatiebehoeften.

In essentie dient een stream als een cruciale programmeerconstructie om de continue overdracht van informatie van de ene locatie naar de andere te vergemakkelijken. Het concept van een stream draait in wezen om de systematische overdracht van bytes langs een vooraf bepaald pad. Volgens de gezaghebbende bronnen van Node.js vormt een stream een hypothetisch raamwerk dat gebruikers in staat stelt om te interageren met gegevens en deze te manipuleren.

De efficiënte overdracht van informatie via een stream is een goed voorbeeld van de toepassing ervan in computersystemen en netwerkcommunicatie.

Streams in Node.js

Streams zijn een cruciale factor geweest in het succes van Node.js vanwege hun geschiktheid voor real-time gegevensverwerking en gebeurtenisgestuurde toepassingen, die beide fundamentele aspecten zijn van de Node.js runtime-omgeving.

Om een nieuwe stroom op te zetten in Node.js, moet men de stream API gebruiken, die strikt gewijd is aan het afhandelen van String-objecten en het bufferen van gegevens binnen het systeem. Er zijn vier hoofdcategorieën streams die door Node.js worden herkend, namelijk beschrijfbare, leesbare, duplex en transformatieve varianten.

Hoe een schrijfbare stream maken en gebruiken

De module File System (fs) biedt een klasse WriteStream waarmee een schrijfbare stream kan worden gemaakt om gegevens naar een bepaalde bestemming te verzenden. Door gebruik te maken van de methode fs.createWriteStream() kan een nieuwe stream worden aangemaakt en het gewenste doelpad worden opgegeven met behulp van de opgegeven parameter. Daarnaast kan, indien nodig, een optionele array met configuratiekeuzes worden toegevoegd.

 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();
})();

De gegeven code importeert de functie createWriteStream() , die wordt gebruikt binnen een anonieme pijlfunctie om een bestandsstroom te genereren die gegevens toevoegt aan een opgegeven bestand, in dit geval “myFile.txt”.Binnen de anonieme functie ligt een ingebedde functie met de naam writeData() , verantwoordelijk voor het schrijven van informatie naar het aangewezen bestand.

De functie createWriteStream() gebruikt een buffer om een reeks cijfers (variërend van 0 tot 9.999) in het opgegeven uitvoerbestand te schrijven. Wanneer dit script wordt uitgevoerd, genereert het een bestand in de huidige map en vult het dit met de volgende informatie:

/nl/images/myfile-initial-data-1.jpg

Het huidige assortiment cijfers eindigt bij 2.915; het zou echter cijfers zo hoog als 9 moeten bevatten. Dit verschil is het gevolg van het feit dat elke WriteStream een cache gebruikt die op elk moment een vooraf bepaalde hoeveelheid informatie vasthoudt. Om de standaardwaarde van deze instelling te achterhalen, moet de optie highWaterMark worden gebruikt.

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

Het opnemen van de bovenstaande instructie in de naamloze functie resulteert in het genereren van het volgende bericht op de opdrachtprompt:

/nl/images/writestream-highwatermark-1.jpg

De weergegeven uitvoer van de terminal geeft aan dat de vooraf geconfigureerde drempelwaarde voor highWaterMark standaard is ingesteld op 16.384 bytes. Daarom wordt de capaciteit van deze buffer beperkt tot niet meer dan 16.384 bytes aan informatie tegelijkertijd. Dienovereenkomstig omvatten de eerste 2.915 tekens (inclusief komma’s of spaties) de limiet van de gegevens die in één keer in de buffer kunnen worden opgeslagen.

Om het probleem van bufferfouten aan te pakken, wordt het aanbevolen om een stream event te gebruiken. Streams ondergaan verschillende gebeurtenissen tijdens het gegevensoverdrachtsproces die op verschillende momenten plaatsvinden. Van deze events is de drain event bijzonder geschikt voor het afhandelen van situaties waarin bufferfouten optreden.

In de implementatie van de writeData() functie retourneert de aanroep van de WriteStream object’s write() methode een booleaans die aangeeft of de toegewezen brok gegevens of interne buffer al dan niet de vooraf bepaalde drempel heeft bereikt die bekend staat als het “hoogwaterteken”. Als aan deze voorwaarde wordt voldaan, betekent dit dat de applicatie in staat is om extra gegevens naar de bijbehorende uitvoerstroom te verzenden. Omgekeerd, als de methode write() een foutieve waarde retourneert, gaat de besturingsstroom verder met het leegmaken van alle resterende gegevens in de buffer, aangezien verder schrijven niet kan worden uitgevoerd totdat de buffer is geleegd.

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

Door de bovengenoemde code voor het leegmaken van de gebeurtenis op te nemen in een anonieme functie, kan de buffer van de WriteStream worden leeggemaakt wanneer deze zijn maximale capaciteit heeft bereikt.Hierdoor wordt de methode writeData() opgeroepen om verdere gegevensoverdracht mogelijk te maken. Bij het uitvoeren van het gewijzigde programma worden de volgende resultaten verkregen:

/nl/images/the-writestream-drain-event-1.jpg

Het is belangrijk om er rekening mee te houden dat de applicatie genoodzaakt was om de buffer WriteStream bij drie afzonderlijke gelegenheden leeg te maken. Daarnaast lijkt het erop dat het tekstbestand ook bepaalde wijzigingen heeft ondergaan.

/nl/images/myfile-updated-data-1.jpg

Hoe een leesbare stream maken en gebruiken

Om het proces van het lezen van gegevens te starten, begin je met het maken van een begrijpelijke stream door gebruik te maken van de fs.createReadStream() functie.

 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.");
  });
})(); 

Het script gebruikt de methode createReadStream() om toegang te krijgen tot het bestand getiteld “myFile.txt” dat eerder werd gegenereerd door een eerdere iteratie van de code. Deze specifieke methode ontvangt een bestandspad, dat kan worden gepresenteerd in een string-, buffer- of URL-indeling, samen met verschillende optionele parameters als argumenten voor verwerking.

In de context van een anonieme functie met betrekking tot streams, is het opmerkelijk dat er meerdere significante stream-gerelateerde gebeurtenissen bestaan. Toch is er geen enkele aanwijzing voor het optreden van de “drain”-gebeurtenis. Dit fenomeen kan worden toegeschreven aan het feit dat een leesbare stream gewoonlijk geen gegevens buffert totdat de functie “stream.push(chunk)” wordt aangeroepen of de gebeurtenis “readable” wordt gebruikt.

Het afvuren van de open event vindt plaats wanneer een bestand wordt geopend voor lezen door de gebruiker. Door de data-event te koppelen aan een inherent continue stroom, gaat de stroom over in een toestand waarin gegevens onmiddellijk kunnen worden verzonden zodra ze beschikbaar zijn. Het uitvoeren van de geleverde code genereert de volgende uitvoer:

/nl/images/read-stream-terminal-output-1.jpg

Hoe maak en gebruik je een duplex stream

Een duplex stream heeft zowel de mogelijkheid om schrijfbaar als leesbaar te zijn, waardoor gelijktijdige lees- en schrijfbewerkingen via dezelfde instantie mogelijk zijn. Een illustratief geval betreft het gebruik van de netmodule in combinatie met het opzetten van een TCP socket.

Een eenvoudige methode om de kenmerken van een duplex communicatiekanaal te illustreren bestaat uit het ontwikkelen van een TCP-server (Transmission Control Protocol) en -clientsysteem die informatie kunnen uitwisselen.

Het bestand server.js

 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);
}); 

Het bestand client.js

 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.');
});

Zowel de server- als clientscripts maken gebruik van een leesbare en schrijfbare stream om de communicatie te vergemakkelijken, waardoor gegevens tussen hen kunnen worden uitgewisseld. Gemakshalve wordt de serverapplicatie als eerste gestart en begint actief te wachten op binnenkomende verbindingen. Na het starten van de client maakt deze een verbinding met de server door

/nl/images/tcp-server-and-client-terminal-output-1.jpg

op te geven. Na het maken van een verbinding vergemakkelijkt de client de gegevensoverdracht door zijn WriteStream te gebruiken om informatie naar de server te sturen. Tegelijkertijd registreert de server de binnenkomende gegevens in de terminal voordat hij zijn eigen WriteStream gebruikt om gegevens terug te sturen naar de client. Vervolgens logt de client de ontvangen gegevens en gaat verder met het uitwisselen van meer informatie, gevolgd door het beëindigen van de verbinding. Op dit punt houdt de server een open status om verdere verbindingen van cliënten te accommoderen.

Hoe een Transform Stream maken en gebruiken

zlib en cryptografische streams. Zlib streams faciliteren de compressie en daaropvolgende decompressie van tekstbestanden tijdens bestandsoverdracht, terwijl cryptografische streams veilige communicatie mogelijk maken door encryptie- en decryptiebewerkingen.

De compressFile.js toepassing

 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);
})(); 

Dit rechttoe rechtaan script neemt het initiële tekstdocument, comprimeert het en archiveert het in de huidige map, dankzij de efficiëntie van de pipe() functionaliteit van de leesbare stream. De eliminatie van buffers door stream pipeline technologie vergemakkelijkt deze procedure.

Voordat de gegevens naar de schrijfbare stream van het script worden geschreven, ondergaan ze een kleine omleiding door het proces van compressie dat mogelijk wordt gemaakt door de methode createGzip() van de zlib-bibliotheek. De genoemde methode genereert een gecomprimeerde versie van het bestand en retourneert een nieuwe instantie van een Gzip-object, dat vervolgens de ontvanger van de schrijfstroom wordt.

De decompressFile.js toepassing

 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);
})(); 

De huidige toepassing breidt het gecomprimeerde document uit en als iemand het nieuw aangemaakte bestand met de naam “myFile2.txt” zou onderzoeken, zou hij zien dat de inhoud exact overeenkomt met die van het originele document.

/nl/images/myfile2-data-1.jpg

Waarom zijn streams belangrijk?

Streams spelen een cruciale rol bij het optimaliseren van gegevensoverdracht door naadloze communicatie tussen clients en serversystemen mogelijk te maken, terwijl ze ook de efficiënte compressie en overdracht van grote bestanden ondersteunen.

Streams verbeteren de functionaliteit van programmeertalen aanzienlijk door het proces van gegevensoverdracht te vereenvoudigen. Het ontbreken van een streamfunctie leidt tot een grotere complexiteit in gegevensoverdrachtoperaties, waardoor ontwikkelaars meer handmatige interventie nodig hebben, wat kan resulteren in een toename van fouten en verminderde prestaties.