You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 21:21:26