一、先搞懂:传统文件传输为什么慢?
先看我们平时写的普通文件传输代码(socket 发送文件):
// 伪代码:传统 read + write 传输
char buf[4096];
read(fd, buf, 4096); // 1. 内核→用户态:拷贝文件到缓冲区
write(sock, buf, 4096);// 2. 用户态→内核:拷贝缓冲区到socket
传统传输的 4 次拷贝 + 2 次上下文切换
硬盘 → 内核缓冲区(DMA拷贝)
内核缓冲区 → 用户缓冲区(CPU拷贝)
用户缓冲区 → Socket内核缓冲区(CPU拷贝)
Socket内核缓冲区 → 网卡(DMA拷贝)
问题:CPU 干了两次无用功,上下文切换频繁,大文件传输极慢。
二、sendfile 核心原理:零拷贝
sendfile 直接让内核在内核态完成文件到 socket 的传输,完全不经过用户态!
sendfile 只有 2 次拷贝 + 0 次用户态切换
硬盘 → 内核文件缓冲区(DMA)
内核文件缓冲区 → Socket缓冲区(DMA/硬件优化)
直接发给网卡
一句话总结:
sendfile 让内核直接把文件数据扔给网卡,用户程序不参与数据拷贝,实现零拷贝。
三、sendfile 函数原型
这是你写代码必须记住的标准格式:
#include <sys/sendfile.h>
ssize_t sendfile(
int out_fd, // 输出文件描述符:必须是 socket!
int in_fd, // 输入文件描述符:必须是普通文件/磁盘文件!
off_t *offset, // 文件读取起始偏移量(传 NULL 表示从当前位置读)
size_t count // 要传输的字节数
);
返回值(必须会)
成功:返回实际传输的字节数
失败:返回 -1,设置 errno
四、sendfile 硬性限制
sendfile 不是万能的,有严格限制,必须记死:
1.out_fd 必须是 socket 描述符
不能是普通文件、管道、终端
只能用于网络发送文件
2.in_fd 必须是普通磁盘文件
不能是 socket、管道、内存文件
必须支持 seek 寻址(普通文件都支持)
3.无法修改数据
内核直接转发,你不能在传输时加密、压缩、修改内容
纯转发场景才用它
4.Linux 专属
Windows、macOS 有类似函数,但不通用
跨平台程序不能只靠 sendfile
五、最简可运行代码
这是最标准的 sendfile 服务端代码:
#include <stdio.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
int main() {
// 1. 打开要发送的文件
int file_fd = open("test.txt", O_RDONLY);
struct stat st;
fstat(file_fd, &st); // 获取文件大小
// 2. 假设已经建立好 socket 连接(conn_fd 是客户端socket)
int conn_fd = accept(...); // 省略socket创建、绑定、监听
// 3. sendfile 零拷贝传输
off_t offset = 0; ssize_t ret = sendfile(conn_fd, file_fd, &offset, st.st_size);
if (ret < 0) {
perror("sendfile failed");
} else {
printf("成功传输 %zd 字节\n", ret);
}
close(file_fd);
close(conn_fd);
return 0;
}
六、必须会的核心知识点
1. 什么是零拷贝?
零拷贝 = 避免 CPU 在用户态和内核态之间拷贝数据 sendfile 是 Linux 最经典的零拷贝实现。
2. sendfile 优势
速度极快(大文件提升 30%~200%)
减少 CPU 占用
代码极简
内核级优化,稳定可靠
3. 适用场景
Web 服务器发送静态文件(html、css、js、图片)
文件服务器下载
纯转发、不修改数据的网络传输
4. 不适用场景
需要加密/压缩文件
跨平台程序
传输非磁盘文件(socket→socket 不行)
5. 与 mmap 的区别(必考题)
sendfile:简单、专用、零拷贝、不能改数据
mmap:映射文件到内存、可以修改数据、复杂
七、一句话总结(最核心)
sendfile = 内核态零拷贝 + 文件直接发socket + 不经过用户态 + 高性能 + 只能转发磁盘文件
总结
原理:内核直接转发文件到 socket,零拷贝、无用户态切换
函数:sendfile(out_socket, in_file, offset, size)
限制:输出必须是 socket,输入必须是普通文件
用途:Web服务器、文件下载、高性能网络转发
优势:比 read/write 快得多,CPU 占用低
参考链接 :0voice · GitHub