Linux高级I/O函数 splice
splice用于在两个文件描述符之间移动数据,也是一种重要零拷贝技术。
splice声明
#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_in是一个管道文件,那么off_in必须被设置为NULL;如果不是,那么off_in表示从输入数据流的何处开始读取数据。此时,若off_in被设置为NULL,则表示从输入数据流的当前偏移位置读入;若off_in不为NULL,则将指出具体的偏移位置。
- fd_out/off_out 参数含义与fd_in/off_in相同,不过用于输出流。
- len 指定移动数据的长度。
- flags 控制数据如何移动。可被设置为下面某些值的按位或:
1)SPLICE_F_MOVE 如果合适的话,按整页内存移动数据。只是给内核的一个提示。不过,因为它的实现存在BUG,所以自内核2.6.21后,实际上没有任何效果。
2)SPLICE_F_NONBLOCK 非阻塞的splice操作,实际效果还会受文件描述符本身的阻塞状态的影响。
3)SPLICE_F_MORE 给内核的一个提示:后续splice调用将读取更多数据。
4)SPLICE_F_GIFT 对splice没有效果。
使用splice时,fd_in和fd_out必须至少有一个是管道文件描述符。
返回值
调用成功,返回移动字节的数量。可能是0,表示没有数据需要移动,发生在从管道中读取数据(fd_in是管道文件描述符),而该管道没有被写入任何数据时。
调用失败返回-1,errno被设置。常见errno:
1)EBADF 参数所指文件描述符有错;
2)EINVAL 目标文件系统不支持splice,或者目标文件以追加方式打开,或两个文件描述符都不说管道文件描述符,或者某个offset参数被用于不支持随机访问的设备(如字符设备,终端设备);
3)ENOMEM 内存不够;
4)EPIPE 参数fd_in(或fd_out)是管道文件描述符,而off_in(或off_out)不为NULL。
示例
使用splice函数实现一个零拷贝的服务器,将客户端发送数据原样返回给客户端。
#include <fcntl.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <libgen.h>
#include <assert.h>
#include <stdlib.h>
/**
* Run command:
* $ ./a.out 127.0.0.1 2009
*/
int main(int argc, char* argv[])
{
if (argc < 3) {
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 addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
inet_pton(AF_INET, ip, &addr.sin_addr);
addr.sin_port = htons(static_cast<uint16_t>(port));
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
assert(sockfd >= 0);
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)); // 端口复用
int ret = bind(sockfd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
assert(ret != -1);
ret = listen(sockfd, 5);
assert(ret != -1);
struct sockaddr_in client;
socklen_t client_addrlen = sizeof(client);
int connfd = accept(sockfd, reinterpret_cast<sockaddr*>(&client), &client_addrlen);
if (connfd < 0) {
perror("accept error");
}
else {
int pipefd[2];
ret = pipe(pipefd);/* 创建管道 */
assert(ret != -1);
while (1) {
ssize_t res;
/* 将connfd上流入数据定向到管道中 */
res = splice(connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
if (res == 0) { // 收到EOF(FIN分节), 则断开连接
break;
}
assert(res != -1);
/* 将管道输出定向到connfd客户连接的文件描述符 */
res = splice(pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
assert(res != -1);
}
close(connfd);
}
close(sockfd);
return 0;
}
参考
《Linux高性能服务器编程》