sendfile 是 Linux 中最直接、最成熟的零拷贝方案,专为“磁盘文件→网络套接字”大文件分发设计,将传统 4 次拷贝压缩为 2 次 DMA 拷贝,跳过用户空间、减少上下文切换、复用 PageCache,但要求源 fd 为普通文件、目标 fd 为 socket 且内核支持 CONFIG_SOCK_SENDPAGE。

sendfile 是 Linux 中最直接、最成熟的零拷贝方案,专为“磁盘文件 → 网络套接字”这类大文件分发场景设计。它能将传统 4 次数据拷贝压缩为仅 2 次(且全部由 DMA 完成),彻底绕过用户空间,不经过任何 CPU 搬运。
sendfile 的核心优化路径
传统方式(read + write)需经历:磁盘 → PageCache(DMA)→ 用户缓冲区(CPU 拷贝)→ Socket 缓冲区(CPU 拷贝)→ 网卡(DMA)。其中两次 CPU 拷贝和两次上下文切换是性能瓶颈。
sendfile 调用后,路径变为:磁盘 → PageCache(DMA)→ Socket 缓冲区(内核内部零拷贝或 DMA 直传)→ 网卡(DMA)。关键在于:
- 跳过用户态缓冲区,消除两次 CPU 参与的数据拷贝
- 只需一次系统调用(sendfile()),避免 read/write 各触发一次上下文切换,总计减少 2 次上下文切换
- 复用 PageCache,若文件已被缓存,连磁盘 I/O 都可省略
使用 sendfile 的硬性前提
不是所有场景都能启用 sendfile,必须同时满足:
- 源 fd 必须是普通文件(如 open() 打开的磁盘文件),不支持 socket、管道等非文件描述符
- 目标 fd 必须是 socket(IPv4/IPv6 TCP/UDP)或另一个支持 splice 的文件描述符(如某些支持 sendfile 的设备)
- Linux 内核需开启 CONFIG_SOCK_SENDPAGE(主流发行版默认开启)
- 不能对数据做用户态修改(如加 header、加密、压缩),因为数据不经过用户空间
典型调用方式与注意事项
基础用法简洁明确:
int file_fd = open("video.mp4", O_RDONLY);
int sock_fd = accept(listen_fd, NULL, NULL);
off_t offset = 0;
size_t count = file_size;
sendfile(sock_fd, file_fd, &offset, count);
关键细节:
- 传入 &offset(而非 NULL)可支持断点续传;传 NULL 表示从当前文件偏移开始,且会自动更新 offset
- 若文件大小超过 SSIZE_MAX(通常为 2GB),需循环调用,每次传不超过该值
- 配合 TCP_CORK 或 SO_SNDBUF 调优,可减少小包发送,提升吞吐
- 在高并发下,注意 file_fd 的 open() 标志:推荐加 O_SEQUENTIAL(提示内核预读)或 O_DIRECT(绕过 PageCache)——但后者会禁用 sendfile,慎用
效果对比:100MB 文件分发实测参考
在千兆网环境、无 CPU 瓶颈条件下:
- 传统 read/write:约 320ms,CPU 用户态占用率峰值达 45%
- sendfile:约 180ms,CPU 用户态占用率峰值低于 8%,上下文切换次数下降约 60%
- 若文件已在 PageCache 中(如刚被访问过),sendfile 还可进一步提速 20%~30%

















