工业通讯协议(五)- EtherNet/IP

EtherNet/IP是由罗克韦尔自动化公司在1990年代后期开发的工业以太网通讯协议,现由ODVA(开放设备网供应商协会)管理
。其名称中的“IP”并非指互联网协议(Internet Protocol),而是“工业协议”(Industrial Protocol)的缩写。
作为一种应用层协议,EtherNet/IP建立在标准以太网技术之上,采用通用的TCP/IP协议栈,并通过CIP(通用工业协议) 实现设备间的无缝通信。这意味着它既能享受标准以太网的成本优势和技术演进红利,又能满足工业环境对实时性和可靠性的苛刻要求。

EtherNet/IP 技术架构

协议栈分层设计

EtherNet/IP采用了基于OSI参考模型的分层架构,巧妙地将工业通信需求融入标准以太网框架

OSI层次 协议/技术 功能说明
物理层 IEEE 802.3 定义电气信号、线缆、接口(如100BASE-TX)
数据链路层 IEEE 802.3 MAC + VLAN MAC地址寻址,支持VLAN隔离和QoS
网络层 IPv4/IPv6 IP地址分配(静态/DHCP),支持子网划分
传输层 TCP(端口44818) + UDP(端口2222) 显性消息用TCP,隐性消息用UDP
应用层 CIP(通用工业协议) 定义设备对象模型、服务接口

核心通信机制

关于EtherNet/IP协议中客户端/服务器的区分以及通信机制,其设计相当独特,它并不完全遵循传统IT网络中的客户端/服务器定义,并且同时采用了类似长连接的机制和广播/多播机制,以满足工业控制对实时性和效率的要求。

下面的表格总结了EtherNet/IP协议在不同通信模式下的核心机制:

通信模式 角色关系 连接机制 使用的传输层协议 主要用途
显式报文 典型的客户端/服务器模型 基于TCP的面向连接通信 TCP 非实时数据(如配置、参数设置、程序上传下载)
隐式报文 生产者/消费者模型 基于UDP的广播或多播通信 UDP 实时性要求高的I/O数据
  • 显式报文与客户端/服务器模型
    显式报文通信用于传输非实时、需要可靠交付的数据,例如设备配置、参数设定或故障诊断信息。这种通信方式完全符合传统的客户端/服务器模型。在这个模型中:

    • 客户端是连接的发起者,主动向服务器请求服务或数据。
    • 服务器(在工业现场通常是PLC、驱动器等设备)在指定的端口上监听连接请求,并对客户端的请求作出响应。
      这种通信基于TCP协议,确保了数据传输的可靠性、顺序性和完整性,其机制类似于我们常说的长连接
  • 隐式报文与生产者/消费者模型
    隐式报文用于传输对实时性要求极高的I/O数据(如传感器读数、控制命令)。它采用了一种更高效的生产者/消费者模型。在这个模型中:

    • 生产者是数据的产生源(例如,一个传感器)。
    • 消费者是数据的使用者(例如,多个需要读取该传感器数据的控制器)。
      这种通信基于UDP协议。生产者将数据一次性发布到网络上,多个消费者可以同时“订阅”并接收这些数据,而无需生产者为每个消费者单独发送。这种机制通常利用广播或多播方式实现,极大地提高了通信效率,减少了网络延迟。由于UDP是无连接的,它本身不是严格意义上的“长连接”,但其周期性的数据发布模式在效果上实现了一种持续的数据流。
  • EtherNet/IP的显式报文通常使用TCP端口44818,而隐式报文(I/O消息)使用UDP端口2222

报文示例

🔍 TCP 显式报文详解

显式报文用于非实时、需要可靠传输的数据交换,例如设备配置、参数设置和程序上下载。它基于 TCP 协议,确保数据准确无误地送达。

以下是一个用于读取设备身份信息的典型请求报文各层结构详解:

协议层 字段名 示例值 (十六进制) 含义与用途说明
以太网帧头 目标MAC地址 00 1A 4B C8 D2 4F 数据帧要发送到的设备的物理地址。
源MAC地址 00 0B 57 75 32 1C 发送数据帧的设备的物理地址。
以太网类型 08 00 标识载荷(Payload)是 IPv4 数据包。
IP 报文头 版本/首部长度 45 IPv4,首部长度为20字节。
总长度 00 3C 整个IP数据包的总长度为60字节。
协议 06 表示上层协议是 TCP。
源IP地址 C0 A8 01 64 发送方的IP地址(192.168.1.100)。
目标IP地址 C0 A8 01 65 接收方的IP地址(192.168.1.101)。
TCP 段头 源端口 C0 28 (49192) 发起连接的应用程序端口。
目标端口 13 8B (5003) EtherNet/IP 显式报文标准端口 (通常为44818,也见5003)。
序列号与确认号 ... 用于TCP可靠传输的序列和确认机制。
EtherNet/IP 封装头 命令 (Command) 6F 00 (0x006F) 封装命令,此处为 SendRRData,表示发送未连接的显式报文。
长度 (Length) 04 00 后面所跟数据(CIP指令)的长度。
会话句柄 (Session Handle) B2 4C 04 01 由目标设备在会话注册时生成的唯一标识,用于维持通信会话。
CIP 数据部分 服务代码 (Service) 0E CIP服务代码,0x0E 代表 Get_Attribute_Single(读取单个属性)。
路径 (Path) 20 06 24 01 指定要访问的CIP对象(此处是Class ID为0x01的身份对象)和实例。

应用场合举例
这种报文典型用于工程师在电脑上使用配置软件(如Rockwell的Studio 5000)连接至一台PLC。当工程师希望查看该PLC的设备信息(如制造商、型号、序列号)时,软件就会构造并发送这样一条读取身份对象的请求报文。PLC收到后,会返回一个包含所请求信息的响应报文。整个过程基于可靠的TCP连接,确保指令和数据的准确交互。

⚡ UDP 隐式报文详解

隐式报文(又称I/O报文)用于传输对实时性要求极高的周期性I/O数据,如传感器读数和控制命令。它基于UDP协议,牺牲部分可靠性以换取速度和效率。

以下是一个用于传输模拟量输入数据的典型报文各层结构详解:

协议层 字段名 示例值 (十六进制) 含义与用途说明
以太网帧头 目标MAC地址 01 00 5E xx xx xx 一个多播MAC地址,意味着数据可被多个订阅该地址的设备同时接收。
源MAC地址 00 1D 9C xx xx xx 数据发送设备(如I/O模块)的物理地址。
以太网类型 08 00 标识载荷是 IPv4 数据包。
IP 报文头 协议 11 表示上层协议是 UDP
源IP地址 C0 A8 01 0A 发送方IP地址(192.168.1.10)。
目标IP地址 EF xx xx xx 一个多播IP地址,用于高效的一对多数据传输。
UDP 数据报头 源端口 08 AE (2222) EtherNet/IP 隐式报文常用源端口
目标端口 08 AE (2222) EtherNet/IP 隐式报文标准目标端口
长度 00 20 UDP数据报的总长度。
EtherNet/IP I/O 数据 连接标识符 (Connection ID) ... 唯一标识一个已建立的I/O连接,由扫描器(主站)在连接建立时分配。
序列号 (Sequence Number) ... 用于检测数据包是否丢失。
I/O 数据载荷 43 21 00 00 核心的实时数据。例如,这4个字节可能表示一个浮点数 165.0(假设是温度传感器读数)。

应用场合举例
在一个汽车装配线的机器人控制单元中,机器人控制器(扫描器)需要实时获取安装在夹具上的多个传感器的数据。这些传感器数据通过UDP隐式报文以多播形式发送。控制器和机器人可以同时“消费”这些数据,从而做出同步的运动控制决策。这种机制延迟极低,确保了生产节拍的高效和精准。

代码实践

由于EtherNet/IP基于以太网,我们无须另外的硬件平台,用PC就可以模拟主站和从站进行通讯,下面给出一个简单的基于EtherNet/IP设备发现示例

从站(服务器)

EtherNet/IP从站相对主站来说较难实现,一般用于真实的设备端(如PLC, IO, 传感器),其实现一般基于C语言的底层实现。
PC上可以用OpENer实现,是一个跨平台的开源EtherNet/IP协议栈。

地址:https://github.com/EIPStackGroup/OpENer

在Windows平台上的编译方法:

从git主页可以了解到,OpENer支持Visual Studio和MingW,本文以MingW为例,直接按照git上介绍的方法操作:

image

操作完,可以看到\bin\mingw\src\ports\MINGW下有个OpENer.exe生成,亲测,后面的参数用IP无效,用网络适配器的名称可以,如:

image

.\OpENer.exe "WLAN 2"

如此,就在WLAN 2的网络下启动了一个EtherNet/IP从站节点

主站(客户端)

EtherNet/IP主站实现途径比较多,RA官方也提供了不少相应工具,下面给出Python实现的示例:

用Python基于pycomm3库实现一个简单的EtherNet/IP的扫描程序

from pycomm3 import CIPDriver


def scan_ethernet_ip_nodes():
    """
    扫描网络中的EtherNet/IP节点并打印其信息。
    """
    try:
        # 使用discover方法扫描网络
        discovered_devices = CIPDriver.discover()

        if discovered_devices:
            print(f"发现了 {len(discovered_devices)} 个EtherNet/IP节点:")
            print("-" * 60)

            for device in discovered_devices:
                print(f"IP地址: {device['ip_address']}")
                print(f"供应商: {device['vendor']}")
                print(f"产品名称: {device['product_name']}")
                print(f"产品类型: {device['product_type']}")
                print("-" * 60)
        else:
            print("未发现任何EtherNet/IP节点。")

    except Exception as e:
        print(f"扫描过程中发生错误: {e}")


if __name__ == "__main__":
    scan_ethernet_ip_nodes()

可以发现上述从站设备

抓包

成功建立通讯,就可以用WireShark抓包,可以抓到一个EtherNet/IP包,并可以看到包里的CIP报文内容,可以看出是一条显示报文。

image

posted @ 2025-09-30 15:58  Asp1rant  阅读(165)  评论(0)    收藏  举报