sendfile函数在两个文件描述符之间传递数据(完全在内核中操作),从而避免内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝。函数的定义如下:

#include<sys/sendfile.h>ssize_t sendfile(int out_fd,int in_fd , off_t* offset ,size_t count);

in_fd参数是待读出内容的文件描述符,out_fd参数是待写入内容的文件描述符。offset参数执行从读入文件流的哪个位置开始读,如果为空,则使用读入文件流的默认起始位置。count参数指定在文件描述符in_fd和out_fd之间传输的字节数。sendfile成功时返回传输的字节数,失败则返回-1并设置errno。该函数的man手册明确指出,in_fd必须是一个支持mmap函数的文件描述符,即它必须指向真实的文件,而不能是socket和管道;则out_fd则必须是一个socket。由此可见,sendfile几乎是专门为在网络上传输文件而设计的。

下面的例子利用sendfile函数将服务器上的一个文件传送给客户端

#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;}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;
}

splice函数用于在两个文件描述符之间移动数据,也是零拷贝。

函数的定义如下

#include<fcntl.h>ssize_t splice(int fd_in,loff_t* off_in,int fd_out,loff_t* off_out,size_t len ,unsigned int flags)

fd_in参数是待输入数据的文件描述符。如果fd_in是一个管道文件描述符,那么off_in参数必须设置为NULL。如果fd_in不是一个管道文件(比如是一个socket),那么off_in表示从输入数据流的何处开始读取数据。此时,如果off_in被设置为NULL,则表示从输入数据流的当前偏移位置读入;若off_in不为NULL,则它将指出具体的偏移位置。fd_out/off_out参数的含义与fd_in/off_in相同,不过用于输出数据流。len参数指定移动数据的长度;flag参数则控制数据如何移动,它可以设置为下表中的某些值的按位或。

使用splice函数时,fd_in 和fd_out必须至少有一个是管道文件描述符。splice函数调用成功时返回移动字节的数量,它可能返回0,表示没有数据需要移动,这发生在从管道中读取数据,而该管道没有被写入任何数据时。spice函数失败时返回-1并设置errno.常见的errno如下图

下面的例子是利用splice函数来实现一个零拷贝的回射服务器,它将客户端发送的数据原样返回客户端。

#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 <fcntl.h>int main( int argc, char* argv[] )
{if( argc <= 2 ){printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );return 1;}const char* ip = argv[1];int port = atoi( argv[2] );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{int pipefd[2];assert( ret != -1 );ret = pipe( pipefd );ret = splice( connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE ); assert( ret != -1 );ret = splice( pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );assert( ret != -1 );close( connfd );}close( sock );return 0;
}

我们通过splice函数将客户端的内容读取到pipefd[1]中,然后在使用splice函数从pipefd[0]中读出该内容到客户端,从而实现了简单高效的回射服务。整个过程未执行recv/send操作,因此也未涉及用户空间和内核空间之间的拷贝。

tee函数在两个管道文件描述符之间复制数据,也是零拷贝操作。它不消耗数据,因此源文件描述符上的数据仍然可以用于后续的读操作。函数原型如下:

#include<fcntl.h>ssize_t tee(int fd_in ,int fd_out,size_t len ,unsigned int flags);

该函数的参数的含义以splice相同(但fd_in 和fd_out都必须是管道文件描述符)。tee函数成功时返回在两个文件描述符之间复制的数据数量(字节数)。返回0表示没有复制任何数据,tee失败时返回-1并设置errno。

如下代码利用tee函数和splice函数,实现了linux下的tee程序(同时输出数据到终端和文件的程序)

#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>int main( int argc, char* argv[] )
{if ( argc != 2 ){printf( "usage: %s <file>\n", argv[0] );return 1;}int filefd = open( argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666 );assert( filefd > 0 );int pipefd_stdout[2];int ret = pipe( pipefd_stdout );assert( ret != -1 );int pipefd_file[2];ret = pipe( pipefd_file );assert( ret != -1 );//close( STDIN_FILENO );// dup2( pipefd_stdout[1], STDIN_FILENO );//write( pipefd_stdout[1], "abc\n", 4 );ret = splice( STDIN_FILENO, NULL, pipefd_stdout[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );assert( ret != -1 );ret = tee( pipefd_stdout[0], pipefd_file[1], 32768, SPLICE_F_NONBLOCK ); assert( ret != -1 );ret = splice( pipefd_file[0], NULL, filefd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );assert( ret != -1 );ret = splice( pipefd_stdout[0], NULL, STDOUT_FILENO, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );assert( ret != -1 );close( filefd );close( pipefd_stdout[0] );close( pipefd_stdout[1] );close( pipefd_file[0] );close( pipefd_file[1] );return 0;
}

linux 高级IO函数之sendfile splice tee相关推荐

  1. linux 高级IO函数之fcntl mmap/munmap

    fcntl函数提供了对文件描述符的各种控制操作.另外一个常见的控制文件描述符属性和行为的系统调用是ioctl,而且ioctl比fcntl能够执行更多的控制.但是对于控制文件描述符常用的属性和行为,fc ...

  2. 【Linux系统IO函数】lseek函数

    Linux系统IO函数-lseek函数 1.1 lseek函数与标准C库的fseek函数 lseek函数对应标准C库中的fseek函数 查看标准C库中的fseek函数使用说明: (shell输入) m ...

  3. 标准C库IO函数和Linux系统IO函数对比

    标准C库IO函数和Linux系统IO函数对比 man 3 fopen #查看函数详情 虚拟地址通过mmu映射到真是的地址空间

  4. 【Linux系统编程学习】Linux系统IO函数(open、read、write、lseek)

    此为牛客网Linux C++课程1.20课程笔记. 1.open函数 open函数有两种,分别是打开一个已经存在的文件和创建并打开一个不存在的文件. #include <sys/types.h& ...

  5. linux io函数,unix/Linux低级IO函数的用法有哪些? 爱问知识人

    内容包括: open() ,尤其是各种常见的参数,到底是什么意思, 比如常用创建一个空文件: fd = open("/tmp/xx.txt",O_RDWR | O_CREAT | ...

  6. Linux系统函数之IO函数

    技术交流 QQ 群:1027579432,欢迎你的加入! 欢迎关注我的微信公众号:CurryCoder的程序人生 1.标准C库IO函数工作流程 IO缓冲区的作用? 大部分硬盘都是机械硬盘,读取寻道时间 ...

  7. 【Linux系统编程学习】C库IO函数与系统IO函数的关系

    此为黑马Linux课程笔记. 1. C标准IO函数工作流程 如图,以C库函数的fopen为例,其返回类型是FILE类型的指针,FILE类型包含很多内容,主要包含三个内容:文件描述符.文件读写指针的位置 ...

  8. Linux高级应用(二)文件IO函数与液晶屏显示

    一.结构体占用的内存 struct test { char a; int b; short c; long d; char *p; }; 求该结构体占用的内存?sizeof(struct test) ...

  9. Linux之高级IO

    目录 一.五种IO模型 以网络为例 什么叫做IO? 什么叫做高效的IO呢? 为什么第二个大爷的效率很高呢? 五种IO模型 感性认识 这五个人,在钓鱼的时候,谁的效率是最高的? 阻塞IO 非阻塞IO 信 ...

最新文章

  1. Python操作dict时避免出现KeyError的几种方法
  2. lucene源码分析(8)MergeScheduler
  3. sort qsort的区别
  4. Eclipse-cvs指南
  5. laravel 分词搜索匹配度_搜索引擎工作原理
  6. git之提交本地代码到远端指定仓库
  7. python求解LeetCode 习题 Excel Sheet Column Title
  8. Webmax简易入门操作手册(二)
  9. html中有序列表的css样式,CSS 列表样式(ul)
  10. 向量微积分——理解梯度
  11. 平安科技软件+金山WPS测试面试题
  12. 数学英语题目理解模型记录(1)
  13. 判断当前时间是否是法定节假日或工作日
  14. Python入门篇(二)
  15. 全新系列手机 配索尼4800万摄像头
  16. Spring中的切入点表达式写法
  17. 嵌入式设备启动过程(ARM )
  18. Swarm容器集群管理安装
  19. 使用135端口进行免杀横向移动(WMIHACKER)
  20. Xiaojie雷达之路---Mailbox深挖

热门文章

  1. 天涯明夜刀手游微信第一服务器,天涯明月刀手游微信哪个区人多 微信一区选哪个好[多图]...
  2. 寄存器分配图着色_【02】从零开始的卡通渲染-着色篇1
  3. centos python_在centos上配置python的虚拟开发环境
  4. 临床必备 | 第 5 期全基因组/外显子组家系分析理论和实战
  5. macos 下 vmware fusion 安装 vmware tools教程
  6. java怎么播放不了声音,java – 为什么这段代码不播放声音文件
  7. 启发式搜索 迭代加深搜索 搜索对象的压缩存储
  8. P1403 [AHOI2005]约数研究--100分(python3实现)
  9. 11产品经理要懂的-人性行为之善良友好的本质
  10. 5分绩点转4分_高考语文如何考上120分?衡中老师建议:这5点高中生必须重视