该函数用于在两个文件描述符之间移动数据,为零拷贝操作,其函数原型如下:
ssize_t splice(int fd_in, loff_t *off_in, int fd_out,
loff_t *off_out, size_t len, unsigned int flags);
splice()在两个文件描述符之间移动数据,而无需在内核地址空间和用户地址空间之间进行复制。它从文件描述中传输最多len字节的数据。将fd_in传递到文件描述符fd_out,其中文件描述符之一必须引用管道。
特别说明:
对于fd_in来说,若其是一个管道文件描述符,则off_in必须被设置为NULL,若它不是一个管道描述符,则off_in表示从输入数据流的何处开始读入数据,此时,其被设置为NULL,则说明从输入数据的当前偏移位置读入。否则off_in指出具体的偏移位置。
以上对于fd_out和off_out同样适用,只不过其用于输出数据流。
flags参数控制数据如何移动,它可以被设置为下表中的某些值的按位或。
常用值 | 含义 |
---|---|
SOLICE_F_MOVE | 如果合适的话,按整页内存移动数据。这只是给内核提示,不过,因为它的实现存在BUG,所以自内核2.6.21后,他实际上没有任何效果 |
SPLICE_F_NONBLOCK | 非阻塞的splice操作,但是实际效果还是会受文件描述符本身的阻塞状态的影响 |
SPLICE_F_MORE | 给内核一个提示,后续的splice调用将读取更多的数据 |
SPLICE_F_GIFT | 对splice没有效果 |
成功返回移动字节的数量,失败返回-1,并设置errno,常见的errno如下表所示。
错误 | 含义 |
EBADF | 参数所指文件描述符有错 |
EINVAL | 目标文件系统不支持splice,或者目标文件以追加方式打开,或者两个文件描述符都不是管道文件描述符,或者某个offset参数被用于不支持随机访问的设备(如字符设备) |
ENOMEM | 内存不够 |
ESPIPE | 参数fd_in(或fd_out)是管道文件描述符,而off_in(或off_out)不为NULL |
下面通过splice来写一个简单回射服务来展示其用法,其实看上面的flags的那些值的含义没啥多大用处,通过本人测试可以传0值。
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<fcntl.h>
#define LEN 655
int main(int argc,char *argv[])
{
if(argc < 3)
{
printf("usage: %s ip port\n",argv[0]);
exit(1);
}
const char *ip = argv[1];
int port = atoi(argv[2]);
int sockfd,connfd;
struct sockaddr_in sockaddr,connaddr;
socklen_t connaddr_len = sizeof(connaddr);
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(port);
inet_pton(AF_INET,ip,&sockaddr.sin_addr);
sockfd = socket(AF_INET,SOCK_STREAM,0);
int ret = bind(sockfd,(struct sockaddr*)&sockaddr,sizeof(sockaddr));
if(ret == -1)
{
perror("bind error");
exit(1);
}
listen(sockfd,15);
connfd = accept(sockfd,(struct sockaddr*)&connaddr,&connaddr_len);
if(connfd == -1)
{
perror("accept error");
exit(1);
}
int pipefd[2];
pipe(pipefd);
while(true)
{
//用splice函数的回射服务
int n = splice(connfd,NULL,pipefd[1],NULL,LEN,SPLICE_F_MORE);
if(n > 0)
{
splice(pipefd[0],NULL,connfd,NULL,n,SPLICE_F_MORE);
}
else if(n == 0)
{
printf("client close\n");
close(pipefd[0]);
close(pipefd[1]);
close(connfd);
close(sockfd);
break;
}
else{
perror("splice error");
exit(1);
}
/*
//用read和write函数的回射服务
char buf[BUFSIZ];
int n = read(connfd,buf,sizeof(buf));
if(n > 0)
{
write(connfd,buf,n);
}
else if(n == 0)
{
printf("client close\n");
close(connfd);
close(sockfd);
break;
}
else{
perror("read error");
exit(1);
}
*/
}
return 0;
}
可以看到我还写了read和write版本的回射服务,看书上说splice函数避免了用户缓冲区和内核缓冲区之间的数据拷贝,是零拷贝,更高效,但是通过测试用splice写的回射服务有明显的延迟,而read和write实现的则没有延迟,我就很迷惑,读者可以在自己的电脑上试一下。