断点续传需手动实现:先发HEAD请求验证Accept-Ranges: bytes及Content-Length;再用HttpClient设Range头(复用实例),服务端返回206才续传;本地写入须FileMode.Open+Seek到startByte,并校验Content-Range对齐。

断点续传下载在 C# 中不是开箱即用的功能,必须手动处理 HTTP 范围请求(Range 头)、本地文件偏移写入、以及服务端是否支持 Accept-Ranges —— 不检查这三点,代码写得再漂亮也会直接退化成普通下载。
怎么判断服务器支持断点续传
不能靠猜,得发一个 HEAD 请求看响应头:
- 发送
HEAD到目标 URL,检查响应中是否存在Accept-Ranges: bytes;没有就别折腾了,强制走完整下载 - 同时确认
Content-Length存在且是固定数值(动态生成的流式接口通常不支持) - 某些 CDN 或 Nginx 默认关闭
Accept-Ranges,需显式配置add_header Accept-Ranges bytes;
如何用 HttpClient 发起带范围的请求
HttpClient 本身不封装断点逻辑,但可以手动加 Range 头。关键点在于:必须复用同一个 HttpClient 实例(避免 socket 耗尽),且每次请求要设置正确的字节区间。
client.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(startByte, null);
-
startByte是本地文件当前长度(即已下载字节数),不是从 0 开始重算 - 如果服务端返回
206 Partial Content,说明成功;若返回200,说明它无视了Range,此时应终止续传、清空文件重下 - 不要用
WebClient—— 它不支持自定义Range头,.NET Core+ 已标记为过时
如何安全地追加写入本地文件
不能用 FileMode.Append 直接写,因为 HTTP 响应体是纯字节流,而 Append 模式会在文件末尾写,不是指定位置。必须用 FileMode.Open + Seek。
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.None); fs.Seek(startByte, SeekOrigin.Begin); await response.Content.CopyToAsync(fs);
- 写入前务必调用
fs.SetLength(expectedTotalSize)预分配文件空间(可选但推荐,避免磁盘碎片) - 多线程并发续传?别碰 —— 同一文件多个
FileStream写会冲突,除非你用MemoryMappedFile+ 分段锁,但复杂度陡增 - 记得在写入前后校验
Content-Range响应头里的区间是否匹配预期(比如bytes 1024-2047/10000),防中间人篡改或代理截断
最常被跳过的环节是:没验证服务端实际返回的 Content-Range 和本地文件偏移是否对齐,结果下着下着文件就错位了——尤其当服务端做了压缩(Content-Encoding: gzip)却没透传原始长度时,整个续传逻辑就失效了。











