Linux网络编程--sendfile零拷贝高效率发送文件
2016-03-11 15:24
686 查看
本博文介绍使用sendfile函数进行零拷贝发送文件,实现高效数据传输,并对其进行验证。
那么什么是sendfile呢?
linux系统使用man sendfile,查看sendfile原型如下:
#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
参数特别注意的是:in_fd必须是一个支持mmap函数的文件描述符,也就是说必须指向真实文件,不能使socket描述符和管道。
out_fd必须是一个socket描述符。
由此可见sendfile几乎是专门为在网络上传输文件而设计的。
Sendfile 函数在两个文件描述符之间直接传递数据(完全在内核中操作,传送),从而避免了内核缓冲区数据和用户缓冲区数据之间的拷贝,操作效率很高,被称之为零拷贝。
传统方式read/write send/recv
在传统的文件传输里面(read/write方式),在实现上其实是比较复杂的,需要经过多次上下文的切换,我们看一下如下两行代码:
1. read(file, tmp_buf, len);
2. write(socket, tmp_buf, len);
以上两行代码是传统的read/write方式进行文件到socket的传输。
当需要对一个文件进行传输的时候,其具体流程细节如下:
1、调用read函数,文件数据被copy到内核缓冲区
2、read函数返回,文件数据从内核缓冲区copy到用户缓冲区
3、write函数调用,将文件数据从用户缓冲区copy到内核与socket相关的缓冲区。
4、数据从socket缓冲区copy到相关协议引擎。
以上细节是传统read/write方式进行网络文件传输的方式,我们可以看到,
在这个过程当中,文件数据实际上是经过了四次copy操作: 硬盘—>内核buf—>用户buf—>socket相关缓冲区(内核)—>协议引擎
新方式sendfile
sendfile系统调用则提供了一种减少以上多次copy,提升文件传输性能的方法。
1、系统调用 sendfile() 通过 DMA 把硬盘数据拷贝到 kernel buffer,然后数据被 kernel 直接拷贝到另外一个与 socket 相关的 kernel buffer。这里没有 user mode 和 kernel mode 之间的切换,在 kernel 中直接完成了从一个 buffer 到另一个 buffer 的拷贝。
2、DMA 把数据从 kernel buffer 直接拷贝给协议栈,没有切换,也不需要数据从 user mode 拷贝到 kernel mode,因为数据就在 kernel 里。
服务端:
客户端:
测试环境:linux Ubuntu 32位系统 CPU Intel i5-4258U @ 2.40GHz *4 内存2G
根据以上对比,使用sendfile的系统负载要低一些,cpu使用率要低很多,整体速度和send基本差不多,估计是当今系统计算速度太快,看不出什么明显区别。不过对比web服务器的话区别还是很大的。
那么什么是sendfile呢?
linux系统使用man sendfile,查看sendfile原型如下:
#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
参数特别注意的是:in_fd必须是一个支持mmap函数的文件描述符,也就是说必须指向真实文件,不能使socket描述符和管道。
out_fd必须是一个socket描述符。
由此可见sendfile几乎是专门为在网络上传输文件而设计的。
Sendfile 函数在两个文件描述符之间直接传递数据(完全在内核中操作,传送),从而避免了内核缓冲区数据和用户缓冲区数据之间的拷贝,操作效率很高,被称之为零拷贝。
传统方式read/write send/recv
在传统的文件传输里面(read/write方式),在实现上其实是比较复杂的,需要经过多次上下文的切换,我们看一下如下两行代码:
1. read(file, tmp_buf, len);
2. write(socket, tmp_buf, len);
以上两行代码是传统的read/write方式进行文件到socket的传输。
当需要对一个文件进行传输的时候,其具体流程细节如下:
1、调用read函数,文件数据被copy到内核缓冲区
2、read函数返回,文件数据从内核缓冲区copy到用户缓冲区
3、write函数调用,将文件数据从用户缓冲区copy到内核与socket相关的缓冲区。
4、数据从socket缓冲区copy到相关协议引擎。
以上细节是传统read/write方式进行网络文件传输的方式,我们可以看到,
在这个过程当中,文件数据实际上是经过了四次copy操作: 硬盘—>内核buf—>用户buf—>socket相关缓冲区(内核)—>协议引擎
新方式sendfile
sendfile系统调用则提供了一种减少以上多次copy,提升文件传输性能的方法。
1、系统调用 sendfile() 通过 DMA 把硬盘数据拷贝到 kernel buffer,然后数据被 kernel 直接拷贝到另外一个与 socket 相关的 kernel buffer。这里没有 user mode 和 kernel mode 之间的切换,在 kernel 中直接完成了从一个 buffer 到另一个 buffer 的拷贝。
2、DMA 把数据从 kernel buffer 直接拷贝给协议栈,没有切换,也不需要数据从 user mode 拷贝到 kernel mode,因为数据就在 kernel 里。
服务端:
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/sendfile.h> int main( int argc, char* argv[] ) { if( argc <= 3 ) { printf( "usage: %s ip_address port_number filename\n", basename( argv[0] ) ); return 1; } longnum=0,sum=0; static char buf[1024]; memset(buf,'\0',sizeof(buf)); 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 ); FILE *fp=fdopen(filefd,"r"); 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 { time_t begintime=time(NULL); while((fgets(buf,1024,fp))!=NULL){ num=send(connfd,buf,sizeof(buf),0); sum+=num; memset(buf,'\0',sizeof(buf)); } // sendfile( connfd, filefd, NULL, stat_buf.st_size ); time_t endtime=time(NULL); printf("sum:%ld\n",sum); printf("need time:%d\n",endtime-begintime); close( connfd ); } close( sock ); return 0; }
客户端:
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/sendfile.h> int main( int argc, char* argv[] ) { if( argc <= 3 ) { printf( "usage: %s ip_address port_number filename\n", basename( argv[0] ) ); return 1; } static char buf[1024]; memset(buf,'\0',sizeof(buf)); const char* ip = argv[1]; int port = atoi( argv[2] ); const char* file_name = argv[3]; int filefd = open( file_name, O_WRONLY ); if(filefd<=0) printf("open error:%s",strerror(errno)); assert( filefd > 0 ); FILE *fp=fdopen(filefd,"w"); struct sockaddr_in address; socklen_t len=sizeof(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 num; int ret=connect(sock,(struct sockaddr*)&address,len); if ( ret < 0 ) { printf( "connect errno: %s\n", strerror(errno) ); } else { while( (num=recv(sock,buf,sizeof(buf),0))>0 ){ fprintf(fp,"%s",buf); memset(buf,'\0',sizeof(buf)); } close( sock ); } close( sock ); return 0; }
测试环境:linux Ubuntu 32位系统 CPU Intel i5-4258U @ 2.40GHz *4 内存2G
根据以上对比,使用sendfile的系统负载要低一些,cpu使用率要低很多,整体速度和send基本差不多,估计是当今系统计算速度太快,看不出什么明显区别。不过对比web服务器的话区别还是很大的。
相关文章推荐
- Linux网络编程--sendfile零拷贝高效率发送文件
- iOS 多个网络请求并行/并发处理
- Android逆向---Fiddler简易使用教程之抓取https包
- Android Https相关完全解析 当OkHttp遇到Https
- python 网络编程攻略第一章 服务器/客户端交互栗子
- Linux中级网络服务与架构基础理论
- http://man.linuxde.net/cp 链接是linux cp指令
- 接口 两种调用http接口的方法
- 网络通信框架Volley使用详细说明
- Stanford机器学习---第四讲. 神经网络的表示
- TCP连接问题复习
- springMVC No mapping found for HTTP request with URI
- 计算机网络常见面试题总结(转)
- iOS9 HTTP 不能正常使用的解决办法
- iOS开发工具——网络封包分析工具Charles
- nginx http2配置
- 多层感知器 - Multilayer Perceptron --(Back-propagation Algorithm - 反馈神经网络)
- httpClient多线程问题
- Http Get和Post区别
- Android笔记--Android Studio 引用第三方开源类okhttp、gson库时的打包混淆