SignalR 本身不支持大文件传输,别直接传 byte[]
SignalR 的 Hub 方法默认序列化走 JSON,且有默认消息大小限制(通常 32 KB)。直接把整个文件读成
byte[]传给
hub.InvokeAsync("UploadFile", fileBytes) 会触发 InvalidOperationException: The message size exceeds the configured maximum或直接超时。这不是配置调大就能解决的——JSON 序列化大数组开销高、内存暴涨、易被浏览器或代理截断。 必须分块(chunk)上传,每块控制在 64–256 KB,用
ArraySegment<byte></byte>或
Memory<byte></byte>持有,避免重复拷贝 客户端需维护上传进度 ID(如 GUID),服务端用
ConcurrentDictionary<string memorystream></string>或临时文件暂存分块 最后一步由客户端发“完成”指令,服务端合并并保存到磁盘或 Blob 存储
前端用 fetch
+ ReadableStream
更稳,别依赖 HubConnection.invoke
SignalR 的 JS 客户端对二进制流支持弱,
invoke只适合小数据。大文件应绕过 Hub,改用普通 HTTP 接口上传,再通过 SignalR 广播状态(如 “上传中”、“已保存”、“校验失败”)。 后端暴露一个
POST /api/upload/{uploadId},接收 multipart/form-data或原始
application/octet-stream前端用
fetch分片上传,配合
AbortController支持暂停/重试 上传过程中调用
connection.invoke("UpdateProgress", uploadId, progress) 同步进度给其他客户端
避免在 Hub 方法里做 IO(如 File.WriteAllBytes),全部交给后台服务或托管服务处理
IFormFile
在 Controller 中接收分片,注意 Request.Body
流不可重复读
ASP.NET Core 默认将
Request.Body缓存在内存或临时文件中,但若你手动调用
Request.Body.ReadAsync一次后没重置位置(
Seek),后续读取会返回空。尤其在用
IFormFile时,它的
OpenReadStream()返回的是新流,但底层
Request.Body已被消耗。 确保
Startup.cs或
Program.cs中启用缓冲:
app.UseRequestBuffering()(.NET 6+) 不要混合使用
IFormFile和直接读
Request.Body;选一种方式到底 分片上传建议用裸流:
await request.Body.CopyToAsync(tempFileStream),比
IFormFile更可控 记得检查
Content-Range头来定位当前块偏移,别依赖表单字段顺序
服务端合并分块前必须校验 SHA256
,别只靠文件名或 ID
多个客户端可能用相同
uploadId并发上传,或网络导致某块重复提交。仅靠内存字典或文件名拼接无法保证完整性。必须在客户端计算每块哈希,服务端验证并最终对完整文件再算一次总哈希。 客户端上传每块时附带
X-Chunk-Hash: sha256...头,服务端用
SHA256.HashData(chunkBytes)校验 所有块接收完成后,从临时存储重新读取全量数据流,计算最终
SHA256,和客户端预提交的
X-File-Hash对比 校验失败则删临时文件,返回 400,并要求客户端重传——别静默覆盖或写坏文件 临时文件路径别硬编码,用
Path.GetTempFileName()或注入
IWebHostEnvironment.WebRootPath下的子目录 SignalR 是实时通信的胶水,不是文件传输协议。真正卡点永远在流控制、内存管理、并发安全和端到端校验——这些细节没压住,光连上 Hub 没用。
