本博文介绍使用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 里。

服务端:

[cpp] view plaincopy print?
  1. #include <sys/socket.h>
  2. #include <netinet/in.h>
  3. #include <arpa/inet.h>
  4. #include <assert.h>
  5. #include <stdio.h>
  6. #include <unistd.h>
  7. #include <stdlib.h>
  8. #include <errno.h>
  9. #include <string.h>
  10. #include <sys/types.h>
  11. #include <sys/stat.h>
  12. #include <fcntl.h>
  13. #include <sys/sendfile.h>
  14. int main( int argc, char* argv[] )
  15. {
  16. if( argc <= 3 )
  17. {
  18. printf( "usage: %s ip_address port_number filename\n", basename( argv[0] ) );
  19. return 1;
  20. }
  21. longnum=0,sum=0;
  22. static char buf[1024];
  23. memset(buf,'\0',sizeof(buf));
  24. const char* ip = argv[1];
  25. int port = atoi( argv[2] );
  26. const char* file_name = argv[3];
  27. int filefd = open( file_name, O_RDONLY );
  28. assert( filefd > 0 );
  29. struct stat stat_buf;
  30. fstat( filefd, &stat_buf );
  31. FILE *fp=fdopen(filefd,"r");
  32. struct sockaddr_in address;
  33. bzero( &address, sizeof( address ) );
  34. address.sin_family = AF_INET;
  35. inet_pton( AF_INET, ip, &address.sin_addr );
  36. address.sin_port = htons( port );
  37. int sock = socket( PF_INET, SOCK_STREAM, 0 );
  38. assert( sock >= 0 );
  39. int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
  40. assert( ret != -1 );
  41. ret = listen( sock, 5 );
  42. assert( ret != -1 );
  43. struct sockaddr_in client;
  44. socklen_t client_addrlength = sizeof( client );
  45. int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
  46. if ( connfd < 0 )
  47. {
  48. printf( "errno is: %d\n", errno );
  49. }
  50. else
  51. {
  52. time_t begintime=time(NULL);
  53. while((fgets(buf,1024,fp))!=NULL){
  54. num=send(connfd,buf,sizeof(buf),0);
  55. sum+=num;
  56. memset(buf,'\0',sizeof(buf));
  57. }
  58. //        sendfile( connfd, filefd, NULL, stat_buf.st_size );
  59. time_t endtime=time(NULL);
  60. printf("sum:%ld\n",sum);
  61. printf("need time:%d\n",endtime-begintime);
  62. close( connfd );
  63. }
  64. close( sock );
  65. return 0;
  66. }

客户端:

[cpp] view plaincopy print?
  1. #include <sys/socket.h>
  2. #include <netinet/in.h>
  3. #include <arpa/inet.h>
  4. #include <assert.h>
  5. #include <stdio.h>
  6. #include <unistd.h>
  7. #include <stdlib.h>
  8. #include <errno.h>
  9. #include <string.h>
  10. #include <sys/types.h>
  11. #include <sys/stat.h>
  12. #include <fcntl.h>
  13. #include <sys/sendfile.h>
  14. int main( int argc, char* argv[] )
  15. {
  16. if( argc <= 3 )
  17. {
  18. printf( "usage: %s ip_address port_number filename\n", basename( argv[0] ) );
  19. return 1;
  20. }
  21. static char buf[1024];
  22. memset(buf,'\0',sizeof(buf));
  23. const char* ip = argv[1];
  24. int port = atoi( argv[2] );
  25. const char* file_name = argv[3];
  26. int filefd = open( file_name, O_WRONLY );
  27. if(filefd<=0)
  28. printf("open error:%s",strerror(errno));
  29. assert( filefd > 0 );
  30. FILE *fp=fdopen(filefd,"w");
  31. struct sockaddr_in address;
  32. socklen_t len=sizeof(address);
  33. bzero( &address, sizeof( address ) );
  34. address.sin_family = AF_INET;
  35. inet_pton( AF_INET, ip, &address.sin_addr );
  36. address.sin_port = htons( port );
  37. int sock = socket( PF_INET, SOCK_STREAM, 0 );
  38. assert( sock >= 0 );
  39. int num;
  40. int ret=connect(sock,(struct sockaddr*)&address,len);
  41. if ( ret < 0 )
  42. {
  43. printf( "connect errno: %s\n", strerror(errno) );
  44. }
  45. else
  46. {
  47. while( (num=recv(sock,buf,sizeof(buf),0))>0 ){
  48. fprintf(fp,"%s",buf);
  49. memset(buf,'\0',sizeof(buf));
  50. }
  51. close( sock );
  52. }
  53. close( sock );
  54. return 0;
  55. }

测试环境:linux Ubuntu 32位系统 CPU Intel i5-4258U  @ 2.40GHz *4  内存2G

根据以上对比,使用sendfile的系统负载要低一些,cpu使用率要低很多,整体速度和send基本差不多,估计是当今系统计算速度太快,看不出什么明显区别。不过对比web服务器的话区别还是很大的。

Linux网络编程--sendfile零拷贝高效率发送文件相关推荐

  1. C#网络编程:4订立协议和发送文件

    前面两篇文章所使用的范例都是传输字符串,有的时候我们可能会想在服务端和客户端之间传递文件.比如,考虑这样一种情况,假如客户端显示了一个菜单,当我们输入S1.S2或S3(S为Send缩写)时,分别向服务 ...

  2. Linux网络编程(六)-高并发服务器03-I/O多路复用03:epoll【红黑树;根节点为监听节点】【无宏FD_SETSIZE限制;不需每次都将要监听的文件描述符从应用层拷贝到内核;不需遍历树】

    一.epoll概述 epoll的本质是一个[红黑树].监听结点为根节点. 大量并发,少量活跃效率高. epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并 ...

  3. linux网络编程—7层网络以及5种Linux IO模型以及相应IO基础

    一.七层网络模型 OSI是Open System Interconnection的缩写,意为开放式系统互联.国际标准化组织(ISO)制定了OSI模型,该模型定义了不同计算机互联的标准,它是一个七层的. ...

  4. Linux网络编程——黑马程序员笔记

    01P-复习-Linux网络编程 02P-信号量生产者复习 03P-协议 协议: 一组规则. 04P-7层模型和4层模型及代表协议 分层模型结构: OSI七层模型: 物.数.网.传.会.表.应TCP/ ...

  5. Linux网络编程——千峰物联网笔记

    B站视频:千峰物联网学科linux网络编程 网址:https://www.bilibili.com/video/BV1RJ411B761?p=1 目录 第一章:计算机网络概述 1.1计算机网络发展简史 ...

  6. Linux网络编程-五

    Linux网络编程-五 1 线程池并发服务器 2 UDP通信 2.1TCP和UDP的区别 2.2 UDP通信相关函数介绍: 2.3 UDP的服务器和客户端编码流程 2.4 代码练习 3 本地socke ...

  7. Linux io模型及函数调用,Linux 网络编程的5种IO模型:信号驱动IO模型

    Linux 网络编程的5种IO模型:信号驱动IO模型 背景 这一讲我们来看 信号驱动IO 模型. 介绍 情景引入: 在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个 ...

  8. linux网络编程--阻塞与非阻塞

    linux网络编程--阻塞与非阻塞 建立连接 接受连接 无阻塞的设置方式 read() write() 读操作 写操作 Linux fcntl函数详解 功能描述 函数原型 fcntl()函数五种功能 ...

  9. 计算机网络(二)Linux网络编程

    layout: post title: 计算机网络(二)Linux网络编程 description: 计算机网络(二)Linux网络编程 tag: 计算机网络 文章目录 资源共享 Linux高性能服务 ...

最新文章

  1. GCC 链接 xxx:No such file or directory 及运行可执行文件 error while loading shared libraries: xxx.so 解决方案
  2. 你知道R中的赋值符号箭头和等号的区别吗?
  3. js---BOW---页面打开方式,跳转方式 2017-03-24
  4. web服务器-nginx
  5. 群晖如何建php网站_群晖webstation搭建typecho博客
  6. JUnit 5 –架构
  7. ant中table表格的多选框如何清空
  8. Apollo进阶课程㊸丨Apollo实战——障碍物感知和路径规划能力实战
  9. linux驱动与dts匹配过程,自己编写从应用到DTS与驱动
  10. Anaconda jupyter-notebook 添加kernel
  11. 数据结构-栈(先进后出表)
  12. docker运行storm及wordcount实例
  13. MyEclipse设置字体大小
  14. 聚合支付的钱先到哪?一文让你读懂什么是聚合支付
  15. 【计算机-CPU】电子电路-机器语言与指令集架构/CPU软核/CPU硬核-汇编语言与与编译器
  16. iOS 技术支持网址
  17. github手把手使用教程
  18. Riverbed助力Interplex成功使用机器人技术
  19. 样本切分器—利用python按比例均匀切分样本
  20. 怎么压缩图片200k以下?这个压缩工具,3秒无损完成

热门文章

  1. java观察者_Java中的观察者模式
  2. python找图里的环_python判断无向图环是否存在的示例
  3. 图片也要查重了?期刊用AI审论文防造假,旋转/翻转/拉伸都不行
  4. “嘿,我们又见面了!”
  5. 【C#桌面应用】第三节:创建一个按钮窗口 您好,世界!
  6. java workthread_Java多线程基础(十)——Work Thread模式
  7. Web前端笔记-使用@media(媒体查询)展示及隐藏div
  8. Qt4工作笔记-Linux中Qt4.8.6在GBK和UTF-8编码转换
  9. 7.1图的定义和术语
  10. 点击部分刷新html ajax,一文全解web前端精简知识点