以太网与工业以太网通信C#开发
目录
- 以太网基础
- 工业以太网概述
- C#网络编程基础
- TCP/IP通信实现
- UDP通信实现
- 工业以太网协议
- [Modbus TCP实现](#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
csharp
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服务器实现
csharp
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客户端实现
csharp
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发送端
csharp
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接收端
csharp
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客户端(主站)
csharp
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) + Data
await _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); // 事务ID
Array.Copy(BitConverter.GetBytes((ushort)0), 0, request, 2, 2); // 协议ID
Array.Copy(BitConverter.GetBytes(ReverseBytes(length)), 0, request, 4, 2); // 长度
request[6] = slaveId; // 单元ID
// PDU
request[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使用示例
csharp
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封装实现
csharp
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 Header
WriteUInt16(request, 0, CMD_REGISTER_SESSION); // Command
WriteUInt16(request, 2, 4); // Length
WriteUInt32(request, 4, _sessionHandle); // Session Handle
WriteUInt32(request, 8, 0); // Status
WriteUInt64(request, 12, _senderContext); // Sender Context
WriteUInt32(request, 20, 0); // Options
// Command Specific Data
WriteUInt16(request, 24, 1); // Protocol Version
WriteUInt16(request, 26, 0); // Options Flags
await _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 Service
request[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 Header
WriteUInt16(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 Handle
WriteUInt16(request, 28, 0); // Timeout
WriteUInt16(request, 30, 2); // Item Count
// Null Address Item
WriteUInt16(request, 32, 0); // Type ID
WriteUInt16(request, 34, 0); // Length
// Unconnected Data Item
WriteUInt16(request, 36, 0xB2); // Type ID
WriteUInt16(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; // 跳过封装头和CPF
var 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数据采集系统
csharp
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:多设备监控系统
csharp
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:完整的工业通信应用程序
csharp
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. 连接管理
csharp
// 使用连接池管理多个连接
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);
else
client.Disconnect();
}
}
2. 异常处理
csharp
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等