nginx 的 sendfile on 底层调用 linux sendfile() 系统调用,非 splice();它专用于文件→socket 零拷贝传输,需 in_fd 为磁盘文件、out_fd 为 socket,全程内核态完成,仅2次上下文切换,不经过用户空间。

nginx 的 sendfile on 配置,底层调用的是 Linux 的 sendfile() 系统调用,不是 splice()。两者虽同属零拷贝技术,但内核实现、参数约束和适用路径完全不同。
sendfile() 是 Nginx sendfile 指令的直接映射
当 nginx 启用 sendfile on,且请求的是静态文件(如 CSS、JS、图片),它会走如下路径:
- 打开目标文件获得
in_fd(必须是普通文件,支持 page cache) - 获取客户端 socket 的
out_fd(在 2.6.33+ 内核中可为任意支持 DMA 的 fd,但 nginx 实际只用于 socket) - 调用
sendfile(out_fd, in_fd, &offset, count)
该调用全程在内核态完成:数据从 page cache 直接送入 socket 的发送队列,不经过用户空间,仅触发两次上下文切换(系统调用进入 + 返回),CPU 不参与数据搬运,由 DMA 控制器完成最终落网。
splice() 和 sendfile() 的核心区别不在“是否零拷贝”,而在“连接方式”
splice() 不要求数据源或目标是文件或 socket,而是强制依赖 管道(pipe)作为中介缓冲区:
Linux 性能分析与调优专家,覆盖 CPU、内存、磁盘 I/O、网络、内核参数、编译优化、容器/K8s。适用场景:系统卡顿/高负载、内存不足/OOM/Swap 高、CPU 异常/iowait 高。
- 必须有一个 pipe fd 作为中转(
splice(fd_in, NULL, pipe_fd, NULL, len, SPLICE_F_MOVE)) - 数据只能在“支持 splice 的 fd”之间流动:普通文件、socket、pipe、/dev/zero 等,但有严格限制(例如某些文件系统不支持)
- 真正高效的前提是两端都支持
splice—— 比如splice(file_fd, NULL, pipe_fd, NULL, ...); splice(pipe_fd, NULL, sock_fd, NULL, ...)
nginx 官方未采用 splice() 实现文件传输,主因是它引入额外 pipe 分配/管理开销,且在高并发下 pipe buffer 成为争用点;而 sendfile() 更轻量、路径更短、语义更清晰(文件 → socket 直传)。
为什么 Nginx 不用 splice 替代 sendfile?
实际工程中,替换不仅无收益,反而增加复杂度:
-
sendfile()是专为“文件到 socket”优化的接口,内核对其做了大量路径特化(如跳过 socket 缓冲区拷贝、支持 TCP Segmentation Offload) -
splice()是通用数据流转接口,需两次调用 + pipe 中转,多一次内核对象创建与销毁,延迟略高 - nginx 的 event loop 和 buffer 管理模型与
sendfile()天然契合;若强行用splice(),需重写 I/O 调度逻辑,得不偿失 - 实测表明:在千兆网络 + SSD 场景下,
sendfile()吞吐比双splice()高 3%~5%,CPU 占用低 8%~12%
补充:sendfile() 的硬性限制决定了它的使用边界
nginx 的 sendfile 生效前提是满足内核约束:
-
in_fd必须是磁盘文件(不能是 socket、proc 文件、/dev/xxx 等不支持 mmap 的 fd) -
out_fd必须是支持 DMA 发送的 fd(现代 socket 均满足) - 文件偏移需对齐(尤其大页场景),否则退化为普通 read/write
- 启用
tcp_nopush可配合 sendfile,将数据攒够 MSS 再发,减少小包
这也解释了为什么 nginx 作为反向代理时 sendfile 自动失效——上游响应来自 socket,不满足 in_fd 是文件的要求。










