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

玩转Sendfile---探索Node.Js中更快的数据传输方式

时间:2023-03-21 19:40:38 科技观察

本文转载自微信公众号《编程杂技》,作者theanarkh。转载本文请联系编程杂技公众号。在Node.js中,当我们向前端返回一个静态文件时,通常是先将文件读入内容,然后通过socket接口写入底层,再返回给前端。无论是一次性读取内存还是使用streaming方式,都免不了要将数据从内核拷贝到用户层,再拷贝到内核。这是一种低效的方式,因为有更多的无效副本。在nginx中,效率可以由sendfile指令提供。Node.jscopyFile底层使用了sendfile系统调用,但是在网络IO的时候并没有使用到这个API。因为Node.js通过队列来控制数据的写入。那么是不是可以实现sendfile方法来提供这个网络IO的效率。首先我们来看看sendfile有什么好处。sendfile()在一个文件描述符和另一个文件描述符之间复制数据。因为这种复制是在内核中完成的,所以sendfile()比read(2)和write(2)的组合更有效,后者需要将数据传入和传出用户空间。我们看到sendfile通过内核完成数据传输,减少了内核和用户层之间的数据重复,从而提高了效率。接下来我们通过napi写一个addon来实现这个功能。#include#include#include#include#includestaticnapi_valuecopyFile(napi_envenv,napi_callback_infoinfo){size_targc=3;napi_valueargs[3];//获取js层的输入参数,这里是三个napi_get_cb_info(env,info,&argc,args,NULL,NULL);intfd1;intfd2;intlen;//js传入的是一个数字,v8转成一个object,这里又把输入参数转成int类型napi_get_value_int32(env,args[0],&fd1);napi_get_value_int32(env,args[1],&fd2);napi_get_value_int32(env,args[2],&len);intwritten=sendfile(fd2,fd1,0,len);napi_valueret;napi_create_int32(env,written,&ret);returnret;}napi_valueInit(napi_envenv,napi_valueexports){napi_valuefunc;//创建一个函数,并设置为函数的getArray属性的值exportsobjectnapi_create_function(env,NULL,NAPI_AUTO_LENGTH,copyFile,NULL,&func);napi_set_named_property(env,exports,"copyFile",func);returnexports;}NAPI_MODULE(NODE_GYP_MODULE_NAME,Init)让我们看看如何使用它。先用这个addon复制文件,类似Node.jsfs.constants;asyncfunctiontest(){const[fd1,fd2]=awaitPromise.all([openFile('1.txt','r'),openFile('2.txt',O_WRONLY|O_CREAT)]);const{size}=awaitgetFileInfo(fd1);console.log(copyFile(fd1,fd2,size));fs.close(fd1,()=>{});fs.close(fd2,()=>{});}functionopenFile(filename,mode){returnnewPromise((resolve,reject)=>{fs.open(filename,mode,(err,fd)=>{if(err){reject(err);}else{resolve(fd);}});})}functiongetFileInfo(fd){returnnewPromise((resolve,reject)=>{fs.fstat(fd,(err,stat)=>{if(err){reject(err)}else{解决(统计);}});})}测试();执行上面的代码,我们可以看到文件会被成功复制到2.txt中。那我们再试试网络IO场景。constfs=require('fs');consthttp=require('http');const{copyFile}=require('./build/Release/sendfile.node');constserver=http.createServer(async(req,res)=>{constfd=awaitopenFile('1.txt','r');const{size}=awaitgetFileInfo(fd);constret=copyFile(fd,res.socket._handle.fd,size);res.socket.end();}).listen(8002);const{O_WRONLY,O_CREAT,}=fs.constants;functionopenFile(filename,mode){returnnewPromise((resolve,reject)=>{fs.open(filename,mode,(err,fd)=>{if(err){reject(err);}else{resolve(fd);}});})}functiongetFileInfo(fd){returnnewPromise((resolve,reject)=>{fs.fstat(fd,(err,stat)=>{if(err){reject(err)}else{resolve(stat);}});})}上面的代码首先启动一个http服务器,然后当收到请求,通过addon调用sendfile返回相应的内容给前端,最后关闭连接。结果如下。sendfile好像可以应用在networkIO中,不过只是一个demo思路,以后有时间继续研究分析。