Blazor中如何通过WebSocket传输图片解决大文件内存溢出问题
Blazor WebSocket 传图无OOM实现方案
核心问题出在两个地方:一是全量将图片流读入MemoryStream会占用和图片等大的内存,二是Convert.ToBase64String会额外增加33%的传输体积,编码过程还会产生双倍内存峰值,大尺寸图片必然触发内存溢出。
以下是可直接落地的实现,全程避免全量数据驻留内存:
Blazor 端分片发送实现
放弃全量读取到内存再转码的逻辑,直接按固定块大小读取图片流,读取一块发送一块,内存峰值始终控制在块大小级别,不会随图片尺寸上涨。
建议分片大小设置为64KB,适配WebSocket单帧传输最佳实践。
private const int ChunkSize = 65536; /// <summary> /// 分片读取图片并通过WebSocket发送 /// </summary> /// <param name="imageFile">浏览器选择的图片文件</param> /// <param name="sendChunkCallback">单块数据发送逻辑,根据你用的WebSocket客户端实现即可</param> /// <returns></returns> public async Task SendImageInChunks(IBrowserFile imageFile, Func<byte[], Task> sendChunkCallback) { var resizedImage = await imageFile.RequestImageFileAsync("image/jpeg", 40000, 20000); await using var imageStream = resizedImage.OpenReadStream(1073741824); byte[] readBuffer = new byte[ChunkSize]; int actualReadLen; while ((actualReadLen = await imageStream.ReadAsync(readBuffer, 0, ChunkSize)) > 0) { byte[] sendBuffer; // 最后一块数据长度不足分片大小时,截取实际读取长度发送 if (actualReadLen < ChunkSize) { sendBuffer = new byte[actualReadLen]; Array.Copy(readBuffer, sendBuffer, actualReadLen); } else { sendBuffer = readBuffer; } await sendChunkCallback(sendBuffer); } // 发送空字节数组作为传输结束标记 await sendChunkCallback(Array.Empty<byte>()); }
如果使用Blazor原生JS互操作而非自建WebSocket客户端,.NET 5及以上版本原生支持byte[]类型直接传递,底层不会自动转Base64,没有额外编码开销。
JS端接收并使用图片
JS侧接收二进制分片后直接组装为Blob使用,全程不需要转Base64,无额外内存开销:
const chunkCache = []; // WebSocket消息监听回调 websocket.onmessage = (evt) => { if (!(evt.data instanceof ArrayBuffer)) return; const chunk = new Uint8Array(evt.data); // 收到结束标记,组装图片 if (chunk.length === 0) { const imageBlob = new Blob(chunkCache, { type: 'image/jpeg' }); // 生成临时地址直接赋值给img标签即可显示 const imgUrl = URL.createObjectURL(imageBlob); document.querySelector('#previewImg').src = imgUrl; // 如果需要转发到服务端,直接上传Blob即可 // 清空缓存 chunkCache.length = 0; return; } chunkCache.push(chunk); }
服务端接收byte[]转存图片
服务端收到分片后直接逐块写入文件,不需要将全量byte[]加载到内存:
/// <summary> /// 接收分片并保存为图片文件 /// </summary> /// <param name="chunkStream">分片数据流</param> /// <param name="saveFullPath">图片保存绝对路径</param> public async Task SaveReceivedImage(IAsyncEnumerable<byte[]> chunkStream, string saveFullPath) { await using var fileWriteStream = new FileStream(saveFullPath, FileMode.Create, FileAccess.Write); await foreach (var chunk in chunkStream) { if (chunk.Length == 0) break; await fileWriteStream.WriteAsync(chunk, 0, chunk.Length); } }
注意事项
- 禁止对超过10MB的二进制数据使用
Convert.ToBase64String转码,除了体积膨胀33%外,编码过程的临时内存占用极易触发OOM - 所有流操作必须使用
await using声明,及时释放非托管资源,避免隐性内存泄漏 - 分片大小不要超过128KB,过大的单帧数据会增加TCP传输拆包重组开销,64KB是公网传输场景下的最优值
- 如果使用Blazor Server模式,不要通过SignalR传超过10MB的文件,建议走前端直连WebSocket的路径传输,避免占用SignalR连接带宽影响其他交互
内容的提问来源于stack exchange,提问作者segu
相关产品推荐
相关产品推荐

