以前我写过一篇关于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至今看到讲的最清楚的一个
谢谢