网关从设备接收字节流,解析记录,再交给存储或上游服务。本项目专注接收侧:一条消息可能分多次到达,多条消息也可能合在一次读取;线路损坏后必须有机会重新找到有效帧。

定义一份小协议

所有多字节数值使用小端编码。帧由同步标记、载荷长度、载荷和 CRC 组成:

字段字节数含义
同步标记20xA5、0x5A
长度1本协议版本固定为 8
版本、标志各 1版本固定为 1,标志保留
序号4无符号递增值
毫伏值2无符号读数,物理范围另行校验
CRC2覆盖长度和载荷,小端存储

CRC 使用多项式 0x1021、初值 0xFFFF,不反射;脚本通过 Python binascii.crc_hqx 计算。CRC 用于检出意外损坏,不提供消息认证。网络部署还需要身份、加密和访问控制。

解析器怎样恢复

解析器保留尚未完整的帧。找不到同步标记时清除无用前缀,但保留可能成为标记起始的最后一个 0xA5。长度不支持或 CRC 不符时向前移动,再搜索下一帧。未知版本有独立计数,不能默默按当前结构解释。

输入按最多 256 字节小块处理,单帧长度有界,防止恶意长度使解析器无限缓存。包合法也不代表数据合理,应用还要校验量程、设备会话和序号;本实验只示范最近 64 个序号的去重窗口,设备重启与长时间重放需要更完整的会话设计。

运行本地测试

下载 网关与测试脚本,在 Python 3.11 或更新版本运行:

python gateway.py

脚本用所有单一切分位置喂入两条连续帧,再注入噪声、坏长度、CRC 损坏、未知版本、重复帧和大量无效字节。它还真正创建本机 socketpair,以小块 recv 读取并解析,确认字节流处理不依赖一次 read 得到一帧。最终打印 PASS。

本地测试已经执行;socketpair 不连接外部服务器,也没有访问串口。测试没有验证真实 UART 电气条件、Linux 设备驱动或 ARM64 目标运行。迁移时用串口或网络读取循环替换输入源,保持同一个解析器和故障测试。

进程和部署边界

在 Linux 上为设备访问设置明确权限,使用非 root 的业务身份;网络、串口和存储各设超时和有界队列。业务处理不能在接收循环中执行无法界定耗时的写入,否则内核缓冲区仍可能溢出。

把 Buildroot 系统 作为部署环境时,打包目标 Python 与需要的标准库模块,安装脚本和服务配置。可写日志与状态放入明确的数据目录,轮转并限制总量。重启进程后去重窗口会丢失,因此它只代表内存内去重;持久副作用应使用幂等键或事务保障。

验收与扩展

验收需要同时记录合法记录数、各类错误、最大缓冲区和断开原因。不能把 CRC 错误一概当成“丢包”;长度损坏、版本不符、设备重启和权限失败需要不同处理。

练习:加入采样时间和启动会话标识,保持协议版本清楚;增加一条“读数超过允许量程”的合法 CRC 帧,确认应用拒绝而解析器仍能继续。再为半帧设置过期时间,使用可注入的单调时钟测试超时,避免依赖真实睡眠。

面试自查:为什么 TCP 或 UART 的 read 边界不等于消息边界?坏长度为何比普通坏数据更危险?CRC、认证和去重各解决什么问题?进程崩溃后怎样避免重复记账?

参考:Python struct 二进制布局、Python binascii CRC 接口、Python socket 接口、Linux 用户空间 I/O 文档。