好玩的Sendfile---探索Node.Js中更快的數(shù)據(jù)傳輸方式
本文轉(zhuǎn)載自微信公眾號「編程雜技」,作者theanarkh。轉(zhuǎn)載本文請聯(lián)系編程雜技公眾號。
在Node.js中,當(dāng)我們給前端返回一個靜態(tài)文件的時候,我們通常會把文件先讀進內(nèi)容,然后通過socket接口寫到底層,從而返回給前端。無論是一次性讀取到內(nèi)存還是使用流式的方式,都不可避免地要把數(shù)據(jù)從內(nèi)核復(fù)制到用戶層,再把數(shù)據(jù)復(fù)制到內(nèi)核,這是一種低效的方式,因為多了無效的復(fù)制。在nginx中,可以通過sendfile指令提供效率。Node.js的copyFile底層使用了sendfile系統(tǒng)調(diào)用,但是網(wǎng)絡(luò)IO的時候,沒有使用該API。因為Node.js通過隊列的方式,控制數(shù)據(jù)的寫入。那么是否可以實現(xiàn)sendfile的方式來提供這網(wǎng)絡(luò)IO的效率。首先我們看一下sendfile的好處是什么。
- sendfile() copies data between one file descriptor and another. Because this copying is done within the kernel, sendfile() is more efficient than the combination of read(2) and write(2), which would require transferring data to and from user space.
我們看到sendfile通過把內(nèi)核完成數(shù)據(jù)的傳輸,減少了內(nèi)核和用戶層的數(shù)據(jù)復(fù)制,從而提高了效率。下面我們通過napi寫一個addon來實現(xiàn)這個功能。
- #include <sys/sendfile.h>
- #include <stdio.h>
- #include <unistd.h>
- #include <fcntl.h>
- #include <node_api.h>
- static napi_value copyFile(napi_env env, napi_callback_info info) {
- size_t argc = 3;
- napi_value args[3];
- // 拿到j(luò)s層的入?yún)?,這里是三個
- napi_get_cb_info(env, info, &argc, args, NULL, NULL);
- int fd1;
- int fd2;
- int len;
- // js傳入的是一個數(shù)字,v8轉(zhuǎn)成了對象,這里再次把入?yún)⑥D(zhuǎn)成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);
- int writed = sendfile(fd2, fd1, 0,len);
- napi_value ret;
- napi_create_int32(env, writed, &ret);
- return ret;
- }
- napi_value Init(napi_env env, napi_value exports) {
- napi_value func;
- // 創(chuàng)建一個函數(shù)并且設(shè)置為exports對象的getArray屬性的值
- napi_create_function(env,
- NULL,
- NAPI_AUTO_LENGTH,
- copyFile,
- NULL,
- &func);
- napi_set_named_property(env, exports, "copyFile", func);
- return exports;
- }
- NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
下面我們看看怎么使用。首先用這個addon來復(fù)制文件,類似Node.js的copyyFile
- const fs= require('fs');
- const { copyFile } = require('./build/Release/sendfile.node');
- const {
- O_WRONLY,
- O_CREAT,
- } = fs.constants;
- async function test() {
- const [fd1, fd2] = await Promise.all([openFile('1.txt', 'r'), openFile('2.txt', O_WRONLY | O_CREAT)]);
- const { size } = await getFileInfo(fd1);
- console.log(copyFile(fd1, fd2, size));
- fs.close(fd1, () => {});
- fs.close(fd2, () => {});
- }
- function openFile(filename, mode) {
- return new Promise((resolve, reject) => {
- fs.open(filename, mode, (err, fd) => {
- if (err) {
- reject(err);
- } else {
- resolve(fd);
- }
- });
- })}
- function getFileInfo(fd) {
- return new Promise((resolve, reject) => {
- fs.fstat(fd, (err, stat) => {
- if (err) {
- reject(err)
- }else {
- resolve(stat);
- }
- });
- })
- }
- test();
執(zhí)行上面代碼,我們可以看到文件會成功復(fù)制2.txt。接著我們再來試一下網(wǎng)絡(luò)IO的場景。
- const fs= require('fs');
- const http = require('http');
- const { copyFile } = require('./build/Release/sendfile.node');
- const server = http.createServer(async (req, res) => {
- const fd = await openFile('1.txt', 'r');
- const { size } = await getFileInfo(fd);
- const ret = copyFile(fd, res.socket._handle.fd, size);
- res.socket.end();
- }).listen(8002);
- const {
- O_WRONLY,
- O_CREAT,
- } = fs.constants;
- function openFile(filename, mode) {
- return new Promise((resolve, reject) => {
- fs.open(filename, mode, (err, fd) => {
- if (err) {
- reject(err);
- } else {
- resolve(fd);
- }
- });
- })}
- function getFileInfo(fd) {
- return new Promise((resolve, reject) => {
- fs.fstat(fd, (err, stat) => {
- if (err) {
- reject(err)
- }else {
- resolve(stat);
- }
- });
- })}
以上代碼首先啟動一個http服務(wù)器,然后收到請求的時候,通過addon調(diào)用sendfile給前端返回對應(yīng)的內(nèi)容,最后關(guān)閉連接。結(jié)果如下。
sendfile似乎在網(wǎng)絡(luò)IO中可以應(yīng)用了,但只是一個demo的思路,后續(xù)有時間繼續(xù)研究分析。