
简介倍福 CX5020 控制器与第三方设备的 TCP/IP 通讯是工业自动化现场常见的调试需求。围绕这一场景文档系统讲解了 TwinCAT3 环境下客户端与服务器两种模式的实现方法重点包括 Socket Tool 以太网调试助手的使用以及连接、发送、接收、关闭四个功能块的变量建立与程序编写。文档以 CX5020 与 Socket Tool 的实际实验为线索清晰列出 IP 地址、端口号、数组收发等关键配置并给出每步操作完成后的验证现象比如连接成功后本地与远程地址显示、数组接收到预设数据等方便读者判断对错。除实验步骤外文档还额外提供软硬件配置清单、配套 PLC 例程下载链接以及通讯完毕后的端口关闭建议整体覆盖从环境搭建到结果验证的完整流程。对于没有接触过 TwinCAT3 Socket 通讯的工程师文档提供了清晰入门路径可复用其中的地址与端口配置思路包内为 1 个 docx 文件约 672KB内容由倍福工程师整理结构紧凑、可操作性强。当前已有 193 人学习浏览适合正在做倍福控制器与第三方设备网络对接、希望快速上手 TwinCAT3 网络编程的自动化工程师参考。1. WinCAT3 的 TCP/IP 通讯到底在做什么如果你第一次看到“WinCAT3 TCP IP 通讯.docx”这个文件大概率会以为它是某台工控机上的配置截图或者是外包售后留下的调试记录。实际上WinCAT3 是德国倍福Beckhoff自动化软件 TwinCAT 3 在中文技术圈里最常见的误写很多人把“Twin”看成了“Win”而 .docx 后缀说明这份文档多半是写给现场工程师的通讯交接说明。TCP/IP 通讯在 WinCAT3 里从来不是“装个库就能通”那么简单它牵涉到实时任务和非实时任务的边界、Windows 网络栈与 TwinCAT 实时内核的协作方式以及上位机到底走标准 Socket 还是走 ADS 协议的选择。这篇文章我会按我习惯的排查路径来写先梳理 TCP/IP 四层模型在 WinCAT3 里怎么映射再给出一套可复现的 PLC 端 TCP 服务器和 C# 客户端的代码最后补上调试和性能优化的关键参数。适合正在写上位机通讯程序的自动化工程师也适合被现场偶发断连折腾得想换网关的售后朋友。2. TCP/IP 四层模型与 WinCAT3 通讯选型2.1 把 TCP/IP 四层模型映射到 WinCAT3 的通讯栈很多 PLC 工程师习惯把“TCP/IP 通讯”理解成一个黑盒子调功能块时填个 IP 和端口就算完事。但是当你面对 WinCAT3 这种实时系统和 Windows 共存的架构就必须清楚 TCP/IP 四层模型里每一层到底由谁负责否则出问题时你连日志都找不对地方。最常见的映射方式是链路层和网络层由 Windows 的 TCP/IP 协议栈驱动承担物理层由网卡和驱动承担而传输层和应用层则取决于你选择的通讯方式。WinCAT3 的实时核如 TwinCAT 3 Runtime跑在 Windows 之上但它的实时任务和 Windows 进程有严格的优先级隔离。标准 Socket 通讯需要在非实时任务里调用或者通过 TF6310 TCP/IP 功能库来让实时任务也能间接使用 Windows 的 Socket而 ADS 协议虽然底层也是 TCP/IP但它有自己的应用层格式常用于 TwinCAT 系统间的通讯。我一般会把这条路分成三类第一类是纯 Windows 进程比如 C# 上位机直接与远端设备建立 Socket第二类是 WinCAT3 PLC 程序里调用 TF6310 库让 PLC 也能作为 TCP 端点第三类是使用 ADS这是倍福专有的应用层协议用于 TwinCAT 与外部系统之间交换过程数据。你看到的“WinCAT3 TCP IP 通讯”多半是指第二种。2.1.1 链路层和网络层为什么不需要你关心在 WinCAT3 环境下网卡的 IP 地址、子网掩码、默认网关都是 Windows 网络配置里设置的TwinCAT 的实时任务不会直接接管这些底层参数。所以排查通讯问题时不要一上来就怀疑 TwinCAT 的底层驱动而是先用 Windows 的 ipconfig 确认 IP 是否存在冲突用 ping 确认物理链路。只有当通讯抖动明显并且你确认是 Windows 网络栈调度延迟过大时才需要考虑把网卡驱动换成 TwinCAT 的实时网卡驱动比如倍福的 EtherCAT 网卡但那已经超出了标准 TCP/IP 通讯的范畴。这也是为什么很多老工程师会强调WinCAT3 的 TCP/IP 通讯链路层和网络层完全透明你能控制的只有传输层的 Socket 参数和应用层的报文格式。2.2 常见做法PLC 端 Socket 功能块 vs 上位机 Socket做 WinCAT3 TCP/IP 通讯第一步要决定谁做客户端谁做服务器。现场最稳的组合是WinCAT3 PLC 做服务器上位机C#、Python、组态王做客户端主动连接。因为服务器端可以长期监听不需要定时重连逻辑简单。反过来如果 PLC 做客户端则要处理服务器的 IP 变化、重连延时和断线粘包逻辑复杂得多。这里我不讨论 ADS因为那是另一个协议族而标准 TCP/IP 通讯的唯一正路就是 Socket。在 WinCAT3 的 PLC 环境里TF6310 库提供了一套和 Windows Socket 几乎一一对应的功能块例如 FB_SocketCreate创建、FB_SocketBind绑定、FB_SocketListen监听、FB_SocketAccept接受连接、FB_SocketSend发送、FB_SocketReceive接收。它们的命名和调用方式和常见嵌入式 socket 风格非常接近。如果你不想引入额外的商业库也可以直接用 TwinCAT 3 的 C 模块编写一个独立的 TCP 通讯程序然后通过 ADS 或共享内存和 PLC 交换数据但那需要编译环境这里不展开。我下面给出的代码是基于 TF6310 的因为这是中文资料里最容易找到的库也是我接触过的大多数工程项目实际使用的路径。2.3 一个对比表格TF6310、普通 TCP 功能块、C# 直接写为了让你更清楚选型我整理了一个对比表格。这个表我没有收录第三方的免费 Socket 库因为那些库的维护状态不明不建议在生产环境直接用。通讯方式性能是否需要额外授权适用场景典型延迟TF6310 功能块中每次收发经过 Windows 网络栈需要 TF6310 授权PLC 需要主动收发数据1-5 msADS高支持实时通讯不需要额外授权基础 TwinCAT 自带TwinCAT 系统间或控制器和上位机交换过程数据0.1-1 msC# 直接写 Socket高使用 Windows 原生 Socket不需要授权非实时上位机、PC 间通讯0.1-1 ms注意表格里 TF6310 的“中”不是指它慢而是它把 Socket 包了一层导致每次调用都涉及实时任务到 Windows Socket 的上下文切换频率太高时吞吐量会受限。如果你的数据量超过几百千字节每秒或者周期要求低于 1 毫秒我建议直接使用 ADS 或把数据采集放到独立上位机程序里PLC 只处理控制逻辑。3. 在 WinCAT3 里跑通一个 TCP/IP 服务器ST 语言3.1 最小工程添加 TF6310 库并创建 FB_TcpServer在 TwinCAT 3 的 PLC 工程项目里右键 References 选择 Add Library在搜索框输入 TCP/IP就能看到 TF6310 的库条目。注意只有安装并授权了 TF6310 之后这个库才可用。没有授权时编译会提示许可证错误程序可以下载但 Socket 功能块运行时会返回错误码 701 之类的本地错误。这个我在现场遇到过所以建议提前检查 TwinCAT 3 的版本和 TF6310 版本是否匹配。下面这段 ST 代码实现了一个最简单的 TCP 服务器在端口 8080 上监听接受连接后把收到的数据原样返回。为了便于阅读我把它写成了一个顺序状态机没有用复杂的嵌套循环。PROGRAM MAIN VAR bInit : BOOL : FALSE; eState : INT : 0; hListener : T_SOCKET : 0; hClient : T_SOCKET : 0; fbCreate : FB_SocketCreate; fbBind : FB_SocketBind; fbListen : FB_SocketListen; fbAccept : FB_SocketAccept; fbReceive : FB_SocketReceive; fbSend : FB_SocketSend; nPort : UDINT : 8080; pBuffer : POINTER TO BYTE; nRecvLen : UDINT : 0; nSendLen : UDINT : 0; aData : ARRAY[1..1024] OF BYTE; i : INT; END_VAR代码里 pBuffer 是指针用来把 ARRAY 类型的地址传给接收功能块。实际使用时需要保证缓冲区长度不小于一次接收的最大报文长度我用 1024 字节是常规值如果要传比较大的 JSON 或图像要按实际需求调大。nPort 是端口号注意不要和 Windows 已有服务冲突比如 8080 常被 HTTP 代理占用现场常常换成 8501 或 8502。3.1.1 声明变量和初始化上面的变量声明里所有 FB_ 开头的变量都是 TF6310 的功能块实例。初始化在程序第一个周期里完成主要工作是创建监听 Socket 并绑定端口。创建一个 Socket 需要指定协议族、类型和协议但 TF6310 的 FB_SocketCreate 已经把大部分参数封装好了只需要在 fbCreate 的输入引脚里给出地址族和类型。典型地址族 AF_INETIPv4类型 SOCK_STREAMTCP。初始化代码可以这样写CASE eState OF 0: IF NOT bInit THEN bInit : TRUE; fbCreate.eAddrFamily : AF_INET; fbCreate.eType : SOCK_STREAM; fbCreate.eProtocol : IPPROTO_TCP; fbCreate(bExecute : TRUE, hSocket hListener); END_IF IF fbCreate.bError THEN eState : 99; ELSIF hListener 0 THEN eState : 1; END_IF 1: fbBind.hSocket : hListener; fbBind.nPort : nPort; fbBind(bExecute : TRUE); IF fbBind.bError THEN eState : 99; ELSIF fbBind.bDone THEN eState : 2; END_IF END_CASE这里犯了一个值得注意的错fbCreate 在 bExecute 为 TRUE 时是边沿触发的需要在 bExecute 置回 FALSE 后再置 TRUE 才有效。但我在代码里只写了一次置位实际工程中必须用一个边沿检测逻辑或者用独立的方法调用功能块。很多新手踩的坑就是这里功能块一直停留在 bBusy 状态因为 bExecute 没有被复位。正确做法是每步启动时用 R_TRIG 检测触发上升沿我下面在完整例子里会处理。这个片段只是让你理解参数结构eAddrFamily、eType、eProtocol 这三个参数决定了 Socket 的类型TCP/IP 通讯基本固定填 AF_INET、SOCK_STREAM、IPPROTO_TCP。3.2 实现连接监听与数据接收监听流程是先 Listen然后进入一个循环每次调用 Accept 等待客户端接入。一旦有客户端连上就调用 Receive 等待数据收到后调用 Send 返回给客户端。我给出的示例没有处理断开重连但足以验证一条完整的通讯链路。CASE eState OF 2: fbListen.hSocket : hListener; fbListen.nBacklog : 5; // 最大挂起连接数 fbListen(bExecute : TRUE); IF fbListen.bError THEN eState : 99; ELSIF fbListen.bDone THEN eState : 3; END_IF 3: fbAccept.hSocket : hListener; fbAccept(bExecute : TRUE); IF fbAccept.bError THEN eState : 99; ELSIF fbAccept.bDone THEN hClient : fbAccept.hSocketNew; eState : 4; END_IF 4: pBuffer : ADR(aData[1]); fbReceive.hSocket : hClient; fbReceive.pData : pBuffer; fbReceive.nMaxLen : SIZEOF(aData); fbReceive(bExecute : TRUE); IF fbReceive.bError THEN eState : 99; ELSIF fbReceive.bDone THEN nRecvLen : fbReceive.nRecv; i : 0; FOR i : 1 TO nRecvLen DO // 可以在此添加解析逻辑 END_FOR; fbSend.hSocket : hClient; fbSend.pData : pBuffer; fbSend.nLen : nRecvLen; fbSend(bExecute : TRUE); IF fbSend.bDone THEN eState : 4; // 继续接收 END_IF END_IF END_CASEfbListen.nBacklog 参数是操作系统层允许挂起的连接数超过这个值新的连接会被 TCP/IP 协议栈直接拒绝。本地调试时设 1 或 5 都可以生产环境如果每个周期都会来几个连接建议设 16 以上。fbReceive.nMaxLen 是缓冲区大小如果实际收到的数据比缓冲区大TF6310 会通过 fbReceive.bError 返回错误码 10040缓冲区空间不足而不是自动截断所以建议在应用层设计好报文长度或者把缓冲区加大到消息上限的两倍。Send 功能块的 nLen 是你要发送的字节数这里我直接用了接收到的长度实现回显。3.3 用 C# 客户端验证通讯PLC 端写好之后在上位机用 C# 写一个小客户端来验证。这里我用的是 .NET 的 TcpClient代码很短但足以验证连接、发送和接收。using System; using System.Net.Sockets; class TcpClientTest { static void Main() { using (var client new TcpClient()) { client.Connect(192.168.0.10, 8080); Console.WriteLine(Connected); byte[] data System.Text.Encoding.ASCII.GetBytes(Hello from C#); client.GetStream().Write(data, 0, data.Length); Console.WriteLine(Sent); var buffer new byte[1024]; int len client.GetStream().Read(buffer, 0, buffer.Length); Console.WriteLine($Received: {System.Text.Encoding.ASCII.GetString(buffer, 0, len)}); } } }注意 C# 的 TcpClient 默认就启用了 Nagle 算法小块数据会延迟发送但这里通讯量小不影响验证。192.168.0.10 是 PLC 网卡的 IP你需要改成你的实际地址。运行这段代码之前先确认 PLC 程序已经运行并且 Socket 已经进入监听状态。如果连接超时先检查 Windows 防火墙是否拦截了 C# 程序的入站连接再检查 PLC 的网卡 IP 和上位机是否在同一网段。3.3.1 C# 代码参数说明代码里的 8080 必须和 PLC 端 nPort 一致。TcpClient.Connect 是同步阻塞方法如果网络不通会等待好几秒才抛异常所以我在验证阶段建议先缩短到 2 秒超时client.Connect(192.168.0.10, 8080)没有重载可以指定超时需要使用client.ReceiveTimeout和client.SendTimeout但它们在连接建立后才有意义。真要快速验证可以用异步 ConnectAsync 加 WaitAsync 实现超时控制但这里不展开。3.4 关键参数说明端口、缓冲区、超时端口号选择上我建议避开 8000-9000 这个段因为很多调试代理和 IDE 服务会占用。如果现场使用了防火墙需要在防火墙入站规则里允许该端口和对应程序。缓冲区大小我建议先用 1024 测试再根据你的应用层协议调整。TF6310 的 Receive 功能块不会自动拆包所以如果你发一个 1500 字节的数据它可能被拆成两段你需要在应用层做封包定义比如前四个字节表示长度或者在客户端一次发送完成后关闭连接半关让服务器知道数据结束。很多项目出问题就是这里通讯一会儿正常一会儿丢包其实是粘包和拆包导致的边界问题。超时方面TF6310 没有内置超时参数你需要自己在 PLC 逻辑里监控功能块的处理时间比如记录启动时间超过 3000 毫秒就放弃本次状态并重置 Socket。这个我在第 4 章会展开讲。4. 调试与排错TCP/IP 通讯连不上的常见原因4.1 先用 Windows 网络命令确认链路当 WinCAT3 通讯连不上时我一般不会直接动 PLC 程序而是先用 Windows 自带的网络命令定位问题。第一步是ipconfig /all确认 PLC 的 IP 和子网掩码第二步是ping 对方IP第三步是telnet 对方IP 端口判断端口是否能建连。如果你在 Windows 10 上发现 telnet 命令不存在可以先用Test-NetConnection -ComputerName 192.168.0.10 -Port 8080代替这是 PowerShell 内置的命令。下面是一个典型的排查序列ipconfig /all ping 192.168.0.10 Test-NetConnection 192.168.0.10 -Port 8080 netstat -ano | findstr :8080最后一条命令用于查看本机是否有程序监听 8080 端口。如果netstat输出里没有LISTENING状态的条目说明 PLC 程序根本没有完成监听初始化。这时候去查 TF6310 功能块返回的错误码最常见的是 10048端口被占用和 10049本地地址错误。注意netstat的 PID 可以和任务管理器的 PID 对应这样可以快速判断是不是别的程序占用了端口。4.2 检查 WinCAT3 实时环境与网络隔离TwinCAT 3 的实时环境有一个特性如果 PLC 程序处于 Stop 模式Socket 功能块运行的行为会变得很奇怪有时报错有时挂起。所以确认 PLC 程序处于 Run 模式下再测试通讯是个基本的先决条件。另外如果你在虚拟机上开发 WinCAT3要注意虚拟机网卡的类型。NAT 网络时外部设备访问不到 PLC桥接模式时需要给虚拟机的 WinCAT3 分配一个独立的静态 IP。我现场就遇到过工程师把虚拟机网卡设为 NAT然后死活连不上后来改桥接就通了。Windows 防火墙也是重灾区尤其是 C# 上位机程序没有被加入白名单时。第一次运行 C# 客户端Windows 会弹出一个防火墙提示如果点了取消后续连接全部超时。对于 PLC 的 Socket 服务要检查的是 PLC 所在 Windows 的入站规则。我会用 PowerShell 一次性添加入站规则New-NetFirewallRule -DisplayName WinCAT3 TCP -Direction Inbound -Protocol TCP -LocalPort 8080 -Action Allow这条命令需要在管理员权限下执行。如果整条链路还是不通使用 Wireshark 抓包看 SYN 包和 SYN-ACK 包是否正常这一步能分辨是路由问题还是 Windows 防火墙静默丢弃问题。4.3 常见错误表10049、10061、超时等我把常见的 TF6310 错误码整理成表格这些错误码有些是 Windows Socket 的 WSA 错误码TF6310 会直接透传。错误码含义排查方向10049本地 IP 地址无效检查 PLC 网卡 IP 是否被释放或者绑定的地址不是本机地址10048端口已被占用用 netstat 查端口占用换端口或杀进程10061目标主机拒绝连接服务端没启动监听或者防火墙拦截用 telnet 测试10060连接超时网络不通或 IP 错误检查路由和网线10040缓冲区空间不足接收缓冲区小于对方发送的数据长度调大缓冲区701TwinCAT 许可证错误TF6310 未授权检查 TwinCAT 3 的 License注意 10061 这个错误码非常误导人因为当 PLC 程序处于断点或停止状态时TCP 层已经不再响应握手客户端看到的也是 10061所以不要一看到“拒绝连接”就去查防火墙先看 PLC 是不是还在运行。5. 进阶多客户端、断线重连与性能调优当基础 TCP 通讯跑通之后现场往往会提出更苛刻的需求多个上位机同时连接、PLC 在一段时间后自动重启通讯、数据丢包率不能超过多少。这时候就需要在 WinCAT3 的 Socket 程序里加入一些惯例的处理逻辑。多客户端的最简单做法是循环调用 FB_SocketAccept并把每个新连接的 Socket 存成静态数组。注意 TF6310 的 Accept 功能块一次只能处理一个连接所以你要在主程序里安排一个旋转轮询先检查是否有新客户端再依次处理已有连接的收发。这个方案最多支撑十几个客户端再多就会导致最后建立的客户端等待时间过长。如果一定要支持几十个客户端我建议放弃 PLC 端 Socket改用 C# 写一个反向代理程序把 Socket 收上来的数据通过 ADS 转发给 PLC这样能把网络栈压力从实时任务里剥离出去。断线重连的常规做法是在 PLC 里维护一个心跳计时器如果超过 5000 毫秒没有收到客户端的数据就主动调用 FB_SocketClose 关闭当前 Socket并把状态机重置回监听状态。注意这里关 Socket 时不要立即重新 Listen因为 TCP 协议栈可能进入 TIME_WAIT 状态端口无法立刻复用。你可以设置 fbBind 的 bReuseAddr 属性为 TRUE但那样会增加连接安全性的风险。我一般习惯等 1000 毫秒再重新初始化简单可靠避免现场出现“明明连上又被拒”的诡异现象。性能调优方面Nagle 算法是常见的一个坑。TCP/IP 默认为了减少小包数量会把小的数据包合并到一定程度再发送。在 PLC 通讯中你常常一次只发几十个字节的报文如果启用了 Nagle延迟可能突然变成 40 毫秒甚至更多。在 C# 客户端里可以用client.NoDelay true来禁用 Nagle 算法在 WinCAT3 TF6310 里你可以通过 fbCreate 的 eOption 参数或者使用 FB_SocketSetOption 功能块设置 TCP_NODELAY。另外接收数据的速率往往比 PLC 程序处理速度高这时最有效的做法是把接收缓冲区尽量调大并且只接收一帧就回到主循环而不是在同一周期内反复 Receive。验证你调优是否有效可以用 Wireshark 抓包看包的时间戳间隔也可以参考 TCP/IP 四层模型来定位应用层封包频率和传输层实际包数量不一致就说明 Nagle 或者 ACK 延迟机制在起作用。如果你使用了防火墙也要注意防火墙的 NAT 会话超时可能比你的通讯周期短导致长时间无数据后连接被强制断开这种情况下在应用层保持一个低频心跳帧是最好的解决办法。本文还有配套的精品资源点击获取