sendfile能省用户态拷贝是因为内核直接在两个fd间搬运数据,绕过cpu拷贝和用户内存;其使用需满足源fd为普通文件、目标fd为socket等条件,且不支持数据修改。

sendfile 系统调用为什么能省掉用户态拷贝
因为 sendfile 让内核直接在两个文件描述符之间搬运数据,不经过用户空间内存。传统 read+write 要把数据从磁盘读到用户缓冲区(一次 DMA 拷贝 + 一次 CPU 拷贝),再从用户缓冲区写到 socket(又是一次 CPU 拷贝 + 一次 DMA 拷贝)。而 sendfile 最多只触发两次 DMA 拷贝(磁盘 → 内核页缓存 → 网卡),中间完全绕过 CPU 拷贝和用户内存分配。
但前提是:源 fd 必须是普通文件(支持 mmap),目标 fd 必须是 socket 或另一个支持 splice 的 fd;且不能对数据做任何修改(比如加 header、加密、压缩)——它不是万能加速器,只是零拷贝传输的特化路径。
Linux 下 C++ 调用 sendfile 的正确姿势
别直接裸写系统调用,用封装好的 sendfile 函数(来自 <sys></sys>),注意参数顺序和返回值语义:
-
sendfile的第 2 个参数是源 fd(必须是 seekable file,如普通文件;不能是 pipe、socket、/proc 文件) - 第 3 个参数是目标 fd(通常是 socket,也支持其他支持 write 的 fd)
- 第 4 个参数是 offset 指针:传入非空指针时,它会自动更新偏移;传
nullptr表示从当前文件位置开始,且不更新源文件 offset(适合多线程共享 fd 场景) - 返回值是实际传输字节数,可能小于请求长度(比如 socket 缓冲区满、被信号中断),必须检查并重试
示例关键片段:
#include <sys>
ssize_t n = sendfile(sockfd, file_fd, &offset, count);
if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 非阻塞 socket 写满,需等待可写事件
} else if (errno == EINTR) {
// 信号中断,可重试
}
} else if (n
<h3>常见失败原因和对应处理</h3>
<p><code>sendfile</code> 失败不等于 bug,多数是环境或用法不匹配:</p>
<ul>
<li>源 fd 是 <code>/dev/zero</code>、<code>pipe</code>、<code>socket</code>:直接返回 <code>EINVAL</code> —— 它只认普通文件(<code>S_ISREG(st.st_mode)</code> 为真)</li>
<li>目标 fd 是普通文件(非 socket):某些旧内核不支持,或返回 <code>EINVAL</code>;新内核支持,但无性能优势</li>
<li>跨文件系统复制(比如 ext4 → xfs):部分内核版本会退化成 <code>read/write</code> 模拟,仍能工作但失去零拷贝意义</li>
<li>使用了 <code>O_DIRECT</code> 打开源文件:禁止 <code>sendfile</code>,返回 <code>EINVAL</code> —— 因为它依赖 page cache</li>
<li>目标 socket 关闭或出错:返回 <code>EPIPE</code> 或 <code>ECONNRESET</code>,需及时清理连接</li>
</ul>
<h3>sendfile 在高并发服务中的实际取舍</h3>
<p>它快,但不是“开箱即用”的银弹:</p>
<ul>
<li>无法与 TLS/SSL 共存:OpenSSL 的 <code>SSL_write</code> 不接受内核零拷贝数据,必须先解密/加密,所以 HTTPS 服务里基本用不上</li>
<li>难以做流控:你不能在传输中途插入逻辑(比如限速、日志、断点续传校验),所有控制都得放在调用前或后</li>
<li>大文件分片时要注意:单次 <code>sendfile</code> 最大长度受 <code>SSIZE_MAX</code> 和 socket 缓冲区限制,通常建议单次不超过 1MB,避免阻塞过久</li>
<li>配合 <code>epoll</code> 使用时,要监听目标 socket 的 <code>EPOLLOUT</code> 事件,而不是一上来就狂发 —— 否则大量 <code>EAGAIN</code>
</li>
</ul>
<p>真正影响性能的,往往不是单次 <code>sendfile</code> 调用本身,而是你怎么组织它的调用时机、如何应对部分成功、以及是否让整个 I/O 路径适配这种“无缓冲”模型。</p></sys>C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











