splice函数

本文深入解析了splice函数的工作原理,展示了如何在两个文件描述符间进行零拷贝操作,对比了传统read/write方法,提供了实际应用案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

该函数用于在两个文件描述符之间移动数据,为零拷贝操作,其函数原型如下:

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实现的则没有延迟,我就很迷惑,读者可以在自己的电脑上试一下。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值