下载中断后续传需服务端支持range头(accept-ranges: bytes),客户端用os.stat获取已下载字节数,构造bytes=start-请求头,校验响应码为206,再seek定位写入;须流式io.copy处理大文件、边下载边计算sha256并原子落盘校验,配合context控制超时与取消。

下载中断后如何续传?用 Range 请求 + 本地文件偏移校验
Go 标准库的 http.Get 默认不支持断点续传,直接重试会从头下载,浪费带宽和时间。关键在于:服务端必须支持 Range 头(查响应头是否有 Accept-Ranges: bytes),且本地文件已存在时,要跳过已写入部分。
- 先用
os.Stat检查目标文件是否存在,获取已写入字节数fi.Size() - 构造请求时加
req.Header.Set("Range", fmt.Sprintf("bytes=%d-", written)) - 检查响应状态码是否为
206 Partial Content,不是则说明服务端不支持或范围越界,需清空文件重下 - 写入前用
file.Seek(written, 0)定位到末尾,避免覆盖
大文件写入卡死或内存暴涨?用 io.Copy + 限速缓冲区
直接 io.ReadAll(resp.Body) 会把整个文件读进内存,几 GB 文件直接 OOM;而裸写 Write 又可能因磁盘 I/O 阻塞导致 goroutine 卡住。核心是流式处理 + 控制 buffer 大小。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 用
io.Copy替代手动循环读写,它内部已做 buffer 复用和 chunk 分片 - 若需限速(比如避免占满带宽),用
io.LimitReader(resp.Body, limit)或封装带 sleep 的io.Reader - buffer 不必设太大,默认 32KB 足够平衡性能与内存,显式指定可用
io.CopyBuffer(dst, src, make([]byte, 32*1024)) - 务必在
defer resp.Body.Close()前完成所有读取,否则连接不释放,复用连接池时会耗尽
校验失败、文件损坏怎么办?边下载边计算 sha256 并原子落盘
只靠 HTTP 状态码 200 不代表文件完整——网络丢包、中间代理篡改、磁盘写入错误都可能导致内容损坏。必须在下载过程中同步算哈希,且避免“写一半就可被其他进程读取”。
- 用
hash.Hash接口包装io.MultiWriter:同时写入文件和哈希器,如multi := io.MultiWriter(file, hasher) - 下载完成后比对预设的
expectedHash,不一致则os.Remove临时文件并返回 error - 落盘用临时文件名(如
fname + ".tmp"),校验通过后再os.Rename原子替换,避免脏读 - 注意:
sha256.Sum的[:]是切片,比较用bytes.Equal(hasher.Sum(nil), expected[:])
超时、重试、取消怎么协同?用 context.WithTimeout + 自定义 RoundTripper
单纯给 http.Client 设 Timeout 会同时限制连接、请求头读取、响应体读取,但大文件下载中,我们希望“连接快、传输慢也允许”,且能外部取消。
- 用
context.WithTimeout(ctx, 5*time.Minute)控制整体耗时,传给http.NewRequestWithContext - 自定义
http.Transport:设ResponseHeaderTimeout(比如 30s)防卡在 header,但IdleConnTimeout和TLSHandshakeTimeout保持合理值 - 重试逻辑不要套在
http.Client外层简单 for 循环,而是捕获具体错误:url.Error中的Timeout()、net.OpError、http.ErrBodyReadAfterClose才重试 - 每次重试前检查 context 是否已取消,避免无效循环
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










