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

WinForm架构RDP客户端/服务端屏幕共享优化方案咨询

Windows Forms 实时屏幕共享优化方案与代码改进

核心问题诊断

当前方案的性能瓶颈主要来自以下几点:

  • 传输媒介不合理:云SQL是持久化存储,并非实时数据传输载体,写入/读取延迟极高
  • 数据冗余严重:全屏幕截图+Base64编码(额外增加33%数据体积),每次传输完整画面
  • 低效的IO操作:截图后写入本地文件再读取,存在完全没必要的磁盘IO开销
  • 帧率控制混乱:两次Thread.Sleep(300)导致单帧间隔至少600ms,帧率不足2帧/秒
  • 错误处理缺失:异常被直接吞掉,无法排查故障

屏幕共享核心优化思路

1. 替换实时传输媒介

放弃云SQL传输实时画面,改用TCP/UDP Socket或SignalR建立客户端与服务端的直接连接:

  • 云SQL仅用于存储设备标识、连接配置等非实时数据
  • 实时画面、输入事件通过点对点/中转网络通道直接传输,彻底消除数据库延迟

2. 减少传输数据量

  • 只传变化区域:对比前后两帧画面,仅传输发生变化的区域,而非全屏幕
  • 高效编码:使用JPEG高压缩率模式,或改用H.264视频编码(适合连续帧传输,压缩比更高)
  • 跳过Base64:直接传输字节流,避免编码带来的体积膨胀

3. 优化截图流程

  • 内存内处理:截图后直接在内存中编码,完全去掉本地文件写入/读取步骤
  • 高效截图API:Windows 10+可使用Windows.Graphics.Capture API(比CopyFromScreen更高效,支持高DPI),旧系统用BitBlt替代CopyFromScreen

4. 合理控制帧率

根据目标流畅度(比如20-30帧/秒)调整等待时间,避免不必要的延迟,同时添加自适应逻辑(网络差时自动降帧率)


优化后的核心代码示例

// 全局变量:保存上一帧截图,用于对比变化区域
private Bitmap _previousFrame;
private readonly object _frameLock = new object();
// 自定义Sender:负责通过Socket发送帧数据
private readonly ScreenShareSender _screenShareSender;

public void StartScreenCapture()
{
    _screenShareSender = new ScreenShareSender("server_ip", 12345); // 替换为服务端地址和端口
    _screenShareSender.Connect();

    while (doInsertImage)
    {
        if (!StartUp.isKeyboardLocked)
        {
            var screenBounds = SystemInformation.VirtualScreen;
            using (Bitmap currentFrame = new Bitmap(screenBounds.Width, screenBounds.Height))
            {
                try
                {
                    // 截图到内存Bitmap
                    using (Graphics g = Graphics.FromImage(currentFrame))
                    {
                        g.CopyFromScreen(screenBounds.Left, screenBounds.Top, 0, 0, currentFrame.Size);
                    }

                    // 获取屏幕变化区域
                    Rectangle changedRegion = GetChangedRegion(currentFrame);
                    if (!changedRegion.IsEmpty)
                    {
                        // 截取变化区域的Bitmap
                        using (Bitmap changedBitmap = currentFrame.Clone(changedRegion, currentFrame.PixelFormat))
                        {
                            // 内存直接编码为JPEG
                            using (MemoryStream ms = new MemoryStream())
                            {
                                // 调整JPEG质量平衡体积和清晰度
                                ImageCodecInfo jpegCodec = ImageCodecInfo.GetImageEncoders().First(c => c.FormatID == ImageFormat.Jpeg.Guid);
                                EncoderParameters encoderParams = new EncoderParameters(1);
                                encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 70L);

                                changedBitmap.Save(ms, jpegCodec, encoderParams);
                                byte[] frameData = ms.ToArray();

                                // 发送变化区域和帧数据到服务端
                                _screenShareSender.SendFrame(frameData, changedRegion);
                            }
                        }
                    }

                    // 更新上一帧截图(线程安全)
                    lock (_frameLock)
                    {
                        _previousFrame?.Dispose();
                        _previousFrame = new Bitmap(currentFrame);
                    }
                }
                catch (Exception ex)
                {
                    // 记录异常日志,不要吞掉
                    Log.Error($"屏幕捕获失败: {ex.Message}", ex);
                }

                // 控制帧率:目标20帧/秒,等待50ms(可根据网络情况动态调整)
                Thread.Sleep(50);
            }

            // 处理输入事件:直接通过Socket发送,替代数据库轮询
            if (serverprocessorid != null)
            {
                ReadMouseHover();
                ReadInputDevicesEvents();
            }
        }
        else
        {
            // 键盘锁定时降低轮询频率
            Thread.Sleep(100);
        }
    }

    // 清理资源
    _screenShareSender.Disconnect();
    lock (_frameLock)
    {
        _previousFrame?.Dispose();
    }
}

// 辅助方法:获取前后帧的变化区域(简化版,实际可优化为分块对比提升效率)
private Rectangle GetChangedRegion(Bitmap currentFrame)
{
    lock (_frameLock)
    {
        if (_previousFrame == null)
            return new Rectangle(0, 0, currentFrame.Width, currentFrame.Height);

        int minX = currentFrame.Width, minY = currentFrame.Height;
        int maxX = -1, maxY = -1;

        // 分块对比像素,减少计算量
        int blockSize = 16; // 16x16像素块
        for (int y = 0; y < currentFrame.Height; y += blockSize)
        {
            for (int x = 0; x < currentFrame.Width; x += blockSize)
            {
                bool blockChanged = false;
                int blockEndX = Math.Min(x + blockSize, currentFrame.Width);
                int blockEndY = Math.Min(y + blockSize, currentFrame.Height);

                for (int by = y; by < blockEndY && !blockChanged; by++)
                {
                    for (int bx = x; bx < blockEndX && !blockChanged; bx++)
                    {
                        if (currentFrame.GetPixel(bx, by) != _previousFrame.GetPixel(bx, by))
                        {
                            blockChanged = true;
                        }
                    }
                }

                if (blockChanged)
                {
                    minX = Math.Min(minX, x);
                    minY = Math.Min(minY, y);
                    maxX = Math.Max(maxX, blockEndX);
                    maxY = Math.Max(maxY, blockEndY);
                }
            }
        }

        return maxX == -1 ? Rectangle.Empty : new Rectangle(minX, minY, maxX - minX, maxY - minY);
    }
}

// 自定义Socket发送类示例(简化版)
public class ScreenShareSender
{
    private readonly TcpClient _tcpClient;
    private NetworkStream _stream;
    private readonly string _serverIp;
    private readonly int _port;

    public ScreenShareSender(string serverIp, int port)
    {
        _serverIp = serverIp;
        _port = port;
        _tcpClient = new TcpClient();
    }

    public void Connect()
    {
        _tcpClient.Connect(_serverIp, _port);
        _stream = _tcpClient.GetStream();
    }

    public void SendFrame(byte[] frameData, Rectangle region)
    {
        // 先发送区域信息(4个int:X,Y,Width,Height)
        byte[] regionBytes = new byte[16];
        Buffer.BlockCopy(BitConverter.GetBytes(region.X), 0, regionBytes, 0, 4);
        Buffer.BlockCopy(BitConverter.GetBytes(region.Y), 0, regionBytes, 4, 4);
        Buffer.BlockCopy(BitConverter.GetBytes(region.Width), 0, regionBytes, 8, 4);
        Buffer.BlockCopy(BitConverter.GetBytes(region.Height), 0, regionBytes, 12, 4);
        _stream.Write(regionBytes, 0, regionBytes.Length);

        // 发送帧数据长度+数据
        byte[] lengthBytes = BitConverter.GetBytes(frameData.Length);
        _stream.Write(lengthBytes, 0, lengthBytes.Length);
        _stream.Write(frameData, 0, frameData.Length);
    }

    public void Disconnect()
    {
        _stream?.Close();
        _tcpClient.Close();
    }
}

整体项目优化建议

  1. 输入事件优化:

    • 鼠标/键盘事件捕获后,直接通过Socket发送到对方端,使用SendInput API模拟输入,替代数据库轮询
    • 减少输入事件的冗余发送(比如鼠标移动只发送关键坐标,而非每帧都传)
  2. 网络层增强:

    • 添加数据压缩(比如Gzip)进一步减少传输体积
    • 实现丢包重传机制,保证画面完整性
    • 支持带宽自适应:根据网络延迟动态调整帧率、图像质量
  3. 多线程架构:

    • 将截图、编码、传输、输入处理拆分为独立的线程/Task,避免单线程阻塞UI
    • 使用线程安全的队列处理待发送的数据,避免并发冲突
  4. 资源与错误管理:

    • 确保所有IDisposable资源(Bitmap、Stream、TcpClient)正确释放,避免内存泄漏
    • 添加详细的日志记录,方便排查网络、截图、编码等环节的故障
  5. 高DPI适配:

    • 使用Windows.Graphics.Capture API替代CopyFromScreen,更好支持高分辨率屏幕和多显示器场景

内容的提问来源于stack exchange,提问作者Mukul Sah

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 03:35:56