C# HTTP范围请求下载 C#如何实现多线程分块下载(Range requests)

来源:这里教程网 时间:2026-02-21 17:43:10 作者:

Range 请求必须手动设置
Range
头,HttpClient 默认不发

HttpClient 不会自动拆分请求或加

Range
头——哪怕你传了
HttpCompletionOption.ResponseHeadersRead
。它只管发完整 GET,服务器返回 200 就完事。要实现分块下载,你得自己构造多个独立的
HttpRequestMessage
,每个都显式设置
Range
头,比如
"bytes=0-1048575"

常见错误是误以为用

WebClient
HttpClient.SendAsync
加个参数就能“启用范围下载”,结果抓包一看全是 200,压根没触发 206 Partial Content。

必须用
HttpRequestMessage
+
client.SendAsync()
手动发请求
Range
值格式严格:
"bytes={start}-{end}"
,不能多空格、不能缺单位、不能写成
"0~1024"
服务器必须支持(响应头含
Accept-Ranges: bytes
),否则返回 416 或 200 全量内容

分块大小不是越大越好,
1–4MB
是多数场景的平衡点

单块太大(如 64MB),内存占用高、失败重试成本大、网络抖动时容易超时;太小(如 64KB),HTTP 头开销占比飙升,线程/连接调度压力大,实际吞吐反而下降。实测在千兆局域网和普通云对象存储上,

2MB
分块通常比
512KB
快 15–30%,又比
8MB
更稳。

注意:分块大小需对齐文件系统或存储服务的最小读单元(如某些 CDN 要求 512KB 对齐),否则可能触发额外 IO 或降速。

起始偏移必须是整数,
end
可等于文件总长减 1(最后一块)
避免让块边界落在压缩流或加密块中间(如 ZIP 内部结构、AES-CBC 段),否则解压/解密失败
FileInfo.Length
获取总大小前,先确认服务器返回了
Content-Length
或通过 HEAD 请求预取

并发控制别硬写
Task.WhenAll
,用
SemaphoreSlim
限流更可靠

直接扔几十个

Task.Run(() => DownloadChunk(...))
Task.WhenAll
,极易打爆连接池、触发服务器限流(429)、或耗尽本地端口(
SocketException: Only one usage of each socket address...
)。.NET 的
HttpClient
连接池默认只允许 2–6 个并发连接(取决于 .NET 版本和配置)。

正确做法是用

SemaphoreSlim
控制同时进行的请求数(建议 4–8),并复用同一个
HttpClient
实例。

不要为每个分块 new 一个
HttpClient
,会泄漏连接
SemaphoreSlim.WaitAsync()
放在发送请求前,不是在回调里
记得 await
SemaphoreSlim.Release()
,即使下载失败也要释放
超时统一设在
HttpRequestMessage.Properties["StartTime"]
或用
CancellationTokenSource
管理

合并文件时顺序错乱是高频坑,别依赖
Task
完成顺序

分块下载完成后,各

Task
返回顺序完全不确定。如果按完成先后往文件里
Write
,最终文件就是乱的——开头可能是第 5 块,中间夹着第 1 块。必须按逻辑偏移位置写入,而不是按执行顺序。

最简方案:每个分块下载完,把

byte[]
和对应
startOffset
存进线程安全集合(如
ConcurrentDictionary<long byte></long>
),全部完成后遍历 offset 升序合并;更省内存的做法是打开
FileStream
并用
fileStream.Position = startOffset
定位写入。

写入前检查
FileStream.CanSeek == true
,某些流(如
NetworkStream
)不支持
fileStream.Write(byteArray, 0, byteArray.Length)
,别用
WriteAsync
配合
Position
——异步写入时
Position
可能被其他线程改
最后一块长度可能小于分块大小,以响应头
Content-Range
中的
*/{total}
为准,别信
byteArray.Length
分块下载真正的复杂点不在发请求,而在错误恢复:断点续传需要记录已成功写入的偏移,而部分失败的块重试时,得确保不会覆盖已写正确的内容。这个状态管理很容易被忽略,但一旦网络不稳定,就变成反复下载、反复覆盖、文件损坏。

相关推荐

热文推荐