简介:这份资源是一套面向工业自动化与上位机开发者的C#以太网通信例程,由国外工程师编写,用于解决AB PLC(Allen Bradley)与PC之间实时数据交互的问题,适合具备一定C#基础、希望掌握PLC通信编程的开发者参考学习。压缩包共45个文件,约308KB,以vb源码、resx与resources资源文件、exe可执行程序、manifest清单、xml配置及sln解决方案文件为主,另含dll库、pfx证书与settings配置,完整保留了Visual Studio工程结构与调试信息。例程围绕建立连接、设备发现、数据映射、读写操作、错误处理、持续监控与关闭连接等关键环节展开,代码可直接在VS中打开调试,帮助读者理解如何借助RSNetworx for Ethernet/IP等库实现PLC状态读取与输出控制。目前已有261人学习下载,可作为工业通信项目开发的实用参考范例。
1. 从一份老外写的 AB PLC 以太网通讯 C# 例程说起
手上拿到一个压缩包,名字叫「AB PLC 与 PC 通过以太网进行通讯 C# 例程 是个老外编写的程序.zip」,很多人第一反应是解压、找 sln、点运行。但真到车间里你会发现,能不能跑通跟代码写得好不好关系不大,卡住你的往往是 RSLinx 的驱动没配对、PLC 的 IP 段跟 PC 不在一个网、或者 ControlLogix 的槽号填错。AB PLC(Allen-Bradley,罗克韦尔自动化旗下品牌)在汽车、冶金、水处理这些行业存量极大,上位机用 C# 通过以太网读写它的标签,是工控上位机方向最典型的一类需求。这份例程的价值不在代码本身多优雅,而在于它把「PC 怎么跟 AB PLC 建立一条以太网通讯链路」这件事的骨架给出来了。适合有 C# 基础、手上有或即将有 AB PLC、需要做数据采集或上位机监控的工程师,新手能照着搭环境,熟手能看清 EtherNet/IP 这条链路上哪些参数不能乱动。
2. EtherNet/IP 与 CIP:AB PLC 以太网通讯到底在传什么
2.1 从 TCP 到 CIP,三层结构别搞混
很多人以为「AB PLC 以太网通讯」就是开个 TCP 连上去发字符串,这是最大的误解。AB PLC 走的是 EtherNet/IP 协议,它底下确实是标准以太网和 TCP/UDP,但上面套了一层 CIP(Common Industrial Protocol,通用工业协议)。CIP 才是真正定义「你怎么读一个标签、怎么写一个值」的那一层。结构上分三段:最底下是以太网物理层和 TCP/IP 协议栈,中间是 EtherNet/IP 封装层(Encapsulation),最上面是 CIP 消息层。你发出去的每一个读写请求,都要先被包成 CIP 消息,再套上 EtherNet/IP 的封装头,最后才走 TCP 或 UDP 发出去。
这里有个关键分叉:显式消息(Explicit Messaging)走 TCP,端口 44818,用来做标签读写、读 PLC 信息这类请求-应答式的操作;隐式消息(Implicit Messaging,也叫 I/O 连接)走 UDP,端口 2222,用来做实时 I/O 数据交换。上位机做数据采集,绝大多数场景用的是显式消息,也就是 TCP 44818。搞清这一点,后面选库、配参数才不会跑偏。CIP 消息本身又分服务码,读标签是 0x4C(Read Tag Service),写标签是 0x4D(Write Tag Service),这些在底层库封装好之后你一般不用手写,但排错时抓包看到这些码,能立刻判断请求有没有发对。
2.2 为什么老外例程常用 EtherNet/IP 封装库而不是自己撸 socket
自己用System.Net.Sockets从零拼 CIP 报文不是不行,但工作量在于:封装头的会话句柄(Session Handle)要自己维护,注册会话(Register Session)要先发一条特定命令,每次读写还要拼 CIP 路径(比如标签名要编码成 ANSI 字符串加长度前缀)。老外写的例程通常直接引用一个封装库,最常见的是基于 .NET 的 EtherNet/IP 库,把 Register Session、Read Tag、Write Tag 都封成方法。这样做的好处是稳定,坏处是你不知道底下发生了什么,一旦连不上就抓瞎。
我一般建议:第一次接触,先用封装库把链路跑通,确认 PLC 侧配置没问题;等要优化性能或者排查诡异断连时,再回头抓包看 CIP 层。选型上,如果只是读几十个标签、刷新周期 100ms 以上,封装库完全够用;如果要读上千点、周期 10ms 级,就得考虑走隐式消息或者换更底层的实现。这份例程属于前者,定位就是「把链路讲清楚」的教学型代码,不要指望它扛高并发。
2.3 最小可跑通的链路:从 PC 到 PLC 的四个前提
在写第一行 C# 之前,有四个前提必须先在 PC 和 PLC 两侧确认,缺一个都连不上。第一,PC 和 PLC 的 IP 要在同一网段,比如 PLC 是 192.168.1.10,PC 就设成 192.168.1.100,子网掩码一致。第二,PLC 侧要在 RSLogix 5000 / Studio 5000 里确认以太网模块的 IP 已经下载进去,不是只在离线工程里改了没下装。第三,PC 上要装 RSLinx Classic 或者 FactoryTalk Linx,用来做驱动配置和连通性验证,虽然 C# 程序不直接依赖它,但它是你排错时判断「是网络问题还是代码问题」的分水岭。第四,防火墙要放行 44818 端口,Windows 防火墙默认可能拦。
验证顺序我习惯这样:先在 PC 上 ping PLC 的 IP,通了说明网络层没问题;再打开 RSLinx,用「Ethernet devices」驱动添加 PLC 的 IP,能看到设备图标说明 EtherNet/IP 层通了;最后才跑 C# 程序。这个顺序能把问题一层层剥开,比一上来就调试代码高效得多。
3. 把例程跑起来:C# 侧的环境、引用与第一段读写代码
3.1 环境准备与依赖引用
先明确环境:Visual Studio 2019 或 2022,.NET Framework 4.7.2 以上(老外例程很多是 Framework 项目,别急着往 .NET Core 迁,先跑通再说),目标平台 x64 或 AnyCPU 都行。解压压缩包后,通常能看到一个 .sln 和若干 .cs 文件,核心逻辑集中在一个通讯类里。如果例程引用了第三方 EtherNet/IP 库,先确认这个库的 DLL 在不在,缺了就去 NuGet 找同名包,或者按例程里的 using 命名空间反推。
下面这段是我按常见封装库的用法整理的最小读写示例,命名空间和类名以你实际用的库为准,逻辑是通用的:
using System; // 假设封装库命名空间为 EthernetIP,实际以例程为准 using EthernetIP; class Program { static void Main(string[] args) { // PLC 的 IP 地址,必须与 PC 同网段 string plcIp = "192.168.1.10"; // ControlLogix 的槽号,CompactLogix 通常为 0 byte slot = 0; // 建立与 PLC 的会话,底层会发 Register Session 命令 var plc = new EthernetIPClient(plcIp, slot); plc.Connect(); // 读一个 DINT 类型标签,标签名要和 Studio 5000 里完全一致 int speed = plc.ReadTag<int>("Line1_Speed"); Console.WriteLine("当前速度: " + speed); // 写一个 BOOL 类型标签 plc.WriteTag<bool>("Line1_Start", true); plc.Disconnect(); } }逻辑说明:Connect()内部做的是 TCP 连接加 Register Session,会话句柄在这一步拿到,后续所有读写都复用这个句柄。ReadTag<T>里的泛型 T 决定按几个字节解析返回数据,DINT 是 4 字节整型,BOOL 是 1 字节。参数说明:plcIp填 PLC 以太网模块的地址,slot对 ControlLogix 是模块在机架上的槽位,对 CompactLogix 一般是 0,填错会连不上或者读到错误数据。标签名大小写敏感,Line1_Speed和line1_speed在 PLC 里是两个东西。
3.2 标签名、数据类型与 CIP 路径的对应关系
C# 侧读标签,最容易翻车的地方是标签名和数据类型对不上。AB PLC 的标签分两类:控制器作用域(Controller Scope)和程序作用域(Program Scope)。控制器作用域的标签直接写名字就行,程序作用域的标签要带程序名,格式类似Program:MainProgram.TagName。例程里如果只演示了控制器标签,你换成程序标签时忘了加前缀,就会报「标签不存在」。
数据类型对应关系也要记牢:PLC 里的 DINT 对应 C# 的int,REAL 对应float,BOOL 对应bool,INT 对应short,STRING 是结构体,不能直接当string读,要按自定义结构解析。数组标签读的时候要指定元素个数,比如Line1_Data[10]读 10 个元素,返回的是数组。这些对应关系错一个,轻则数据乱码,重则程序抛异常。我一般会在 Studio 5000 里先把要读的标签列个表,标好名字、类型、作用域,再照着写 C# 代码,比边写边猜靠谱得多。
3.3 用 RSLinx 验证链路再跑代码
代码写好了别急着 F5。先在 RSLinx Classic 里建一个 Ethernet devices 驱动,把 PLC 的 IP 加进去,看能不能浏览到设备。能浏览到,说明 PC 到 PLC 的 EtherNet/IP 链路是通的,这时候再跑 C# 程序,如果还连不上,问题就在代码或库的用法上。如果 RSLinx 都连不上,那跟代码没关系,回去查网段、防火墙、PLC 的以太网模块配置。
RSLinx 里还有个有用的功能是「RSWho」,能看到整个网络的设备树。如果 PLC 在树里但 C# 连不上,重点查端口 44818 有没有被占、有没有被杀毒软件拦。这一步花五分钟,能省掉后面半小时的瞎调。跑通之后,建议把读写周期、超时时间这些参数记下来,后面做稳定性优化时是基准。
4. 避坑与排查:AB PLC 以太网通讯最常见的五类翻车
4.1 连不上,报「无法建立连接」
现象:程序一启动Connect()就抛异常,提示连接超时或拒绝。原因通常有三个:PC 和 PLC 不在同一网段;PLC 的以太网模块 IP 没下装;Windows 防火墙拦了 44818。解决:先 ping,不通就改网段;ping 通但 RSLinx 也连不上,回 Studio 5000 确认 IP 已下载并重启模块;都通了还不行,临时关防火墙测试,确认后加放行规则。血泪经验是,很多人改完 PLC 的 IP 忘了下装,离线工程里看着是对的,实际模块还是老地址。
4.2 能连上但读标签报「标签不存在」
现象:连接成功,一读标签就报错,提示 Tag not found。原因:标签名拼错、大小写不对、程序作用域标签没加Program:前缀、或者标签在 PLC 里根本没创建。解决:在 Studio 5000 的标签监视器里复制标签名,粘贴到代码里,别手敲;程序标签确认前缀格式;确认标签已经下载到 PLC,不是只在离线工程里。注意,AB PLC 标签名区分大小写,这是新手最容易忽略的点。
4.3 读回来的数据是乱码或明显不对
现象:读 DINT 返回一个巨大或负得离谱的数,读 REAL 返回 NaN。原因:数据类型不匹配,比如 PLC 里是 REAL,C# 按 int 解析,字节序就错了;或者读数组时元素个数填错,把相邻标签的数据也读进来了。解决:对照标签表逐个核对类型;数组读取时明确元素个数;REAL 类型确认库有没有做字节序转换,EtherNet/IP 底层是大端,C# 是小端,封装库一般会处理,但自己拼报文时要注意。
4.4 跑一段时间后断连
现象:程序刚跑正常,几小时或几天后连接断开,重连又好了。原因:PLC 侧对会话数量或空闲时间有限制,长时间不通讯会被回收;或者网络里有其他设备抢占带宽;也可能是 PC 休眠导致网卡断开。解决:加心跳,定期读一个固定标签保持会话活跃;设置合理的重连逻辑,断连后自动重连而不是直接崩;PC 侧关掉网卡节能和休眠。这个坑在长时间运行的上位机里特别常见,后悔药就是一开始就把重连机制写好。
4.5 写标签没反应或把 PLC 写停机
现象:写 BOOL 启动标签,PLC 没动作;或者写了一个不该写的值,PLC 报故障停机。原因:写标签的权限或作用域不对;写入了 PLC 正在用作逻辑判断的标签,造成冲突;写的数据类型或范围超限。解决:确认要写的标签允许外部写入;关键标签在 PLC 侧做写保护或互锁;写入前在代码里做范围校验。这个坑最严重,调试阶段建议先在 PLC 里做好安全逻辑,别让上位机随便写。
5. 进阶:把例程改造成能长期跑的数据采集程序
5.1 批量读与订阅式采集的取舍
例程演示的是单标签读写,实际项目里往往要读几十上百个标签。逐个读效率低,因为每次读都是一次完整的 CIP 请求-应答往返。常见做法是批量读:把连续的标签放在一个数组里,一次读回来再拆。AB PLC 支持读一个数组标签的多个元素,如果标签本身不连续,可以读一个自定义结构体(UDT)标签,把要采集的点打包进 UDT,一次读回。这样能把请求次数从上百次降到几次。
再进一步是订阅式采集,也就是用隐式消息建立 I/O 连接,PLC 主动推数据。这种方式实时性最好,但配置复杂,要在 PLC 侧配置 Produced/Consumed 标签,C# 侧也要支持隐式消息。我一般建议:刷新周期 100ms 以上、点数几百以内,批量显式读就够;要到 10ms 级、点数上千,再考虑隐式。别为了追求实时性一上来就上隐式,调试成本高,收益未必对得上。
5.2 重连、超时与线程安全
长期运行的程序,重连逻辑是必须的。我的习惯是包一层:每次读写前检查连接状态,断了就重连,重连失败按指数退避重试,比如 1 秒、2 秒、4 秒、8 秒,避免疯狂重试把 PLC 拖垮。超时时间设 3 到 5 秒,太短容易误判,太长卡界面。线程安全方面,如果多个线程共用一个通讯对象,读写要加锁,或者每个线程独立连接。AB PLC 对同时建立的会话数有限制,别开太多连接。
// 带重连的读写封装示意 private bool EnsureConnected() { if (plc != null && plc.IsConnected) return true; // 指数退避重连,最多重试 5 次 for (int i = 0; i < 5; i++) { try { plc = new EthernetIPClient(plcIp, slot); plc.Connect(); return true; } catch { // 1s, 2s, 4s, 8s, 16s System.Threading.Thread.Sleep(1000 * (int)Math.Pow(2, i)); } } return false; }逻辑说明:IsConnected判断当前会话是否有效,无效则重建。退避间隔用 2 的幂次增长,避免网络恢复瞬间大量重连请求。参数说明:重试次数 5 次是经验值,网络稳定的现场可以减到 3 次,网络差的可以加到 8 次,但总等待时间别超过上位机的刷新周期,否则界面会卡。
5.3 用抓包和日志定位偶发问题
偶发断连、数据跳变这类问题,光看代码看不出来,得靠抓包和日志。抓包用 Wireshark,过滤tcp.port == 44818,能看到每一次 CIP 请求和应答。如果看到请求发出去了但没应答,是 PLC 侧或网络问题;如果应答回来了但数据不对,是解析问题。日志方面,把每次连接、断开、读写失败的时间点和错误码记下来,出问题时按时间线对,比猜快得多。
我自己的习惯是,任何要长期跑的采集程序,上线前先挂一周日志,观察有没有规律性的断连。有一次就是靠日志发现每天凌晨三点断一次,最后查到是网络里另一台设备定时重启占了网关。这种问题不靠日志根本找不到。希望帮到你。
本文还有配套的精品资源,点击获取