以太网与工业以太网通信C#开发
以太网与工业以太网通信C#开发
目录
- 以太网基础
- 工业以太网概述
- C#网络编程基础
- TCP/IP通信实现
- UDP通信实现
- 工业以太网协议
- Modbus TCP实现
- EtherNet/IP实现
- 实战案例
以太网基础
什么是以太网
以太网(Ethernet)是一种计算机局域网技术,是当今现有局域网采用的最通用的通信协议标准。
核心特点:
- 物理层和数据链路层:工作在OSI模型的第1层和第2层
- CSMA/CD协议:载波侦听多路访问/冲突检测
- MAC地址:48位物理地址,用于设备识别
- 帧结构:数据封装在以太网帧中传输
以太网帧结构
+---------------+---------------+----------+---------+-----+
| 目的MAC(6字节) | 源MAC(6字节) | 类型(2) | 数据 | FCS |
+---------------+---------------+----------+---------+-----+
OSI七层模型与TCP/IP四层模型
OSI七层模型 | TCP/IP四层模型 | 功能 |
---|---|---|
应用层 | 应用层 | HTTP, FTP, DNS |
表示层 | ↑ | 数据格式化 |
会话层 | ↑ | 会话管理 |
传输层 | 传输层 | TCP, UDP |
网络层 | 网络层 | IP, ICMP |
数据链路层 | 数据链路层 | 以太网, WiFi |
物理层 | ↑ | 电气信号 |
工业以太网概述
工业以太网的特点
工业以太网是将标准以太网技术应用于工业自动化领域,相比标准以太网具有以下特点:
-
实时性要求高
- 确定性通信延迟
- 支持硬实时和软实时通信
-
可靠性强
- 抗电磁干扰能力
- 冗余机制
- 工业级温度范围(-40℃ ~ 85℃)
-
安全性
- 工业级安全认证
- 故障安全机制
-
兼容性
- 向下兼容标准以太网
- 支持多种工业协议
主流工业以太网协议
协议 | 制定者 | 特点 | 应用领域 |
---|---|---|---|
Modbus TCP | Schneider | 简单、开放 | 过程控制 |
EtherNet/IP | ODVA | CIP协议族 | 离散制造 |
PROFINET | Siemens | 三种通信类别 | 自动化 |
EtherCAT | Beckhoff | 极高实时性 | 运动控制 |
Powerlink | B&R | 开源、实时 | 运动控制 |
CC-Link IE | CLPA | 高速、大容量 | 工厂自动化 |
C#网络编程基础
System.Net命名空间
C#通过System.Net
和System.Net.Sockets
命名空间提供网络编程支持。
主要类:
Socket
: 底层套接字操作TcpClient
/TcpListener
: TCP客户端/服务器UdpClient
: UDP通信IPAddress
: IP地址表示IPEndPoint
: 网络端点(IP+端口)
基础示例:获取本机IP
using System;
using System.Net;
using System.Net.Sockets;
using System.Linq;public class NetworkHelper
{/// <summary>/// 获取本机IPv4地址/// </summary>public static string GetLocalIPv4(){var host = Dns.GetHostEntry(Dns.GetHostName());var ipAddress = host.AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork);return ipAddress?.ToString() ?? "127.0.0.1";}/// <summary>/// 获取所有网络接口信息/// </summary>public static void PrintNetworkInterfaces(){var interfaces = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();foreach (var ni in interfaces){Console.WriteLine($"接口: {ni.Name}");Console.WriteLine($"状态: {ni.OperationalStatus}");Console.WriteLine($"速度: {ni.Speed / 1_000_000} Mbps");Console.WriteLine($"MAC: {ni.GetPhysicalAddress()}");Console.WriteLine();}}
}
TCP/IP通信实现
TCP服务器实现
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;public class TcpServer
{private TcpListener _listener;private bool _isRunning;private List<TcpClient> _clients = new List<TcpClient>();public event Action<string> OnMessageReceived;public event Action<string> OnClientConnected;public event Action<string> OnClientDisconnected;/// <summary>/// 启动TCP服务器/// </summary>public async Task StartAsync(string ipAddress, int port){_listener = new TcpListener(IPAddress.Parse(ipAddress), port);_listener.Start();_isRunning = true;Console.WriteLine($"TCP服务器启动: {ipAddress}:{port}");while (_isRunning){try{var client = await _listener.AcceptTcpClientAsync();_clients.Add(client);var clientEndPoint = client.Client.RemoteEndPoint.ToString();Console.WriteLine($"客户端连接: {clientEndPoint}");OnClientConnected?.Invoke(clientEndPoint);// 为每个客户端创建独立的处理任务_ = Task.Run(() => HandleClientAsync(client));}catch (Exception ex){Console.WriteLine($"接受连接错误: {ex.Message}");}}}/// <summary>/// 处理客户端通信/// </summary>private async Task HandleClientAsync(TcpClient client){var clientEndPoint = client.Client.RemoteEndPoint.ToString();var stream = client.GetStream();var buffer = new byte[4096];try{while (client.Connected && _isRunning){var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);if (bytesRead == 0){// 客户端断开连接break;}var message = Encoding.UTF8.GetString(buffer, 0, bytesRead);Console.WriteLine($"收到 [{clientEndPoint}]: {message}");OnMessageReceived?.Invoke(message);// 回显数据var response = $"服务器收到: {message}";var responseBytes = Encoding.UTF8.GetBytes(response);await stream.WriteAsync(responseBytes, 0, responseBytes.Length);}}catch (Exception ex){Console.WriteLine($"客户端通信错误 [{clientEndPoint}]: {ex.Message}");}finally{Console.WriteLine($"客户端断开: {clientEndPoint}");OnClientDisconnected?.Invoke(clientEndPoint);_clients.Remove(client);client.Close();}}/// <summary>/// 广播消息给所有客户端/// </summary>public async Task BroadcastAsync(string message){var data = Encoding.UTF8.GetBytes(message);var disconnectedClients = new List<TcpClient>();foreach (var client in _clients.ToArray()){try{if (client.Connected){var stream = client.GetStream();await stream.WriteAsync(data, 0, data.Length);}else{disconnectedClients.Add(client);}}catch{disconnectedClients.Add(client);}}// 清理断开的客户端foreach (var client in disconnectedClients){_clients.Remove(client);client.Close();}}/// <summary>/// 停止服务器/// </summary>public void Stop(){_isRunning = false;foreach (var client in _clients){client.Close();}_clients.Clear();_listener?.Stop();Console.WriteLine("TCP服务器已停止");}
}
TCP客户端实现
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;public class TcpClientHelper
{private TcpClient _client;private NetworkStream _stream;private bool _isConnected;public event Action<string> OnMessageReceived;public event Action OnConnected;public event Action OnDisconnected;/// <summary>/// 连接到服务器/// </summary>public async Task<bool> ConnectAsync(string serverIp, int port, int timeoutMs = 5000){try{_client = new TcpClient();// 设置超时var connectTask = _client.ConnectAsync(serverIp, port);if (await Task.WhenAny(connectTask, Task.Delay(timeoutMs)) == connectTask){_stream = _client.GetStream();_isConnected = true;Console.WriteLine($"已连接到服务器: {serverIp}:{port}");OnConnected?.Invoke();// 开始接收数据_ = Task.Run(() => ReceiveDataAsync());return true;}else{Console.WriteLine("连接超时");_client?.Close();return false;}}catch (Exception ex){Console.WriteLine($"连接失败: {ex.Message}");return false;}}/// <summary>/// 接收数据/// </summary>private async Task ReceiveDataAsync(){var buffer = new byte[4096];try{while (_isConnected && _client.Connected){var bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length);if (bytesRead == 0){// 服务器断开连接break;}var message = Encoding.UTF8.GetString(buffer, 0, bytesRead);Console.WriteLine($"收到服务器消息: {message}");OnMessageReceived?.Invoke(message);}}catch (Exception ex){Console.WriteLine($"接收数据错误: {ex.Message}");}finally{Disconnect();}}/// <summary>/// 发送数据/// </summary>public async Task<bool> SendAsync(string message){if (!_isConnected || _stream == null){Console.WriteLine("未连接到服务器");return false;}try{var data = Encoding.UTF8.GetBytes(message);await _stream.WriteAsync(data, 0, data.Length);Console.WriteLine($"发送消息: {message}");return true;}catch (Exception ex){Console.WriteLine($"发送失败: {ex.Message}");return false;}}/// <summary>/// 发送字节数据/// </summary>public async Task<bool> SendBytesAsync(byte[] data){if (!_isConnected || _stream == null)return false;try{await _stream.WriteAsync(data, 0, data.Length);return true;}catch{return false;}}/// <summary>/// 断开连接/// </summary>public void Disconnect(){if (_isConnected){_isConnected = false;_stream?.Close();_client?.Close();Console.WriteLine("已断开连接");OnDisconnected?.Invoke();}}
}
UDP通信实现
UDP发送端
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;public class UdpSender
{private UdpClient _udpClient;private IPEndPoint _remoteEndPoint;/// <summary>/// 初始化UDP发送端/// </summary>public UdpSender(string remoteIp, int remotePort){_udpClient = new UdpClient();_remoteEndPoint = new IPEndPoint(IPAddress.Parse(remoteIp), remotePort);}/// <summary>/// 发送文本消息/// </summary>public async Task SendMessageAsync(string message){try{var data = Encoding.UTF8.GetBytes(message);await _udpClient.SendAsync(data, data.Length, _remoteEndPoint);Console.WriteLine($"UDP发送: {message}");}catch (Exception ex){Console.WriteLine($"UDP发送失败: {ex.Message}");}}/// <summary>/// 发送字节数据/// </summary>public async Task SendBytesAsync(byte[] data){try{await _udpClient.SendAsync(data, data.Length, _remoteEndPoint);Console.WriteLine($"UDP发送 {data.Length} 字节");}catch (Exception ex){Console.WriteLine($"UDP发送失败: {ex.Message}");}}/// <summary>/// 广播消息/// </summary>public async Task BroadcastAsync(string message, int port){try{using (var client = new UdpClient()){client.EnableBroadcast = true;var data = Encoding.UTF8.GetBytes(message);var broadcastEp = new IPEndPoint(IPAddress.Broadcast, port);await client.SendAsync(data, data.Length, broadcastEp);Console.WriteLine($"广播消息: {message}");}}catch (Exception ex){Console.WriteLine($"广播失败: {ex.Message}");}}public void Close(){_udpClient?.Close();}
}
UDP接收端
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;public class UdpReceiver
{private UdpClient _udpClient;private bool _isRunning;private int _port;public event Action<string, IPEndPoint> OnMessageReceived;/// <summary>/// 启动UDP接收/// </summary>public async Task StartAsync(int port){_port = port;_udpClient = new UdpClient(port);_isRunning = true;Console.WriteLine($"UDP接收器启动在端口: {port}");while (_isRunning){try{var result = await _udpClient.ReceiveAsync();var message = Encoding.UTF8.GetString(result.Buffer);Console.WriteLine($"UDP收到 [{result.RemoteEndPoint}]: {message}");OnMessageReceived?.Invoke(message, result.RemoteEndPoint);}catch (Exception ex){if (_isRunning){Console.WriteLine($"UDP接收错误: {ex.Message}");}}}}/// <summary>/// 停止接收/// </summary>public void Stop(){_isRunning = false;_udpClient?.Close();Console.WriteLine("UDP接收器已停止");}
}
工业以太网协议
协议栈对比
标准以太网栈 工业以太网栈(如Modbus TCP)
+-------------+ +-------------------+
| 应用层 | | Modbus应用协议 |
+-------------+ +-------------------+
| 传输层 | | TCP/IP |
+-------------+ +-------------------+
| 网络层 | | IP |
+-------------+ +-------------------+
| 数据链路层 | | 以太网 |
+-------------+ +-------------------+
Modbus TCP实现
Modbus TCP协议结构
MBAP头部 (7字节) + PDU (功能码 + 数据)
+------+------+------+------+------+------+------+----------+
| 事务 | 协议 | 长度 | 单元 | 功能 | 数据域 |
| ID | ID | | ID | 码 | |
| (2) | (2) | (2) | (1) | (1) | (0-252) |
+------+------+------+------+------+----------+
Modbus TCP客户端(主站)
using System;
using System.Net.Sockets;
using System.Threading.Tasks;public class ModbusTcpClient
{private TcpClient _client;private NetworkStream _stream;private ushort _transactionId = 0;private readonly object _lockObj = new object();/// <summary>/// 连接Modbus TCP服务器/// </summary>public async Task<bool> ConnectAsync(string ipAddress, int port = 502){try{_client = new TcpClient();await _client.ConnectAsync(ipAddress, port);_stream = _client.GetStream();Console.WriteLine($"已连接到Modbus服务器: {ipAddress}:{port}");return true;}catch (Exception ex){Console.WriteLine($"连接失败: {ex.Message}");return false;}}/// <summary>/// 读保持寄存器 (功能码0x03)/// </summary>public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort count){// 构建Modbus请求var request = BuildRequest(slaveId, 0x03, BitConverter.GetBytes(ReverseBytes(startAddress)).Concat(BitConverter.GetBytes(ReverseBytes(count))).ToArray());// 发送请求await _stream.WriteAsync(request, 0, request.Length);// 接收响应var response = new byte[9 + count * 2]; // MBAP(7) + FC(1) + ByteCount(1) + Dataawait _stream.ReadAsync(response, 0, response.Length);// 解析数据return ParseRegisters(response, count);}/// <summary>/// 写单个保持寄存器 (功能码0x06)/// </summary>public async Task<bool> WriteSingleRegisterAsync(byte slaveId, ushort address, ushort value){var data = new byte[4];Array.Copy(BitConverter.GetBytes(ReverseBytes(address)), 0, data, 0, 2);Array.Copy(BitConverter.GetBytes(ReverseBytes(value)), 0, data, 2, 2);var request = BuildRequest(slaveId, 0x06, data);await _stream.WriteAsync(request, 0, request.Length);var response = new byte[12]; // MBAP(7) + FC(1) + Address(2) + Value(2)await _stream.ReadAsync(response, 0, response.Length);return response[7] == 0x06; // 检查功能码}/// <summary>/// 写多个保持寄存器 (功能码0x10)/// </summary>public async Task<bool> WriteMultipleRegistersAsync(byte slaveId, ushort startAddress, ushort[] values){var count = (ushort)values.Length;var byteCount = (byte)(count * 2);var data = new byte[5 + byteCount];Array.Copy(BitConverter.GetBytes(ReverseBytes(startAddress)), 0, data, 0, 2);Array.Copy(BitConverter.GetBytes(ReverseBytes(count)), 0, data, 2, 2);data[4] = byteCount;for (int i = 0; i < count; i++){Array.Copy(BitConverter.GetBytes(ReverseBytes(values[i])), 0, data, 5 + i * 2, 2);}var request = BuildRequest(slaveId, 0x10, data);await _stream.WriteAsync(request, 0, request.Length);var response = new byte[12];await _stream.ReadAsync(response, 0, response.Length);return response[7] == 0x10;}/// <summary>/// 读线圈状态 (功能码0x01)/// </summary>public async Task<bool[]> ReadCoilsAsync(byte slaveId, ushort startAddress, ushort count){var request = BuildRequest(slaveId, 0x01,BitConverter.GetBytes(ReverseBytes(startAddress)).Concat(BitConverter.GetBytes(ReverseBytes(count))).ToArray());await _stream.WriteAsync(request, 0, request.Length);var byteCount = (count + 7) / 8;var response = new byte[9 + byteCount];await _stream.ReadAsync(response, 0, response.Length);return ParseCoils(response, count);}/// <summary>/// 写单个线圈 (功能码0x05)/// </summary>public async Task<bool> WriteSingleCoilAsync(byte slaveId, ushort address, bool value){var coilValue = value ? (ushort)0xFF00 : (ushort)0x0000;var data = new byte[4];Array.Copy(BitConverter.GetBytes(ReverseBytes(address)), 0, data, 0, 2);Array.Copy(BitConverter.GetBytes(ReverseBytes(coilValue)), 0, data, 2, 2);var request = BuildRequest(slaveId, 0x05, data);await _stream.WriteAsync(request, 0, request.Length);var response = new byte[12];await _stream.ReadAsync(response, 0, response.Length);return response[7] == 0x05;}/// <summary>/// 构建Modbus TCP请求/// </summary>private byte[] BuildRequest(byte slaveId, byte functionCode, byte[] data){lock (_lockObj){_transactionId++;}var length = (ushort)(data.Length + 2); // 单元ID + 功能码 + 数据var request = new byte[7 + 1 + data.Length];// MBAP头部Array.Copy(BitConverter.GetBytes(ReverseBytes(_transactionId)), 0, request, 0, 2); // 事务IDArray.Copy(BitConverter.GetBytes((ushort)0), 0, request, 2, 2); // 协议IDArray.Copy(BitConverter.GetBytes(ReverseBytes(length)), 0, request, 4, 2); // 长度request[6] = slaveId; // 单元ID// PDUrequest[7] = functionCode;Array.Copy(data, 0, request, 8, data.Length);return request;}/// <summary>/// 解析寄存器数据/// </summary>private ushort[] ParseRegisters(byte[] response, ushort count){var byteCount = response[8];var registers = new ushort[count];for (int i = 0; i < count; i++){registers[i] = (ushort)((response[9 + i * 2] << 8) | response[10 + i * 2]);}return registers;}/// <summary>/// 解析线圈数据/// </summary>private bool[] ParseCoils(byte[] response, ushort count){var byteCount = response[8];var coils = new bool[count];for (int i = 0; i < count; i++){var byteIndex = i / 8;var bitIndex = i % 8;coils[i] = (response[9 + byteIndex] & (1 << bitIndex)) != 0;}return coils;}/// <summary>/// 字节序转换 (大端/小端)/// </summary>private ushort ReverseBytes(ushort value){return (ushort)((value >> 8) | (value << 8));}/// <summary>/// 断开连接/// </summary>public void Disconnect(){_stream?.Close();_client?.Close();Console.WriteLine("已断开Modbus连接");}
}
Modbus TCP使用示例
using System;
using System.Linq;
using System.Threading.Tasks;public class ModbusExample
{public static async Task RunModbusDemo(){var client = new ModbusTcpClient();// 连接到Modbus服务器if (await client.ConnectAsync("192.168.1.100", 502)){try{// 读取保持寄存器Console.WriteLine("\n--- 读取保持寄存器 ---");var registers = await client.ReadHoldingRegistersAsync(1, 0, 10);for (int i = 0; i < registers.Length; i++){Console.WriteLine($"寄存器 {i}: {registers[i]}");}// 写单个寄存器Console.WriteLine("\n--- 写入单个寄存器 ---");bool success = await client.WriteSingleRegisterAsync(1, 0, 1234);Console.WriteLine($"写入结果: {(success ? "成功" : "失败")}");// 写多个寄存器Console.WriteLine("\n--- 写入多个寄存器 ---");var values = new ushort[] { 100, 200, 300, 400, 500 };success = await client.WriteMultipleRegistersAsync(1, 10, values);Console.WriteLine($"写入结果: {(success ? "成功" : "失败")}");// 读取线圈Console.WriteLine("\n--- 读取线圈 ---");var coils = await client.ReadCoilsAsync(1, 0, 16);for (int i = 0; i < coils.Length; i++){Console.WriteLine($"线圈 {i}: {coils[i]}");}// 写入线圈Console.WriteLine("\n--- 写入线圈 ---");success = await client.WriteSingleCoilAsync(1, 0, true);Console.WriteLine($"写入结果: {(success ? "成功" : "失败")}");}finally{client.Disconnect();}}}
}
EtherNet/IP实现
EtherNet/IP协议简介
EtherNet/IP是基于CIP(Common Industrial Protocol)的工业以太网协议,主要用于罗克韦尔自动化(Rockwell Automation)的设备。
封装结构:
+------------------+
| Encapsulation |
| Header (24字节) |
+------------------+
| Command Data |
+------------------+
EtherNet/IP封装实现
using System;
using System.Net.Sockets;
using System.Threading.Tasks;public class EtherNetIPClient
{private TcpClient _client;private NetworkStream _stream;private uint _sessionHandle = 0;private uint _senderContext = 0;// EIP命令private const ushort CMD_NOP = 0x0000;private const ushort CMD_LIST_SERVICES = 0x0004;private const ushort CMD_LIST_IDENTITY = 0x0063;private const ushort CMD_LIST_INTERFACES = 0x0064;private const ushort CMD_REGISTER_SESSION = 0x0065;private const ushort CMD_UNREGISTER_SESSION = 0x0066;private const ushort CMD_SEND_RR_DATA = 0x006F;private const ushort CMD_SEND_UNIT_DATA = 0x0070;/// <summary>/// 连接到EtherNet/IP设备/// </summary>public async Task<bool> ConnectAsync(string ipAddress, int port = 44818){try{_client = new TcpClient();await _client.ConnectAsync(ipAddress, port);_stream = _client.GetStream();Console.WriteLine($"已连接到EIP设备: {ipAddress}:{port}");// 注册会话return await RegisterSessionAsync();}catch (Exception ex){Console.WriteLine($"连接失败: {ex.Message}");return false;}}/// <summary>/// 注册会话/// </summary>private async Task<bool> RegisterSessionAsync(){// 构建注册会话请求var request = new byte[28];// Encapsulation HeaderWriteUInt16(request, 0, CMD_REGISTER_SESSION); // CommandWriteUInt16(request, 2, 4); // LengthWriteUInt32(request, 4, _sessionHandle); // Session HandleWriteUInt32(request, 8, 0); // StatusWriteUInt64(request, 12, _senderContext); // Sender ContextWriteUInt32(request, 20, 0); // Options// Command Specific DataWriteUInt16(request, 24, 1); // Protocol VersionWriteUInt16(request, 26, 0); // Options Flagsawait _stream.WriteAsync(request, 0, request.Length);// 接收响应var response = new byte[28];await _stream.ReadAsync(response, 0, response.Length);_sessionHandle = ReadUInt32(response, 4);Console.WriteLine($"会话注册成功, Session Handle: 0x{_sessionHandle:X8}");return true;}/// <summary>/// 列出设备标识/// </summary>public async Task<DeviceIdentity> ListIdentityAsync(){var request = new byte[24];WriteUInt16(request, 0, CMD_LIST_IDENTITY);WriteUInt16(request, 2, 0);WriteUInt32(request, 4, 0);WriteUInt32(request, 8, 0);WriteUInt64(request, 12, _senderContext++);WriteUInt32(request, 20, 0);await _stream.WriteAsync(request, 0, request.Length);var response = new byte[1024];var bytesRead = await _stream.ReadAsync(response, 0, response.Length);return ParseIdentity(response, bytesRead);}/// <summary>/// 读取标签数据 (简化版)/// </summary>public async Task<byte[]> ReadTagAsync(string tagName){// 构建CIP请求var cipRequest = BuildCIPReadRequest(tagName);var request = BuildSendRRDataRequest(cipRequest);await _stream.WriteAsync(request, 0, request.Length);var response = new byte[1024];var bytesRead = await _stream.ReadAsync(response, 0, response.Length);return ParseCIPResponse(response, bytesRead);}/// <summary>/// 构建CIP读取请求/// </summary>private byte[] BuildCIPReadRequest(string tagName){// CIP Read Tag Service (0x4C)var tagBytes = System.Text.Encoding.ASCII.GetBytes(tagName);var request = new byte[2 + tagBytes.Length + (tagBytes.Length % 2)];request[0] = 0x4C; // Read Tag Servicerequest[1] = (byte)(tagBytes.Length / 2);Array.Copy(tagBytes, 0, request, 2, tagBytes.Length);return request;}/// <summary>/// 构建SendRRData请求/// </summary>private byte[] BuildSendRRDataRequest(byte[] cipData){var dataLength = cipData.Length + 16; // CPF数据长度var request = new byte[24 + dataLength];// Encapsulation HeaderWriteUInt16(request, 0, CMD_SEND_RR_DATA);WriteUInt16(request, 2, (ushort)dataLength);WriteUInt32(request, 4, _sessionHandle);WriteUInt32(request, 8, 0);WriteUInt64(request, 12, _senderContext++);WriteUInt32(request, 20, 0);// CPF (Common Packet Format)WriteUInt32(request, 24, 0); // Interface HandleWriteUInt16(request, 28, 0); // TimeoutWriteUInt16(request, 30, 2); // Item Count// Null Address ItemWriteUInt16(request, 32, 0); // Type IDWriteUInt16(request, 34, 0); // Length// Unconnected Data ItemWriteUInt16(request, 36, 0xB2); // Type IDWriteUInt16(request, 38, (ushort)cipData.Length);Array.Copy(cipData, 0, request, 40, cipData.Length);return request;}/// <summary>/// 解析CIP响应/// </summary>private byte[] ParseCIPResponse(byte[] response, int length){// 简化的解析逻辑var dataStart = 44; // 跳过封装头和CPFvar dataLength = length - dataStart;var data = new byte[dataLength];Array.Copy(response, dataStart, data, 0, dataLength);return data;}/// <summary>/// 解析设备标识/// </summary>private DeviceIdentity ParseIdentity(byte[] response, int length){var identity = new DeviceIdentity();// 简化的解析if (length > 63){identity.VendorId = ReadUInt16(response, 48);identity.DeviceType = ReadUInt16(response, 50);identity.ProductCode = ReadUInt16(response, 52);identity.Revision = $"{response[54]}.{response[55]}";identity.Status = ReadUInt16(response, 56);identity.SerialNumber = ReadUInt32(response, 58);var nameLength = response[62];identity.ProductName = System.Text.Encoding.ASCII.GetString(response, 63, nameLength);}return identity;}/// <summary>/// 注销会话/// </summary>public async Task UnregisterSessionAsync(){if (_sessionHandle == 0) return;var request = new byte[24];WriteUInt16(request, 0, CMD_UNREGISTER_SESSION);WriteUInt32(request, 4, _sessionHandle);await _stream.WriteAsync(request, 0, request.Length);Console.WriteLine("会话已注销");}public void Disconnect(){_stream?.Close();_client?.Close();}// 辅助方法private void WriteUInt16(byte[] buffer, int offset, ushort value){buffer[offset] = (byte)(value & 0xFF);buffer[offset + 1] = (byte)((value >> 8) & 0xFF);}private void WriteUInt32(byte[] buffer, int offset, uint value){buffer[offset] = (byte)(value & 0xFF);buffer[offset + 1] = (byte)((value >> 8) & 0xFF);buffer[offset + 2] = (byte)((value >> 16) & 0xFF);buffer[offset + 3] = (byte)((value >> 24) & 0xFF);}private void WriteUInt64(byte[] buffer, int offset, ulong value){for (int i = 0; i < 8; i++){buffer[offset + i] = (byte)((value >> (i * 8)) & 0xFF);}}private ushort ReadUInt16(byte[] buffer, int offset){return (ushort)(buffer[offset] | (buffer[offset + 1] << 8));}private uint ReadUInt32(byte[] buffer, int offset){return (uint)(buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16) | (buffer[offset + 3] << 24));}
}public class DeviceIdentity
{public ushort VendorId { get; set; }public ushort DeviceType { get; set; }public ushort ProductCode { get; set; }public string Revision { get; set; }public ushort Status { get; set; }public uint SerialNumber { get; set; }public string ProductName { get; set; }public override string ToString(){return $"产品名称: {ProductName}\n" +$"厂商ID: {VendorId}\n" +$"设备类型: {DeviceType}\n" +$"产品代码: {ProductCode}\n" +$"版本: {Revision}\n" +$"序列号: {SerialNumber}";}
}
实战案例
案例1:PLC数据采集系统
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;public class PLCDataCollector
{private ModbusTcpClient _modbusClient;private bool _isRunning;private int _pollingInterval = 1000; // 轮询间隔(毫秒)public event Action<Dictionary<string, object>> OnDataCollected;/// <summary>/// 启动数据采集/// </summary>public async Task StartCollectionAsync(string plcIp, int port = 502){_modbusClient = new ModbusTcpClient();if (await _modbusClient.ConnectAsync(plcIp, port)){_isRunning = true;Console.WriteLine("数据采集已启动");while (_isRunning){try{var data = await CollectDataAsync();OnDataCollected?.Invoke(data);// 显示数据Console.WriteLine($"\n[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 采集数据:");foreach (var item in data){Console.WriteLine($" {item.Key}: {item.Value}");}}catch (Exception ex){Console.WriteLine($"采集错误: {ex.Message}");}await Task.Delay(_pollingInterval);}}}/// <summary>/// 采集数据/// </summary>private async Task<Dictionary<string, object>> CollectDataAsync(){var data = new Dictionary<string, object>();// 读取温度传感器 (地址 0-3)var tempRegisters = await _modbusClient.ReadHoldingRegistersAsync(1, 0, 4);data["温度1"] = ConvertToFloat(tempRegisters[0], tempRegisters[1]);data["温度2"] = ConvertToFloat(tempRegisters[2], tempRegisters[3]);// 读取压力传感器 (地址 10-11)var pressureRegisters = await _modbusClient.ReadHoldingRegistersAsync(1, 10, 2);data["压力"] = ConvertToFloat(pressureRegisters[0], pressureRegisters[1]);// 读取运行状态 (线圈 0-7)var coils = await _modbusClient.ReadCoilsAsync(1, 0, 8);data["电机运行"] = coils[0];data["报警状态"] = coils[1];data["自动模式"] = coils[2];// 读取计数器 (地址 20)var counterRegisters = await _modbusClient.ReadHoldingRegistersAsync(1, 20, 1);data["生产计数"] = counterRegisters[0];return data;}/// <summary>/// 将两个寄存器转换为浮点数/// </summary>private float ConvertToFloat(ushort high, ushort low){var bytes = new byte[4];Array.Copy(BitConverter.GetBytes(high), 0, bytes, 2, 2);Array.Copy(BitConverter.GetBytes(low), 0, bytes, 0, 2);return BitConverter.ToSingle(bytes, 0);}/// <summary>/// 停止采集/// </summary>public void Stop(){_isRunning = false;_modbusClient?.Disconnect();Console.WriteLine("数据采集已停止");}
}
案例2:多设备监控系统
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;public class MultiDeviceMonitor
{private Dictionary<string, ModbusTcpClient> _devices;public MultiDeviceMonitor(){_devices = new Dictionary<string, ModbusTcpClient>();}/// <summary>/// 添加设备/// </summary>public async Task<bool> AddDeviceAsync(string deviceName, string ipAddress, int port = 502){var client = new ModbusTcpClient();if (await client.ConnectAsync(ipAddress, port)){_devices[deviceName] = client;Console.WriteLine($"设备 [{deviceName}] 已添加");return true;}return false;}/// <summary>/// 批量读取所有设备数据/// </summary>public async Task<Dictionary<string, DeviceData>> ReadAllDevicesAsync(){var results = new Dictionary<string, DeviceData>();var tasks = new List<Task<(string name, DeviceData data)>>();foreach (var device in _devices){tasks.Add(ReadDeviceDataAsync(device.Key, device.Value));}var allResults = await Task.WhenAll(tasks);foreach (var result in allResults){results[result.name] = result.data;}return results;}/// <summary>/// 读取单个设备数据/// </summary>private async Task<(string, DeviceData)> ReadDeviceDataAsync(string deviceName, ModbusTcpClient client){var data = new DeviceData { DeviceName = deviceName };try{// 读取状态寄存器var registers = await client.ReadHoldingRegistersAsync(1, 0, 10);data.Status = registers[0];data.Speed = registers[1];data.Temperature = registers[2] / 10.0;data.Pressure = registers[3] / 100.0;data.IsOnline = true;data.LastUpdate = DateTime.Now;}catch (Exception ex){data.IsOnline = false;data.ErrorMessage = ex.Message;}return (deviceName, data);}/// <summary>/// 生成监控报告/// </summary>public void GenerateReport(Dictionary<string, DeviceData> devicesData){Console.WriteLine("\n========== 设备监控报告 ==========");Console.WriteLine($"报告时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}\n");var onlineDevices = devicesData.Values.Count(d => d.IsOnline);var offlineDevices = devicesData.Count - onlineDevices;Console.WriteLine($"设备总数: {devicesData.Count}");Console.WriteLine($"在线设备: {onlineDevices}");Console.WriteLine($"离线设备: {offlineDevices}\n");Console.WriteLine("设备详情:");Console.WriteLine(new string('-', 80));Console.WriteLine($"{"设备名称",-15} {"状态",-8} {"速度",-10} {"温度",-10} {"压力",-10} {"更新时间",-20}");Console.WriteLine(new string('-', 80));foreach (var device in devicesData.Values.OrderBy(d => d.DeviceName)){if (device.IsOnline){Console.WriteLine($"{device.DeviceName,-15} {"在线",-8} {device.Speed,-10} " +$"{device.Temperature,-10:F1} {device.Pressure,-10:F2} " +$"{device.LastUpdate:HH:mm:ss}");}else{Console.WriteLine($"{device.DeviceName,-15} {"离线",-8} {"-",-10} {"-",-10} " +$"{"-",-10} {device.ErrorMessage}");}}Console.WriteLine(new string('=', 80));}/// <summary>/// 关闭所有连接/// </summary>public void DisconnectAll(){foreach (var client in _devices.Values){client.Disconnect();}_devices.Clear();}
}public class DeviceData
{public string DeviceName { get; set; }public bool IsOnline { get; set; }public int Status { get; set; }public int Speed { get; set; }public double Temperature { get; set; }public double Pressure { get; set; }public DateTime LastUpdate { get; set; }public string ErrorMessage { get; set; }
}
案例3:完整的工业通信应用程序
using System;
using System.Threading.Tasks;class Program
{static async Task Main(string[] args){Console.WriteLine("===== 工业以太网通信演示程序 =====\n");// 示例1: 基础TCP通信await Demo1_BasicTcpCommunication();// 示例2: Modbus TCP通信await Demo2_ModbusTcpCommunication();// 示例3: 多设备监控await Demo3_MultiDeviceMonitor();// 示例4: 数据采集系统await Demo4_DataCollectionSystem();Console.WriteLine("\n演示完成,按任意键退出...");Console.ReadKey();}static async Task Demo1_BasicTcpCommunication(){Console.WriteLine("\n--- 示例1: 基础TCP通信 ---");var server = new TcpServer();var serverTask = server.StartAsync("127.0.0.1", 8888);await Task.Delay(500); // 等待服务器启动var client = new TcpClientHelper();if (await client.ConnectAsync("127.0.0.1", 8888)){await client.SendAsync("Hello Server!");await Task.Delay(1000);}server.Stop();client.Disconnect();}static async Task Demo2_ModbusTcpCommunication(){Console.WriteLine("\n--- 示例2: Modbus TCP通信 ---");Console.WriteLine("(需要实际的Modbus设备或模拟器)");// 如果有Modbus设备,取消下面的注释/*var client = new ModbusTcpClient();if (await client.ConnectAsync("192.168.1.100", 502)){var registers = await client.ReadHoldingRegistersAsync(1, 0, 10);Console.WriteLine($"读取到 {registers.Length} 个寄存器");client.Disconnect();}*/}static async Task Demo3_MultiDeviceMonitor(){Console.WriteLine("\n--- 示例3: 多设备监控 ---");Console.WriteLine("(需要实际的设备)");var monitor = new MultiDeviceMonitor();// 添加设备(实际使用时修改IP地址)// await monitor.AddDeviceAsync("PLC1", "192.168.1.101");// await monitor.AddDeviceAsync("PLC2", "192.168.1.102");// 读取所有设备// var data = await monitor.ReadAllDevicesAsync();// monitor.GenerateReport(data);// monitor.DisconnectAll();}static async Task Demo4_DataCollectionSystem(){Console.WriteLine("\n--- 示例4: 数据采集系统 ---");Console.WriteLine("(需要实际的PLC设备)");var collector = new PLCDataCollector();collector.OnDataCollected += (data) =>{// 处理采集到的数据// 例如:存储到数据库、显示到界面等};// 启动采集(实际使用时修改IP地址)// await collector.StartCollectionAsync("192.168.1.100");}
}
最佳实践与注意事项
1. 连接管理
// 使用连接池管理多个连接
public class ConnectionPool
{private Queue<ModbusTcpClient> _pool = new Queue<ModbusTcpClient>();private int _maxSize = 10;public async Task<ModbusTcpClient> GetConnectionAsync(){if (_pool.Count > 0)return _pool.Dequeue();var client = new ModbusTcpClient();await client.ConnectAsync("192.168.1.100", 502);return client;}public void ReturnConnection(ModbusTcpClient client){if (_pool.Count < _maxSize)_pool.Enqueue(client);elseclient.Disconnect();}
}
2. 异常处理
public async Task<ushort[]> SafeReadRegisters(ModbusTcpClient client, byte slaveId, ushort address, ushort count, int retries = 3)
{for (int i = 0; i < retries; i++){try{return await client.ReadHoldingRegistersAsync(slaveId, address, count);}catch (Exception ex){Console.WriteLine($"读取失败 (尝试 {i + 1}/{retries}): {ex.Message}");if (i == retries - 1) throw;await Task.Delay(1000);}}return null;
}
3. 性能优化
- 批量读写操作减少网络往返
- 使用异步操作避免阻塞
- 合理设置超时时间
- 实现数据缓存机制
4. 安全考虑
- 验证输入参数
- 使用TLS/SSL加密通信
- 实现访问控制
- 记录操作日志
总结
本文档详细介绍了以太网和工业以太网的基础知识,并提供了完整的C#实现代码,包括:
- 标准以太网通信:TCP/IP和UDP的完整实现
- Modbus TCP协议:工业自动化中最常用的协议
- EtherNet/IP协议:罗克韦尔自动化设备通信
- 实战案例:PLC数据采集、多设备监控等
学习建议:
- 从基础TCP/UDP通信开始练习
- 使用Modbus模拟器测试代码
- 逐步扩展到实际工业设备
- 注意异常处理和连接管理
- 关注实时性和可靠性要求
推荐工具:
- Modbus Poll/Slave:Modbus协议测试
- Wireshark:网络数据包分析
- Visual Studio:C#开发环境
- Modbus模拟器:ModRSsim2等
参考资源
- Modbus协议规范
- EtherNet/IP开发者指南
- PROFINET官方文档
- Microsoft .NET网络编程文档