以前我写过一篇关于nginx sendfile的文章,讲的是sendfile零拷贝理论方面,这次主要是讲实现的细节。 http://xiaorui.cc/?p=1673
sendfile的流程示意图 :
nginx有个sendfile参数,我想大家一般都是开启的。 简单说sendfile可以高效传输静态文件,尤其针对静态文件下载的逻辑。 sendfile的好处主要是两点, 普通的sendall流程是,kernel buffer — > user buffer — > kernel socket buffer –> 协议栈,对于sendfile来说,他的流程是kernel buffer –> kernel socket buffer –> 协议栈. 很明显减少copy的次数,另外这也减少了上线文切换的次数。
该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新。http://xiaorui.cc/?p=3225
python是无法直接实现sendfile的,只能是借用PyObject + C语言实现。
讲解py库之前,先介绍下C语言的sendfile函数使用方法:
#xiaorui.cc #include <sys/sendfile.h> ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
sendfile在两个文件描术符之间直接传递数据,完全在内核中操作,从而避免了内核缓冲区到用户缓冲区的拷贝,因此效率很高,称为零拷贝。
out_fd参数是待写入描述符。
in_fd参数是待读出的描述符。
offset参数指定从读入文件流的哪个位置开始读,如果为空,则从文件流的默认超始位置读入。
count参数指定传输的字节数。
调用成功时返回传输的字节数,失败则为-1,并设置errno .
需要注意的是,in_fd必须是进程打开文件后产生的文件描述符,你不能直接传入文件名,不能是socket和管道;而out_fd必须是一个socket。可见,sendfile专为网络传输文件而生。
python sendfile已经有个意大利的同学实现了,我们直接套用该模块就可以了。模块名是 pysendfile .
#xiaorui.cc from sendfile import sendfile file = open("xiaorui.cc", "rb") blocksize = os.path.getsize("xiaorui.cc") sock = socket.socket() sock.connect(("127.0.0.1", 80)) offset = 0 while True: # socket 文件描述符 #文件的文件描述符 #读取位置,seek #传输文件的大小 sent = sendfile(sock.fileno(), file.fileno(), offset, blocksize) if sent == 0: break # EOF offset += sent
再观摩下他的sendfilemodule.c的源码实现,可以看到sendfile函数并不是一次性把数据塞入到socket buffer,而是用while循环的copy到kernel socket端. 另外还针对EAGAIN做了及时返回。避免了阻塞读取写入的情况. 如果你有类似ioloop的调度实现,完全可以针对这事件进行io调度,当然如果是同步的逻辑,跟我上面的python实例一样,那么就无所谓了。
method_sendfile(PyObject *self, PyObject *args) { int out_fd, in_fd; off_t offset; size_t nbytes; char *hdr=NULL, *trail=NULL; int hdrsize, trailsize; ssize_t sts=0; struct sf_parms sf_iobuf; int rc; if (!PyArg_ParseTuple(args, "iiLk|s#s#", &out_fd, &in_fd, &offset, &nbytes, &hdr, &hdrsize, &trail, &trailsize)) return NULL; if(hdr != NULL) { sf_iobuf.header_data = hdr; sf_iobuf.header_length = hdrsize; } else { sf_iobuf.header_data = NULL; sf_iobuf.header_length = 0; } if(trail != NULL) { sf_iobuf.trailer_data = trail; sf_iobuf.trailer_length = trailsize; } else { sf_iobuf.trailer_data = NULL; sf_iobuf.trailer_length = 0; } sf_iobuf.file_descriptor = in_fd; sf_iobuf.file_offset = offset; sf_iobuf.file_bytes = nbytes; Py_BEGIN_ALLOW_THREADS; do { sf_iobuf.bytes_sent = 0; /* Really needed? */ rc = send_file(&out_fd, &sf_iobuf, SF_DONT_CACHE); sts += sf_iobuf.bytes_sent; } while( rc == 1 || (rc == -1 && errno == EINTR) ); Py_END_ALLOW_THREADS; offset = sf_iobuf.file_offset; if (rc == -1) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } else { #if defined(HAVE_LARGEFILE_SUPPORT) return Py_BuildValue("L", sts); #else return Py_BuildValue("l", sts); #endif } } if (ret < 0) { if ((errno == EAGAIN) || (errno == EBUSY) || (errno == EWOULDBLOCK)) { if (sent != 0) { // some data has been sent errno = 0; goto done; } else { // no data has been sent; upper application is supposed // to retry on EAGAIN / EBUSY / EWOULDBLOCK PyErr_SetFromErrno(PyExc_OSError); return NULL; } } PyErr_SetFromErrno(PyExc_OSError); return NULL; }
sendfile对于静态文件的处理是很高效的,也是web服务器必有的模式. 在python的主流web框架(tornado,flask,django)里默认是没有sendfile模块,原因是大多数的架构都是让nginx顶在python web框架前面的,原因不用废话。 按照实际应用的场景来描述,python sendfile更适合于client的角色,那么我写这篇关于web框架之python sendfile的用意是什么?学习性质的造轮子 !
END.
关于sendfile至今看到讲的最清楚的一个
谢谢