sendfile 比 read+write 快,因其在内核态直接搬运数据,省去两次内存拷贝和上下文切换;实测大文件传输吞吐提升20%–40%,但要求 in_fd 为普通文件、out_fd 为 socket,且需注意参数设置与常见 EINVAL 错误原因。

sendfile 为什么比 read + write 快
因为 sendfile 在内核态直接完成文件到 socket 的数据搬运,全程不经过用户空间——省掉两次内存拷贝(read 到用户缓冲区、write 从用户缓冲区到 socket 缓冲区),也避免了上下文切换开销。对大文件尤其明显,比如传输 1GB 文件时,实测吞吐能提升 20%–40%,取决于磁盘 I/O 和网卡带宽是否均衡。
但注意:sendfile 要求输入 fd 是普通文件(支持 mmap),输出 fd 必须是 socket 或另一个支持 splice 的文件类型;不能用于任意 fd 组合,比如 pipe → socket 就不行(得用 splice)。
Linux 下 sendfile 的基本用法和参数陷阱
sendfile 系统调用原型是:ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count)。关键点不在函数本身,而在参数怎么设:
-
out_fd必须是已连接的 socket(SOCK_STREAM),且不能是监听 socket(listen_fd) -
in_fd必须通过open(..., O_RDONLY)打开,不能是stdin或管道;O_DIRECT会破坏 page cache,反而拖慢sendfile,别加 -
offset如果为NULL,则从当前文件偏移开始读;但多数场景建议传入指针并初始化为0,否则多次调用时位置错乱 -
count不宜设太大(如 128MB),内核单次处理上限受/proc/sys/fs/pipe-max-size和 socket 缓冲区影响,通常设64*1024或128*1024更稳
遇到 “Invalid argument” 错误的常见原因
调用 sendfile 返回 -1 并设置 errno = EINVAL 是最常踩的坑,不是代码写错了,而是环境或 fd 状态不对:
立即学习“C++免费学习笔记(深入)”;
- 目标 socket 还没完成三次握手(比如
connect没返回成功就调sendfile) -
in_fd对应的文件被截断(truncate)或删除(unlink),但 fd 仍打开——此时sendfile会失败,而read可能只返回 0 - 在 ext4 上用
sendfile读加密文件(fscrypt)或某些 overlayfs 场景,内核可能不支持零拷贝路径 - 容器环境(如 Docker)中,宿主机内核版本太低(CONFIG_SENDFILE(极少见,但定制内核可能)
如何确认 sendfile 真正生效了
光看代码调用不等于零拷贝发生。可用 strace -e trace=sendfile,read,write 观察系统调用次数;更准的是用 perf record -e 'syscalls:sys_enter_sendfile' -p $PID 看内核是否进入该路径。另外注意:
- 如果
in_fd是 tmpfs 或 ramfs 上的文件,sendfile会退化为read+write(因为没有磁盘 block mapping) - 启用 TCP Segmentation Offload(TSO)或 Generic Receive Offload(GRO)时,
sendfile吞吐表现更好,但需网卡驱动支持;可通过ethtool -k eth0查看 - 不要在循环里无脑调
sendfile直到返回 0:当 socket 发送缓冲区满时,它会阻塞或返回EAGAIN(非阻塞模式下),必须配合epoll或检查返回值做重试
真正难的不是调用接口,而是确保整个数据通路——文件系统、VFS 层、TCP 栈、网卡驱动——都支持并启用了零拷贝链路。少一个环节,就退回传统路径。



















