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

C#二次转换Stream为Image抛ArgumentException异常排查

异常原因
  • 根本原因是未处理TCP流的报文边界:TCP是面向字节流的协议,本身不维护应用层数据的报文边界,在同一长连接上连续传输多帧时,接收端的Read调用可能返回半帧、单帧、甚至多帧拼接的任意长度数据。你的反序列化逻辑用do-while循环持续读取直到Read返回0才停止,但Read方法返回0的唯一触发条件是TCP发送端主动关闭连接,这也是第一次执行能正常工作的原因——首次传输时要么连接刚建立数据刚好凑齐整帧,要么单帧传完后连接被释放触发读取结束。进入第二次循环传输时,要么前后帧数据被拼接在一起,要么读到被截断的损坏数据,Image.FromStream接收到无效的JPEG字节流自然抛出ArgumentException。
  • 接收端硬编码500000字节固定缓冲区存在两个致命问题:单张JPEG截图压缩后体积超过500KB时会直接触发数组越界异常;单帧体积小于500KB时缓冲区尾部的填充空字节会被混入图片数据,同样会导致图片解析失败。
  • 之前使用BinaryFormatter传输速度慢,是因为该类会序列化Bitmap对象的全量类型元数据、非图片属性等冗余信息,序列化后的体积远大于纯JPEG压缩字节,和二进制序列化本身的性能无关。
修复方案

在应用层实现极简帧协议解决粘包拆包问题:每帧发送前先写入4字节Int32值标识当前帧JPEG数据的实际长度,再写入对应长度的图片字节;接收端先读满4字节拿到当前帧长度,再循环读取直到凑齐指定长度的完整帧数据,解析完成后直接进入下一帧读取流程,不需要等待连接关闭。
注意所有流操作的可释放对象需要指定leaveOpen: true参数,避免单帧处理完就把底层TCP连接关闭,同时及时释放临时内存流避免内存泄漏。
修复后的代码如下:

public static void SerializeScreen(Stream stream, Bitmap image)
{
    // 指定leaveOpen为true,不关闭底层TCP流以支持连续传帧
    using (BinaryWriter binaryWriter = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true))
    using (MemoryStream memory = new MemoryStream())
    {
        image.Save(memory, System.Drawing.Imaging.ImageFormat.Jpeg);
        byte[] frameBytes = memory.ToArray();
        // 先写入帧长度头,再写入实际图片字节
        binaryWriter.Write(frameBytes.Length);
        binaryWriter.Write(frameBytes);
        binaryWriter.Flush(); // 确保数据立即发送到网络层
    }
}

public static Image DeserializeScreen(Stream stream)
{
    // 指定leaveOpen为true,不关闭底层TCP流以支持连续读帧
    using (BinaryReader binaryReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true))
    {
        // 先读取4字节长度头,获取当前帧的实际大小
        int frameLength = binaryReader.ReadInt32();
        byte[] buffer = new byte[frameLength];
        int totalRead = 0;
        // 循环读取直到凑齐完整一帧,自动处理TCP拆包、粘包
        while (totalRead < frameLength)
        {
            int bytesRead = binaryReader.Read(buffer, totalRead, frameLength - totalRead);
            if (bytesRead == 0)
            {
                throw new IOException("网络连接已意外断开,无法读取完整帧");
            }
            totalRead += bytesRead;
        }
        using (MemoryStream memory = new MemoryStream(buffer))
        {
            // 生成Bitmap副本,避免原内存流释放后图片操作报错
            return new Bitmap(Image.FromStream(memory));
        }
    }
}
  • 优化提示:如果需要进一步提升传输速度,可以自定义JPEG压缩参数,根据网络情况调整压缩质量平衡画质和单帧体积;发送、接收逻辑建议放在独立后台线程执行,避免阻塞主UI线程。

内容的提问来源于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.27 20:18:13