断点续传的核心机制是客户端通过HTTP Range请求头告知服务端起始字节,服务端返回206 Partial Content响应,客户端按偏移量将数据写入本地文件对应位置;C#中需用HttpClient手动设置Range头、校验状态码、定位文件流写入,不可用DownloadFileAsync。

什么是断点续传的核心机制
断点续传不是某个现成 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);
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 或分块哈希),单纯靠长度无法保证一致性。










