استریم و بافر در Node.js – به زبان ساده
برای مدیریت و دستکاری دادههای استریمی مانند ویدئو، فایلهای بزرگ و غیره در Node.js از استریم (Stream) استفاده میکنیم. ماژول streams در این محیط اجرایی، همه استریمها را مدیریت میکند. در این مقاله روی مفاهیمی مرتبط با استریم و بافر در Node.js تمرکز خواهیم داشت.
انواع استریم
در Node.js چهار نوع متفاوت از استریم وجود دارد:
- استریمهای خواندنی (Readable streams): برای ایجاد استریمهای داده جهت خواندن استفاده میشوند. برای نمونه میتوان یک فایل بزرگ را به صورت بخش به بخش خواند.
- استریمهای نوشتنی (Writable streams): برای ایجاد یک استریم از دادهها برای نوشتن استفاده میشوند. برای نمونه میتوانیم مقادیر زیادی از دادهها را در یک فایل بنویسیم.
- استریمهای داپلکس (Duplex streams): برای ایجاد یک استریم استفاده میشود که همزمان هم خواندنی و هم نوشتنی است.
- استریمهای تبدیلی (Transform streams): برای ایجاد استریمی استفاده میشود که خواندنی و نوشتنی است، اما دادهها پس از نوشته شدن در استریم قابل ویرایش هستند. فرض کنید میخواهید دادهها را از سوی کلاینت و سرور پیش از ارسال درخواست، فشردهسازی کنید.
بافرها در استریمها
استریمها بر مبنای مفهومی به نام «بافر» (buffer) عمل میکنند. منظور از بافر در برنامه نویسی حافظه موقتی است که استریم برای نگهداری برخی دادهها تا زمان مصرف اشغال میکند. اندازه بافر در یک استریم بر اساس مشخصه highWatermark در آن وهله از استریم تعیین میشود که عدد مربوطه نشاندهنده اندازه بافر برحسب بایت است.
حافظه بافر در Node به صورت پیشفرض روی String و Buffer کار میکند. میتوان حافظه بافر را روی اشیای جاوا اسکریپت نیز استفاده کرد. به این منظور باید مشخصه objectMode روی شیء استریم به صورت true تنظیم شود. اگر تلاش کنیم تا دادهها به استریم push کنیم، دادهها به بافر استریم push میشوند. دادهای push شده در بافر در آنجا میمانند تا این که مصرف شوند. اگر بافر پر شده باشد و تلاش کنیم تا دادهها را به بافر push کنیم، استریم آن دادهها را نمیپذیرد و مقدار false برای عمل push بازگشت میدهد.
استریمها و EventEmitters
استریمها اقدام به بسط EventEmitters میکنند. استریمهای Node.js کلاس EventEmitters را بسط میدهند. میتوان به رویدادهایی مانند data و end در استریمها گوش داد. برای گوش کردن به یک رویداد باید از تابع ()stream.on که در استریم موجود است استفاده کنیم. برای کسب اطلاعات بیشتر در مورد EventEmitters در Node.js پیشنهاد میکنیم این مقاله (+) را مطالعه کنید.
استریمهای خواندن در Node.js
استریمی که برای خواندن دادههای استریمشده استفاده میشود به نام «استریم خواندن» (Read Stream) نامیده میشود. استریم خواندن میتواند فایل را از سرور بخواند یا یک ویدئو را به صورت آنلاین استریم کند.
برای مقایسه میتوانید استریمهای خواندن را مانند یک شیر آب تصور کنید. از شیر آب، آب جریان مییابد (در این مورد داده) و از سوی فردی مصرف میشود. در این بخش یک استریم خواندن ایجاد میکنیم که فایل متنی بزرگی را ایجاد میکند تا عملکرد آن را مشاهده کنیم.
1import { createReadStream, ReadStream } from 'fs';
2
3var readStream: ReadStream = createReadStream('./data.txt');
4
5readStream.on('data', chunk => {
6 console.log('---------------------------------');
7 console.log(chunk);
8 console.log('---------------------------------');
9});
10
11readStream.on('open', () => {
12 console.log('Stream opened...');
13});
14
15readStream.on('end', () => {
16 console.log('Stream Closed...');
17});
زمانی که آن را اجرا کنیم، خروجی زیر به دست میآید:
Stream opened... --------------------------------- <Buffer 4c 6f 72 65 6d 20 69 70 73 75 6d 20 64 6f 6c 6f 72 20 73 69 74 20 61 6d 65 74 2c 20 63 6f 6e 73 65 63 74 65 74 75 72 20 61 64 69 70 69 73 63 69 6e 67 ... > --------------------------------- --------------------------------- <Buffer 74 20 6e 75 6e 63 20 76 69 74 61 65 20 66 65 72 6d 65 6e 74 75 6d 2e 20 49 6e 20 75 74 20 61 72 63 75 20 74 65 6d 70 6f 72 2c 20 66 61 75 63 69 62 75 ... > --------------------------------- --------------------------------- <Buffer 20 76 69 74 61 65 2c 20 65 67 65 73 74 61 73 20 69 64 20 73 65 6d 2e 20 44 6f 6e 65 63 20 75 74 20 75 6c 74 72 69 63 69 65 73 20 6c 6f 72 65 6d 2c 20 ... > --------------------------------- Stream Closed...
به این ترتیب دادههای بافر را به دست آوردهایم که چیزی به جز دادههای بایتی محتوایی که در حافظه بافر استریم قرار گرفتهاند نیست.
ایجاد مکث و یا از سرگیری فعالیت یک استریم خواندن
یک استریم را در Node.js میتوان با فراخوانی تابعهای ()pause و ()resume روی استریم به حالت مکث برده یا فعالیت آن را از سر گرفت. در نتیجه فراخوانی تابع ()pause، رویداد data تحریک نمیشود تا این که دوباره تابع ()resume استریم را فراخوانی کنیم.
استریمهای Flowing و غیر Flowing
دو نوع استریم خواندنی وجود دارند:
- استریم Flowing: استریمی است که ارسال دادهها را به صورت پیوسته انجام میدهد و امکان شنیدن مستقیم با استفاده از رویداد data روی استریم وجود دارد.
- استریم غیر Flowing: استریمی است که دادهها را به صورت خودکار push نمیکند. به جای آن استریم دادهها را در بافر ذخیره میکند و باید متد ()read استریم را فراخوانی کنیم تا آن را بخوانیم.
کد فوق مثالی از استریم Flowing است که در آن به رویداد data استریم گوش میدهیم و شنونده به صورت خودکار هر بار که دادههای جدیدی از استریم میرسند، تحریک میشود.
در ادامه مثالی ساده از استریمهای غیر Flowing میبینید:
1import { createReadStream, ReadStream } from 'fs';
2
3var readStream: ReadStream = createReadStream('./data.txt');
4
5setTimeout(() => {
6 const data = readStream.read(10);
7 console.log(data);
8}, 10);
هنگامی که کد فوق را اجرا کنیم، خروجی زیر به دست میآید:
<Buffer 4c 6f 72 65 6d 20 69 70 73 75>
طرز کار آن چگونه است؟
دلیل خروجی فوق این است که استریم خواندن با استفاده از متد createReadStream از ماژول FS کار میکند. به محض این که استریم ایجاد شود، دادههای فایل شروع به استریم شدن به متغیر استریم میکنند. همچنین با استفاده از متد setTimeout میتوانیم امکان تعیین مقداری زمان برای استریم شدن بدهیم تا مقداری از دادهها در بافر آن پر شود.
پس از 10 میلیثانیه یک Callback به نام setTimeout اجرا میشود و 10 بایت نخست بافر را با استفاده از متد read() که با 10 (بایت) از یک آرگومان فراخوانی میشود را میخواند.
مدیریت بافر به وسیله استریم خواندنی
در کد فوق، اگر تابع ()read را بار دیگر پس از console.log(data) فراخوانی کنیم، و دادههای جدید را پرینت کنیم، میبینیم که دادهها از لاگ قبلی متفاوت هستند:
1import { createReadStream, ReadStream } from 'fs';
2
3var readStream: ReadStream = createReadStream('./data.txt');
4
5setTimeout(() => {
6 const data = readStream.read(10);
7 console.log(data);
8
9 const data2 = readStream.read(10);
10 console.log(data2);
11}, 10);
در کد فوق، خروجی به صورت زیر است:
<Buffer 4c 6f 72 65 6d 20 69 70 73 75> <Buffer 6d 20 64 6f 6c 6f 72 20 73 69>
مقادیر لاگ شده به این دلیل متفاوت هستند که بافر دادهها را پس از خوانده شدن از سوی مصرفکننده را حذف میکند. از این رو در فراخوانی نخست متد ()read، ده بایت نخست دادههای بافر را میخوانیم و در فراخوانی دوم متد ()read یازدهمین تا بیستمین بایت دادههای واقعی که در حال حاضر در 10 بایت نخست بافر قرار دارند خوانده میشوند. بدین ترتیب به پایان این مقاله در مورد استریمهای خواندن در Node.js میرسیم.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- مجموعه آموزشهای برنامهنویسی
- آموزش JavaScript ES6 (جاوا اسکریپت)
- Node.js چیست؟ — به زبان ساده
- آموزش Node.js: آشنایی با استریم ها و کار با MySQL — بخش دوازدهم
==