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高性能服务器编程》

posted @ 2022-05-01 09:55  明明1109  阅读(1269)  评论(0编辑  收藏  举报