c# 如何实现断点续传

断点续传的核心机制是客户端通过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 位置错乱)
  • 务必用 usingtry/finally 确保流关闭,否则下次打开会报“文件正由另一进程使用”

服务端不支持 Range 时怎么降级处理

遇到 416 Range Not Satisfiable 或直接返回 200(非

206),说明服务端不支持断点续传。此时有两种选择:

  • 清空本地文件,重新下载(最简单,但浪费已下载数据)
  • 先 HEAD 请求获取 Content-Length,对比本地文件大小;若一致则跳过下载,不一致才全量重下
  • 注意:某些 CDN 或反向代理会吞掉 Range 头并返回完整响应,此时 Content-Range 响应头为空,需主动检查

真正麻烦的是中间网络中断后文件损坏却长度恰好匹配——这需要额外做校验(如服务端提供 ETag 或分块哈希),单纯靠长度无法保证一致性。