
为什么 sendfile() 不能直接用于断点续传
因为 sendfile() 从文件描述符直接送数据到 socket,不经过用户态缓冲,你没法在中间插入偏移控制或校验逻辑。断点续传必须能从任意 offset 开始读、带长度限制、可中断重试——这要求对文件 I/O 有完全掌控权。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
pread()替代read():避免修改文件游标,线程安全,显式传入offset和count - 每次传输前校验
stat()获取文件大小,防止客户端请求的Range超出当前文件长度(尤其文件正在被写入) - 别依赖
lseek() + read()组合:多线程下易因游标竞争导致错位,pread()是唯一可靠选择
HTTP Range 请求怎么解析和响应才不崩
客户端发来的 Range: bytes=1024-2047 看似简单,但实际要处理边界模糊、格式错误、重叠请求、负偏移等二十多种边缘 case。标准 RFC 7233 明确要求服务器对非法 Range 返回 416 Range Not Satisfiable,否则客户端会卡死或乱序拼接。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
std::regex解析 Range 头开销大且易漏匹配,直接手写strtol()提取数字更稳(注意errno清零和溢出检查) - 支持单段 Range 即可,不用实现多段(
bytes=0-50,100-150),绝大多数下载器只用单段,且多段响应需设Content-Type: multipart/byteranges,复杂度陡增 - 响应头必须包含
Content-Range(如bytes 1024-2047/1048576)和Accept-Ranges: bytes,少一个,curl/wget 就当普通响应处理,丢弃断点信息
高并发下如何避免 pread() 成为性能瓶颈
pread() 是系统调用,频繁小块读(比如每次 8KB)在万级连接时会把内核调度压垮。这不是代码写得不够“高性能”,而是 I/O 模式错了。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
mmap()+writev()替代反复pread():对中小文件(mmap() 一次映射,后续内存访问无系统调用;再配合writev()批量投递多个iovec,减少 socket 写入次数 - 对超大文件或内存受限场景,改用预分配的环形缓冲区 + 异步预读:用
posix_fadvise(POSIX_FADV_WILLNEED)提示内核预加载下一段,同时用独立线程提前pread()到缓冲区,主 IO 线程只做 memcpy + send - 绝对不要在事件循环里同步
pread():epoll/kqueue 本身不阻塞,但pread()会,一卡全卡。必须用线程池或 io_uring(Linux 5.1+)卸载
断点续传状态到底该存在哪
很多人第一反应是存数据库或 Redis,但这是典型工程误判——断点续传的“状态”本质是“当前已收多少字节”,它必须和连接生命周期强绑定,且要抗进程重启。存外部服务反而引入一致性难题和延迟毛刺。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 状态只存内存:每个活跃连接对应一个
struct TransferState { off_t offset; size_t total_size; std::string file_path; },用 connection id 或 socket fd 作 key 存在std::unordered_map中 - 进程意外退出时,靠客户端重试机制兜底:所有标准 HTTP 下载器(aria2、curl -C)都会在连接断开后自动查
HEAD或GET响应头里的Content-Length和已传长度,重新计算 Range - 如果真需要持久化(如上传断点),写轻量级 mmap 文件,每条记录固定 32 字节,用
msync()同步,别碰 fsync —— 频繁刷盘比网络慢两个数量级
真正难的不是怎么传,是怎么让每次 pread() 不卡住 event loop、怎么让 Range 解析不被畸形请求拖垮、以及怎么让内存里的 offset 在崩溃后还能被客户端自然续上。这些细节没对齐,再多的“高性能”设计都只是纸面参数。











