Redian新闻
>
一文读懂零拷贝技术|splice 使用

一文读懂零拷贝技术|splice 使用

公众号新闻
服务端要向客户端连接发送一个文件,一般过程如下:
  • 服务端首先调用 read() 函数读取文件内容。
  • 服务端通过调用 write()/send() 函数将文件内容发送给客户端连接。

上面过程如下图所示:

从上图可以看出,在发送文件的过程中,首先需要将文件页缓存(Page Cache)从内核态复制到用户态缓存中,然后再从用户态缓存复制到客户端的 Socket 缓冲区中。

其实在上面的过程中,复制文件数据到用户态缓存这个操作是多余的,我们完全可以直接把文件页缓存的数据复制到 Socket 缓冲区即可,这样就可以减少一次拷贝数据的操作。

为了实现这样的功能,内核提供了一个名为 splice() 的系统调用,使用 splice() 系统调用可以避免从内核态拷贝数据到用户态。

不需要将内核态的数据拷贝到用户态缓存的技术被称为:零拷贝技术

下面我们将介绍 splice() 系统调用的原理和实现。

splice 使用实例

如果服务端要发送文件给客户端,使用 read()/write() 方式来实现的话,代码如下所示:

/**
 * 发送文件给客户端(read/write版本)
 */

int send_file_to_client(int client_fd, char *file)
{
    int fd;
    struct stat fstat;
    int blocks, remain;
    char buf[4096]; // 每次发送4096个字节

    fd = open(file, O_RDONLY);
    if (fd == -1) {
        return -1;
    }

    stat(file, &fstat);               // 用于获取文件的大小
    blocks = fstat.st_size / 4096;    // 需要发送的次数
    remain = fstat.st_size % 4096;    // 如果文件的大小不是4096的倍数,要额外发送这些数据

    for (i = 0; i < blocks; i++) {
        read(fd, buf, 4096);          // 读取文件内容
        write(client_fd, buf, 4096);  // 发送文件内容给客户端
    }

    if (remain > 0) {
        read(fd, buf, remain);
        write(client_fd, buf, remain);
    }

    return 0;
}

上面代码的流程比较简单,如下:

  • 首先通过调用 stat() 系统调用获取文件的大小。
  • 然后通过调用 read() 系统调用读取文件内容。
  • 最后通过调用 write() 系统调用将文件内容发送给客户端连接。

从上面的代码可以看出,使用 read()/write() 方式发送文件给客户端,首先需要将文件内容读到用户态缓存中,然后才能发送给客户端连接。

然而,将文件内容读取到用户态缓存这个过程是多余的,我们看看怎么使用 splice() 系统调用来避免将文件内容拷贝到用户态缓存。

使用 splice() 发送文件时,需要创建一个管道作为中转,代码如下:

/**
 * 发送文件给客户端(splice版本)
 */

int send_file_to_client(int client_fd, char *file)
{
    int fd;
    struct stat fstat;
    int blocks, remain;
    int pipefd[2];

    fd = open(file, O_RDONLY);
    if (fd == -1) {
        return -1;
    }

    stat(file, &fstat);

    blocks = fstat.st_size / 4096;
    remain = fstat.st_size % 4096;

    pipe(pipefd);  // 创建管道作为中转

    for (i = 0; i < blocks; i++) {
        // 1. 将文件内容读取到管道
        splice(fd, NULL, pipefd[1], NULL4096, SPLICE_F_MOVE|SPLICE_F_MORE);
        // 2. 将管道的数据发送给客户端连接
        splice(pipefd[0], NULL, client_fd, NULL4096, SPLICE_F_MOVE|SPLICE_F_MORE);
    }

    if (remain > 0) {
        splice(fd, NULL, pipefd[1], NULL, remain, SPLICE_F_MOVE|SPLICE_F_MORE);
        splice(pipefd[0], NULL, client_fd, NULL, remain, SPLICE_F_MOVE|SPLICE_F_MORE);
    }

    return 0;
}

从上面代码可以看出,使用 splice() 发送文件时,我们并不需要将文件内容读取到用户态缓存中,但需要使用管道作为中转。

其实这里的管道只是作为一个通道,并不会产生数据拷贝的,如下图所示:

对比 read()/write() 版本的原理图,可以看出 splice() 版本省去了拷贝文件内容到用户态缓存这个步骤。

总结

本文主要介绍了使用 read()/write() 方式传输文件与使用 splice() 方式传输文件的原理,也提供了这两种方式的实例代码。

当然,从原理上看,使用 splice() 方式传输文件会比 read()/write() 方式性能要高。但如果真实测试这两种方式,会发现性能相差并不大。这是由于 splice() 方式虽然减少了数据拷贝过程,但是其处理逻辑比 read()/write() 方式更为复杂,所以性能提升并不理想,有兴趣的读者可以自己测试一下。

- EOF -

推荐阅读  点击标题可跳转

1、为什么永远不会有语言取代 C / C++ ?

2、漫谈 C++:良好的编程习惯与编程要点

3、现代 C++ 测试工具链(是时候抛弃 gtest/google bench 了)


↓推荐关注↓

「CPP开发者」在 Github 维护着 9000+ star 的C/C++开发资源。日常分享 C语言 和 C++ 开发相关技术文章,每篇文章都经过精心筛选,一篇文章讲透一个知识点,让读者读有所获~


点赞和在看就是最大的支持❤️

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
「虽然活着,但像一具行尸走肉」:一文读懂真实的 PTSD 创伤一文读懂 Linux 内存分配策略头部动力电池厂商未来拼什么?数字研发技术|数字时氪深度研究你对免治肉放心吗?一文读懂AIGC:万亿新赛道为何今年获得爆发?从历史上看中国工业化建设成形为什么ChatGPT这么强?—— 一文读懂ChatGPT原理!一文读懂,16种“杀手”型心电图《部队大院的八零后》第五章 手榴弹要结婚了美股SPAC|中国SPAC Horizo​​n Space Acquisition I申请6000万美元IPO,目标新兴成长公司WLSA/华旭双语/英澳美加入“春招抢人大战”,一文读懂沪上国际高中怎么选、怎么考?一文读懂阿里、京东的底层逻辑一文读懂零拷贝技术|splice使用HPV九价疫苗扩龄!一文读懂疫苗接种年龄和种类选择双11熊猫外卖下血本了 史上最大优惠!!- 拷贝 - 拷贝青山处处埋忠骨 还是忠骨埋一处?1986年伦敦穷游记(4)一文读懂碳中和的战略逻辑和技术需求门静脉高压常见并发症——门脉性肺动脉高压 | 一文读懂Rosalía 登意大利版《VOGUE》封面!一文读懂最近爆火的ChatGPT【一文读懂】芯片板块行情重启?该pick哪个芯片主题ETF?一文读懂联邦催办令!一文读懂如何计算英国永居离境天数!签证断开、离境超时该如何挽救?疫情政策还奏效吗?一文读懂零拷贝技术|splice 原理与实现C919适航取证有多难?距离商业载客运行还远吗?一文读懂上海中国画院首批画师介绍一文读懂零拷贝技术|splice原理与实现美股SPAC|新加坡生物技术公司 AUM Biosciences 宣布计划通过与SPAC合并上市【租房】12.21短租到5.10|家具齐全|South Boston高级公寓|studio|租金2565一文读懂币圈“惊天一战”来龙去脉一文读懂字符编码IPO动态 | 一文读懂IPO各板块审核流程一文读懂JCR分区与中科院分区一文读懂个人养老金:关于税收优惠、额度上限、账户开通...一文读懂数据加密!
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。