خبر و ترفند روز

خبر و ترفند های روز را اینجا بخوانید!

مقدمه ای بر استفاده از Streams در Node.js

استریم‌ها در Node.js می‌توانند پیچیده باشند، اما ارزش آن را دارد که برای درک آنها وقت بگذارید.

نکات کلیدی

  • Stream ها در Node.js یک ابزار اساسی برای پردازش و انتقال داده ها هستند و آنها را برای برنامه های زمان واقعی و رویداد محور ایده آل می کند.
  • برای ایجاد یک جریان قابل نوشتن در Node.js، می توانید از تابع ()createWriteStream ماژول fs استفاده کنید که داده ها را در یک مکان خاص می نویسد.
  • قابل خواندن، نوشتن، دوبلکس و تبدیل چهار نوع جریان در Node.js هستند که هر کدام مورد استفاده و عملکرد خاص خود را دارند.

جریان یک ابزار برنامه نویسی اساسی است که با جریان داده ها سروکار دارد. در هسته خود، یک جریان معمولاً نشان دهنده انتقال متوالی بایت ها از یک نقطه به نقطه دیگر است. مستندات رسمی Node.js یک جریان را به عنوان یک رابط انتزاعی تعریف می کند که می توانید از آن برای کار با داده ها استفاده کنید.

انتقال داده بر روی یک کامپیوتر یا از طریق یک شبکه یک استفاده ایده آل از یک جریان است.

جریان در Node.js

استریم ها نقش اساسی در موفقیت Node.js داشته اند. آنها برای پردازش داده های بلادرنگ و برنامه های رویداد محور، دو ویژگی برجسته محیط اجرا Node.js ایده آل هستند.

برای ایجاد یک جریان جدید در Node.js، باید از stream API استفاده کنید که منحصراً با داده های بافر Strings و Node.js کار می کند. Node.js دارای چهار نوع جریان است: قابل نوشتن، خواندنی، دوطرفه و تبدیل.

نحوه ایجاد و استفاده از یک جریان قابل نوشتن

یک جریان قابل نوشتن به شما امکان می دهد داده ها را به یک مکان خاص بنویسید یا ارسال کنید. ماژول fs (سیستم فایل) دارای یک کلاس WriteStream است که می توانید از آن برای ایجاد یک جریان جدید با تابع ()fs.createWriteStream استفاده کنید. این تابع مسیر فایلی را که می خواهید داده ها را روی آن بنویسید و همچنین یک آرایه اختیاری از گزینه ها را می پذیرد.

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

این کد تابع ()createWriteStream را وارد می‌کند، که سپس تابع فلش ناشناس از آن برای ایجاد جریانی استفاده می‌کند که داده‌ها را در myFile.txt می‌نویسد. تابع ناشناس حاوی یک تابع درونی به نام writeData() است که داده ها را می نویسد.

تابع createWriteStream() با یک بافر کار می کند تا مجموعه ای از اعداد (0-9999) را در فایل مقصد بنویسد. با این حال، هنگامی که اسکریپت بالا را اجرا می کنید، یک فایل در همان دایرکتوری ایجاد می کند که حاوی داده های زیر است:

مطلب مرتبط:   نحوه اضافه کردن اسکرول بی نهایت در React.js

داده های اولیه myFile

مجموعه فعلی اعداد به 2915 ختم می شود، اما باید اعداد تا 9999 را شامل می شد. این اختلاف به این دلیل رخ می دهد که هر WriteStream از یک بافر استفاده می کند که مقدار ثابتی از داده را در یک زمان ذخیره می کند. برای اینکه بدانید این مقدار پیش‌فرض چیست، باید به گزینه highWaterMark مراجعه کنید.

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

با افزودن خط کد بالا به تابع ناشناس خروجی زیر در ترمینال ایجاد می شود:

جریان highWaterMark را بنویسید

خروجی ترمینال نشان می دهد که مقدار پیش فرض highWaterMark (که قابل تنظیم است) 16384 بایت است. این بدان معنی است که شما فقط می توانید کمتر از 16384 بایت داده را در یک زمان در این بافر ذخیره کنید. بنابراین، تا عدد 2915 (به‌علاوه تمام کاماها و فاصله‌ها) نشان‌دهنده حداکثر مقدار داده‌ای است که بافر می‌تواند در یک زمان ذخیره کند.

راه حل خطای بافر استفاده از رویداد جریان است. یک جریان در مراحل مشخصی از فرآیند انتقال داده با رویدادهای مختلفی روبرو می شود. رخداد تخلیه گزینه مناسب برای این وضعیت است.

در تابعwriteData() بالا، فراخوانی تابع WriteStream’s write() true برمی گرداند اگر تکه داده (یا بافر داخلی) کمتر از مقدار highWaterMark باشد. این نشان می دهد که برنامه می تواند داده های بیشتری را به جریان ارسال کند. با این حال، به محض اینکه تابع write() false را برگرداند، حلقه شکسته می‌شود زیرا باید بافر را تخلیه کنید.

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

درج کد رویداد تخلیه در بالا در تابع ناشناس، بافر WriteStream را زمانی که ظرفیت دارد خالی می‌کند. سپس، متد writeData() را فراخوانی می کند تا بتواند به نوشتن داده ها ادامه دهد. با اجرای برنامه به روز شده خروجی زیر تولید می شود:

رویداد تخلیه جریان را بنویسید

باید توجه داشته باشید که برنامه باید سه بار در طول اجرای خود بافر WriteStream را تخلیه کند. فایل متنی نیز تغییراتی را تجربه کرد:

داده های myFile به روز شد

نحوه ایجاد و استفاده از یک جریان خواندنی

برای خواندن داده ها، با ایجاد یک جریان قابل خواندن با استفاده از تابع ()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.");
  });
})();

اسکریپت بالا از متد createReadStream() برای دسترسی به فایلی که کد قبلی ایجاد کرده است استفاده می کند: myFile.txt. تابع createReadStream() یک مسیر فایل (که می تواند به شکل رشته، بافر یا URL باشد) و چندین گزینه اختیاری را به عنوان آرگومان می پذیرد.

مطلب مرتبط:   templateUrl و styleUrl در Angular چیست؟

در تابع ناشناس، چندین رویداد جریان مهم وجود دارد. با این حال، هیچ نشانه ای از رخداد تخلیه وجود ندارد. این به این دلیل است که یک جریان قابل خواندن تنها زمانی داده ها را بافر می کند که شما تابع stream.push(chunk) را فراخوانی کنید یا از رویداد خواندنی استفاده کنید.

رویداد باز زمانی فعال می شود که fs فایلی را که می خواهید از آن بخوانید باز می شود. هنگامی که رویداد داده را به یک جریان پیوسته پیوست می‌کنید، باعث می‌شود جریان به حالت جریان تبدیل شود. این اجازه می دهد تا داده ها به محض در دسترس شدن از طریق آنها عبور کنند. با اجرای برنامه بالا خروجی زیر تولید می شود:

خروجی ترمینال جریان را بخوانید

نحوه ایجاد و استفاده از جریان دوبلکس

یک جریان دوبلکس هر دو رابط جریان قابل نوشتن و خواندن را پیاده سازی می کند، بنابراین شما می توانید در چنین جریانی بخوانید و بنویسید. یک مثال یک سوکت TCP است که برای ایجاد آن به ماژول شبکه متکی است.

یک راه ساده برای نشان دادن ویژگی‌های یک جریان دوطرفه، ایجاد یک سرور TCP و مشتری است که داده‌ها را انتقال می‌دهد.

فایل 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);
});

فایل 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.');
});

متوجه خواهید شد که هر دو اسکریپت سرور و سرویس گیرنده از یک جریان قابل خواندن و نوشتن برای برقراری ارتباط (انتقال و دریافت داده) استفاده می کنند. به طور طبیعی، برنامه سرور ابتدا اجرا می شود و شروع به گوش دادن برای اتصالات می کند. به محض راه اندازی کلاینت، با استفاده از شماره پورت TCP به سرور متصل می شود.

مطلب مرتبط:   نحوه استفاده از Docker Compose

سرور TCP و خروجی ترمینال مشتری

پس از برقراری ارتباط، کلاینت با نوشتن روی سرور با استفاده از WriteStream خود، انتقال داده را آغاز می کند. سرور داده هایی را که دریافت می کند در ترمینال ثبت می کند، سپس داده ها را با استفاده از WriteStream خود می نویسد. در نهایت، کلاینت داده‌هایی را که دریافت می‌کند ثبت می‌کند، داده‌های اضافی را می‌نویسد و سپس از سرور جدا می‌شود. سرور برای اتصال سایر مشتریان باز می ماند.

نحوه ایجاد و استفاده از یک جریان تبدیل

جریان‌های تبدیل جریان‌های دوطرفه‌ای هستند که در آنها خروجی با ورودی مرتبط است، اما با آن متفاوت است. Node.js دو نوع استریم Transform دارد: جریان zlib و کریپتو. یک جریان zlib می تواند یک فایل متنی را فشرده کند و پس از انتقال فایل، آن را از حالت فشرده خارج کند.

برنامه 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);
})();

این اسکریپت ساده فایل متنی اصلی را می گیرد، آن را فشرده می کند و در فهرست فعلی ذخیره می کند. این یک فرآیند ساده به لطف روش خواندنی جریان ()pipe است. خطوط لوله جریان استفاده از بافرها و داده های لوله را مستقیماً از یک جریان به جریان دیگر حذف می کند.

با این حال، قبل از اینکه داده ها به جریان قابل نوشتن در اسکریپت برسند، از طریق متد ()createGzip zlib کمی انحراف را طی می کند. این روش فایل را فشرده می کند و یک شی Gzip جدید را برمی گرداند که جریان نوشتن آن را دریافت می کند.

برنامه 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);
})();

این اسکریپت بالا فایل فشرده شده را می گیرد و آن را از حالت فشرده خارج می کند. اگر فایل myFile2.txt جدید را باز کنید، خواهید دید که حاوی همان داده های فایل اصلی است:

داده myFile2

چرا استریم ها مهم هستند؟

استریم ها کارایی انتقال داده را افزایش می دهند. جریان های قابل خواندن و نوشتن به عنوان پایه ای عمل می کنند که ارتباط بین کلاینت ها و سرورها و همچنین فشرده سازی و انتقال فایل های بزرگ را امکان پذیر می کند.

استریم ها همچنین عملکرد زبان های برنامه نویسی را بهبود می بخشد. بدون جریان، فرآیند انتقال داده پیچیده تر می شود و نیاز به ورودی دستی بیشتری از توسعه دهندگان دارد و منجر به خطاها و مشکلات عملکرد بیشتر می شود.