sendfile 仅支持普通文件到 socket 的零拷贝传输,不适用于两文件间或用户态内存;需正确处理 offset 参数与错误码,多线程下推荐传地址;splice 和 copy_file_range 更通用。

sendfile 在 Linux 上只能用于文件到 socket 的零拷贝传输
sendfile 不是通用零拷贝接口,它只支持从一个文件描述符(必须是普通文件,且需支持 mmap)直接复制数据到另一个支持写入的 socket fd。不能用在两个普通文件之间,也不能用于用户态内存(比如 char* 缓冲区)——想发内存数据,得用 write 或 send,它们不零拷贝。
常见误用:把 sendfile 当成“任意数据高效发送”工具,结果调用失败返回 -1,errno 是 EINVAL(源 fd 不是正规文件)或 EBADF(目标 fd 不是 socket)。
- 源 fd 必须由
open()打开,且不能是管道、socket、/dev/zero 等伪文件 - 目标 fd 必须是已连接的 TCP socket(UDP 不支持)或支持
splice的 UNIX domain socket - Linux 2.6.33+ 支持将
sendfile用于 splice-style 零拷贝到 pipe,但标准用法仍限于 file→socket
正确调用 sendfile 的关键参数和错误处理
函数原型是:ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count)。最容易出错的是 offset 参数:传 NULL 表示从当前文件偏移开始,且会自动更新;传非空指针时,offset 必须指向合法地址,且调用后不会自动更新,需手动维护。
典型安全写法:
off_t offset = 0;
ssize_t sent = sendfile(sockfd, filefd, &offset, filesize);
if (sent == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 非阻塞 socket 写满,需重试
} else if (errno == EINTR) {
// 被信号中断,可重试
} else {
// 其他错误:如 ENOTSOCK、EINVAL,应记录并 fallback 到 read/write
}
}
- 务必检查返回值,
sendfile可能只写出部分字节(尤其非阻塞 socket) - 不要假设
count一次全发完;循环调用时,offset必须持续更新 - 若
in_fd是普通文件且offset为NULL,内核会从当前lseek位置读,但该位置可能被其他线程修改,多线程下更推荐传&offset
sendfile 和 splice / copy_file_range 的实际区别
很多人搜 “C++ 零拷贝”,看到 sendfile 就止步了,其实它能力有限。真正灵活的替代方案是 splice(Linux 2.6.17+)和 copy_file_range(Linux 4.5+):
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
-
splice支持 pipe 作为中转,可组合实现 socket←pipe←file 或 userbuf←pipe(配合vmsplice),比sendfile更通用 -
copy_file_range支持任意两个文件(包括 block device),且跨文件系统也尽量零拷贝(底层支持时) -
sendfile在 ext4/xfs 上性能通常略优于splice(少一次上下文切换),但功能边界更窄
如果你需要发内存数据、或要兼容非 Linux(如 macOS/BSD),sendfile 直接不可用——macOS 有 sendfile 但语义不同(不支持 offset 指针),Windows 完全没有对应 API。
在 C++ 中封装 sendfile 的注意事项
C++ 没有标准封装,用时就是裸调 POSIX。但要注意 RAII 和异常安全:
- 别在
std::fstream对象上直接取 fd:它可能缓冲、不保证底层 fd 有效;用open()+intfd 更可靠 - 如果封装成类,析构时别自动
close()源 fd——你可能复用该文件多次,关闭权应在业务层 - 避免在
std::vector<char></char>或std::string上幻想零拷贝:它们的数据仍在用户空间,sendfile根本不碰它们
一个最小可用片段:
int filefd = open("data.bin", O_RDONLY);
if (filefd == -1) { /* handle */ }
struct stat st;
fstat(filefd, &st);
off_t offset = 0;
while (offset <p>真正难的不是调用,而是判断什么时候该用、什么时候该退回到 <code>read</code>+<code>send</code>——比如文件太小(</p>C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










