1. การวิเคราะห์เปิด
สตรีมเป็นอินเทอร์เฟซนามธรรมที่นำมาใช้โดยวัตถุจำนวนมากในโหนด ตัวอย่างเช่นการร้องขอไปยังเซิร์ฟเวอร์ HTTP เป็นสตรีมและ stdout ก็เป็นสตรีมด้วย สตรีมสามารถอ่านได้เขียนได้หรือทั้งสองอย่าง
การติดต่อครั้งแรกกับสตรีมเริ่มต้นด้วย UNIX ต้น ทศวรรษของการปฏิบัติได้พิสูจน์แล้วว่าความคิดของสตรีมสามารถพัฒนาระบบขนาดใหญ่บางอย่างได้
ใน UNIX สตรีมจะถูกนำไปใช้ผ่าน "|" ในโหนดเป็นโมดูลสตรีมในตัวจะใช้โมดูลหลักจำนวนมากและโมดูลสามพรรค
เช่นเดียวกับ UNIX การทำงานหลักของสตรีมโหนดคือ. pipe () และผู้ใช้สามารถใช้กลไกการตอบโต้เพื่อควบคุมความสมดุลระหว่างการอ่านและการเขียน
สตรีมสามารถให้อินเทอร์เฟซแบบครบวงจรแก่นักพัฒนาที่สามารถนำกลับมาใช้ใหม่และควบคุมความสมดุลของการอ่านและการเขียนระหว่างสตรีมผ่านอินเทอร์เฟซสตรีมนามธรรม
การเชื่อมต่อ TCP เป็นทั้งสตรีมที่อ่านได้และสตรีมที่เขียนได้ในขณะที่การเชื่อมต่อ HTTP นั้นแตกต่างกัน วัตถุคำขอ HTTP เป็นสตรีมที่อ่านได้ในขณะที่วัตถุตอบกลับ HTTP เป็นสตรีมที่เขียนได้
กระบวนการส่งสัญญาณสตรีมจะถูกส่งในรูปแบบของบัฟเฟอร์โดยค่าเริ่มต้นเว้นแต่คุณจะตั้งค่าแบบฟอร์มการเข้ารหัสอื่น ๆ สำหรับมันต่อไปนี้เป็นตัวอย่าง:
การคัดลอกรหัสมีดังนี้:
var http = ต้องการ ('http');
var server = http.createServer (ฟังก์ชั่น (req, res) {
Res.WriteHeader (200, {'Content-type': 'text/plain'});
res.end ("สวัสดี, Big Bear!");
-
Server.Listen (8888);
console.log ("เซิร์ฟเวอร์ http ทำงานบนพอร์ต 8888 ... ");
หลังจากทำงานแล้วรหัสที่อ่านไม่ออกจะปรากฏขึ้นเนื่องจากชุดอักขระที่ระบุไม่ได้ตั้งค่าเช่น: "UTF-8"
เพียงแค่แก้ไข:
การคัดลอกรหัสมีดังนี้:
var http = ต้องการ ('http');
var server = http.createServer (ฟังก์ชั่น (req, res) {
Res.WriteHeader (200, {
'เนื้อหาประเภท': 'ข้อความ/ธรรมดา; charset = utf-8' // เพิ่ม charset = utf-8
-
res.end ("สวัสดี, Big Bear!");
-
Server.Listen (8888);
console.log ("เซิร์ฟเวอร์ http ทำงานบนพอร์ต 8888 ... ");
ผลการทำงาน:
ทำไมต้องใช้สตรีม
I/O ในโหนดเป็นแบบอะซิงโครนัสดังนั้นการอ่านและการเขียนลงในดิสก์และเครือข่ายต้องมีการอ่านและอ่านข้อมูลผ่านฟังก์ชั่นการโทรกลับ ต่อไปนี้เป็นตัวอย่างของการดาวน์โหลดไฟล์
บนรหัส:
การคัดลอกรหัสมีดังนี้:
var http = ต้องการ ('http');
var fs = ต้องการ ('fs');
var server = http.createServer (ฟังก์ชั่น (req, res) {
fs.readfile (__ dirname + '/data.txt', ฟังก์ชั่น (err, data) {
Res.end (ข้อมูล);
-
-
Server.Listen (8888);
รหัสสามารถใช้ฟังก์ชั่นที่ต้องการได้ แต่บริการจำเป็นต้องแคชข้อมูลไฟล์ทั้งหมดไปยังหน่วยความจำก่อนที่จะส่งข้อมูลไฟล์ หากไฟล์ "data.txt" เป็นอย่างมาก
หากมีขนาดใหญ่และมีการพร้อมกันขนาดใหญ่หน่วยความจำจำนวนมากจะสูญเปล่า เนื่องจากผู้ใช้จำเป็นต้องรอจนกว่าไฟล์ทั้งหมดจะถูกแคชไปที่หน่วยความจำเพื่อรับข้อมูลไฟล์สิ่งนี้จะนำไปสู่
ประสบการณ์ผู้ใช้ค่อนข้างแย่ โชคดีที่พารามิเตอร์ทั้งสอง (req, res) เป็นสตรีมดังนั้นเราสามารถใช้ fs.createReadStream () แทน fs.readfile () ดังนี้:
การคัดลอกรหัสมีดังนี้:
var http = ต้องการ ('http');
var fs = ต้องการ ('fs');
var server = http.createServer (ฟังก์ชั่น (req, res) {
var stream = fs.createReadstream (__ dirname + '/data.txt');
Stream.pipe (res);
-
Server.Listen (8888);
เมธอด. pipe () ฟังเหตุการณ์ 'ข้อมูล' และ 'สิ้นสุด' ของ fs.createReadStream () เพื่อให้ไฟล์ "data.txt" ไม่จำเป็นต้องแคช
ไฟล์สามารถส่งไปยังไคลเอนต์ได้ทันทีหลังจากการเชื่อมต่อไคลเอนต์เสร็จสมบูรณ์ ประโยชน์อีกประการหนึ่งของการใช้. pipe () คือสามารถแก้ไขได้เมื่อลูกค้า
อ่านและเขียนความไม่สมดุลที่เกิดจากความล่าช้าที่มีขนาดใหญ่มาก
มีห้าสตรีมพื้นฐาน: อ่านได้เขียนได้การแปลงดูเพล็กซ์และ "คลาสสิก" (โปรดตรวจสอบรายละเอียด API)
2. แนะนำตัวอย่าง
เมื่อข้อมูลที่ต้องประมวลผลไม่สามารถโหลดได้ในหน่วยความจำในครั้งเดียวหรือเมื่อการประมวลผลมีประสิทธิภาพมากขึ้นในขณะที่อ่านเราจำเป็นต้องใช้สตรีมข้อมูล NodeJS ให้การดำเนินการเกี่ยวกับสตรีมข้อมูลผ่านสตรีมต่างๆ
การใช้โปรแกรมคัดลอกไฟล์ขนาดใหญ่เป็นตัวอย่างเราสามารถสร้างสตรีมข้อมูลแบบอ่านได้อย่างเดียวสำหรับแหล่งข้อมูลตัวอย่างมีดังนี้:
การคัดลอกรหัสมีดังนี้:
var rs = fs.createReadstream (ชื่อพา ธ );
Rs.on ('data', ฟังก์ชั่น (ก้อน) {
Dosomething (ก้อน); // ใช้รายละเอียดเฉพาะตามที่คุณต้องการ
-
rs.on ('end', function () {
ทำความสะอาด ();
-
เหตุการณ์ข้อมูลในรหัสจะถูกทริกเกอร์อย่างต่อเนื่องโดยไม่คำนึงว่าฟังก์ชัน Dosomething สามารถประมวลผลได้หรือไม่ รหัสสามารถแก้ไขได้ดังนี้เพื่อแก้ปัญหานี้
การคัดลอกรหัสมีดังนี้:
var rs = fs.createReadstream (SRC);
Rs.on ('data', ฟังก์ชั่น (ก้อน) {
Rs.pause ();
Dosomething (chunk, function () {
Rs.resume ();
-
-
rs.on ('end', function () {
ทำความสะอาด ();
-
มีการเพิ่มการเรียกกลับไปยังฟังก์ชัน Dosomething ดังนั้นเราสามารถหยุดการอ่านข้อมูลชั่วคราวก่อนที่จะประมวลผลข้อมูลและอ่านข้อมูลต่อไปหลังจากการประมวลผลข้อมูล
นอกจากนี้เรายังสามารถสร้างสตรีมข้อมูลแบบเขียนได้อย่างเดียวสำหรับเป้าหมายข้อมูลดังนี้:
การคัดลอกรหัสมีดังนี้:
var rs = fs.createReadstream (SRC);
var ws = fs.createwritestream (DST);
Rs.on ('data', ฟังก์ชั่น (ก้อน) {
ws.write (ก้อน);
-
rs.on ('end', function () {
ws.end ();
-
หลังจาก Dosomething ถูกแทนที่ด้วยการเขียนข้อมูลลงในสตรีมแบบเขียนอย่างเดียวรหัสด้านบนดูเหมือนโปรแกรมคัดลอกไฟล์ อย่างไรก็ตามรหัสข้างต้นมีปัญหาที่กล่าวถึงข้างต้น หากความเร็วในการเขียนไม่สามารถติดตามความเร็วในการอ่านได้เฉพาะการเขียนแคชภายในสตรีมข้อมูลจะระเบิด เราสามารถตัดสินได้ว่าข้อมูลขาเข้าถูกเขียนไปยังเป้าหมายหรือวางไว้ชั่วคราวในแคชตามค่าการส่งคืนของวิธี. เขียนและตัดสินเมื่อมีเพียงข้อมูลการเขียนที่เขียนไปยังเป้าหมายตามเหตุการณ์การระบายน้ำและส่งผ่านข้อมูลถัดไปที่จะเขียน ดังนั้นรหัสมีดังนี้:
การคัดลอกรหัสมีดังนี้:
var rs = fs.createReadstream (SRC);
var ws = fs.createwritestream (DST);
Rs.on ('data', ฟังก์ชั่น (ก้อน) {
if (ws.write (chunk) === false) {
Rs.pause ();
-
-
rs.on ('end', function () {
ws.end ();
-
ws.on ('drain', function () {
Rs.resume ();
-
ในที่สุดการถ่ายโอนข้อมูลจากสตรีมข้อมูลแบบอ่านอย่างเดียวไปยังสตรีมข้อมูลแบบเขียนเท่านั้นจะรับรู้และรวมการควบคุมคลังสินค้าป้องกันการระเบิดแล้ว เนื่องจากมีสถานการณ์การใช้งานมากมายเช่นโปรแกรมคัดลอกไฟล์ขนาดใหญ่ด้านบน NodeJS จึงให้วิธีการ. pipe โดยตรงในการทำเช่นนี้และวิธีการใช้งานภายในนั้นคล้ายกับรหัสด้านบน
นี่คือกระบวนการคัดลอกไฟล์ที่สมบูรณ์ยิ่งขึ้น:
การคัดลอกรหัสมีดังนี้:
var fs = ต้องการ ('fs')
PATH = ต้องการ ('เส้นทาง')
out = process.stdout;
var filepath = '/bb/bigbear.mkv';
var readstream = fs.createReadstream (filepath);
var writestream = fs.createWritestream ('file.mkv');
var stat = fs.statsync (filepath);
var totalsize = stat.size;
var passedLength = 0;
var LastSize = 0;
var startTime = date.now ();
readStream.on ('ข้อมูล', ฟังก์ชั่น (ก้อน) {
PassedLength += chunk.length;
if (writestream.write (chunk) === false) {
readstream.pause ();
-
-
readstream.on ('end', function () {
writestream.end ();
-
writestream.on ('drain', function () {
readstream.resume ();
-
settimeout (function show () {
var เปอร์เซ็นต์ = math.ceil ((passedlength / totalsize) * 100);
var size = math.ceil (PassedLength / 10,00000);
var diff = size - LastSize;
LastSize = Size;
out.clearline ();
Out.cursorto (0);
out.write ('เสร็จสิ้น' + ขนาด + 'mb,' + เปอร์เซ็นต์ + '%, ความเร็ว:' + diff * 2 + 'mb/s');
if (PassedLength <totalSize) {
settimeout (แสดง, 500);
} อื่น {
var endtime = date.now ();
console.log ();
console.log ('เมื่อแชร์:' + (endtime - starttime) / 1000 + 'วินาที');
-
}, 500);
คุณสามารถบันทึกรหัสด้านบนเป็น "copy.js" การทดลอง: เราเพิ่ม settimeout แบบเรียกซ้ำ (หรือใช้ setInterval โดยตรง) เพื่อเป็นผู้ดูแล
สังเกตความคืบหน้าของความสำเร็จทุก 500ms และเขียนขนาดที่สมบูรณ์เปอร์เซ็นต์และความเร็วในการคัดลอกไปยังคอนโซลด้วยกัน เมื่อการคัดลอกเสร็จสิ้นเวลาทั้งหมดจะถูกคำนวณ
สามกันมาสรุปกันเถอะ
(1) เข้าใจแนวคิดของสตรีม
(2) มีความเชี่ยวชาญในการใช้ API สตรีมที่เกี่ยวข้อง
(3) ให้ความสนใจกับการควบคุมรายละเอียดเช่น: การคัดลอกไฟล์ขนาดใหญ่โดยใช้รูปแบบของ "ข้อมูลก้อน" สำหรับการทำลายล้าง
(4) การใช้ท่อ
(5) เน้นแนวคิดอีกครั้ง: การเชื่อมต่อ TCP เป็นทั้งสตรีมที่อ่านได้และสตรีมที่เขียนได้ในขณะที่การเชื่อมต่อ HTTP นั้นแตกต่างกัน วัตถุคำขอ HTTP เป็นสตรีมที่อ่านได้ในขณะที่วัตถุตอบกลับ HTTP เป็นสตรีมที่เขียนได้