串口、TCP、丢包、断电:Qt 工业项目真正难的是异常现场
第一次做工业上位机时,最容易产生一种错觉:界面能打开,串口能连上,数据能刷新,曲线能跑起来,这项目差不多稳了。
后来到现场才发现,开发环境里的“正常”,很多时候只是设备还没开始折磨你。
办公室里设备电源稳定,网络稳定,测试人员也很配合。你点连接,它就连接;你发指令,它就回包;你关程序,它也安安静静退出。可现场不是这样。设备可能突然断电,网线可能被工人碰掉,PLC 可能重启,串口可能返回半包,传感器可能给你一串明显不符合协议的脏数据。
Qt 工业上位机解决的不是“把数据显示到界面上”这么简单。它真正要解决的是:在不可靠的设备、不可靠的网络、不可靠的人为操作下,程序还能不能稳住。
为什么 demo 没问题,项目里就开始炸
很多 demo 的问题在于,它默认世界是有秩序的。比如 QSerialPort 收到 readyRead,就 readAll,然后立刻解析。TCP 收到数据,就当成一帧完整报文。按钮点一下,就直接发命令。
项目里这么写,很快就会遇到第一个坑:通信数据不是按你想象的边界来的。
串口和 TCP 都是流式的。一次 readyRead 可能只有半包,也可能粘了三包。现场一抖动,设备一重启,缓存里还可能混着旧数据和异常数据。所以我一般不会在 readyRead 里直接做业务,而是先丢进缓冲区,再按协议头、长度、校验慢慢拆。
buffer.append(serial->readAll());while(tryParseOneFrame(buffer,frame)){handleFrame(frame);}这段代码看着不高级,但项目里很救命。它的意义不是“解析数据”,而是承认一个事实:现场数据到达方式不可控,程序必须自己兜底。
真正要盯住的是状态
工业上位机里,我越来越不喜欢“连上了就是连上了”这种简单判断。设备连接状态至少要区分:未连接、连接中、已连接、通信超时、重连中、协议异常、设备离线。
这不是为了显得架构复杂,而是为了让程序知道自己现在该干什么。比如 TCP 断开后,不应该让 UI 还显示“运行中”;连续三次心跳超时,不应该继续发控制命令;设备刚重连成功,也不应该默认之前的配置仍然有效。
状态不清楚,后面所有异常都会变成玄学问题。
项目里我一般会给通信层加心跳、超时、重连和错误上报。UI 只关心状态变化,不直接判断底层 socket 或串口细节。
connect(comm,&DeviceComm::stateChanged,panel,&MainPanel::updateDeviceState);这类连接很普通,但它把 UI 和设备通信隔开了。否则后期一个页面里同时塞着按钮逻辑、协议解析、重连判断和报警提示,维护起来真会想重写。
现场最怕没有日志
还有一个经验:工业项目不要等出问题才想起加日志。现场机器崩一次,客户只会说“刚才突然不动了”。至于刚才收到了什么包、线程有没有退出、数据库有没有写失败,没人知道。
所以关键动作必须落日志:连接、断开、重连、发送命令、收到异常帧、校验失败、超时、线程启动和退出。日志不用花哨,但要能还原现场。
没有日志的现场问题,最后基本都会变成猜。
常见坑或经验提醒
第一个坑,是只测正常流程。按钮点一遍没问题,不代表项目稳定。要主动测试断电、拔网线、设备重启、连续重连、异常报文和程序退出。
第二个坑,是在通信线程里直接操作 UI。Qt 的跨线程规则别侥幸,现场压力一上来,偶发崩溃特别难查。通信线程负责收发和解析,UI 线程负责显示,中间用信号槽传数据。
第三个坑,是重连逻辑太激进。设备刚断开就疯狂 connect,日志刷屏,CPU 飙升,甚至把设备端也拖死。重连最好有间隔、有次数、有状态提示。
第四个坑,是异常数据不丢弃。协议校验失败后,该丢就丢,该重新找帧头就重新找。不要指望设备永远干净。
最后说两句
我对 Qt 工业上位机的判断很简单:能跑起来只是第一步,能在现场乱七八糟的环境里持续跑,才算项目做完了一半。
Qt 本身不差,信号槽、事件循环、线程、定时器都很好用。真正容易翻车的,是我们把开发机上的顺利当成了现场的常态。
设备现场不会讲道理。它会断电,会丢包,会返回奇怪数据,也会在你最不想出问题的时候出问题。上位机开发要做的,就是提前把这些“不讲道理”当成正常情况处理掉。
