什么是断点续传的核心机制
断点续传不是某个现成 API,而是靠 HTTP 协议的
Range请求头 + 服务端支持 + 客户端本地文件偏移写入协同实现的。关键在于:客户端要能告诉服务端“我要从第 N 字节开始下载”,服务端得返回
206 Partial Content,客户端再把响应体追加写入到本地文件对应位置。
C# 中用 HttpClient
发起带 Range 的请求
必须手动设置
Range头,并检查响应状态码是否为
206;不能用
DownloadFileAsync这类封装方法,它不支持断点控制。
HttpClient实例建议复用(避免 socket 耗尽),且需启用
AllowAutoRedirect = false,防止重定向丢失
Range头 请求前用
FileInfo.Length获取已下载字节数,作为
Range: bytes={length}-
响应中通过 response.Content.Headers.ContentRange解析实际返回的字节范围,验证是否匹配预期
var client = new HttpClient { AllowAutoRedirect = false };
var req = new HttpRequestMessage(HttpMethod.Get, "https://example.com/large.zip");
req.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(localFileLength, null);
<p>var res = await client.SendAsync(req);
if (res.StatusCode != HttpStatusCode.PartialContent) {
throw new InvalidOperationException($"Expected 206, got {res.StatusCode}");
}如何安全地追加写入已存在文件
直接
FileMode.Append不行——它会在文件末尾写入,但服务端返回的可能是中间某段(比如断点在 10MB,服务端返回的是 10MB–15MB),必须按字节偏移写入。 打开文件用
FileMode.Open+
FileAccess.Write调用
stream.Seek(offset, SeekOrigin.Begin)定位到指定位置 用
stream.Write(buffer, 0, read)写入,而非
WriteAsync(避免因异步调度导致 seek 位置错乱) 务必用
using或
try/finally确保流关闭,否则下次打开会报“文件正由另一进程使用”
服务端不支持 Range 时怎么降级处理
遇到
416 Range Not Satisfiable或直接返回
200(非
206),说明服务端不支持断点续传。此时有两种选择: 清空本地文件,重新下载(最简单,但浪费已下载数据) 先 HEAD 请求获取
Content-Length,对比本地文件大小;若一致则跳过下载,不一致才全量重下 注意:某些 CDN 或反向代理会吞掉
Range头并返回完整响应,此时
Content-Range响应头为空,需主动检查
真正麻烦的是中间网络中断后文件损坏却长度恰好匹配——这需要额外做校验(如服务端提供
ETag或分块哈希),单纯靠长度无法保证一致性。
