断点续传前必须校验本地碎片文件完整性。需依据Content-Range头定位本次续传字节范围,仅对该区间做SHA256哈希比对,而非全文件校验,以避免I/O浪费。

断点续传前必须校验本地碎片文件是否完整
断点下载中,本地已有的 .part 文件很可能因中断、磁盘错误或手动修改而损坏。直接基于它继续写入,会导致最终文件内容错乱——校验不是可选项,是续传的前提。
核心做法:用服务器返回的 Content-Range 头确认本次请求应覆盖的字节范围,再读取本地对应区间,比对 SHA256(或 MD5,但推荐 SHA256)哈希值。不校验整个文件,只校验「本次将要续传的那块」。
- 只校验当前请求段(例如
bytes 1024-2047/10000),而非整个.part文件,避免 I/O 浪费 - 若本地文件长度
- 若本地文件长度 ≥ 结束偏移 + 1,但该段哈希不匹配,说明数据已损坏,同样应回退为全量重下该段
用 fseek + fread 安全读取局部字节并计算 SHA256
C++ 标准库无内置 SHA256,但 OpenSSL 或 libsodium 可用;更轻量的选择是嵌入一个 BSD/MIT 协议的纯头文件实现(如 sha2.h)。关键在读取时避免越界和内存泄漏。
示例逻辑(伪代码级):
FILE* fp = fopen("file.zip.part", "rb");
if (!fp) return false;
if (fseek(fp, start_offset, SEEK_SET) != 0) { /* 失败 */ }
uint8_t buf[8192];
size_t to_read = end_offset - start_offset + 1;
size_t n = fread(buf, 1, to_read, fp);
if (n != to_read) { /* 实际读取不足 → 损坏或截断 */ }
SHA256_CTX ctx;
SHA256_Init(&ctx);
SHA256_Update(&ctx, buf, n);
uint8_t hash[SHA256_DIGEST_LENGTH];
SHA256_Final(hash, &ctx);
-
fseek后必须检查返回值,Windows 下对某些文件模式(如"r")可能失败,建议统一用"rb" - 不要用
std::ifstream的seekg配合read—— 它在二进制大文件中易受 locale 影响,且异常处理更重 - 缓冲区大小选 4K–64K,太小增加系统调用次数,太大无益(现代 OS 有预读)
服务端没提供分块哈希?那就只能依赖长度 + 最后修改时间 + 强制重试机制
绝大多数 HTTP 服务器(包括 Nginx、Apache)默认不返回每段的哈希。此时无法做内容级校验,只能退到元数据层面防御。
可行组合策略:
- 比对本地文件长度是否 ≥
Content-Range中声明的end_offset - 用
stat()检查本地文件st_mtime是否与上次成功下载时间接近(需你自己记录) - 若任一条件不满足,跳过续传,改用
Range: bytes=0-从头下载整文件(或至少该段) - 在重试逻辑里加入「连续 2 次校验失败则放弃断点,走全量」的兜底规则
多线程并发续传时,fseek + fread 不是线程安全的
多个线程共用一个 FILE* 句柄操作同一文件,fseek 和 fread 的内部文件位置指针会互相覆盖,导致读错区域。
正确做法:
- 每个线程独立
fopen("file.zip.part", "rb"),各自维护文件偏移 - 避免共享
FILE*,哪怕只读也不行(POSIX / C standard 明确未保证线程安全) - 如果用
open()+pread()(Linux/macOS),天然线程安全,且避免了fseek的状态干扰
真正难的不是算哈希,而是把「哪一段该读」「读出来有没有被别的线程动过」「读错了怎么无声降级」这三件事,在没有服务端配合的情况下,靠本地信息闭环判断清楚。很多人卡在这一步,不是不会写 SHA256,是没想明白校验的触发边界在哪。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











