You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

POSIX/Linux下可读与可写文件描述符高效连接方法

零拷贝数据传输方案

在UNIX/Linux系统下,确实存在无需经过用户空间的零拷贝数据传输方法,完全满足你的需求,以下是几种关键实现方式:

1. sendfile() 系统调用

这是最常用的零拷贝方案,直接在内核空间完成数据从源文件描述符到目标描述符的传输,完全跳过用户态缓冲区。

适用场景

  • 源是常规文件(或支持mmap的对象),目标是socket(比如网络传输场景);Linux 2.6.33及以上版本也支持源和目标都是常规文件。

代码示例

#include <sys/sendfile.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

ssize_t copy_with_sendfile(int src_fd, int dst_fd) {
    struct stat stat_buf;
    fstat(src_fd, &stat_buf);
    off_t offset = 0;
    ssize_t total_bytes = 0;
    ssize_t bytes_sent;

    while ((bytes_sent = sendfile(dst_fd, src_fd, &offset, stat_buf.st_size - offset)) > 0) {
        total_bytes += bytes_sent;
    }

    return total_bytes;
}

sendfile()会自动处理循环传输直到EOF,无需手动管理缓冲区。

2. splice() 系统调用

这是更通用的零拷贝方案,支持任意两个管道类描述符之间的数据传输(包括socket、管道、常规文件等,只要它们支持seek或属于管道类型)。

优势

相比sendfile(),splice()不要求源必须是常规文件,能处理更多场景(比如socket到socket、管道到文件等)。

代码示例

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

ssize_t copy_with_splice(int src_fd, int dst_fd) {
    ssize_t total_bytes = 0;
    ssize_t bytes_moved;
    const size_t chunk_size = 1024 * 1024; // 可根据系统性能调整

    while (1) {
        bytes_moved = splice(src_fd, NULL, dst_fd, NULL, chunk_size, SPLICE_F_MOVE | SPLICE_F_NONBLOCK);
        if (bytes_moved > 0) {
            total_bytes += bytes_moved;
        } else if (bytes_moved == 0) {
            break; // 遇到EOF
        } else {
            if (errno == EAGAIN || errno == EINTR) {
                continue; // 非阻塞重试或中断后继续
            } else {
                return -1; // 其他错误
            }
        }
    }

    return total_bytes;
}

SPLICE_F_MOVE提示内核直接移动数据页而非拷贝,SPLICE_F_NONBLOCK避免阻塞(可根据场景选择是否启用)。

3. tee() 系统调用(多目标场景)

如果需要将一份数据同时传输到多个目标描述符,tee()可以在两个管道描述符之间复制数据,同样不经过用户空间。它通常和splice()配合使用,先通过tee()复制数据到多个管道,再用splice()将管道数据分发到目标描述符。

关于shell管道的原理

UNIX shell的管道本质上利用了内核管道机制,当执行类似cat file | grep xxx的命令时,内核会直接在两个进程的文件描述符之间传递数据,无需用户态拷贝,底层逻辑和上述系统调用同源。

总结

  • 文件到socket的传输,优先用sendfile(),实现最简单;
  • 通用描述符组合(如socket到socket、管道到文件),用splice();
  • 这些方法都能完全避免用户空间的缓冲区拷贝,性能远高于手动read/write循环。

内容的提问来源于stack exchange,提问作者Logan R. Kearsley

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 00:31:12