(sendfile)零拷贝与NIO
原文:https://blog.csdn.net/u014303647/article/details/82081451
sendfile函数在两个文件描述符之间传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,被称为零拷贝。函数定义为:
1 #include<sys/sendfile.h> 2 ssize_t senfile(int out_fd,int in_fd,off_t* offset,size_t count);
in_fd参数是待读出内容的文件描述符,out_fd参数是待写入内容的文件描述符。offset参数指定从读入文件流的哪个位置开始读,如果为空,则使用读入文件流默认的起始位置。count参数指定文件描述符in_fd和out_fd之间传输的字节数。
in_fd必须是一个支持类似mmap函数的文件描述符,即它必须指向真实的文件,不能是socket和管道,而out_fd必须是一个socket
首先我们来看看传统的read/write方式进行socket的传输。
当需要对一个文件进行传输的时候,具体流程细节如下:
1:调用read函数,文件数据copy到内核缓冲区
2:read函数返回,文件数据从内核缓冲区copy到用户缓冲区
3:write函数调用,将文件数据从用户缓冲区copy到内核与socket相关的缓冲区
4:数据从socket缓冲区copy到相关协议引擎。
在这个过程中发生了四次copy操作。
硬盘->内核->用户->socket缓冲区(内核)->协议引擎。
而sendfile的工作原理呢??
1、系统调用 sendfile() 通过 DMA 把硬盘数据拷贝到 kernel buffer,然后数据被 kernel 直接拷贝到另外一个与 socket 相关的 kernel buffer。这里没有 用户态和核心态 之间的切换,在内核中直接完成了从一个 buffer 到另一个 buffer 的拷贝。
2、DMA 把数据从 kernel buffer 直接拷贝给协议栈,没有切换,也不需要数据从用户态和核心态,因为数据就在 kernel 里。
测试代码:
#include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<signal.h> #include<unistd.h> #include<stdlib.h> #include<assert.h> #include<stdio.h> #include<string.h> #include<sys/sendfile.h> #include<fcntl.h> #include<sys/stat.h> #include<sys/types.h> #include<errno.h> int main(int argc,char *argv[]) { if(argc<=3) { printf("usage:%s ip_address port_number filename\n",basename(argv[0])); return 1; } const char* ip = argv[1]; int port = atoi(argv[2]); const char* file_name = argv[3]; int filefd = open(file_name,O_RDONLY); assert(filefd>0); struct stat stat_buf; fstat(filefd,&stat_buf); struct sockaddr_in address; bzero(&address,sizeof(address)); address.sin_family = AF_INET; inet_pton(AF_INET,ip,&address.sin_addr); address.sin_port = htons(port); int sock = socket(PF_INET,SOCK_STREAM,0); assert(sock>=0); int ret = bind(sock,(struct sockaddr*)&address,sizeof(address)); assert(ret!=-1); ret = listen(sock,5); assert(ret!=-1); struct sockaddr_in client; socklen_t client_addrlength = sizeof(client); int connfd = accept(sock,(struct sockaddr*)&client,&client_addrlength); if(connfd<0) { printf("errno is %d\n",errno); } else { sendfile(connfd,filefd,NULL,stat_buf.st_size); close(connfd); } close(sock); return 0; }