go分块下载总返回200而非206,因服务端未返回accept-ranges: bytes,导致客户端不发range请求;需确保nginx开启accept_ranges on、静态文件含content-length,并显式设range头、校验206状态码、用writeat替代seek+write、通过带缓冲channel限流、正确计算边界并截断文件。

Go 用 http.Range 请求分块时,为什么总返回 200 而不是 206?
服务端没开 Accept-Ranges: bytes 支持,http.Client 就不会发带 Range 头的请求,更不会校验响应状态码。别怪 Go,先 curl 看响应头:curl -I https://example.com/file.zip,确认有 Accept-Ranges: bytes 和 Content-Length。
常见错误现象:协程各自发起完整下载(重复写同一文件),或写入偏移错乱。
- 确保服务端支持字节范围请求,Nginx 需开启
accept_ranges on;,静态文件需有明确Content-Length - Go 中必须显式设置
req.Header.Set("Range", "bytes=0-1023"),不能只靠http.NewRequest自动推断 - 收到响应后立刻检查
resp.StatusCode == http.StatusPartialContent,否则直接 panic 或跳过该块
io.Copy 写入文件偏移位置失败,多个协程写花文件
Go 的 os.File 不是线程安全的写入句柄,io.Copy 底层调用 Write 会覆盖当前文件指针位置——而并发下指针位置不可控。
正确做法是每个协程打开独立的 *os.File,用 os.OpenFile 带 os.O_WRONLY | os.O_CREATE,再用 file.WriteAt(buf, offset) 直接写到指定位置。
立即学习“go语言免费学习笔记(深入)”;
ERMEB云盘发卡系统官方正版系统,发卡系统操作简单、方便、易懂。 系统微信小程序前端采用nuiapp后端采用think PHP6PC前端采用vue开发 使用场景:文件上传储存,适合个人/个体/中小企业使用。本系统配合微信小程序端进行使用,文件下载以及发卡商品卡密领取都需要进入小程序内获取下载码以及卡密领取,小程序内可设置积分充值以及任务获取积分,支持微信激励广告领取文件下载码以及卡密商品,可实现
- 不要复用同一个
*os.File实例给多个 goroutine - 避免用
file.Seek+file.Write组合,Seek 是竞态源 -
WriteAt是原子操作(对大多数现代文件系统而言),且不依赖当前指针,适合分块写入 - 注意:Windows 上
WriteAt对某些网络文件系统可能退化为锁文件,Linux ext4/xfs 下表现稳定
如何控制并发数又不让 goroutine 泛滥?用 semaphore 还是 channel?
用带缓冲的 channel 最直接:它天然限流、无额外依赖、语义清晰。第三方 semaphore 包反而容易在 panic 恢复时漏释放信号量。
典型错误是把 channel 当作任务队列塞满再启 goroutine,结果内存爆掉——分块任务本身轻量,但每个都开 goroutine,数量应严格等于并发度,不是等于块数。
- 先算好总块数
n,设并发数concurrency := runtime.NumCPU()(通常 4–8) - 建
sem := make(chan struct{}, concurrency),每启动一个 goroutine 前sem ,结束后 <code> - 不要用
for i := range chunks { go downloadChunk(...) }这种放任式启动 - 如果下载中间要重试,记得重试也占一个 slot,避免卡死
下载完成校验失败:为什么 sha256.Sum256 算出来和服务器不一致?
分块下载后直接拼文件,没考虑块之间是否严格对齐、最后一块是否截断——最常见原因是用 (fileSize + chunkSize - 1) / chunkSize 算块数,但没让最后一块的 end 取 min(end, fileSize-1),导致写入越界或留空洞。
校验前务必用 os.Stat 确认文件大小等于原始 Content-Length,否则 sha256 必然错。
- 每块的
start和end要用int64计算,避免大文件(>2GB)整型溢出 - 最后一块的
end必须是fileSize - 1,不是start + chunkSize - 1 - 写完所有块后,用
os.Truncate强制截断到准确长度,清除可能残留的旧数据 - 校验建议在所有 goroutine return 后统一做,别边下边算 hash(会竞争读同一文件)
分块下载真正的坑不在并发,而在边界计算和文件系统行为差异。Windows 上 WriteAt 可能不如 Linux 稳定,小文件(









