以太网与工业以太网通信C#开发

以太网与工业以太网通信C#开发

目录

  1. 以太网基础
  2. 工业以太网概述
  3. C#网络编程基础
  4. TCP/IP通信实现
  5. UDP通信实现
  6. 工业以太网协议
  7. [Modbus TCP实现](#Modbus TCP实现)
  8. EtherNet/IP实现
  9. 实战案例

以太网基础

什么是以太网

以太网(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
物理层 电气信号

工业以太网概述

工业以太网的特点

工业以太网是将标准以太网技术应用于工业自动化领域,相比标准以太网具有以下特点:

  1. 实时性要求高

    • 确定性通信延迟
    • 支持硬实时和软实时通信
  2. 可靠性强

    • 抗电磁干扰能力
    • 冗余机制
    • 工业级温度范围(-40℃ ~ 85℃)
  3. 安全性

    • 工业级安全认证
    • 故障安全机制
  4. 兼容性

    • 向下兼容标准以太网
    • 支持多种工业协议

主流工业以太网协议

协议 制定者 特点 应用领域
Modbus TCP Schneider 简单、开放 过程控制
EtherNet/IP ODVA CIP协议族 离散制造
PROFINET Siemens 三种通信类别 自动化
EtherCAT Beckhoff 极高实时性 运动控制
Powerlink B&R 开源、实时 运动控制
CC-Link IE CLPA 高速、大容量 工厂自动化

C#网络编程基础

System.Net命名空间

C#通过System.NetSystem.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#实现代码,包括:

  1. 标准以太网通信:TCP/IP和UDP的完整实现
  2. Modbus TCP协议:工业自动化中最常用的协议
  3. EtherNet/IP协议:罗克韦尔自动化设备通信
  4. 实战案例:PLC数据采集、多设备监控等

学习建议:

  • 从基础TCP/UDP通信开始练习
  • 使用Modbus模拟器测试代码
  • 逐步扩展到实际工业设备
  • 注意异常处理和连接管理
  • 关注实时性和可靠性要求

推荐工具:

  • Modbus Poll/Slave:Modbus协议测试
  • Wireshark:网络数据包分析
  • Visual Studio:C#开发环境
  • Modbus模拟器:ModRSsim2等

参考资源

相关推荐
野猪亨利6674 小时前
Qt day1
开发语言·数据库·qt
lastHertz4 小时前
Golang 项目中使用 Swagger
开发语言·后端·golang
惜月_treasure5 小时前
LlamaIndex多模态RAG开发实现详解
开发语言·python·机器学习
isaki1375 小时前
qt day1
开发语言·数据库·qt
流星白龙5 小时前
【Qt】4.项目文件解析
开发语言·数据库·qt
iuuia5 小时前
05--JavaScript基础语法(1)
开发语言·javascript·ecmascript
郝学胜-神的一滴5 小时前
深入解析Linux下的`lseek`函数:文件定位与操作的艺术
linux·运维·服务器·开发语言·c++·软件工程
一晌小贪欢5 小时前
Python爬虫第4课:XPath与lxml高级解析技术
开发语言·爬虫·python·网络爬虫·python爬虫·python3·python办公
蓝色汪洋5 小时前
string字符集
java·开发语言