一句话结论:半包和粘包通常是正常的读取现象;丢包是数据真正缺失;校验失败是发现数据异常的结果。
串口是一根连续输送字节的管道。发送端依次发送字节,接收端依次读取字节,但串口本身通常不知道哪些字节属于一条完整业务消息。
帧头 + 数据长度 + 命令字 + 数据内容 + 校验码 + 帧尾
一次串口读取可能得到半包、一个完整包或多个包。因此,不能把一次 read() 直接等同于一个业务数据包。
| 现象 | 含义 | 是否一定表示串口故障 |
|---|---|---|
| 半包 | 一条完整数据被分成两次或多次读取 | 否,通常是正常现象 |
| 粘包 | 一次读取同时拿到两条或多条数据 | 否,通常是正常现象 |
| 丢包 | 应该收到的字节或完整帧没有被正确保留下来 | 是,需要排查 |
| 校验失败 | 接收数据与发送端生成校验码时的数据不一致 | 表示异常,但不能单独确定根因 |
发送数据:帧头 + 长度 + 数据 + 校验码 + 帧尾 第一次读取:帧头 + 长度 + 部分数据 第二次读取:剩余数据 + 校验码 + 帧尾
字节仍在传输、驱动分批交付缓存以及接收线程调度,都可能产生半包。它通常不代表数据丢失;程序应先缓存,等完整帧到齐后再解析。
一次读取:完整包 A + 完整包 B + 包 C 的一部分
发送端连续发送多条数据时,接收程序可能一次全部取出。程序应循环提取完整帧,并保留剩余字节。
有些表面上的“串口丢包”,实际上是数据已经到达,但被接收程序错误丢弃。
数据包携带的校验值 == 接收端重新计算的校验值
不相等就是校验失败。可能是字节变化、缺失、多出、帧定位错误、长度字段损坏,或者双方使用的算法和协议版本不一致。校验失败只能证明当前帧不可信,不能单独确定具体根因。
串口不是天然不可靠,但它通常只保证按顺序传输字节,不天然保证业务消息完整送达,也不一定具备确认、重传和去重能力。
当电气设计合理、参数一致、接收及时,并且程序具备缓存组帧、校验和异常恢复时,串口可以长期稳定运行。
关键业务还应增加序列号、CRC、应答、超时重传、重复包去重、心跳重连和错误统计。
读取串口字节
│
▼
追加到接收缓存
│
▼
寻找帧头并读取长度
├─ 数据不足:继续等待
▼
提取完整数据帧
│
▼
校验长度、帧尾和 CRC/校验和
├─ 校验失败:丢弃坏帧并重新同步
▼
解析业务数据
│
▼
按序列号去重、应答或触发重传
程序必须允许一次读取包含半帧、多帧或任意数量字节,而不是依赖固定读取边界。
| 观察到的现象 | 更可能的方向 |
|---|---|
| 后一次读取能补齐前一次数据 | 正常半包 |
| 一次读取出现多条完整帧 | 正常粘包 |
| 字节数量持续减少或序列号跳号 | 真正丢包或程序丢弃 |
| 校验值不一致 | 数据损坏、错位或协议不一致 |
| 大负载时问题明显增加 | 缓存溢出或线程处理不及时 |
| 设备启停、电机动作时问题增加 | 供电或电磁干扰 |
| 所有数据持续乱码 | 串口参数或电平标准不匹配 |
| 重连后恢复 | 串口连接、驱动或设备状态异常 |
建议日志至少记录接收时间、原始字节长度、脱敏后的十六进制内容、帧序列号、校验结果、缓存长度和重连事件。
半包和粘包通常是串口字节流的正常读取现象;丢包是数据真正缺失;校验失败是发现数据异常的结果。可靠的串口系统依靠缓存组帧、校验、重同步、序列号、应答和重传来保证业务数据完整。