资讯详情

资讯详情

博途TSEND_C连接ID管理:避免TCP数据错位的实战指南

1. 项目缘起一个让产线停摆三小时的“幽灵故障”去年冬天我在一个汽车零部件装配线项目上做调试。产线用的是西门子S7-1200 PLC通过TSEND_C指令往工控机上的MES系统推送拧紧枪的扭矩数据。调试阶段一切正常数据包整整齐齐地出现在MES的日志里。但试生产第三天夜班操作工打电话说“MES上扭矩数据全是乱的A工位的扭矩值跑到B工位去了”。我远程连上去一看MES收到的数据确实错位了——本该是工位号扭矩值时间戳的固定结构结果变成了扭矩值时间戳工位号的乱序组合。这个故障最诡异的地方在于重启PLC后恢复正常运行几个小时后又开始错位。我花了整整三个小时排查最后发现问题出在TSEND_C指令的连接ID参数上。这个参数在博途里看起来人畜无害填个数字就行但它背后牵扯到TCP连接的建立、保持和复用机制。一旦连接ID管理不当多个TSEND_C实例就会“串线”导致数据错位。这篇文章就是把我踩过的坑、排查的思路、以及最终总结出的一套连接ID管理方法完整地分享出来。如果你正在用博途做TCP通讯尤其是S7-1200/1500通过TSEND_C/TRCV_C与上位机或其他PLC交换数据那这篇内容应该能帮你省下至少三个小时的产线停摆时间。我会从TCP通讯的基本原理讲起把连接ID的分配逻辑、数据错位的根因、以及一套可直接复用的连接管理方案讲透。即使你刚接触博途的通讯指令跟着走一遍也能理解背后的门道。2. 先搞懂TSEND_C到底在干什么2.1 TSEND_C不是“发数据”那么简单很多刚接触博途的工程师会把TSEND_C理解成一个“发送数据的指令”就像串口通讯里的XMT一样填好数据区、触发一下数据就出去了。但TSEND_C的全称是“TCP Send with Connection Management”它本质上是一个带连接管理功能的发送指令。这意味着它不仅要发数据还要负责建立连接、维护连接、在连接断开时尝试重连。这个区别非常关键。串口通讯是物理层直连没有“连接”的概念你往发送缓冲区写数据硬件就发出去了。但TCP是面向连接的协议两个设备之间必须先完成三次握手建立连接才能传输数据。TSEND_C把“建立连接”和“发送数据”这两件事打包在一起用起来方便但也埋下了连接ID管理的隐患。在博途的指令库里TSEND_C属于“通信”-“开放式用户通信”-“其它”分类。它的引脚包括REQ上升沿触发发送CONT连接控制为1时建立并保持连接为0时断开连接CONNECT连接描述数据块包含IP地址、端口号、连接类型等信息DATA要发送的数据区LEN发送数据长度DONE发送完成BUSY正在处理ERROR出错STATUS状态码看起来引脚不多但每个引脚背后都有讲究。尤其是CONNECT引脚指向的那个连接描述数据块里面有一个连接ID字段这个字段就是所有问题的源头。2.2 连接ID到底是什么连接ID是博途在编译时分配给每个TCP连接的唯一标识符。你可以把它理解成“连接的门牌号”——PLC内部用这个号码来区分不同的TCP连接。当你调用TSEND_C时系统会根据CONNECT数据块里的连接ID找到对应的连接通道然后把数据发出去。问题在于这个连接ID是手动填写的而不是系统自动分配的。在博途里新建一个TCP连接时系统会弹出一个对话框让你填连接ID默认值通常是1。很多工程师为了省事所有连接都填1或者随便填个数字。在只有一个连接的时候这没问题。但一旦有多个TSEND_C/TRCV_C实例或者同一个连接被多个指令复用连接ID管理不当就会导致数据错位。我见过最典型的情况是一个S7-1200通过两个TSEND_C分别往两台工控机发数据两个CONNECT数据块里的连接ID都填了1。编译时博途不报错下载后运行一开始也正常。但运行一段时间后两个连接的数据开始互相串——本该发给A机的数据发到了B机或者A机的数据里混入了B机的字段。这就是连接ID冲突导致的“串线”。2.3 TCP连接的生命周期与连接ID的关系要理解为什么连接ID冲突会导致数据错位得先搞清楚TCP连接的生命周期。一个TCP连接从建立到关闭经历三个阶段建立阶段客户端发起SYN服务端回复SYNACK客户端再回复ACK三次握手完成连接建立。数据传输阶段双方通过这个连接发送数据数据按序到达有确认重传机制保证可靠性。关闭阶段一方发送FIN另一方回复ACK再发送FIN再回复ACK四次挥手完成连接关闭。TSEND_C的CONT引脚控制的就是这个生命周期。当CONT1时TSEND_C会尝试建立连接并保持当CONT0时TSEND_C会关闭连接。如果CONT一直为1连接就保持这就是所谓的“长连接”。如果每次发送都让CONT从0变1再变0那就是“短连接”。连接ID在这个生命周期里扮演什么角色它是PLC内部连接管理表的索引。PLC维护一张连接表每个表项记录了一个连接的状态已建立、正在建立、已关闭等、对端IP和端口、以及发送/接收缓冲区。连接ID就是这张表的行号。当TSEND_C被调用时它根据连接ID找到对应的表项把数据放到该表项的发送缓冲区里。如果两个TSEND_C实例用了同一个连接ID它们就会操作同一个表项。第一个实例把数据放进缓冲区第二个实例也把数据放进同一个缓冲区两个数据包就会混在一起。更糟糕的是如果两个实例的触发时机不同还可能一个实例的数据覆盖另一个实例的数据导致接收端收到错乱的数据。3. 数据错位的根因分析连接ID冲突的三种典型场景3.1 场景一多个TSEND_C共用一个连接ID这是最常见的情况。一个PLC需要往多个目标发送数据工程师图省事所有CONNECT数据块里的连接ID都填1。编译能过下载能跑但运行一段时间后数据开始错位。为什么一开始正常因为TCP连接建立需要时间。第一个TSEND_C触发时连接ID1的表项被创建连接建立数据发送。第二个TSEND_C触发时它发现连接ID1的表项已经存在就直接复用这个连接。如果两个TSEND_C的目标IP和端口不同第二个TSEND_C实际上会把数据发到第一个连接的目标去。但如果两个TSEND_C的目标相同比如都发给同一台工控机的不同端口那问题更隐蔽——数据会发到同一个连接上接收端按顺序读取就可能把两个不同来源的数据混在一起。我遇到的那个汽车零部件项目就是这种情况。两个TSEND_C都往同一台工控机发数据一个发扭矩数据一个发角度数据连接ID都填了1。工控机上的MES系统按固定长度读取数据包扭矩数据包和角度数据包长度不同结果MES把角度数据包的前半段当成了扭矩数据包的后半段数据就错位了。3.2 场景二TRCV_C与TSEND_C共用连接IDTRCV_C是接收指令它也需要一个连接ID来标识从哪个连接接收数据。如果TSEND_C和TRCV_C用了同一个连接ID就会出现“自己发给自己”的混乱。正常情况下TSEND_C和TRCV_C应该配对使用一个连接ID对应一个TCP连接TSEND_C往这个连接发TRCV_C从这个连接收。但如果连接ID分配不当比如TSEND_C用了ID1TRCV_C也用了ID1但它们的CONNECT数据块指向不同的连接描述就会导致接收指令从错误的连接上读数据。这种场景下数据错位表现为TRCV_C读到的数据不是对端发来的而是本地TSEND_C发出去的数据被“回环”了。或者TRCV_C读到了另一个连接的数据。排查起来非常困难因为从PLC的角度看指令执行都是正常的DONE和ERROR状态都正常但数据就是不对。3.3 场景三连接ID在运行中被动态修改有些工程师喜欢在程序里动态修改连接ID比如根据配方切换不同的目标IP。这种做法极其危险。连接ID是编译时确定的运行中修改会导致PLC的连接管理表混乱。轻则连接建立失败重则PLC进入STOP模式。博途的在线帮助里明确写了连接ID必须在编译前确定运行中不可修改。但总有人想“灵活”一点结果就是给自己挖坑。我见过一个项目工程师用MOVE指令把不同的连接ID写入CONNECT数据块想实现“一个TSEND_C往多个目标发数据”。结果PLC运行几分钟后就报通讯故障重启后正常再运行几分钟又故障。最后查出来就是动态修改连接ID导致的。3.4 连接ID冲突的底层机制从PLC的固件层面看连接ID是开放式用户通信的连接句柄。每个连接句柄对应一个TCBTCP Control BlockTCB里保存了连接的状态、序列号、窗口大小、重传队列等信息。当TSEND_C被调用时固件根据连接ID找到对应的TCB把数据封装成TCP报文段发送出去。如果两个TSEND_C用了同一个连接ID它们就共享同一个TCB。第一个TSEND_C发送数据时TCB的序列号增加第二个TSEND_C发送数据时TCB的序列号继续增加。但接收端可能还没收到第一个数据包的确认第二个数据包就发出去了。如果两个数据包的目标端口不同接收端的TCP栈会根据端口号把它们分给不同的socket。但如果目标端口相同接收端就会按序列号顺序读取两个数据包的内容就可能被拼接错位。更严重的是如果两个TSEND_C的触发频率不同一个快一个慢快的那个会不断占用TCB的发送缓冲区慢的那个的数据可能被覆盖或延迟发送。接收端看到的就是数据时有时无、顺序错乱。4. 一套可复用的连接ID管理方案4.1 连接ID分配原则经过多个项目的摸索我总结出一套连接ID分配原则核心就三条第一条一个TCP连接对应一个唯一的连接ID。不管是TSEND_C还是TRCV_C只要它们操作的是同一个TCP连接就用同一个连接ID。如果操作的是不同的TCP连接就必须用不同的连接ID。第二条连接ID从1开始连续分配不要跳号。博途的连接管理表是按连接ID索引的跳号会浪费表项虽然不影响功能但不利于后期维护。我习惯从1开始按连接建立的顺序依次分配。第三条连接ID在项目文档中必须记录。我见过太多项目程序里连接ID填得乱七八糟问工程师为什么这么填答“当时随便填的”。后期维护时改一个连接ID可能影响多个指令没有文档根本不敢动。所以我在每个项目的通讯文档里都会列一张表记录每个连接ID对应的目标IP、端口、用途、关联的指令。4.2 连接描述数据块的规范写法在博途里每个TCP连接都需要一个CONNECT数据块。这个数据块的类型是TCON_IP_v4对于S7-1200/1500的开放式用户通信。我习惯为每个连接单独建一个数据块命名规则是“CONNECT_对端设备名_用途”比如“CONNECT_MES_扭矩数据”。数据块里的关键字段包括字段名含义填写要点InterfaceId本地接口硬件标识通常填64PLC内置以太网口用多个网口时需查硬件标识ID连接ID唯一从1开始连续分配ConnectionType连接类型TCP填16#0BISO-on-TCP填16#12ActiveEstablished主动/被动建立客户端填TRUE服务端填FALSERemoteAddress对端IP地址按字节填入如192.168.1.100填16#C0A80164RemotePort对端端口号0-65535常用2000-5000范围LocalPort本地端口号被动建立时需填主动建立时可填0由系统分配这里有个细节RemoteAddress的填写方式。博途的数据块里RemoteAddress是一个4字节的数组每个字节对应IP地址的一段。比如192.168.1.100四个字节分别是16#C0、16#A8、16#01、16#64。很多工程师直接填十进制192编译报错因为数据类型是Byte范围0-255但192在Byte范围内啊问题在于博途的Byte类型默认显示十六进制你填192它会当成十进制192但实际存储的是16#C0。所以要么填16#C0要么在数据块里把显示格式改成十进制再填192。4.3 多连接场景下的程序结构当一个PLC需要管理多个TCP连接时程序结构就很重要。我的做法是每个连接一个功能块FB功能块内部封装TSEND_C/TRCV_C和对应的CONNECT数据块。功能块的输入参数包括触发信号、发送数据区、接收数据区输出参数包括完成、忙、错误、状态。这样做的好处是连接ID在功能块内部固定不会被外部误改每个连接的逻辑独立互不干扰后期增加或删除连接时只需增删功能块不影响其他连接。功能块的接口设计如下FUNCTION_BLOCK FB_SendToMES VAR_INPUT Trigger: Bool; // 上升沿触发发送 SendData: Array[0..99] of Byte; // 发送数据区 DataLength: Int; // 发送长度 END_VAR VAR_OUTPUT Done: Bool; Busy: Bool; Error: Bool; Status: Word; END_VAR VAR SendInstance: TSEND_C; // TSEND_C实例 ConnectDB: TCON_IP_v4; // 连接描述 EdgeDetect: R_TRIG; // 上升沿检测 END_VAR在功能块的代码里ConnectDB的ID字段固定填一个值比如1。这个值在整个项目里是唯一的。如果项目里有三个连接就建三个功能块ID分别填1、2、3。4.4 连接ID冲突的检测方法即使有了规范也难免出错。我总结了几种检测连接ID冲突的方法方法一编译时检查。博途在编译时会检查连接ID的唯一性。如果两个CONNECT数据块用了同一个ID编译会报错“连接ID重复”。但注意这个检查只在同一个PLC的同一个通讯接口下有效。如果两个连接用了不同的接口比如一个用以太网口一个用CM模块即使ID相同编译也不报错但运行时可能冲突。方法二在线诊断。在博途的“在线和诊断”里可以查看“连接”状态。每个已建立的连接会显示连接ID、本地端口、对端IP和端口、连接状态。如果看到两个连接的对端信息相同但连接ID不同或者连接ID相同但对端信息不同就说明有问题。方法三抓包分析。用Wireshark抓PLC网口的包看TCP流的分布。每个TCP连接对应一个四元组源IP、源端口、目的IP、目的端口。如果两个TSEND_C用了同一个连接ID它们的数据会出现在同一个TCP流里。如果两个TSEND_C的目标不同你会看到同一个TCP流里出现了发往不同目的端口的数据这就是连接ID冲突的铁证。5. 实操过程从故障复现到问题解决5.1 故障复现环境搭建为了彻底搞清楚连接ID冲突的机制我在实验室搭了一个复现环境硬件S7-1200 CPU 1214C DC/DC/DC固件V4.5两台工控机分别运行MES模拟软件和Modbus TCP Server模拟软件。软件博途V16Wireshark 3.6。网络PLC和两台工控机接在同一台交换机上网段192.168.1.0/24。PLC程序里放两个TSEND_C实例都往工控机1的2000端口发数据。第一个TSEND_C发10字节的“AAAA...”第二个TSEND_C发10字节的“BBBB...”。两个CONNECT数据块的连接ID都填1。触发方式是两个TSEND_C交替触发间隔100ms。5.2 故障现象记录下载程序后工控机1的MES模拟软件开始接收数据。前几秒正常收到的数据包要么全是A要么全是B。但大约10秒后开始出现“AB混排”的数据包——比如前5字节是A后5字节是B。用Wireshark抓包看到TCP流里出现了这样的序列No. Time Source Destination Protocol Length Info 1 0.000000 192.168.1.10 192.168.1.100 TCP 60 2000 → 49152 [ACK] Seq1 Ack1 Win256 Len0 2 0.100000 192.168.1.10 192.168.1.100 TCP 70 2000 → 49152 [PSH, ACK] Seq1 Ack1 Win256 Len10 3 0.200000 192.168.1.10 192.168.1.100 TCP 70 2000 → 49152 [PSH, ACK] Seq11 Ack1 Win256 Len10 4 0.300000 192.168.1.10 192.168.1.100 TCP 70 2000 → 49152 [PSH, ACK] Seq21 Ack1 Win256 Len10第2个包是A数据第3个包是B数据第4个包又是A数据。但接收端MES软件按固定长度读取它读到第2个包后缓冲区里还剩0字节读到第3个包后缓冲区里有10字节B读到第4个包后缓冲区里有10字节B10字节A。如果MES软件每次读15字节就会把B的后5字节和A的前10字节拼成一个包数据就错位了。5.3 根因确认抓包结果证实了连接ID冲突的假设两个TSEND_C用了同一个连接ID它们的数据被发到了同一个TCP连接上。接收端无法区分哪些数据来自哪个TSEND_C只能按字节流顺序读取导致数据错位。进一步分析PLC的连接管理表发现连接ID1的表项只有一个两个TSEND_C实例都在操作这个表项。第一个TSEND_C触发时它把数据写入表项的发送缓冲区TCP栈把数据封装成报文段发出。第二个TSEND_C触发时它发现表项已存在直接复用把数据追加到发送缓冲区。两个数据包在TCP流里紧挨着接收端就混在一起了。5.4 解决方案实施解决方案很简单给两个TSEND_C分配不同的连接ID。第一个填1第二个填2。同时两个CONNECT数据块的目标端口也改成不同比如2000和2001这样接收端可以按端口区分数据来源。修改后重新下载抓包看到两个独立的TCP流No. Time Source Destination Protocol Length Info 1 0.000000 192.168.1.10 192.168.1.100 TCP 60 2000 → 49152 [ACK] Seq1 Ack1 Win256 Len0 2 0.100000 192.168.1.10 192.168.1.100 TCP 70 2000 → 49152 [PSH, ACK] Seq1 Ack1 Win256 Len10 3 0.100000 192.168.1.10 192.168.1.100 TCP 60 2001 → 49153 [ACK] Seq1 Ack1 Win256 Len0 4 0.200000 192.168.1.10 192.168.1.100 TCP 70 2001 → 49153 [PSH, ACK] Seq1 Ack1 Win256 Len10两个连接分别用2000和2001端口数据流完全独立接收端按端口读取不再错位。5.5 连接ID分配检查表为了在项目中避免类似问题我做了一张连接ID分配检查表每次新建连接时对照填写检查项要求备注连接ID是否唯一同一PLC内不重复不同接口也建议不重复连接ID是否连续从1开始不跳号便于维护CONNECT数据块是否独立每个连接一个DB不共用目标IP和端口是否明确记录在项目文档避免后期遗忘TSEND_C和TRCV_C是否配对同一连接用同一ID收发分离时注意是否动态修改连接ID禁止编译前确定6. 常见问题与排查技巧实录6.1 TSEND_C报错16#8085是什么意思16#8085是TSEND_C的常见错误码含义是“连接已存在但参数不匹配”。出现这个错误通常是因为同一个连接ID被两个CONNECT数据块使用但两个数据块的参数如目标IP、端口不同。PLC在建立第二个连接时发现连接ID对应的表项已经存在但参数和当前请求的不一致就报这个错。解决方法检查所有CONNECT数据块确保连接ID唯一。如果确实需要多个连接分配不同的ID。6.2 为什么重启PLC后故障消失运行一段时间又出现这是连接ID冲突的典型特征。重启PLC后连接管理表被清空所有连接重新建立。如果两个TSEND_C的触发时机不同第一个先建立连接第二个后触发时可能还没建立自己的连接就暂时复用了第一个的连接。运行一段时间后两个TSEND_C的触发节奏稳定下来冲突就显现了。另一种可能是TCP连接有超时重连机制。当连接断开时TSEND_C会尝试重连。如果两个TSEND_C共用连接ID重连时可能一个先重连成功另一个复用了这个连接导致数据错位。6.3 抓包时如何快速定位连接ID冲突用Wireshark抓包过滤条件设为“tcp.port 目标端口”。如果看到同一个TCP流里出现了不同格式的数据包比如有的包是10字节有的包是20字节而且交替出现基本可以确定是连接ID冲突。更精确的方法是看TCP流的源端口。正常情况下每个TCP连接有独立的源端口。如果两个TSEND_C用了同一个连接ID它们的数据会出现在同一个源端口的TCP流里。如果两个TSEND_C的目标不同你会看到同一个源端口发往不同目的端口的数据这就是冲突。6.4 连接ID和本地端口的关系连接ID是PLC内部的标识本地端口是TCP协议层的标识。一个连接ID对应一个本地端口。当TSEND_C主动建立连接时本地端口由系统自动分配通常是49152-65535范围内的随机端口。当TSEND_C被动建立连接时本地端口需要在CONNECT数据块里指定。如果两个连接ID用了同一个本地端口且都是被动建立PLC会报错“端口已被占用”。所以被动建立时本地端口也必须唯一。6.5 常见问题速查表现象可能原因排查方法解决措施数据错位连接ID冲突抓包看TCP流分配唯一连接ID报错16#8085连接ID重复且参数不匹配检查CONNECT数据块修改连接ID重启后正常运行后故障连接ID冲突导致重连混乱在线诊断看连接状态分配唯一连接ID连接建立失败本地端口被占用检查被动连接的本地端口修改本地端口数据发送不出去CONT引脚为0检查CONT引脚逻辑保持CONT1接收数据为空TRCV_C连接ID错误检查TRCV_C的CONNECT与TSEND_C配对6.6 一个容易被忽略的细节连接ID的数据类型在博途里连接ID的数据类型是INT范围-32768到32767。但实际可用的连接ID是1到4095不同CPU型号可能不同。填0或负数会导致编译错误。填超过4095的值编译可能通过但运行时PLC可能无法建立连接。我习惯用1到100的范围足够大多数项目使用。如果项目连接数超过100说明架构可能需要优化——一个PLC管理上百个TCP连接CPU的通讯负载会很高建议用通讯模块或换更高性能的CPU。7. 连接管理的最佳实践与经验总结7.1 长连接还是短连接TSEND_C的CONT引脚决定了连接是长连接还是短连接。CONT1时连接建立后保持直到CONT变0或连接超时。CONT0时每次发送都建立新连接发送完关闭。长连接的优点是建立连接的开销只付一次后续发送延迟低。缺点是连接一直占用PLC的连接资源如果对端异常断开PLC需要时间检测并重连。短连接的优点是每次发送都是新连接对端状态不影响下次发送。缺点是每次发送都要三次握手延迟高且频繁建立关闭连接会增加网络负载。我的经验是数据发送频率高于1次/秒的用长连接低于1次/分钟的用短连接介于两者之间的看对端稳定性。如果对端是工控机上的SCADA通常用长连接如果对端是云端服务器可能用短连接更稳妥。7.2 连接断开后的重连策略TSEND_C在检测到连接断开后如果CONT仍为1会自动尝试重连。重连的时间间隔由PLC的通讯栈控制通常是几秒到几十秒。如果对端长时间不可达TSEND_C会报错ERROR1STATUS显示具体错误码。我的做法是在程序里监控TSEND_C的ERROR和STATUS如果连续多次重连失败就触发报警提示操作工检查网络或对端设备。同时可以加一个计数器记录重连次数超过阈值后强制将CONT置0再置1触发一次全新的连接建立。7.3 连接ID与程序可读性连接ID是数字程序里看到“ID1”不知道是哪个连接。我的做法是在CONNECT数据块的注释里写清楚这个连接的用途比如“ID1: 发往MES的扭矩数据目标192.168.1.100:2000”。同时在功能块的命名上体现连接用途比如“FB_SendTorqueToMES”。这样后期维护时看到功能块名字就知道是哪个连接看到注释就知道目标地址和端口不用翻文档。7.4 一个真实的教训连接ID冲突导致产线停摆回到开头那个汽车零部件项目。故障发生后我一开始怀疑是MES软件的问题查了半天MES的日志和代码没发现问题。后来用Wireshark抓包才看到两个TSEND_C的数据混在同一个TCP流里。修改连接ID后故障消失。这次故障导致产线停摆三个小时直接损失不小。事后复盘根本原因就是新建连接时图省事两个CONNECT数据块都用了默认的连接ID1。如果当时花一分钟检查一下连接ID就能避免这三个小时的损失。所以我现在养成了一个习惯每次新建TCP连接第一件事就是分配一个唯一的连接ID并在项目文档里记录。这个习惯看起来麻烦但比起产线停摆的代价这点麻烦不值一提。7.5 连接ID管理的检查清单最后我把连接ID管理的要点整理成一张检查清单每次项目调试前对照检查所有CONNECT数据块的连接ID是否唯一连接ID是否从1开始连续分配每个连接ID是否在项目文档中有记录TSEND_C和TRCV_C是否配对使用同一连接ID被动连接的本地端口是否唯一CONT引脚的逻辑是否正确长连接保持为1是否有动态修改连接ID的代码如有删除在线诊断里是否能看到所有连接状态正常这张清单我贴在工位上每次调试前花五分钟过一遍省下了无数排查时间。TCP通讯本身不复杂复杂的是连接管理。把连接ID管好TSEND_C用起来就很顺手管不好它就是产线上的定时炸弹。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →