断点续传必须本地计算并验证分片md5,因网络篡改、磁盘错误或客户端重启可能导致数据不一致;md5须写入后从磁盘重读计算,且需原子化落盘校验结果。

为什么断点续传必须自己算分片 MD5,不能只依赖服务器返回?
因为网络传输可能中途篡改、磁盘写入可能出错、客户端重启后恢复的文件片段未必和上次上传的一致——服务器给的 MD5 是它“认为”的,而你本地必须验证“实际写入磁盘的是不是这个”。fseek 和 fread 读取任意偏移处的分片时,若没校验,后续续传就等于在错误数据上叠加新数据。
- 常见错误现象:
HTTP 206 Partial Content响应成功,但最终合并后文件损坏,用md5sum对比全量文件发现不一致 - 关键点:MD5 必须在每次写入分片后立即计算,且必须从磁盘重新读取(不能用内存 buffer 缓存值),否则绕过文件系统缓存或 page cache 的异常无法捕获
- 性能影响:小分片(如 1MB)下 MD5 计算开销可忽略;但分片 >8MB 时建议用
std::thread异步计算,避免阻塞 I/O 线程
用 fopen + fseek 安全读取指定偏移分片并计算 MD5
别用 std::ifstream 默认构造,它不保证二进制模式且 seek 可能失败;必须显式用 "rb" 模式打开,并检查 fseek 返回值。
- 正确姿势:
FILE* fp = fopen("part.bin", "rb"); if (!fp) return false; if (fseek(fp, offset, SEEK_SET) != 0) { fclose(fp); return false; } - 读取时用
fread(buf, 1, chunk_size, fp),不要用fread(buf, chunk_size, 1, fp)—— 后者在读不满时返回 0,容易误判为 EOF - 注意:Windows 下若文件是 CRLF 换行创建的,但以
"rb"打开则完全无影响;用"r"才会触发自动换行转换,破坏二进制一致性
openssl/evp.h 计算 MD5 时最容易漏掉的三件事
很多代码直接抄 OpenSSL 示例,但断点续传场景下有三个硬性要求:必须重置上下文、必须处理最后一块不足 64 字节的情况、必须用 EVP_DigestFinal_ex 而非 EVP_DigestFinal。
- 漏
EVP_MD_CTX_reset(ctx)或EVP_MD_CTX_init(ctx):多分片连续计算时,残留状态导致 MD5 错误 - 调用
EVP_DigestUpdate后未确保所有数据已送入:若分片长度不是 64 字节整数倍,OpenSSL 内部缓冲区里还有未处理数据,必须再调一次EVP_DigestFinal_ex - 用
EVP_DigestFinal会清空 ctx,导致无法复用;而断点续传中常需反复计算不同 offset 的分片,复用 ctx 能省掉重复初始化开销
本地 MD5 缓存文件怎么设计才不怕崩溃丢校验结果?
不能把所有分片 MD5 存一个 JSON 里一次性写入——进程崩溃时整个文件可能损坏。必须每个分片校验完成后,立刻原子化落盘其 MD5。
- 推荐方案:每个分片对应一个
.md5后缀文件,如file.part_001.md5,内容仅为 32 字符小写 hex(如9e107d9d372bb6826bd81d3542a419d6) - 写入流程必须是:先写临时文件
file.part_001.md5.tmp→fflush+fsync→rename替换原文件;Linux/macOS 上rename是原子的,Windows 需用MoveFileEx带MOVEFILE_REPLACE_EXISTING - 校验前先检查
.md5文件 mtime 是否晚于对应分片文件 mtime —— 防止上次写了一半就崩溃,留下陈旧校验值
实际中最容易被忽略的是:分片写入磁盘后,没有强制刷盘就去算 MD5。尤其在 Linux 上,默认 ext4 使用 write-back cache,fwrite 返回成功 ≠ 数据已落盘。务必在 fclose 前加 fflush(fp),必要时补 fsync(fileno(fp))。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











