高级I/O函数(2)-splice函数
splice函数:
功能描述:用于在两个文件描述符之间移动数据,也是零拷贝操作。函数定义如下:
1 #include <fcntl.h> 3 ssize_t splice(int fd_in,loff_t* off_t,int fd_out,loff_t* off_out,size_t len,unsigned int flags);
参数描述:
fd_in:待输入数据的文件描述符.
off_t:如果fd_in是一个管道文件描述符,那么off_t参数必须是NULL,表示从数据流的当前偏移位置读入;如果fd_in不是一个管道文件描述符(例如socket),则它将指出具体的偏移位置.
len:指定移动数据的长度.
flags:则控制数据如何移动,它可以被设置为下表中值的按位异或.
表 splice的flags参数的常用取值及其含义
常用值 | 含义 |
SPLICE_F_MOVE | 如果合适的话,按整页内存移动数据. |
SPLICE_F_NONBLOCK | 非阻塞的splice操作,但实际效果还是会受文件描述符本身的阻塞状态的影响. |
SPLICE_F_MORE | 给内核一个提示:后续的splice调用将读取更多的数据 |
SPLICE_F_GIFT | 对splice没有效果. |
注意:
使用splice函数时,fd_in和fd_out必须至少有一个管道文件描述符.调用成功后返回移动字节的数量.它可能返回0,这发生从管道中读取数据时而该管道没有被写入任何数据.错误返回-1并设置errno.
例子:利用splice函数来实现一个零拷贝的回射服务器模型。
1 #include <sys/socket.h> 2 #include <netinet/in.h> 3 #include <arpa/inet.h> 4 #include <unistd.h> 5 #include <stdlib.h> 6 #include <string.h> 7 #include <fcntl.h> 8 9 int main(int argc,const char* argv[]){ 10 if(argc!=2){ 11 printf("usage:%s ip_address port_number\n",argv[0]); 12 return -1; 13 }' 14 15 const char* ip=argv[1]; 16 int port=atoi(argv[2]); 17 18 int ret; 19 struct sockaddr_in address; 20 bzero(&address,sizeof(address)); 21 address.sin_family=AF_INET; 22 inet_pton(AF_INET,ip,&address.sin_addr); 23 address.sin_port=htons(port); 24 25 int sockfd=socket(AF_INET,SOCK_STREAM,0); 26 assert(sockfd!=-1); 27 28 ret=bind(sockfd,(struct sockaddr*)&address,sizeof(address)); 29 assert(ret!=-1); 30 31 ret=listen(sockfd,5); 32 assert(ret!=-1); 33 34 while(1){ 35 struct sockaddr_in peer; 36 bzero(&peer,sizeof(peer)); 37 socklen_t len=sizeof(peer); 38 39 int connfd=accept(sockfd,(struct sockaddr*)&peer,len); 40 if(connfd<0){ 41 printf("errno is:%d\n",errno); 42 break; 43 } 44 else{ 45 int pipefd[2]; 46 ret=pipe(pipefd); 47 assert(ret!=-1); 48 49 /*将connfd上流入的客户端数据定向到管道中*/ 50 ret=splice(connfd,NULL,pipefd[1],NULL,32768,SPLICE_F_MORE| 51 SPLICE_F_MOVE); 52 assert(ret!=-1); 53 /*将管道中的数据定向到connfd的客户端文件描述符上*/ 54 splice(pipefd[0],NULL,connfd,NULL,32768,SPLICE_F_MORE| 55 SPLICE_F_MOVE); 56 assert(ret!=-1); 57 } 58 } 59 60 close(connfd); 61 close(sockfd); 62 return 0; 63 }
我们通过splice函数将从客户端的内容读入到pipefd[1]中,然后再使用splice函数从pipefd[0]中读出该内容到客户端。从而实现了简单高效的回射服务。整个过程为执行recv/send操作,因此未涉及用户空间和内核空间之间的数据拷贝。