using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Infrastructure.Helper { using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Threading.Tasks; /// /// Modbus TCP 客户端帮助类(支持常用功能码) 客户端 = 主站 /// public class ModbusTcpClientHelper : IDisposable { private TcpClient _tcpClient; private NetworkStream _networkStream; private readonly object _lockObj = new object(); // 线程安全锁 /// /// 连接Modbus TCP服务器 服务端 = 从站 /// /// 服务器IP /// 端口(默认502) /// 超时时间(毫秒,默认3000) public void Connect(string ip, int port = 502, int timeoutMs = 3000) { if (_tcpClient != null && _tcpClient.Connected) return; lock (_lockObj) { try { Console.WriteLine($"连接Modbus TCP服务器已启动"); _tcpClient = new TcpClient(); _tcpClient.SendTimeout = timeoutMs; _tcpClient.ReceiveTimeout = timeoutMs; _tcpClient.Connect(ip, port); _networkStream = _tcpClient.GetStream(); Console.WriteLine($"连接Modbus TCP服务器已成功"); } catch (Exception ex) { Console.WriteLine($"连接Modbus TCP服务器失败: {ex.Message}"); } } } /// /// 断开连接 /// public void Disconnect() { lock (_lockObj) { _networkStream?.Close(); _tcpClient?.Close(); _networkStream = null; _tcpClient = null; } } /// /// 读取保持寄存器(功能码0x03) /// /// 从站地址(单元ID) /// 起始地址(0-based) /// 寄存器数量 /// 寄存器值列表(每个寄存器2字节,高位在前) public List ReadHoldingRegisters(byte unitId, ushort startAddress, ushort numberOfRegisters) { var request = BuildReadRequest(unitId, 0x03, startAddress, numberOfRegisters); var response = SendAndReceive(request); return ParseReadRegistersResponse(response, numberOfRegisters); } /// /// 读取输入寄存器(功能码0x04) /// public List ReadInputRegisters(byte unitId, ushort startAddress, ushort numberOfRegisters) { var request = BuildReadRequest(unitId, 0x04, startAddress, numberOfRegisters); var response = SendAndReceive(request); return ParseReadRegistersResponse(response, numberOfRegisters); } /// /// 写入单个保持寄存器(功能码0x06) /// /// 从站地址 /// 寄存器地址(0-based) /// 要写入的值(16位) public void WriteSingleRegister(byte unitId, ushort address, ushort value) { var request = BuildWriteSingleRequest(unitId, 0x06, address, value); var response = SendAndReceive(request); ValidateWriteResponse(response, 0x06, address, value); } /// /// 写入多个保持寄存器(功能码0x10) /// /// 从站地址 /// 起始地址(0-based) /// 要写入的值列表(每个值16位) public void WriteMultipleRegisters(byte unitId, ushort startAddress, List values) { var request = BuildWriteMultipleRequest(unitId, 0x10, startAddress, values); var response = SendAndReceive(request); ValidateWriteResponse(response, 0x10, startAddress, (ushort)values.Count); } /// /// 读取线圈状态(功能码0x01) /// /// 从站地址 /// 起始地址(0-based) /// 线圈数量(1-2000) /// 线圈状态列表(true=ON,false=OFF) public List ReadCoils(byte unitId, ushort startAddress, ushort numberOfCoils) { var request = BuildReadRequest(unitId, 0x01, startAddress, numberOfCoils); var response = SendAndReceive(request); return ParseReadCoilsResponse(response, numberOfCoils); } /// /// 写入单个线圈(功能码0x05) /// public void WriteSingleCoil(byte unitId, ushort address, bool value) { var request = BuildWriteSingleCoilRequest(unitId, 0x05, address, value); var response = SendAndReceive(request); ValidateWriteResponse(response, 0x05, address, value ? (ushort)0xFF00 : (ushort)0x0000); } #region 私有方法(协议构建与解析) /// /// 构建读请求报文(功能码0x01、0x02、0x03、0x04通用) /// private byte[] BuildReadRequest(byte unitId, byte functionCode, ushort startAddress, ushort quantity) { var request = new List { unitId, // 单元ID functionCode // 功能码 }; request.AddRange(BitConverter.GetBytes(startAddress).Reverse()); // 起始地址(大端序) request.AddRange(BitConverter.GetBytes(quantity).Reverse()); // 数量(大端序) AddCrcOrLength(request); // 添加长度(Modbus TCP无CRC,用MBAP长度) return request.ToArray(); } /// /// 构建写单个寄存器请求(功能码0x06) /// private byte[] BuildWriteSingleRequest(byte unitId, byte functionCode, ushort address, ushort value) { var request = new List { unitId, functionCode }; request.AddRange(BitConverter.GetBytes(address).Reverse()); request.AddRange(BitConverter.GetBytes(value).Reverse()); AddCrcOrLength(request); return request.ToArray(); } /// /// 构建写多个寄存器请求(功能码0x10) /// private byte[] BuildWriteMultipleRequest(byte unitId, byte functionCode, ushort startAddress, List values) { var request = new List { unitId, functionCode, (byte)(startAddress >> 8), // 起始地址高字节 (byte)(startAddress & 0xFF),// 起始地址低字节 (byte)(values.Count >> 8), // 数量高字节 (byte)(values.Count & 0xFF),// 数量低字节 (byte)(values.Count * 2) // 字节数(每个寄存器2字节) }; foreach (var value in values) { request.AddRange(BitConverter.GetBytes(value).Reverse()); // 寄存器值(大端序) } AddCrcOrLength(request); return request.ToArray(); } /// /// 构建写单个线圈请求(功能码0x05) /// private byte[] BuildWriteSingleCoilRequest(byte unitId, byte functionCode, ushort address, bool value) { var request = new List { unitId, functionCode }; request.AddRange(BitConverter.GetBytes(address).Reverse()); request.AddRange(value ? new byte[] { 0xFF, 0x00 } : new byte[] { 0x00, 0x00 }); // ON=0xFF00,OFF=0x0000 AddCrcOrLength(request); return request.ToArray(); } /// /// 添加MBAP头的长度字段(Modbus TCP无CRC,用长度标识后续字节数) /// private void AddCrcOrLength(List data) { // Modbus TCP格式:[事务ID(2)][协议ID(2)][长度(2)][单元ID(1)][PDU...] // 此处简化:假设事务ID=0,协议ID=0,长度=单元ID+PDU长度(即data.Length) var mbapHeader = new List { 0x00, 0x00, // 事务ID=0 0x00, 0x00, // 协议ID=0(Modbus) 0x00, (byte)(data.Count) // 长度=单元ID+PDU长度(data已包含单元ID+PDU) }; mbapHeader.AddRange(data); data.Clear(); data.AddRange(mbapHeader); } private byte[] SendAndReceive(byte[] request) { if (_networkStream == null || !_tcpClient.Connected) throw new InvalidOperationException("未连接到Modbus TCP服务器"); lock (_lockObj) { try { // 1. 清空流缓存(仅用DataAvailable+循环读取,不依赖Length) if (_networkStream.DataAvailable) { byte[] discardBuffer = new byte[1024]; while (_networkStream.DataAvailable) { _networkStream.Read(discardBuffer, 0, discardBuffer.Length); } } // 2. 发送请求 _networkStream.Write(request, 0, request.Length); _networkStream.Flush(); // 3. 读取MBAP头(6字节):循环读取直到获取完整6字节 byte[] headerBuffer = new byte[6]; int totalRead = 0; DateTime startTime = DateTime.Now; int timeoutMs = _tcpClient.ReceiveTimeout; while (totalRead < 6) { // 检查超时 if (DateTime.Now.Subtract(startTime).TotalMilliseconds > timeoutMs) throw new TimeoutException(); int bytesRead = _networkStream.Read(headerBuffer, totalRead, 6 - totalRead); if (bytesRead == 0) break; // 连接关闭 totalRead += bytesRead; } if (totalRead != 6) throw new Exception($"接收MBAP头失败:预期6字节,实际读取{totalRead}字节"); // 4. 解析长度字段 int length = (headerBuffer[4] << 8) | headerBuffer[5]; if (length <= 0 || length > 1024) throw new Exception($"MBAP头长度字段无效:{length}"); // 5. 读取PDU数据 byte[] pduBuffer = new byte[length]; totalRead = 0; startTime = DateTime.Now; while (totalRead < length) { if (DateTime.Now.Subtract(startTime).TotalMilliseconds > timeoutMs) throw new TimeoutException(); int bytesRead = _networkStream.Read(pduBuffer, totalRead, length - totalRead); if (bytesRead == 0) break; totalRead += bytesRead; } if (totalRead != length) throw new Exception($"接收响应数据不完整:预期{length}字节,实际读取{totalRead}字节"); // 6. 拼接响应 byte[] responseBuffer = new byte[6 + length]; Array.Copy(headerBuffer, 0, responseBuffer, 0, 6); Array.Copy(pduBuffer, 0, responseBuffer, 6, length); return responseBuffer; } catch (TimeoutException) { throw new Exception("接收响应超时"); } catch (Exception ex) { throw new Exception($"通信失败:{ex.Message}", ex); } } } /// /// 确保读取指定长度的字节(纯流式读取,无seek/长度操作) /// /// 网络流 /// 接收缓冲区 /// 偏移量 /// 需要读取的字节数 /// 超时时间(毫秒) /// 实际读取的字节数 private int ReadExactly(NetworkStream stream, byte[] buffer, int offset, int count, int timeoutMs) { int totalRead = 0; DateTime startTime = DateTime.Now; while (totalRead < count) { // 检查是否超时 if (DateTime.Now.Subtract(startTime).TotalMilliseconds > timeoutMs) throw new TimeoutException(); // 读取剩余字节(NetworkStream.Read是非阻塞,可能只返回部分字节) int bytesRead = stream.Read(buffer, offset + totalRead, count - totalRead); if (bytesRead == 0) break; // 流已关闭,无法继续读取 totalRead += bytesRead; } return totalRead; } /// /// 解析读寄存器响应(功能码0x03、0x04) /// private List ParseReadRegistersResponse(byte[] response, ushort expectedCount) { // 响应格式:[MBAP头(7)][单元ID(1)][功能码(1)][字节数(1)][数据(n)] var dataStartIndex = 7; // MBAP头7字节(事务ID2+协议ID2+长度2+单元ID1)+ 功能码1?不,重新算: // 实际响应结构:MBAP头(7字节) = [事务ID(2),协议ID(2),长度(2),单元ID(1)]?不,标准MBAP头是7字节: // 正确的MBAP头:事务ID(2)、协议ID(2)、长度(2)、单元ID(1) → 共7字节。然后PDU是功能码(1)+数据。 // 所以response的结构: // 0-1: 事务ID // 2-3: 协议ID // 4-5: 长度(=单元ID+PDU长度) // 6: 单元ID // 7: 功能码 // 8: 字节数(=n*2,n为寄存器数量) // 9~: 数据(每个寄存器2字节,大端序) if (response[7] != 0x03 && response[7] != 0x04) // 检查功能码(成功时无异常码) throw new Exception($"Modbus异常:功能码={response[7]:X2}"); var byteCount = response[8]; if (byteCount != expectedCount * 2) throw new Exception($"响应字节数与预期不符:实际={byteCount},预期={expectedCount * 2}"); var registers = new List(); for (int i = 0; i < expectedCount; i++) { var index = 9 + i * 2; var highByte = response[index]; var lowByte = response[index + 1]; registers.Add((ushort)((highByte << 8) | lowByte)); } return registers; } /// /// 解析读线圈响应(功能码0x01) /// private List ParseReadCoilsResponse(byte[] response, ushort expectedCount) { if (response[7] != 0x01) throw new Exception($"Modbus异常:功能码={response[7]:X2}"); var byteCount = response[8]; var expectedBytes = (expectedCount + 7) / 8; // 向上取整 if (byteCount != expectedBytes) throw new Exception($"响应字节数与预期不符:实际={byteCount},预期={expectedBytes}"); var coils = new List(); for (int i = 0; i < expectedCount; i++) { var byteIndex = 9 + i / 8; var bitIndex = i % 8; var mask = (byte)(1 << bitIndex); coils.Add((response[byteIndex] & mask) != 0); } return coils; } /// /// 验证写操作响应(确保从站正确接收) /// private void ValidateWriteResponse(byte[] response, byte functionCode, ushort address, ushort value) { if (response[7] != functionCode) // 功能码应与原请求一致(无异常) throw new Exception($"写操作失败:响应功能码={response[7]:X2}"); // 解析响应中的地址和值(应与请求一致) var respAddress = (ushort)((response[8] << 8) | response[9]); var respValue = (ushort)((response[10] << 8) | response[11]); if (respAddress != address || respValue != value) throw new Exception($"写操作响应不匹配:地址={respAddress:X4}(预期={address:X4}),值={respValue:X4}(预期={value:X4})"); } #endregion #region IDisposable实现 private bool _disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { Disconnect(); } _disposed = true; } } ~ModbusTcpClientHelper() { Dispose(false); } #endregion } // ===== 新增这两行 ===== public class ThreeColorLightModbus : ModbusTcpClientHelper { } public class AlarmLightModbus : ModbusTcpClientHelper { } }