断点续传必须自行持久化管理偏移量,不可依赖fseek/ftell;windows需用_fseeki64/_ftelli64防32位截断;offset文件须原子写入、严格ascii十进制格式、与目标文件同目录且受互斥锁保护。

断点续传必须自己管理偏移量,fseek 和 ftell 不可靠
大文件传输中断后重连,不能依赖系统自动“记住写到哪”,C++ 标准库的 FILE* 在关闭/重打开时不会保留上次写入位置。哪怕你用 fseek(fp, 0, SEEK_END) 获取当前长度,也只反映磁盘上已有字节,不等于“上次成功写入的逻辑偏移”——尤其当写入中途崩溃、缓存未刷盘、或文件被其他进程截断时,ftell 返回值可能错乱。
真正能信任的只有你主动记录并持久化的偏移量。常见错误是:把 ftell 当作断点依据,结果续传时重复写或跳过一段。
- 每次成功写入一批数据(比如 64KB)后,立刻更新本地记录文件(如
resume.offset),内容仅为纯数字,例如123456789 - 写入前先调用
fflush(fp),再写 offset 文件;两个操作都成功才算本次写入落地 - offset 文件必须和目标文件同目录、同名加后缀,避免路径不一致导致找不到
- 不要用
std::ofstream默认构造打开 offset 文件——它会清空内容;要用std::ios::out | std::ios::trunc显式控制
用 mmap 写大文件?别碰,msync + 偏移记录更难搞
有人想用内存映射加速大文件写入,但断点续传场景下 mmap 反而增加复杂度:msync 的同步粒度难控,MAP_SHARED 下其他进程改文件会导致映射失效,且偏移量无法直接从映射地址推算(要考虑页面对齐、prot 权限等)。实际测试中,普通 fwrite 配合 setvbuf 调大缓冲区(如 1MB),性能差距微乎其微,还省去信号处理和段错误兜底。
- 坚持用
fopen(..., "ab")追加模式打开目标文件,避免"r+b"引发的定位混乱 - 写入前用
fseek(fp, offset, SEEK_SET)定位,但仅用于校验:调用ftell(fp)确认是否真到了该位置,不等就 abort - 如果传输协议本身带分块 hash(如 HTTP Range + ETag),优先比对已写块的哈希,而非只信 offset 数字
多线程写同一个文件?必须加锁,且 offset 文件也要互斥访问
并发写入时,即使每个线程写不同区间,write 系统调用在内核层仍可能因缓存合并导致偏移错位。更危险的是多个线程同时更新 resume.offset:A 线程刚读出 100MB,B 线程写完 50MB 后覆盖成 150MB,A 再写 20MB 后覆盖成 120MB——最终 offset 文件变成 120MB,但实际磁盘有 170MB 数据,下次续传直接损坏。
- 所有对目标文件的
fwrite和对 offset 文件的写入,必须包裹在同一把互斥锁里(如std::mutex) - offset 文件建议用
rename原子替换:先写临时文件resume.offset.tmp,再rename覆盖原文件,避免写一半崩溃留下脏数据 - 不要用
flock锁文件——Windows 不支持,跨平台项目直接废掉
Windows 上 _fseeki64 和 _ftelli64 是刚需
32 位偏移函数在 Windows 下遇到 >2GB 文件就会回绕,fseek(fp, 10000000000, SEEK_SET) 可能实际跳到 1410065408 字节。Linux 的 off_t 默认是 64 位,但 Windows 的 long 是 32 位,fseek 参数类型不匹配。不换函数,续传超过 2GB 必然错位。
- Windows 平台必须用
_fseeki64/_ftelli64,参数类型为__int64,头文件是io.h - Linux 保持用
fseek/ftell即可,但注意编译时定义_FILE_OFFSET_BITS=64(glibc 默认已开) - 跨平台代码可用宏封装:
#ifdef _WIN32分支调用_fseeki64,否则走标准函数
最易被忽略的一点:offset 文件的内容必须是十进制 ASCII 数字,不能带空格、换行或 BOM。哪怕多一个 \r,std::stoll 解析就会失败,程序可能静默回退到 0 偏移重传。写 offset 文件时用 fprintf(fp, "%lld", offset),读时用 fgets + std::stoll,别图省事用 operator>> —— 它对格式异常不敏感,容易放过脏数据。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











