Contents

Wprowadzenie do korzystania ze strumieni w Node.js

Kluczowe wnioski

Strumienie odgrywają istotną rolę w Node.js, ponieważ ułatwiają wydajne przetwarzanie i przesyłanie danych, zapewniając w ten sposób optymalne wsparcie dla aplikacji działających w czasie rzeczywistym i wyzwalanych zdarzeniami.

Korzystając z oferty modułu systemu plików Node.js, można wykorzystać funkcję createWriteStream() do utworzenia zapisywalnego strumienia, który kieruje dane do określonego obszaru.

Środowisko Node.js obejmuje różne typy strumieni, które zaspokajają różne cele i wymagania. Te cztery podstawowe kategorie obejmują strumienie do odczytu, zapisu, dupleksu i transformacji. Każdy typ pełni odrębną funkcję i zapewnia określone możliwości, umożliwiając programistom wybór najbardziej odpowiedniej opcji dla ich konkretnych potrzeb aplikacji.

Zasadniczo strumień służy jako kluczowa konstrukcja programistyczna ułatwiająca ciągłe przesyłanie informacji z jednej lokalizacji do drugiej. Koncepcja strumienia zasadniczo obraca się wokół systematycznego przekazywania bajtów wzdłuż z góry określonej ścieżki. Zgodnie z autorytatywnymi zasobami Node.js, strumień stanowi hipotetyczną strukturę, która umożliwia użytkownikom interakcję z danymi i manipulowanie nimi.

Wydajna transmisja informacji za pośrednictwem strumienia jest doskonałym przykładem jego zastosowania w systemach komputerowych i komunikacji sieciowej.

Strumienie w Node.js

Strumienie były kluczowym czynnikiem w rozwoju Node.js ze względu na ich przydatność do przetwarzania danych w czasie rzeczywistym i aplikacji sterowanych zdarzeniami, które są podstawowymi aspektami środowiska uruchomieniowego Node.js.

Aby ustanowić nowy przepływ w Node.js, należy użyć interfejsu API strumienia, który jest ściśle poświęcony obsłudze obiektów String i buforowaniu danych w systemie. Warto zauważyć, że istnieją cztery główne kategorie strumieni rozpoznawane przez Node.js, a mianowicie zapisywalne, czytelne, dupleksowe i transformacyjne.

Jak utworzyć i używać zapisywalnego strumienia

Moduł File System (fs) udostępnia klasę WriteStream, która umożliwia utworzenie zapisywalnego strumienia do przesyłania danych do wyznaczonego miejsca docelowego. Korzystając z metody fs.createWriteStream(), można utworzyć nowy strumień i określić żądaną ścieżkę docelową za pomocą dostarczonego parametru. Dodatkowo, w razie potrzeby, można dołączyć opcjonalną tablicę opcji konfiguracyjnych.

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

Podany kod importuje funkcję createWriteStream() , która jest wykorzystywana w anonimowej funkcji strzałki do generowania strumienia plików, który dołącza dane do określonego pliku, w tym przypadku “myFile.txt”.W anonimowej funkcji znajduje się wbudowana funkcja o nazwie writeData() , odpowiedzialna za zapisywanie informacji do wskazanego pliku.

Funkcja createWriteStream() wykorzystuje bufor do wpisania serii cyfr (od 0 do 9 999) w określonym pliku wyjściowym. Po wykonaniu skrypt ten generuje plik znajdujący się w bieżącym katalogu i wypełnia go następującymi informacjami:

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

Obecny asortyment cyfr kończy się na 2,915; jednak powinien obejmować cyfry aż do 9. Ta rozbieżność wynika z tego, że każdy WriteStream wykorzystuje pamięć podręczną, która przechowuje z góry określoną ilość informacji w danym momencie. Aby poznać domyślną wartość tego ustawienia, należy odwołać się do opcji highWaterMark.

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

Włączenie wspomnianej instrukcji do nienazwanej funkcji spowoduje wygenerowanie następującego komunikatu w wierszu polecenia:

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

Wyświetlone dane wyjściowe z terminala wskazują, że wstępnie skonfigurowany próg highWaterMark jest domyślnie ustawiony na 16 384 bajty. W rezultacie ogranicza to pojemność tego bufora, aby pomieścić nie więcej niż 16 384 bajtów informacji jednocześnie. W związku z tym pierwsze 2 915 znaków (w tym wszelkie przecinki lub spacje) obejmuje limit danych, które mogą być przechowywane w buforze jednocześnie.

Aby rozwiązać problem błędów bufora, zaleca się wykorzystanie zdarzenia strumienia. Strumienie przechodzą kilka zdarzeń podczas procesu transmisji danych, które występują w różnych punktach w czasie. Wśród tych zdarzeń, zdarzenie drain jest szczególnie odpowiednie do obsługi sytuacji, w których pojawiają się błędy bufora.

W implementacji funkcji writeData() wywołanie metody WriteStream obiektu write() zwraca wartość logiczną wskazującą, czy przydzielony fragment danych lub bufor wewnętrzny osiągnął z góry określony próg znany jako “high water mark”. Jeśli ten warunek jest spełniony, oznacza to, że aplikacja jest w stanie przesłać dodatkowe dane do powiązanego strumienia wyjściowego. I odwrotnie, gdy metoda write() zwróci wartość false, przepływ sterowania przechodzi do opróżniania wszelkich pozostałych danych w buforze, ponieważ dalszy zapis nie może być wykonywany, dopóki bufor nie zostanie opróżniony.

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

Włączenie wyżej wymienionego kodu zdarzenia drain do anonimowej funkcji pozwala na opróżnienie bufora WriteStream, gdy osiągnie on maksymalną pojemność.W konsekwencji powoduje to wywołanie metody writeData() w celu umożliwienia dalszej transmisji danych. Po wykonaniu zmodyfikowanego programu otrzymujemy następujące wyniki:

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

Ważne jest, aby wziąć pod uwagę, że aplikacja była zmuszona do wyczyszczenia bufora WriteStream przy trzech oddzielnych okazjach podczas jej działania. Ponadto wydaje się, że plik tekstowy również został poddany pewnym zmianom.

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

Jak utworzyć i używać strumienia do odczytu

Aby zainicjować proces odczytu danych, należy rozpocząć od utworzenia zrozumiałego strumienia za pomocą funkcji fs.createReadStream() .

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

Skrypt wykorzystuje metodę createReadStream(), aby uzyskać dostęp do pliku o nazwie “myFile.txt”, który został wcześniej wygenerowany przez wcześniejszą iterację kodu. Ta konkretna metoda otrzymuje ścieżkę do pliku, która może być przedstawiona w formacie łańcucha, bufora lub adresu URL, wraz z różnymi opcjonalnymi parametrami jako argumentami do przetworzenia.

W kontekście anonimowej funkcji odnoszącej się do strumieni, warto zauważyć, że istnieje wiele znaczących wystąpień związanych ze strumieniami. Niemniej jednak można zaobserwować brak jakichkolwiek wskazówek dotyczących wystąpienia zdarzenia “drain”. Zjawisko to można przypisać faktowi, że odczytywalny strumień zazwyczaj nie buforuje danych, dopóki nie zostanie wywołana funkcja “stream.push(chunk)” lub nie zostanie wykorzystane zdarzenie “readable”.

Uruchomienie zdarzenia open następuje za każdym razem, gdy plik jest otwierany do odczytu przez użytkownika. Poprzez dołączenie zdarzenia danych do z natury ciągłego strumienia, strumień przechodzi w stan, w którym dane mogą być przesyłane natychmiast po ich udostępnieniu. Wykonanie dostarczonego kodu wygeneruje następujące dane wyjściowe:

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

Jak utworzyć i używać strumienia dupleksowego

Strumień dupleksowy obejmuje zarówno możliwość zapisu, jak i odczytu, umożliwiając w ten sposób jednoczesne operacje odczytu i zapisu za pośrednictwem tej samej instancji. Przykładowy przypadek obejmuje wykorzystanie modułu sieciowego w połączeniu z ustanowieniem gniazda TCP.

Prosta metoda zilustrowania charakterystyki dupleksowego kanału komunikacyjnego polega na opracowaniu serwera Transmission Control Protocol (TCP) i systemu klienckiego zdolnego do wymiany informacji.

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

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

Rzeczywiście, zarówno serwer, jak i skrypty klienckie wykorzystują odczytywalny i zapisywalny strumień w celu ułatwienia komunikacji, umożliwiając wymianę danych między nimi. Dogodnie, aplikacja serwera jest inicjowana jako pierwsza i zaczyna aktywnie czekać na połączenia przychodzące. Po uruchomieniu klient nawiązuje połączenie z serwerem, określając

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

Po utworzeniu połączenia klient ułatwia transmisję danych, wykorzystując swój WriteStream do wysyłania informacji do serwera. Jednocześnie serwer rejestruje przychodzące dane w terminalu przed wykorzystaniem własnego WriteStream do przesłania danych z powrotem do klienta. Następnie klient rejestruje otrzymane dane i kontynuuje wymianę większej ilości informacji, po czym kończy połączenie. W tym momencie serwer utrzymuje stan otwarty, aby pomieścić wszelkie dalsze połączenia od klientów.

Jak utworzyć i używać strumienia transformującego

Strumienie zlib i kryptograficzne. Strumienie Zlib ułatwiają kompresję i późniejszą dekompresję plików tekstowych podczas przesyłania plików, podczas gdy strumienie kryptograficzne umożliwiają bezpieczną komunikację poprzez operacje szyfrowania i deszyfrowania.

Aplikacja compressFile.js

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

Ten prosty skrypt działa poprzez pobranie początkowego dokumentu tekstowego, skompresowanie go i zarchiwizowanie w obecnym folderze, dzięki skuteczności funkcji pipe() strumienia do odczytu. Eliminacja buforów dzięki technologii potoku strumieniowego ułatwia tę procedurę.

Zanim dane zostaną zapisane w zapisywalnym strumieniu skryptu, przechodzą niewielką zmianę w procesie kompresji, którą ułatwia metoda createGzip() biblioteki zlib. Wspomniana metoda generuje skompresowaną wersję pliku i zwraca nową instancję obiektu Gzip, który następnie staje się odbiorcą strumienia zapisu.

Aplikacja decompressFile.js

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

Dzisiejsza aplikacja rozszerza skompresowany dokument, a gdyby ktoś miał zbadać nowo utworzony plik o nazwie “myFile2.txt”, przekonałby się, że jego zawartość dokładnie odpowiada zawartości oryginalnego dokumentu.

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

Dlaczego strumienie są ważne?

Strumienie odgrywają kluczową rolę w optymalizacji transmisji danych, ułatwiając płynną komunikację między klientami a systemami serwerów, jednocześnie wspierając wydajną kompresję i przesyłanie plików o znacznych rozmiarach.

Strumienie znacząco zwiększają funkcjonalność języków programowania poprzez uproszczenie procesu przesyłania danych. Brak funkcji strumieni prowadzi do zwiększonej złożoności operacji przesyłania danych, wymagając większego stopnia ręcznej interwencji ze strony programistów, co może skutkować wzrostem liczby błędów i zmniejszeniem wydajności.