ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C# 串口通信:为什么你的数据总是「半截」或「粘一坨」?——粘包/半包的根因与解决方案

C# 串口通信:为什么你的数据总是「半截」或「粘一坨」?——粘包/半包的根因与解决方案 在我们日常使用的软件中发送信息是一件普通且必要的事。但真正把信息发送与接收做好并不容易——不同协议、不同系统的数据格式各不相同。这篇文章从一个真实踩过的坑出发讲清一个具体的问题串口通信里的粘包与半包。我是反抗者Z一个正在往 C# 上位机方向走的开发者。我们平时讲话时有一句的概念——不会说一半也不会和下一句混乱地连在一起。但机器没有这个意识。它只会机械地收发字节不会自动把数据分成一句一句清晰的片段。这里以我实现的项目为例我使用ESP32-S3一种芯片和DHT11(一种温湿度传感器)配合使用来传输数据给上位机我的电脑。一、问题现象如大家所想做好配置连上电脑便开始传输数据。而问题正在这我将ESP32传输的数据格式为Serial.print(t); //先传输temp 温度的数据 Serial.print(,); //再用一个逗号隔开 Serial.println(h);//最后传输humidity 湿度的数据预期收到的每一帧数据是 25.3,9.0\r\n我使用如下代码接收数据SerialPort _serialPort new SerialPort(COM3, 115200); // 串口 COM3波特率 115200 private string ReadData() { return _serialPort.ReadExisting(); }结果收到的数据长这样有时一次收到完整一帧25.3,9.0\r\n✅有时收到半截25.⚠️有时一次来两条粘一起25.3,9.0\r\n26.1,8.8⚠️有时还夹着乱码或空行这看起来是个小问题——但是它会让你的解析层直接崩掉。下面我从为什么会乱讲到怎么彻底解决。二、原因分析根本原因只有一条机器没有数据边界的概念。所以数据可能在任何位置断开半包也可能多帧粘在一起粘包。另外如果读取不够及时驱动层的接收缓冲区默认 4KB会被填满——超出的部分会被直接丢弃导致数据缺一段。不过这不是本文要解决的核心问题知道有这回事就行。三、解决方案解决思路是先攒着按分隔符切。引入一个StringBuilder作为缓冲池下文叫水池。串口每来一段碎片先Append进水池然后反复查找水池里有没有\n分隔符——找到一个就切出一段完整帧找不到就跳出剩余碎片留在水池里等下次拼接。public void DataHandle(string data) { try { sb.Append(data); // 碎片入池 while (true) { int position sb.ToString().IndexOf(\n); // 找第一个 \n if (position 0) break; // 没有完整帧跳出 if (position 0) { sb.Remove(0, 1); continue; } // 空帧跳过 string frame sb.ToString(0, position).Trim(); // 切出一帧 sb.Remove(0, position 1); // 从水池移除 // 解析帧 → SensorData然后派发给 UI / 数据库 var result tempHumidityAnalysis.Analysis(frame); if (result ! null) DisplaySensorData?.Invoke(result); } } catch (Exception ex) { Debug.WriteLine(ex.Message); } }四、完整代码移步下面链接https://gitee.com/rebel-z/simple-hmihttps://gitee.com/rebel-z/simple-hmi五、验证ESP32-S3代码如下用来模拟粘包 多次发送void setup() { Serial.begin(115200); delay(1000); } void loop() { delay(2000); // 一帧 Serial.println(25.0,60.0); // 两帧连发测粘包 Serial.print(26.0,61.0\n); Serial.print(27.0,62.0\n); // 半帧测半包 Serial.print(28.0,); delay(500); Serial.println(63.0); // 500ms 后才补上后半帧 }ESP32 发送内容上位机日志验证点25.0,60.025.0,60.0t:25 h:60单帧正常解析26.0,61.0\n27.0,62.0\n连发一次触发输出两条粘包正确处理28.0,→ 500ms →63.0\n第二次触发时才输出半包正确拼接三组测试全部通过说明切帧逻辑健壮。如图获取的数据六、总结串口粘包/半包的本质是字节流没有帧的概念。解决思路就两步1. 攒 —— 碎片先进水池不直接解析2. 切 —— 按 \n 切出完整帧剩余的留着下次拼一个容易忽略的细节Arduino/ESP32 的 Serial.println() 默认追加的是 \r\nCR LF不是单独 \n。所以切帧时找 \n 兼容性更好切完后 Trim() 去掉残留的 \r 即可。本文聚焦于切帧本身。我是反抗者Z一个正在往 C# 上位机方向走的开发者。如果这篇文章对你有帮助欢迎在 Gitee 上给我个 Star点赞。下一篇文章讲串口数据的解析与跨线程 UI 更新欢迎关注。
返回列表