SignalR位图远程桌面公网卡顿优化及base64替代方案咨询
公网卡顿核心原因
- base64编码会把二进制数据体积膨胀33%,公网带宽有限的情况下传输开销直接上涨1/3
- 当前用的PNG编码即使压到最低质量,对桌面这类静态内容多的场景压缩效率远低于视频编码,单帧体积仍然过大
- 逐帧全量传输,没有做差帧优化,每次都传整个屏幕的内容,公网延迟高的情况下很容易出现帧堆积
- 手动调用
GC.Collect()会带来不必要的性能损耗,桌面端截图线程容易出现延迟波动 - SignalR默认消息大小限制、公网传输丢包重传的叠加效应会进一步放大卡顿
针对性优化方案
一、传输层优化(优先落地,可直接解决80%问题)
首先移除base64编码,SignalR原生支持二进制消息传输,直接把二进制数组发出去即可,无需转base64,可直接减少33%的传输体积,修改代码如下:
- 桌面端修改
await _hubConnection.StartAsync(); var thread = new Thread(async () => { while (true) { Thread.Sleep(1); Rectangle bound = Screen.PrimaryScreen.Bounds; ImageCodecInfo pngEncoder = GetEncoder(ImageFormat.Png); System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality; EncoderParameters myEncoderParameters = new EncoderParameters(1); EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, 0L); myEncoderParameters.Param[0] = myEncoderParameter; Bitmap screenshot = new Bitmap(bound.Width, bound.Height, System.Drawing.Imaging.PixelFormat.Format16bppRgb555); Graphics graphics = Graphics.FromImage(screenshot); graphics.CopyFromScreen(bound.X, bound.Y, 0, 0, bound.Size, CopyPixelOperation.SourceErase); using (var ms = new MemoryStream()) { screenshot.Save(ms, pngEncoder, myEncoderParameters); // 直接传输二进制数组,删除base64转换逻辑 await _hubConnection.InvokeAsync("Image", ms.ToArray()); } Thread.Sleep(150); // 删掉手动GC触发逻辑,GC自动回收性能远高于手动调用 } }); thread.Start(); thread.Join();
- Hub端修改
public async Task Image(byte[] imgData) { await Clients.All.SendAsync("ViewImage", imgData); }
同时调整SignalR消息大小限制,在Program.cs中添加配置:
builder.Services.AddSignalR(options => { // 调整单条消息最大体积为10M,适配大帧传输 options.MaximumReceiveMessageSize = 10 * 1024 * 1024; }) // 开启MessagePack二进制序列化,比JSON序列化体积更小、速度更快 .AddMessagePackProtocol();
- 前端修改
<div class="text-center"> <canvas id="received" width="1200" height="800" style="border: solid 1px black"></canvas> </div> <script> connection.on("ViewImage", function (imgData) { var canvas = document.getElementById('received'); var ctx = canvas.getContext('2d'); // 二进制转Blob生成临时URL,性能远高于base64的dataurl let blob = new Blob([imgData], {type: 'image/png'}); let source = URL.createObjectURL(blob); var img = new Image(); img.src = source; img.onload = function () { ctx.drawImage(img, 0, 0, canvas.width, canvas.height); // 用完释放临时URL,避免内存泄漏 URL.revokeObjectURL(source); } }); </script>
二、编码层优化
- 把PNG替换成WebP编码,相同画质下WebP体积比PNG小25%-50%,C#端可通过ImageSharp库实现WebP编码,前端原生支持WebP解码
- 如果卡顿仍然明显,可改用视频编码方案:桌面端把多帧编码成H.264裸流,发送完关键帧之后只传增量帧,公网传输体积可以降到PNG的1/10以下,前端用MediaSource Extensions解码播放,帧率可稳定在30帧以上
三、内容逻辑优化
- 增加差帧计算逻辑:只有屏幕内容变化的区域才编码传输,静态桌面场景下可把单帧体积降到原来的10%以内
- 增加帧率动态调整逻辑:检测到公网延迟升高时,自动降低截图帧率或者编码质量,避免帧堆积导致的画面冻结
四、更高性能的替代方案
如果上述优化仍然达不到要求,可改用WebRTC做点对点流传输,只需要用SignalR做信令传输,流数据直接走UDP点对点传输,不需要通过服务器中转,公网延迟可降到100ms以内,抗丢包能力远高于TCP传输的SignalR,不会因为丢包出现卡顿冻结,非常适合远程桌面这类低延迟要求的场景。
内容的提问来源于stack exchange,提问作者Cəfərov Murad
相关产品推荐
相关产品推荐

