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

如何优化C# TCP远程桌面的屏幕收发速度?

优化基于TCP的C#远程桌面传输速度的方案

问题背景

我使用C#开发基于TCP的远程桌面应用,局域网环境下运行正常,但通过VPN连接远程服务器时,图像传输耗时0.5至5秒。现有实现流程如下:

发送流程

  • 使用Bitmap的CopyFromScreen方法捕获屏幕
  • 用MemoryStream保存位图
  • 通过TCP Socket序列化发送位图

接收流程

  • 用ReadBytes方法接收消息
  • 用MemoryStream存储字节数组
  • 通过Image.FromStream创建图像

现有实现代码:

Desktop类

internal static class Desktop
{
    public static Image TakeScreenShoot()
    {
        Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
        try
        {
            using (Graphics Graphics = Graphics.FromImage(bitmap))
            {
                Graphics.CopyFromScreen(0, 0, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
            }
        }
        catch
        {
            bitmap = null;
        }
        return bitmap;
    }

    public static void SerializeScreen(Stream stream, Image Image)
    {
        MemoryStream memory = new MemoryStream();
        Image.Save(memory, System.Drawing.Imaging.ImageFormat.Jpeg);
        int numBytes = (int)memory.Length;
        BinaryWriter binaryWriter = new BinaryWriter(stream);
        binaryWriter.Write(numBytes);
        binaryWriter.Write(memory.GetBuffer(), 0, numBytes);
        stream.Flush();
    }

    public static Image DeserializeScreen(Stream stream)
    {
        BinaryReader binaryReader = new BinaryReader(stream);
        int numBytes = binaryReader.ReadInt32();
        byte[] buffer = binaryReader.ReadBytes(numBytes);
        MemoryStream memory = new MemoryStream(buffer);
        return Image.FromStream(memory);
    }
}

Host类

private void SendImage()
{
    while (Status == ServerStatus.Connected)
    {
        try
        {
            Image bitmap = Desktop.TakeScreenShoot();
            Desktop.SerializeScreen(_NetStream,bitmap);
        }
        catch
        {
            
        }
    }
}

Client类

protected void ReciveMessage()
{
 while(Status == ServerStatus.Connected)
  {
    try
    {
        ImageRecibed?.Invoke(Desktop.DeserializeScreen(_NetStream));
    }
    catch
    {

    }
  }
}

优化方案

1. 修复TCP流式传输的读取完整性问题

TCP是流式协议,ReadBytes(numBytes)不一定能一次性读取到完整字节数,网络不稳定时会导致数据截断,既影响解析也拖慢传输。修正DeserializeScreen方法:

public static Image DeserializeScreen(Stream stream)
{
    using BinaryReader binaryReader = new BinaryReader(stream);
    int numBytes = binaryReader.ReadInt32();
    byte[] buffer = new byte[numBytes];
    int totalRead = 0;
    
    // 循环读取直到获取完整数据
    while (totalRead < numBytes)
    {
        int readCount = binaryReader.Read(buffer, totalRead, numBytes - totalRead);
        if (readCount == 0)
        {
            throw new IOException("远程连接已中断");
        }
        totalRead += readCount;
    }
    
    using MemoryStream memory = new MemoryStream(buffer);
    return Image.FromStream(memory);
}

2. 优化图像压缩,减少传输字节数

默认JPEG压缩质量较高,可降低质量大幅减少数据量,同时视觉损失可控。修改SerializeScreen方法:

public static void SerializeScreen(Stream stream, Image image)
{
    using MemoryStream memory = new MemoryStream();
    // 获取JPEG编码器
    ImageCodecInfo jpegCodec = ImageCodecInfo.GetImageEncoders()
        .FirstOrDefault(e => e.FormatID == ImageFormat.Jpeg.Guid);
    if (jpegCodec == null)
    {
        image.Save(memory, ImageFormat.Jpeg);
    }
    else
    {
        // 设置压缩质量(范围0-100,数值越低压缩率越高)
        using EncoderParameters encoderParams = new EncoderParameters(1);
        encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 60L);
        image.Save(memory, jpegCodec, encoderParams);
    }
    
    int numBytes = (int)memory.Length;
    using BinaryWriter binaryWriter = new BinaryWriter(stream);
    binaryWriter.Write(numBytes);
    // 用ToArray()替代GetBuffer(),避免发送多余的未使用缓冲区字节
    binaryWriter.Write(memory.ToArray());
    stream.Flush();
}

3. 只传输屏幕变化区域

全量发送整屏是最大性能瓶颈,改为只发送上一帧与当前帧的差异区域:

  • 保存上一次捕获的Bitmap
  • 逐块对比两帧,提取所有变化的矩形区域
  • 发送区域坐标+区域图像数据
  • 客户端收到后将变化区域绘制到原有图像上

简化示例思路:

private static Bitmap _lastFrame;

public static List<(Rectangle Rect, Bitmap Region)> GetScreenChanges()
{
    Bitmap currentFrame = TakeScreenShoot();
    List<(Rectangle, Bitmap)> changes = new List<(Rectangle, Bitmap)>();
    
    if (_lastFrame == null)
    {
        changes.Add((Screen.PrimaryScreen.Bounds, currentFrame));
        _lastFrame = new Bitmap(currentFrame);
        return changes;
    }
    
    // 分块检测差异(比逐像素更高效)
    int blockSize = 32;
    for (int y = 0; y < currentFrame.Height; y += blockSize)
    {
        for (int x = 0; x < currentFrame.Width; x += blockSize)
        {
            int blockWidth = Math.Min(blockSize, currentFrame.Width - x);
            int blockHeight = Math.Min(blockSize, currentFrame.Height - y);
            Rectangle blockRect = new Rectangle(x, y, blockWidth, blockHeight);
            
            bool hasChange = false;
            for (int by = 0; by < blockHeight && !hasChange; by++)
            {
                for (int bx = 0; bx < blockWidth && !hasChange; bx++)
                {
                    if (currentFrame.GetPixel(x + bx, y + by) != _lastFrame.GetPixel(x + bx, y + by))
                    {
                        hasChange = true;
                    }
                }
            }
            
            if (hasChange)
            {
                using Bitmap region = currentFrame.Clone(blockRect, currentFrame.PixelFormat);
                changes.Add((blockRect, region));
            }
        }
    }
    
    _lastFrame.Dispose();
    _lastFrame = new Bitmap(currentFrame);
    return changes;
}

4. 控制发送帧率,避免网络拥塞

当前代码无延迟循环发送,会导致CPU占用过高且网络过载,添加帧率限制(如每秒15帧):

private void SendImage()
{
    int frameInterval = 1000 / 15; // 15帧/秒,约66ms间隔
    while (Status == ServerStatus.Connected)
    {
        try
        {
            using Image bitmap = Desktop.TakeScreenShoot();
            Desktop.SerializeScreen(_NetStream, bitmap);
        }
        catch (Exception ex)
        {
            // 替换空catch,记录错误便于排查
            Console.WriteLine($"发送失败: {ex.Message}");
        }
        Thread.Sleep(frameInterval);
    }
}

5. 优化屏幕捕获效率

CopyFromScreen属于GDI+方式,可改用更高效的Windows API(如BitBlt)减少捕获耗时:

[DllImport("gdi32.dll")]
private static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop);

[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("user32.dll")]
private static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);

public static Bitmap TakeScreenShootFast()
{
    Rectangle screenBounds = Screen.PrimaryScreen.Bounds;
    Bitmap bitmap = new Bitmap(screenBounds.Width, screenBounds.Height);
    using (Graphics g = Graphics.FromImage(bitmap))
    {
        IntPtr hdcDest = g.GetHdc();
        IntPtr hdcSrc = GetDC(IntPtr.Zero);
        BitBlt(hdcDest, 0, 0, screenBounds.Width, screenBounds.Height, hdcSrc, 0, 0, 0x00CC0020); // SRCCOPY
        g.ReleaseHdc(hdcDest);
        ReleaseDC(IntPtr.Zero, hdcSrc);
    }
    return bitmap;
}

6. 清理代码中的不良实践

  • 移除空的catch块,添加错误日志,便于排查网络或捕获异常
  • 及时释放Bitmap、MemoryStream等实现IDisposable的资源,避免内存泄漏

内容的提问来源于stack exchange,提问作者Luis Garcia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 23:33:22