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
相关产品推荐
相关产品推荐

