Go实现断点续传需客户端和服务端协同:客户端用os.OpenFile、io.Seek、http.NewRequest发送Range请求,服务端须返回206状态及Content-Range头;否则无法真正续传。

Go 本身不提供断点续传的内置函数,必须自己组合 os.OpenFile、io.Seek、http.NewRequest 和服务端支持(如 Range 请求头)来实现。没有服务端配合,客户端单方面“续传”只是本地文件追加,不是真正断点续传。
服务端必须支持 HTTP Range 请求
断点续传本质是客户端告诉服务端“我要从第 N 字节开始下载”,服务端返回对应字节段。如果服务端不响应 206 Partial Content,或忽略 Range 头返回 200 OK 全量内容,客户端续传逻辑会重复写入或校验失败。
- 静态文件服务(如 Nginx、Caddy、Go 的
http.FileServer)默认支持Range,但需确认未禁用Accept-Ranges: bytes响应头 - 自定义 API 接口必须显式解析
Range请求头,调用http.ServeContent或手动读取文件偏移并设置Content-Range响应头 - 用
curl -H "Range: bytes=1000-" -I http://example.com/file.zip测试:若返回206且含Content-Range,说明可用
客户端用 io.Seek + Range 请求续传
核心是两步同步:本地文件已写入多少字节,就让 HTTP 请求从那个位置开始;写入时必须在文件末尾追加,不能覆盖。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 打开文件用
os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)——O_APPEND确保写入总在末尾,但注意:它会忽略Seek,所以实际要先用O_RDWR获取当前长度,再切回O_WRONLY|O_APPEND追加 - 获取已下载长度:
fi, _ := os.Stat(path); downloaded := fi.Size(),这是Range起始字节 - 构造请求:
req, _ := http.NewRequest("GET", url, nil); req.Header.Set("Range", fmt.Sprintf("bytes=%d-", downloaded)) - 检查响应状态:
if resp.StatusCode != 206就不能续传,得清空重下(常见于服务端不支持或 CDN 缓存了全量响应)
写入时避免覆盖和并发冲突
多个 goroutine 同时写一个文件会错乱,即使用了 O_APPEND,因为 Write 不是原子操作;而单纯用 Seek + Write 又可能因竞态覆盖已有内容。
立即学习“go语言免费学习笔记(深入)”;
- 最简方案:整个下载过程单 goroutine,用
io.Copy直接写入,安全但无法并发加速 - 若需分块并发下载(如多线程下载工具),必须为每块分配独立偏移,并用
file.WriteAt(buf, offset)—— 注意WriteAt不受O_APPEND影响,可精准写入任意位置 - 临时文件更稳妥:下载到
file.part,完成后再os.Rename替换原文件,避免中断时留下损坏文件 - 务必校验:下载完成后用
Content-Length或服务端提供的ETag/SHA256校验完整性和一致性
真正的难点不在 Go 语法,而在对 HTTP 协议细节的把握:比如 Range 请求失败时如何降级、服务端返回的 Content-Range 是否可信、网络中断后连接复用是否影响 Seek 位置。这些地方一不留神,续传就变成重复下载或文件碎片。

















