Introducción a las transmisiones de Node.js

I

Introducción

Las transmisiones son un concepto algo avanzado de entender. Entonces, en este artículo, seguiremos con algunos ejemplos para una mejor comprensión y le presentaremos algunos conceptos en el camino.

¿Qué es una corriente?

En términos simples, los flujos se utilizan para leer desde la entrada o escribir en la salida de forma secuencial. La mayoría de las veces, las transmisiones se utilizan para leer o escribir desde una fuente continua o comparativamente grande.

Por ejemplo, supongamos que tiene que leer un archivo grande. Si el tamaño del archivo es mayor que su espacio de memoria libre, no puede leer el archivo completo en la memoria para procesarlo. Tienes que leerlo pieza por pieza y procesar cada fragmento, que puede estar separado por una línea, por ejemplo.

Otro ejemplo de una fuente continua es la comunicación en red, como una aplicación de chat donde los datos deben fluir continuamente del remitente al receptor.

Secuencias en Node.js

los Stream module es un módulo nativo que se envía de forma predeterminada en Node.js. los Stream es una instancia del EventEmitter class, que maneja eventos de forma asincrónica en Node.js. Debido a su superclase, las transmisiones se basan inherentemente en eventos.

Hay 4 tipos de transmisiones en Node.js:

  • Escribible: Se usa para escribir datos secuencialmente
  • Legible: Se usa para leer datos secuencialmente
  • Dúplex: Se utiliza para leer y escribir datos secuencialmente
  • Transformar: Donde los datos se pueden modificar al escribir o leer. Tomemos la compresión como ejemplo, con un flujo como este, puede escribir datos comprimidos y leer datos descomprimidos.

Echemos un vistazo a algunos ejemplos de transmisiones.

Secuencias grabables

En primer lugar, creemos un flujo de escritura y escribamos algunos datos en un archivo:

const fs = require('fs');
const file = fs.createWriteStream('file.txt');

file.write('hello world');
file.end(', from streams!');

En este código, hemos utilizado el módulo del sistema de archivos para crear una secuencia de escritura en un archivo (file.txt) y escribirle 2 fragmentos separados: hello world y , from streams.

A diferencia del fs.writeFile() donde necesitamos escribir el contenido del archivo a la vez, usando una secuencia podemos escribir el contenido fragmento por fragmento.

Para simular una entrada continua, podríamos hacer algo como:

const fs = require('fs');
const file = fs.createWriteStream('file.txt');

for (let i = 0; i < 10000; i++) {
    file.write('Hello world ' + i);
}
file.end();

Esto escribirá Hello world + {i} diez mil veces y luego terminar la corriente:

Hello world 0
Hello world 1
Hello world 2
Hello world 3
Hello world 4
...

Por favor reString .end() sus transmisiones una vez que haya terminado de usarlas, ya que finish El evento se envía después del .end() se ha llamado al método.

Esto significa que el cuerpo de la secuencia se ha vaciado en nuestro archivo.

Secuencias legibles

Ahora echemos un vistazo a otro ejemplo simple leyendo un archivo usando una secuencia. Podemos leer un archivo fragmento por fragmento, en lugar de leer el contenido completo en la memoria, utilizando una secuencia legible:

const fs = require('fs');

const readableStream = fs.createReadStream('./article.md', {
    highWaterMark: 10
});

readableStream.on('readable', () => {
    process.stdout.write(`[${readableStream.read()}]`);
});

readableStream.on('end', () => {
    console.log('DONE');
});

De manera similar a la creación de una secuencia de escritura, hemos creado una secuencia legible llamando al .createReadStream() método.

Mientras se almacena en búfer (segmentando los datos en fragmentos), el tamaño del búfer depende de la highWaterMark parámetro, que se pasa al constructor de la secuencia.

El valor predeterminado de este parámetro es 16384 bytes (16 kb), por lo que si no anula el parámetro, la secuencia leerá fragmentos de 16 kb y se los pasará para que los procese.

Dado que estamos usando un archivo de texto pequeño, tiene más sentido usar un valor pequeño para nuestro ejemplo, por lo que el texto tendrá 10 caracteres.

En nuestro ejemplo anterior, simplemente imprimimos el fragmento de datos que recibimos, excepto con corchetes alrededor para que pueda ver fácilmente los diferentes fragmentos. La salida de nuestro código se ve así:

[### Introd][uction

St][reams are ][a somewhat][ advanced ][concept to][ understan][d. So in t][his articl][e, we will][ go along ][with some ][examples f][or a bette][r understa][nding and ][introduce ][you to a f][ew concept][s along th][e way.

##][# What is ][a Stream

][In simple ]...

Secuencias dúplex

Con los flujos de escritura y legibles fuera del camino, podemos saltar a un ejemplo usando flujos dúplex, que esencialmente combinan ambos.

Los demostraremos usando un servidor HTTP simple construido con el nativo de Node.js http módulo. El ejemplo utilizado aquí es del oficial Documentación de Node.js.

Dado que los servidores reciben solicitudes y luego envían respuestas, son un buen ejemplo de transmisiones dúplex, que manejan ambos: una transmisión legible actuará como una solicitud continua y una transmisión de escritura actuará como respuesta.

Primero, importemos el módulo HTTP:

const http = require('http');

Ahora creemos un servidor HTTP simple:

const server = http.createServer((req, res) => {
    // `req` is an http.IncomingMessage, which is a Readable Stream.
    // `res` is an http.ServerResponse, which is a Writable Stream.

    let body = '';

    // Get the data as utf8 strings.
    // If an encoding is not set, Buffer objects will be received.
    req.setEncoding('utf8');

    // Readable streams emit 'data' events once a listener is added.
    req.on('data', (chunk) => {
        body += chunk;
    });

    // The 'end' event indicates that the entire body has been received.
    req.on('end', () => {
        consol.log(body);

        try {
            // Send 'Hello World' to the user
            res.write('Hello World');
            res.end();
        } catch (er) {
            res.statusCode = 400;
            return res.end(`error: ${er.message}`);
        }
    });
});

los req El parámetro es un flujo legible, que procesaremos al recibirlo como una solicitud HTTP. Luego enviaremos res como respuesta, que es, de nuevo, un simple flujo de escritura.

Luego, usando el .on() método, leemos el cuerpo de la solicitud en trozos de 64 KB y lo almacenamos en el body, provocado por el data evento.

Tenga en cuenta el uso del setEncoding() antes de leer de la secuencia.

De esta forma, la secuencia emitirá cadenas y emitirá Buffer objetos de otra manera. Sin embargo, también puede realizar esa conversación dentro del data devolución de llamada de evento si lo prefiere.

los end El evento se activa cuando no queda nada para leer en una secuencia legible. Hablaremos de otros eventos útiles más adelante en este artículo.

Ahora, escuchemos al servidor:

server.listen(1337);

Golpeando http://localhost:1337, deberías ver un simple Hello World respuesta del servidor HTTP.

Canalizaciones de flujo

Mediante el uso de canales de flujo, podemos canalizar directamente flujos legibles a un flujo de escritura sin almacenar el búfer temporalmente, por lo que podemos ahorrar espacio en la memoria.

Considere un escenario en el que un usuario solicita un archivo grande del servidor y no hay espacio de memoria para cargarlo en la memoria, o miles de clientes diferentes solicitan el mismo archivo. En este caso, no podemos leer el contenido del archivo en la memoria y luego volver a escribirlo en el cliente.

Aquí es donde el pipe El método es útil, ya que canalizaremos un flujo legible (una solicitud) en un flujo de escritura (una respuesta) y se lo entregaremos al usuario sin retenerlo en el búfer.

Primero, hagamos esto sin usar transmisiones:

const fs = require('fs');
const server = require('http').createServer();

server.on('request', (req, res) => {
    fs.readFile('./video.mkv', (err, data) => {
        if (err) throw err;

        res.end(data);
    });
});

server.listen(1337);

Este método es leer directamente el archivo en la memoria usando el .readFile() método y lo envía al usuario.

Abra su navegador web y vaya a http://localhost:1337, esto es lo que sucede detrás de escena:

Ahora, sirvamos el video usando una transmisión:

const http = require('http');
const fs = require('fs');

const server = http.createServer((req, res) => {
    const src = fs.createReadStream('./video.mkv');
    src.pipe(res);
});

server.listen(1337);

En este código, hemos creado una secuencia legible al archivo y la canalizamos directamente a la respuesta HTTP, por lo que, en lugar de cargarla en la memoria, la entrada del disco HDD se escribe directamente en la red sin consumir memoria.

Aquí está la captura de pantalla del uso de memoria mientras se envía el archivo usando una secuencia:

Como puede ver, el uso de memoria es demasiado bajo en comparación con el primer método.

Eventos útiles en una secuencia

Desde el Stream la clase hereda el EventEmitter clase, cada transmisión tendrá su propio tipo de eventos a los que puede suscribirse mediante el EventEmitteres on() método. Este evento dependerá del tipo de transmisión.

Eventos en transmisiones legibles

  • data: Se emite cuando se lee un fragmento de datos de la transmisión. De forma predeterminada, el fragmento será un Buffer objeto. Si quieres cambiarlo puedes usar el .setEncoding() método.
  • error: Emitido cuando ocurre un error durante la lectura. Esto puede suceder si la secuencia de escritura no puede generar datos debido a alguna falla interna o cuando se inserta un fragmento no válido en la secuencia.
  • end: Emitido cuando no hay más datos en la transmisión.
  • close: Se emite cuando se cierra el recurso de transmisión e indica que no se emitirán más eventos en el futuro.
  • readable: Emitido cuando los datos están disponibles en la secuencia legible para leer.

Eventos en transmisiones grabables

  • close: Se emite cuando se cierra el recurso de transmisión e indica que no se emitirán más eventos en el futuro.
  • error: Emitido cuando ocurre un error durante la lectura. Esto puede suceder si la secuencia de escritura no puede generar datos debido a alguna falla interna o cuando se envían datos de fragmentos no válidos a la secuencia.
  • finish: Emitido cuando todos los datos se han vaciado de la secuencia de escritura.
  • pipe: Emitido cuando la secuencia de escritura se canaliza a una secuencia legible.
  • unpipe: Emitido cuando la secuencia de escritura no se canaliza de una secuencia legible.

Conclusión

En términos simples, los flujos se utilizan para leer desde la entrada o escribir en la salida de forma secuencial. La mayoría de las veces, las transmisiones se utilizan para leer o escribir desde una fuente continua o comparativamente grande.

El módulo Stream es un módulo nativo que se envía de forma predeterminada en Node.js. los Stream es una instancia del EventEmitter class, que maneja eventos de forma asincrónica en Node.js. Debido a su superclase, las transmisiones se basan inherentemente en eventos.

Las corrientes de transformación no se trataron en este artículo, ya que justifican su propio artículo.

El código fuente de este proyecto está disponible en GitHub como siempre. Use esto para comparar su código si se atascó en el tutorial.

Si desea obtener más información sobre transmisiones o conocimientos avanzados, se recomienda seguir las documentación oficial para Streams.

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad