分片上传需手动实现HTTP分块逻辑,HttpClient不自动支持;须用FileStream控制偏移与长度、设ContentRange头、复用HttpClient并配置超时、服务端校验续传、上传后检查状态码并调用merge接口。

分片上传必须自己实现 HTTP 分块逻辑,HttpClient 不自动支持
HTTP 协议本身没有“分片上传”原语,所谓分片是业务层把一个大文件切开、逐个发请求、再由服务端拼接。C# 的 HttpClient 只负责发单个请求,不会帮你读文件、切片、重试或合并。你得自己控制 FileStream 的偏移和长度,构造每个分片的 HttpContent。
常见错误是直接把整个文件塞进 ByteArrayContent 或 StreamContent——这等于没分片,内存爆掉或超时失败。
- 用
FileStream打开文件时加FileOptions.Asynchronous,避免阻塞线程 - 每片大小建议 2–10 MB,太小增加 HTTP 开销,太大降低并发容错性
- 用
StreamContent包裹子流(fileStream.Skip(offset).Take(length)不行——得用fileStream.Seek()+ 新建SubArrayStream或MemoryStream拷贝) - 务必设置
content.Headers.ContentRange,否则服务端无法定位该片位置
HttpClient 复用与超时必须显式配置
默认 HttpClient 实例的 Timeout 是 100 秒,上传大文件很容易触发 TaskCanceledException;而且不复用实例会导致 socket 耗尽(SocketException: Too many open files)。
- 全局只用一个
static readonly HttpClient,或用IHttpClientFactory(.NET Core+) - 设置
Timeout = TimeSpan.FromMinutes(30),别依赖默认值 - 禁用自动重定向:
client.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "application/json")避免重定向丢失Content-Range - 上传过程中不能调用
client.CancelPendingRequests(),会中断所有未完成分片
断点续传靠服务端返回已传分片列表,客户端不做猜测
客户端无法可靠判断“哪片传成功了”,网络抖动、服务端写磁盘失败、503 响应都可能导致分片丢失。正确做法是:上传前先发一个 GET /upload/{uploadId}/chunks 请求,拿到服务端已接收的 chunkIndex 列表,跳过这些索引再上传剩余分片。
- 不要用本地文件修改时间或 MD5 做续传依据——服务端可能清理过临时文件
- 上传每个分片后,必须检查 HTTP 状态码(
200或201),且响应体含{"success":true,"index":3}这类明确确认 - 遇到
409 Conflict表示该片已存在,可直接跳过;500则需退避重试(指数退避,最多 3 次) - 上传完成后必须调用
POST /upload/{uploadId}/merge,不能靠客户端拼接
.NET Framework 和 .NET 5+ 的 Stream 行为差异容易踩坑
.NET Framework 中 StreamContent 会自动 dispose 底层 stream,而 .NET 5+ 默认不 dispose——如果传入的是 FileStream,上传完它就被关掉了,后续分片会抛 ObjectDisposedException。
- 在 .NET 5+ 中,构造
StreamContent后手动设content.Headers.ContentDisposition并确保stream.CanSeek == true - 更稳妥做法:不用
StreamContent,改用ByteArrayContent(内存换安全),或封装一个ReusableStreamContent类,重写SerializeToStreamAsync并跳过 dispose - 测试时务必覆盖 .NET Framework 4.8 和 .NET 6/8 双环境,
Stream.Length在某些压缩流下可能抛NotSupportedException
最麻烦的从来不是怎么切片,而是服务端没按 RFC 7233 返回标准 Content-Range 头,或者前端传了 Range 头但服务端根本没解析——这种问题只能抓包看实际 HTTP 流量,光看 C# 代码找不到根因。


















