一、函数介绍
#define _GNU_SOURCE /* See feature_test_macros(7) */
#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_out
参数
- fd_in:待输入数据的文件描述符
- off_in:表示从输入数据流的何处开始读取数据,如果从输入数据流的当前偏移位置读入,那么此参数设置为NULL(如果fd_in是一个管道文件描述符,那么off_in必须设置为NULL)
- fd_out:待输出数据的文件描述符
- off_out:表示接收的数据存放到何处,如果存放到当前偏移位置,那么此参数设置为NULL(如果fd_out是一个管道文件描述符,那么off_out必须设置为NULL)
- len:指定移动数据的长度
- flags:控制数据如何移动,为下面的某些值的按位或
常用值 | 含义 |
SPLICE_F_MOVE | 如果合适的话,按整页内存移动数据。这只是给内核的一个提示。不过,因为它的实现存在BUG,所以子内核2.6.21后,它实际上没有任何效果 |
SPLICE_F_NONBLOCK | 非阻塞splice操作,但实际效果还会受到文件描述符本身的阻塞状态的影响 |
SPLICE_F_MORE | 给内核的一个提示;后续的splice调用将读取更多数据 |
SPLICE_F_GIFT | 对splice没有效果 |
返回值
- 执行成功返回移动字节的数量
- 返回0:表示没有数据需要移动,这发生在从管道中读取数据(fd_in是管道文件描述符)而该管道没有被写入任何数据时
- 失败:返回-1并设置errno,常见的errno如下
错误 | 含义 |
EBADF | 参数所指文件描述符出错 |
EINVAL | 目标文件系统不支持splice,或者目标文件以追加方式打开,或者两个文件描述符都不是管道文件描述符,或者某个offset参数被用于不支持随机访问的设备(如字符设备) |
ENOMEM | 内存不够 |
ESPIPE | 参数fd_in或fd_out是管道文件描述符,而off_in或off_out不为NULL |
注意事项
- 使用此函数时,fd_in和fd_out必须至少有一个是管道文件描述符
- 宏_GNU_SOURCE必须定义在头文件fcntl的前面,否则编译会出错
二、演示案例
- 下面使用splice函数来实现一个零拷贝的回射服务器,它将客户端发送的数据原样返回给客户端
//splice_serv.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
if(argc!=3){
printf("usage:%s ip port\n",argv[0]);
exit(EXIT_FAILURE);
}
int serv_fd,cli_fd;
pid_t pid;
struct sockaddr_in serv_addr;
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(atoi(argv[2]));
if(inet_pton(AF_INET,argv[1],&serv_addr.sin_addr)==-1){
perror("inet_pton:");
exit(EXIT_FAILURE);
}
if((serv_fd=socket(AF_INET,SOCK_STREAM,0))==-1){
perror("inet_pton:");
exit(EXIT_FAILURE);
}
if(bind(serv_fd,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1){
perror("bind:");
exit(EXIT_FAILURE);
}
if(listen(serv_fd,10)==-1){
perror("listen:");
exit(EXIT_FAILURE);
}
while(1){
if((cli_fd=accept(serv_fd,NULL,NULL))==-1){
continue;
}
if((pid=fork())==0){
close(serv_fd);
int pipe_arr[2];
if(pipe(pipe_arr)==-1){
perror("pipe:");
exit(EXIT_FAILURE);
}
if(splice(cli_fd,NULL,pipe_arr[1],NULL,1024,SPLICE_F_MORE|SPLICE_F_MOVE)==-1){
perror("splice:");
exit(EXIT_FAILURE);
}
if(splice(pipe_arr[0],NULL,cli_fd,NULL,1024,SPLICE_F_MORE|SPLICE_F_MOVE)==-1){
perror("splice:");
exit(EXIT_FAILURE);
}
printf("splice success\n");
close(cli_fd);
exit(EXIT_SUCCESS);
}
close(cli_fd);
}
exit(EXIT_SUCCESS);
}