SoftControl
📚 使用教程

UDP 通、TCP 不通:中控集成里的消息分帧问题

SoftControl Team2026-08-2511 分钟阅读
TCPUDPframingAV controlintegration

症状

你在把一个第三方系统——PLC、触摸屏、排期服务器、另一家的中控——接进你的中控软件。集成方用 UDP 测试,一切正常。他们想换成 TCP(因为想要一条能监控的连接),结果一模一样的命令字符串什么也不做。

没有报错。没有拒绝。连接建立了并且一直保持。字节确实发出去了。接收端毫无反应。

这几乎从来不是网络问题,而几乎总是分帧问题。

根因:UDP 有消息边界,TCP 没有

这就是全部解释,而且值得说精确,因为它的后果并不显然。

UDP 是数据报式的。 一次 send() 产生一个数据报,接收端一次 recv() 就拿到那个数据报。消息边界由协议本身保证。 你发 PLAY,接收端就收到完整的一个 PLAY。分帧是免费的。

TCP 是字节流。 协议保证字节按序、不丢地到达——而对"一条消息在哪结束、下一条在哪开始"不做任何保证。 你的 PLAY 可能作为 PLAY 到达,也可能先到 PL 再到 AY,还可能和下一条命令粘成 PLAYSTOP。

所以 TCP 接收端必须自己决定每条命令在哪结束,它需要一条分帧规则。 几乎所有人的选择都是行结束符——读到 \n 为止。

而问题恰恰出在这里:大量第三方工具、PLC 和网络调试助手发送命令时完全不追加结束符,并且保持长连接。接收端在等一个永远不会来的换行,于是把这条命令永远攥在缓冲区里。

命令没有丢失,也没有被拒绝——它就坐在缓冲区里等着。 这就是为什么什么都没发生,也什么都不报错。

三重分帧

光靠换行分帧,在真实集成里是不够的。 一个健壮的 TCP 接收端需要三条规则并存。SoftControl 的外部接口服务器就是这么做的:

1. 换行成帧

正常情况。换行到达,它之前的内容就是一条命令。

2. 空闲成帧——解决缓冲区滞留的那一条

如果超过 300 毫秒没有新字节到达,就把缓冲区里的内容当作一条完整命令。

这个取值有讲究,300ms 是刻意选的:局域网内属于同一条消息的 TCP 分片,到达间隔通常小于 10 毫秒。 300 毫秒的阈值远高于这个间隔,所以不会把一条只收到一半的消息切成两条命令,同时又快到让按下按钮的人感觉不到延迟。

太短——比如 20ms——在拥塞网络上有把一条命令切成两条的风险。太长则系统显得迟钝。300ms 舒服地落在两者之间。

3. 断开成帧

有些客户端发完一条命令就立刻关闭连接,完全不带结束符。如果接收端只有换行和空闲两条规则,这最后一条命令会在套接字关闭时被丢掉。

所以关闭处理里也必须把缓冲区冲出去。

实现第 3 条时的一个陷阱

这一条值得专门点出来,因为它在代码评审里是隐形的。在 Dart 里——其他事件驱动运行时也有同样的形状——事后再挂关闭处理:

final sub = client.listen(onData);
sub.onDone(myFlushLogic);     // 错误

是替换已有的 done 处理器,而不是追加。如果分帧逻辑本来是注册在 listen 调用里的,这一行会静默地把它移除:

client.listen(
  onData,
  onError: ...,
  onDone: myFlushLogic,       // 正确 —— 作为具名参数传入
);

弄错之后的症状非常精确:带换行的命令一切正常,不带换行的命令被静默丢弃——这看起来完全像是客户端的问题,会让你去连接的另一端排查错误的东西。

给缓冲区设上限

一个不带结束符、又永不断开地灌数据的客户端,会让你的缓冲区无限增长。要设上限——SoftControl 用的是 8192 字符——超过就丢弃。超过这个尺寸的就不是命令了,而是配置错误的客户端或垃圾数据,唯一真实的风险是把机器内存撑爆。

TCP 失败的另一个原因:你在跟一个 HTTP 服务器说话

还有第二个完全不同的原因值得知道。

现代中控设备越来越多地暴露 HTTP 接口,而不是裸 TCP 套接字。如果你把裸 TCP 命令打到 80 或 8080 端口并发一段 JSON,服务器会一直等永远不来的 HTTP 头,最终超时或关闭。

它的特征很鲜明:发送看起来像 JSON 的载荷时,TCP 超时或 EOF。 SoftControl 专门识别这个模式,并给出提示建议改用 http_post——因为真正的修法不是调分帧,而是换对协议驱动。

如果你的载荷以 { 开头而 TCP 超时,先查这台设备是不是想要 HTTP。

排查顺序

1. 走 UDP 通吗?
通,就说明命令内容和设备逻辑都是对的。问题在分帧,不在命令。

2. 加一个换行结尾能不能好?
能,就确认了接收端按换行分帧、而你的发送端没加结束符。要么让发送端补一个,要么让接收端支持空闲成帧。

3. 命令是不是在你关闭连接之后才生效?
那是断开成帧在起作用、空闲成帧缺失——或者是上面那个 onDone 被替换掉的陷阱。

4. 快速连发两条命令,是不是粘在一起到达的?
经典的缺分帧。 TCP 把它们拼起来了,因为没有边界。

5. 发 JSON 载荷时超时?
怀疑是 HTTP 端点,不是裸套接字。

6. 第一条命令好使、后面的都失败?
第一条被某种机制(空闲或关闭)冲出去了,而后续的都在一个永远不排空的缓冲区里堆积。

给接口规范文档的实用建议

当你把一个控制接口公开给别的厂商去对接时,要显式地把分帧写进文档。"往 TCP 8819 端口发 PLAY"不是一份完整的规范。要写明:

  • 是否需要结束符,是哪一个

  • 连接应该是短连接还是长连接

  • 不带结束符时会怎样

  • 命令最大长度

而且接收端无论如何都要把三条分帧规则都实现上,因为对面那个集成工程师不会仔细读你的文档,而且他那台 PLC 可能就算想加换行也加不了。

接收端宽容一次,成本是五十行代码;接收端严格,成本是此后每一次集成都要接一通电话。

用 SoftControl 控制

SoftControl 的外部接口同时接受 UDP(默认 8818)和 TCP(默认 8819),TCP 这条路实现了上面全部三条分帧规则——换行、300ms 空闲、断开——所以一个什么都不追加的第三方系统照样能用。

两个通道的运行状态也是分开记录的:其中一个绑定失败时另一个照常服务,失败原因会被暴露出来而不是被吞掉。这一点重要,因为一个协议上的端口冲突不该把另一个协议静默地一起搞死。

传输方式怎么选见 AV 控制协议详解;命令内容本身见指令格式:结束符、ASCII 与 HEX、校验和。

立即体验 SoftControl

免费下载,无需注册即可开始 30 天试用

免费下载查看功能

留言讨论

评论发布后会被人工复核,违规内容将被删除。

    还没有人评论,来说说你的看法