当前位置: 首页 > 科技观察

Node.js中stream模块详解

时间:2023-03-14 08:17:46 科技观察

什么是stream?英文stream定义为stream。Stream是一个抽象的数据接口。Node.js中的许多对象都实现了流。Stream是EventEmitter对象的一个??实例。简而言之,就是会取数据(以缓冲为单位),或能吸取数据的东西,其本质是让数据流动起来。看一张图可能更直观:bucketpipeline流程图注:stream并不是node.js特有的概念,而是一个操作系统最基本的操作方式,但是node.js有API支持这种操作方法。|linux命令的一部分是stream。为什么需要学习流视频播放示例?我的朋友一定在线看过电影。对比一下定义中的图——桶流水线图。source是服务器端的视频,dest是自己的播放器(或者浏览器里的flash和h5.video)。大家想一想,看电影的方式就像上图给管道换水一样。视频从服务器一点一点的推流到本地播放器,边推边播,推流后视频播放完毕。说明:在这个视频播放的例子中,如果我们不使用管道和流式传输,我们会直接从服务器加载视频文件然后播放。会因为内存占用过多而引起很多问题,导致系统死机或崩溃。因为我们的网速、内存、cpu运算速度有限,而且有多个程序共享使用。一个视频文件加载后可能有好几个g。很大。读取大文件数据的例子就有这样的需求。读取大文件数据的例子使用文件读取consthttp=require('http');constfs=require('fs');constpath=require('path');constserver=http.createServer(function(req,res){constfileName=path.resolve(__dirname,'data.txt');fs.readFile(fileName,function(err,data){res.end(data);});});服务器.listen(8000);使用文件读取这段代码的语法没有问题,但是如果data.txt文件很大,会达到几百M,而且是在响应用户的大量并发请求,有时,程序可能会消耗大量内存,这可能会导致用户连接缓慢的问题。而且,如果并发请求太大,服务器内存开销也会很大。这个时候我们再来看一下带stream的实现。consthttp=require('http');constfs=require('fs');constpath=require('path');constserver=http.createServer(function(req,res){constfileName=path.resolve(__dirname,'data.txt');letstream=fs.createReadStream(fileName);//这一行已经改了stream.pipe(res);//这一行已经改了});server.listen(8000);你不需要使用stream把所有的文件都读出来再返回,但是边读边返回,数据通过管道流向客户端,真正减轻了服务端的压力。看了两个例子,想必小伙伴们应该知道为什么要用stream了吧!因为一次性读取和操作大文件,内存和网络都受不了,所以让数据流一点点操作一下。stream循环的过程再看这张bucketpipeline流程图,可以看出stream的整个流动过程包括source,dest,以及连接两者的pipeline(stream的核心),介绍这三者指导大家了解流循环过程。流从何而来?soucrestream常见的源码方式有以下三种:在控制台的http请求中输入request,读取文件。下面说一下从控制台输入的方法。流应用场景的第2章和第3章会有详细的讲解。看一段process.stdin的代码process.stdin.on('data',function(chunk){console.log('streambystdin',chunk)console.log('streambystdin',chunk.toString())})//控制台输入koalakoala后,输出streambystdinstreambystdinkoalakoala运行上面的代码:那么从控制台输入的任何内容都会被data事件监听,process.stdin是一个流对象,data是一个使用的流对象监控传入的数据是一个自定义函数。从输出中可以看出process.stdin是一个流对象。说明:流对象可以监听“data”、“end”、“opne”、“close”、“error”等事件。node.js中监听自定义事件使用.on方法,如process.stdin.on('data',…),req.on('data',…),这样可以直观的监听流数据的输入和结束连接到桶管道。从bucketpipeline流程图可以看出,source和dest之间有一条相连的pipeline管道。它的基本语法是source.pipe(dest),source和dest是通过管道连接,数据从source流向dest。流向何处?deststream常见的输出方式有3种:在控制台输出http请求的response写入文件stream应用场景stream的应用场景主要是处理IO操作,http请求和文件操作都是IO操作。这里再提一下stream的本质——因为一次性的IO操作太大,硬件开销太大,影响软件的运行效率,所以IO分批、分段操作,让数据像水管一样流动,直到流完。即,操作完成。下面介绍几个常用的应用场景,介绍一个压力测试小工具ab,一个压力测试网络请求的工具。ab的全称是Apachebench,是Apache自带的工具。因此,必须安装Apache才能使用ab。macos系统自带Apache,windows用户可以根据自己的情况安装。在运行ab之前启动Apache。macos的启动方式是sudoapachectlstart。Apachebench对应参数的详细学习地址,有兴趣的可以看看Apachebench对应参数的详细学习地址。改进。stream的需求在get请求中应用:使用node.js实现一个http请求,读取data.txt文件,创建服务,监听8000端口,读取文件返回给客户端,使用a说get请求时的套路和文件读取对比,看下面的例子。常规使用文件读取返回客户端响应示例,文件名为getTest1.js//getTest.jsconsthttp=require('http');constfs=require('fs');constpath=require('path');constserver=http.createServer(function(req,res){constmethod=req.method;//获取请求方法if(method==='GET'){//获取请求方法判断constfileName=path.resolve(__dirname,'data.txt');fs.readFile(fileName,function(err,data){res.end(data);});}});server.listen(8000);使用stream返回clientresponse,将上面代码部分修改,文件名为getTest2.js//getTest2.js//主要展示修改的部分constserver=http.createServer(function(req,res){constmethod=req.method;//获取请求方法if(method==='GET'){//获取请求constfileName=path.resolve(__dirname,'data.txt');letstream=fs.createReadStream(fileName);stream.pipe(res);//使用res作为流的目标}});服务器.listen(8000);对于下面get请求中使用stream的例子,可能有朋友会质疑response是不是也是一个stream对象,是的,对于bucket流水线图来说,response是一个dest。虽然在get请求中可以使用流,但是与直接读取文件相比,读取res.end(data)有什么好处呢?这个时候就用到了我们刚刚推荐的压测小工具。两段代码getTest1和getTest2,增加data.txt的内容,使用ab工具测试,运行命令ab-n100-c100http://localhost:8000/其中-n100表示??发送100个请求依次,-c100表示??一次发送的请求数为100。对比结果发现,使用stream后,性能有非常大的提升,大家自己看吧。贴子中使用stream请求微信小程序地址,通过贴子生成二维码。/**微信生成二维码接口*paramssrc微信url/其他图片请求链接*paramslocalFilePath:本地路径*paramsdata:微信请求参数**/constdownloadFile=async(src,localFilePath,data)=>{try{constws=fs.createWriteStream(localFilePath);returnnewPromise((resolve,reject)=>{ws.on('finish',()=>{resolve(localFilePath);});if(data){request({method:'POST',uri:src,json:true,body:data}).pipe(ws);}else{request(src).pipe(ws);}});}catch(e){logger.error('wxdownloadFileerror:',e);throwe;}}看这段使用stream的代码,为本地文件对应的路径创建一个stream对象,然后直接.pipe(ws),将post请求的数据流传输到这个本地文件,这种流式应用在node后端开发过程中还是比较常用的。post和get使用stream总结一下request和response都是stream对象,可以利用stream的特性。两者的区别在于我们先看一下桶流水线流程图。request是source类型,就是图中的source,response是dest的类型,就是图中的destination。文件操作中使用流复制文件的例子constfs=require('fs')constpath=require('path')//两个文件名constfileName1=path.resolve(__dirname,'data.txt')constfileName2=path.resolve(__dirname,'data-bak.txt')//读取文件的流对象constreadStream=fs.createReadStream(fileName1)//写入文件的流对象constwriteStream=fs.createWriteStream(fileName2)//执行通过pipeCopy,数据流readStream.pipe(writeStream)//数据读取完成并监听,即复制完成readStream.on('end',function(){console.log('copycompleted')})看完这段代码,要判断它是否是副本似乎很简单。创建可读数据流readStream和可写数据流writeStream,然后通过pipe管道直接传输数据流。这种使用流的拷贝比读写存储文件的拷贝性能要高很多。因此,当你遇到文件操作的需求时,首先要评估是否需要使用stream来实现。一些前端打包工具的底层实现目前流行的一些前端打包构建工具都是用node.js写的。打包构建的过程必然是一个频繁的文件操作过程,离不开流。比如现在流行的gulp,有兴趣的朋友可以看看源码。流的类型ReadableStreamReadableStreamWriteableStreamWritableDataStreamDuplexStreamBidirectionalDataStream,可以同时读写TransformStream转换后的数据流,可读可写,可以转换(处理)数据(不常用)之前的文章都是关于前两种可读数据流和可写数据流的。第四个流不常用。如果你需要,你可以在网上搜索。接下来,我将解释第三种数据流,DuplexStream。DuplexStream是双向的,既可读又可写。双工流实现了Readable和Writable接口。Duplexstreams的例子有tcpsocketszlibstreamscryptostreams我没有在项目中使用过duplexstreams,DuplexStreams的一些内容可以参考这篇文章NodeJSStream双工流有什么缺点使用rs.pipe(ws)的方式writeafile并不是把rs的内容追加到ws的后面,而是直接用rs的内容覆盖掉ws原来的内容。ended/closed流不能被重用,必须重新创建数据流。pipe方法返回的是目标数据Stream,比如a.pipe(b)返回的是b,所以在监听事件的时候请注意监听的对象是否正确。如果你想监控多个数据流,并且你使用pipe方法序列化数据流,你应该这样写:代码示例:data.on('end',function(){console.log('dataend');}).pipe(a).on('end',function(){console.log('aend');}).pipe(b).on('end',function(){console.log('弯曲');});stream的常用类库event-stream用于函数式编程感觉awesome-nodejs#streams也是一个不错的第三方流库。有兴趣的可以看看github总结一下。本文属于进阶路线【节点必知系列】。看完这篇文章,是不是适合stream?有了一定的了解,知道node对于文件处理还是有完美解决方案的。在本文中,桶管道流程图显示了三次。重要的事希望你记住三遍。除了上面的内容,大家还会有一些想法,比如stream数据流的具体内容是什么?二进制或者字符串类型或者其他类型,这种类型给stream带来什么好处?桶流水线图中的水管是什么时候触发的,即pipe函数?什么情况下会转发流?底层机制是什么?以上问题(由于篇幅分两篇)会在我的流的第二篇文章中详细解释