断点续传本质是记录并恢复文件偏移量,需用std::streamoff管理偏移、二进制模式打开文件、seekg()/seekp()跳转、clear()清理流状态、原子化持久化断点信息。

断点续传本质是“记录并恢复文件偏移量”
断点续传在 C++ 里不是某个函数直接支持的功能,而是靠自己管理已写入的字节数(即偏移量),再用 std::ifstream 或 std::ofstream 的 seekg()/seekp() 跳到对应位置继续读写。关键不在“怎么传”,而在“怎么记”和“怎么跳”。
常见错误现象:seekg() 后读不到数据、文件末尾多出乱码、续传后校验失败——八成是偏移量没对齐或流状态没清。
- 必须用二进制模式打开:
std::ios::binary,否则 Windows 下换行符会被转换,偏移错乱 - 记录偏移量不能只存整数,得用
std::streamoff类型(而非int或size_t),它能跨平台表示大文件偏移 - 每次
seekg()或seekp()后,检查fail():比如if (file.fail()) { /* 处理错误 */ } - 续传前先
clear()流状态,否则上次失败会卡住后续所有操作
用 seekp() 从指定位置继续写入
服务端或客户端保存了上次写到第 N 字节,下次就得让 std::ofstream 从那里开始追加,而不是覆盖开头。注意:C++ 没有“append from offset”这种模式,std::ios::app 只能追加到末尾,必须用 seekp(N) + std::ios::out。
使用场景:下载中断后重新连接,本地文件已存在且部分完成,需接着写。
立即学习“C++免费学习笔记(深入)”;
- 打开文件时用
std::ios::out | std::ios::binary,不要加std::ios::trunc - 调用
file.seekp(N)后,立刻file.clear(),再检查file.good() - 如果
N大于当前文件长度,写入会在中间留空(表现为 \0 字节),这是合法行为,但需确保接收方能处理稀疏区域 - 写完记得
file.flush(),尤其在非阻塞传输中,避免缓冲区未落盘就认为写成功
读取时用 seekg() 跳过已接收部分
上传端要从第 N 字节开始读原文件,避免重复发送。这里容易忽略的是:读取偏移必须和写入偏移单位一致(都是字节),且不能跨块对齐——除非协议明确要求分块哈希,否则按字节续最稳妥。
性能影响:小文件无所谓;大文件频繁 seekg() 不影响吞吐,但会增加系统调用开销;若配合内存映射(mmap)可避免,但跨平台性下降。
- 用
std::ifstream file(path, std::ios::binary)打开,别用std::ios::ate,它会把指针直接移到末尾 -
file.seekg(N)后,用file.read(buf, size),别依赖gcount()前先检查file.good() - 读到 EOF 时
gcount()可能小于请求长度,需主动判断file.eof()和file.fail()区分原因 - Windows 下若文件被其他进程以独占方式打开,
seekg()会失败,错误码可能是ERROR_SHARING_VIOLATION
偏移量持久化与校验不能只靠文件名
断点信息(如已传字节数、MD5 分段值)如果只存在内存里,崩溃就全丢;如果硬编码进文件名(比如 file.part.123456789),遇到重命名或清理脚本就失效。必须单独存,且带校验。
容易踩的坑:多个线程/进程同时更新同一断点文件,导致偏移量写乱;或者用文本格式存数字但没处理换行符,读出来多一个 \r。
- 推荐用小 JSON 或纯二进制文件存:
struct { std::streamoff offset; uint64_t timestamp; char md5[16]; } - 写断点文件时,先写到临时路径(如
info.tmp),fsync()后再rename()覆盖,保证原子性 - 每次读断点前,用
stat()检查文件大小是否匹配预期结构体长度,防损坏 - 别把断点信息和下载文件放同一目录又用相似名,同步工具(如 rsync、OneDrive)可能误同步未完成状态
偏移量对齐、流状态清理、断点存储原子性——这三点漏掉任一,续传就会变成“看似在续,实则重头来”。特别是多线程环境下,seekp() 和写入之间没有锁保护的话,两个线程一前一后跳位置,数据就彻底错位了。










