String[] arrayIP= null;的典型错误

本文讲解了如何正确初始化数组以防止出现空指针异常的问题,通过对比错误与正确的代码示例,帮助读者理解并掌握这一基本概念。

String[] arrayIP= null;的典型错误,这个并为给这个数组分配存储空间,接下来你这样用的话会抛出空指针异常

arrayIP[i] = child.getAttributeValue("id");

 

将数组这样定义后,就会分配存储空间了,再用就不会抛出空指针异常

String[] arrayIP =new String[5];

我用的是Easymodbus的包哦,5.6.0版本的。声明我是这样写的// 声明静态数组 static ModbusClient[] modbusArray = new ModbusClient[100]; // 创建长度为5的数组 // 声明线程数组 private Thread[] modbusThreads = new Thread[100]; // 创建100个线程的数组 // 声明取消线程标记数组 private volatile bool[] threadStopRequests = new bool[100]; // 声明StringBuilder数组 private StringBuilder[] M_receivedDataBuffers = new StringBuilder[100]; 这是连接设备和打开线程的: private void PLC_Connectivity(int ArrayNumber, string CommProtocol, string IPaddress, string Port, string COM, string Baudrate, string Parity_str, string IDnum) { if (CommProtocol == "modbus_TCP") { if (connStateBools[ArrayNumber] == false) { try { modbusArray[ArrayNumber] = new ModbusClient(); modbusArray[ArrayNumber].Connect(IPaddress, Convert.ToInt16(Port)); M_receivedDataBuffers[ArrayNumber] = new StringBuilder(); threadStopRequests[ArrayNumber] = false; // 重置停止标志 modbusThreads[ArrayNumber] = new Thread(() => ModbusThreadMethod(ArrayNumber)); modbusThreads[ArrayNumber].IsBackground = true; modbusThreads[ArrayNumber].Start(); connStateBools[ArrayNumber] = true; } catch (Exception ex) { MessageBox.Show(ex.ToString()); } } } else if (CommProtocol == "modbus_RTU") { if (connStateBools[ArrayNumber] == false) { try { modbusArray[ArrayNumber] = new ModbusClient(COM); modbusArray[ArrayNumber].UnitIdentifier = byte.Parse(IDnum);//从站ID modbusArray[ArrayNumber].Baudrate = int.Parse(Baudrate); // 获取选择的波特率 modbusArray[ArrayNumber].StopBits = StopBits.One;//停止位 modbusArray[ArrayNumber].ConnectionTimeout = 1000;//连接超时 //校验位 if (Parity_str == "None") { modbusArray[ArrayNumber].Parity = Parity.None; } else if (Parity_str == "Odd") { modbusArray[ArrayNumber].Parity = Parity.Odd; } else if (Parity_str == "Even") { modbusArray[ArrayNumber].Parity = Parity.Even; } modbusArray[ArrayNumber].Connect();//modbus连接 M_receivedDataBuffers[ArrayNumber] = new StringBuilder(); threadStopRequests[ArrayNumber] = false; // 重置停止标志 modbusThreads[ArrayNumber] = new Thread(() => ModbusThreadMethod(ArrayNumber)); modbusThreads[ArrayNumber].IsBackground = true; modbusThreads[ArrayNumber].Start(); connStateBools[ArrayNumber] = true; } catch (Exception ex) { MessageBox.Show(ex.ToString()); } } } } 这个是关闭线程的: public void StopModbusThread(int arrayNumber) { if (modbusThreads[arrayNumber] != null && modbusThreads[arrayNumber].IsAlive) { // 1. 设置停止标志 threadStopRequests[arrayNumber] = true; // 2. 等待线程完成当前周期(最多等待500ms) if (!modbusThreads[arrayNumber].Join(1000)) { // 3. 如果超时仍未停止,尝试中断(比Abort更安全) modbusThreads[arrayNumber].Interrupt(); } // 4. 重置线程对象 modbusThreads[arrayNumber] = null; } } 可以再帮我整合一下吗,因为第一次接触多线程真的难理解
08-03
#region ModbusTCP客户端 public class ModbusTCP : IDisposable { #region 对象 /*定义连接的Socket*/ Socket clientSocket; //-----------------------------通讯的socket /*定义socket线程*/ Thread receiveTh; //--------------------------------接收socket线程 private readonly object socketLock = new object(); //-----Socket锁 #endregion #region 事件 public del LogInfoDisplay; //--------------------委托变量-信息显示 public Action<string> returnReceiveEvent; //----接收返回数据事件 #endregion #region 变量 string nameClient; //---------------------------客户端名称 public InfoMsg info = new InfoMsg("", ""); //----显示信息 private volatile bool isConnected; //------------连接状态 private bool disposed = false; //----------------否已释放资源 private ushort transactionId = 0; //-------------事务标识符 private byte[] currentResponse; //---------------当前响应数据 private readonly ManualResetEvent responseReceived = new ManualResetEvent(false); private readonly object responseLock = new object(); private byte[] receiveBuffer = new byte[1024]; // 自动重连配置 private bool autoReconnect = true; private int reconnectInterval = 5000; //重连间隔(ms) private int maxReconnectAttempts = 3; private int currentReconnectAttempts = 0; private string currentIP; private int currentPort; public bool connecte { get { return isConnected && clientSocket != null && clientSocket.Connected; } private set { isConnected = value; } } #endregion #region 命令枚举 /// <summary> /// 命令枚举 /// </summary> enum Command : short { 读线圈寄存器 = 1, //-------功能码01 单个或多个(M寄存器) 读输入状态 = 2, //---------功能码02 - 离散输入 读保存寄存器 = 3, //-------功能码03 单个或多个(D寄存器) 读输入寄存器 = 4, //-------功能码04 单个或多个 写单个线圈寄存器 = 5, //---功能码05 (M寄存器) 写单个保存寄存器 = 6, //---功能码06 (D寄存器) 写多个线圈寄存器 = 15, //--功能码15 (M寄存器) 写多个保持寄存器 = 16, //--功能码16 (D寄存器) } #endregion #region 客户端初始化 /// <summary> /// 客户端初始化 /// </summary> /// <param name="name">客户端名称</param> /// <param name="receiveData">接收数据事件</param> /// <param name="info">消息显示事件</param> public void Initialize(string name = "客户端", Action<string> receiveData = null, del info = null) { nameClient = name; //-------------------客户端名称 returnReceiveEvent += receiveData; //---接收数据事件 LogInfoDisplay += info; //--------------消息事件 this.info.msgType = nameClient; //------log消息类型 } /// <summary> /// 配置自动重连 /// </summary> /// <param name="enabled">开启重连true/false</param> /// <param name="intervalMs">重连间隔</param> /// <param name="maxAttempts">重连次数</param> public void ConfigureAutoReconnect(bool enabled, int intervalMs = 5000, int maxAttempts = 3) { autoReconnect = enabled; reconnectInterval = intervalMs; maxReconnectAttempts = maxAttempts; } #endregion #region 客户端断开 /// <summary> /// 断开连接 /// </summary> public void Disconnect() { lock (socketLock) { try { info.msgType = nameClient; // 停止接收线程 if (receiveTh != null && receiveTh.IsAlive) { if (!receiveTh.Join(500)) { receiveTh.Abort(); } receiveTh = null; } if (clientSocket != null) { if (clientSocket.Connected) { try { clientSocket.Shutdown(SocketShutdown.Both); } catch { } clientSocket.Close(); clientSocket.Dispose(); } clientSocket = null; } connecte = false; info.msg = "连接已断开"; LogInfoDisplay?.Invoke(info); } catch (Exception ex) { info.msg = $"断开连接异常: {ex.Message}"; LogInfoDisplay?.Invoke(info); } } } #endregion #region 连接的Socket /// <summary> /// 连接的Socket /// </summary> public void Connection(string IP, string Port) { if (!int.TryParse(Port, out int port)) { info.msg = "端口号格式错误"; LogInfoDisplay?.Invoke(info); return; } Connection(IP, port); } /// <summary> /// 连接的Socket /// </summary> /// <param name="IP">IP地址</param> /// <param name="port">端口号</param> public void Connection(string IP, int port) { lock (socketLock) { try { currentIP = IP; currentPort = port; if (connecte) { Disconnect(); Thread.Sleep(100); } IPAddress ip = IPAddress.Parse(IP.Trim()); IPEndPoint point = new IPEndPoint(ip, port); clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 设置超时 clientSocket.SendTimeout = 5000; clientSocket.ReceiveTimeout = 5000; var result = clientSocket.BeginConnect(point, null, null); bool success = result.AsyncWaitHandle.WaitOne(5000, true); if (success) { clientSocket.EndConnect(result); connecte = true; currentReconnectAttempts = 0; // 重置重连计数 info.msgType = nameClient; info.msg = "连接成功"; LogInfoDisplay?.Invoke(info); // 启动接收线程 if (receiveTh == null || !receiveTh.IsAlive) { receiveTh = new Thread(Receive) { IsBackground = true }; receiveTh.Start(); } } else { clientSocket.Close(); info.msg = "连接超时"; LogInfoDisplay?.Invoke(info); HandleReconnection(); } } catch (Exception e) { info.msg = $"连接失败: {e.Message}"; LogInfoDisplay?.Invoke(info); clientSocket?.Close(); clientSocket = null; connecte = false; HandleReconnection(); } } } /// <summary> /// 处理自动重连 /// </summary> private void HandleReconnection() { if (!autoReconnect || currentReconnectAttempts >= maxReconnectAttempts) return; currentReconnectAttempts++; info.msg = $"尝试重连 ({currentReconnectAttempts}/{maxReconnectAttempts})..."; LogInfoDisplay?.Invoke(info); ThreadPool.QueueUserWorkItem(_ => { Thread.Sleep(reconnectInterval); if (!connecte && !disposed) { Connection(currentIP, currentPort); } }); } #endregion #region 核心通信方法 /// <summary> /// 发送请求并等待响应 /// </summary> private byte[] SendRequest(byte[] request, int expectedLength) { if (!connecte) { info.msg = "未连接,无法发送请求"; LogInfoDisplay?.Invoke(info); return null; } lock (socketLock) { try { responseReceived.Reset(); currentResponse = null; // 发送请求 int sent = clientSocket.Send(request); if (sent != request.Length) { info.msg = "发送数据不完整"; LogInfoDisplay?.Invoke(info); return null; } // 等待响应(带超时) if (responseReceived.WaitOne(5000)) { return currentResponse; } else { info.msg = "等待响应超时"; LogInfoDisplay?.Invoke(info); connecte = false; HandleReconnection(); return null; } } catch (Exception ex) { info.msg = $"发送请求失败: {ex.Message}"; LogInfoDisplay?.Invoke(info); connecte = false; HandleReconnection(); return null; } } } /// <summary> /// 生成事务ID /// </summary> private ushort GetNextTransactionId() { return transactionId++ == ushort.MaxValue ? (ushort)0 : transactionId; } #endregion #region 地址解析 /// <summary> /// 地址解析 /// </summary> /// <param name="addr">地址</param> /// <returns>地址数组</returns> private byte[] AddrResolver(string addr) { if (string.IsNullOrEmpty(addr) || addr.Length < 2) throw new ArgumentException("地址格式错误"); // 解析地址(去掉首字符后的数字支持不同前缀:M(线圈)、D(保持寄存器)、I(输入)) string addrNumStr = addr.Substring(1); if (!ushort.TryParse(addrNumStr, out ushort addrNum)) throw new ArgumentException("地址格式错误"); return BitConverter.GetBytes(addrNum).Reverse().ToArray(); } #endregion #region 读取输入状态 /// <summary> /// 读取输入状态 /// </summary> /// <param name="ID">客户端ID</param> /// <param name="addr">地址</param> /// <param name="length">长度</param> /// <returns>值</returns> public bool[] ReadInput(byte ID, string addr, int length) { if (length < 1 || length > 2000) { info.msg = "读取长度必须在1-2000之间"; LogInfoDisplay?.Invoke(info); return new bool[Math.Max(1, Math.Min(length, 2000))]; } return ReadInputStatusInternal(ID, addr, (ushort)length); } private bool[] ReadInputStatusInternal(byte ID, string addr, ushort length) { byte[] addrBytes = AddrResolver(addr); ushort transactionId = GetNextTransactionId(); // 构建请求 byte[] request = new byte[12]; BitConverter.GetBytes(transactionId).CopyTo(request, 0); // 事务ID BitConverter.GetBytes((ushort)0).CopyTo(request, 2); // 协议ID BitConverter.GetBytes((ushort)6).CopyTo(request, 4); // 长度 request[6] = ID; // 设备ID request[7] = (byte)Command.读输入状态; // 功能码02 addrBytes.CopyTo(request, 8); // 地址 BitConverter.GetBytes(length).Reverse().ToArray().CopyTo(request, 10); // 数量 // 计算预期响应长度 int byteCount = (length + 7) / 8; int expectedResponseLength = 9 + byteCount; byte[] response = SendRequest(request, expectedResponseLength); if (response == null || response.Length < 9) { info.msg = "读取输入状态失败或无响应"; LogInfoDisplay?.Invoke(info); return new bool[length]; } return ParseDiscreteInputsResponse(response, length); } /// <summary> /// 解析离散输入响应 /// </summary> private bool[] ParseDiscreteInputsResponse(byte[] response, ushort length) { bool[] result = new bool[length]; int byteCount = response[8]; // 字节数 for (int i = 0; i < length; i++) { int byteIndex = i / 8; int bitIndex = i % 8; if (byteIndex < byteCount) { // 正确的位解析 result[i] = (response[9 + byteIndex] & (1 << bitIndex)) != 0; } } return result; } #endregion #region 读取输入寄存器 /// <summary> /// 读取输入寄存器 /// </summary> /// <param name="ID">客户端ID</param> /// <param name="addr">地址</param> /// <param name="length">长度</param> /// <param name="type">类型</param> /// <returns>值</returns> public long[] ReadInputRegister(byte ID, string addr, int length, ReadTypeEnum type = ReadTypeEnum.字) { if (length < 1 || length > 125) { info.msg = "读取长度必须在1-125之间"; LogInfoDisplay?.Invoke(info); return new long[Math.Max(1, Math.Min(length, 125))]; } return ReadInputRegisterInternal(ID, addr, (ushort)length, type); } private long[] ReadInputRegisterInternal(byte ID, string addr, ushort length, ReadTypeEnum type) { byte[] addrBytes = AddrResolver(addr); ushort registerCount = (ushort)(length * (short)type); ushort transactionId = GetNextTransactionId(); // 构建请求 byte[] request = new byte[12]; BitConverter.GetBytes(transactionId).CopyTo(request, 0); BitConverter.GetBytes((ushort)0).CopyTo(request, 2); BitConverter.GetBytes((ushort)6).CopyTo(request, 4); request[6] = ID; request[7] = (byte)Command.读输入寄存器; // 功能码04 addrBytes.CopyTo(request, 8); BitConverter.GetBytes(registerCount).Reverse().ToArray().CopyTo(request, 10); // 计算预期响应长度 int expectedResponseLength = 9 + registerCount * 2; byte[] response = SendRequest(request, expectedResponseLength); if (response == null || response.Length < 9) { info.msg = "读取输入寄存器失败或无响应"; LogInfoDisplay?.Invoke(info); return new long[length]; } return ParseInputRegisterResponse(response, length, type); } /// <summary> /// 解析输入寄存器响应 /// </summary> private long[] ParseInputRegisterResponse(byte[] response, ushort length, ReadTypeEnum type) { long[] result = new long[length]; int byteCount = response[8]; // 字节数 if (type == ReadTypeEnum.字) { for (int i = 0; i < length; i++) { int dataIndex = 9 + i * 2; if (dataIndex + 1 < response.Length) { // 正确的字节序:高位在前 result[i] = (response[dataIndex] << 8) | response[dataIndex + 1]; // 处理16位有符号数 if (result[i] > 32767) result[i] -= 65536; } } } else // 双字 { for (int i = 0; i < length; i++) { int dataIndex = 9 + i * 4; if (dataIndex + 3 < response.Length) { // 正确的字节序:高位在前 result[i] = ((long)response[dataIndex] << 24) | ((long)response[dataIndex + 1] << 16) | ((long)response[dataIndex + 2] << 8) | response[dataIndex + 3]; // 处理32位有符号数 if (result[i] > 2147483647) result[i] -= 4294967296; } } } return result; } #endregion #region 读取线圈寄存器(M寄存器) /// <summary> /// 读取线圈寄存器 /// </summary> /// <param name="ID">客户端ID</param> /// <param name="addr">地址</param> /// <param name="length">长度</param> /// <returns>值</returns> public bool[] ReadCoils(byte ID, string addr, ushort length) { byte[] addrBytes = AddrResolver(addr); ushort transactionId = GetNextTransactionId(); // 构建请求 byte[] request = new byte[12]; BitConverter.GetBytes(transactionId).CopyTo(request, 0); // 事务ID BitConverter.GetBytes((ushort)0).CopyTo(request, 2); // 协议ID BitConverter.GetBytes((ushort)6).CopyTo(request, 4); // 长度 request[6] = ID; // 设备ID request[7] = (byte)Command.读线圈寄存器; // 功能码 addrBytes.CopyTo(request, 8); // 地址 BitConverter.GetBytes(length).Reverse().ToArray().CopyTo(request, 10); // 数量 byte[] response = SendRequest(request, 9 + (length + 7) / 8); if (response == null || response.Length < 9) return new bool[length]; return ParseCoilsResponse(response, length); } /// <summary> /// 解析线圈响应 /// </summary> private bool[] ParseCoilsResponse(byte[] response, ushort length) { bool[] result = new bool[length]; int byteCount = response[8]; for (int i = 0; i < length; i++) { int byteIndex = i / 8; int bitIndex = i % 8; if (byteIndex < byteCount) { result[i] = (response[9 + byteIndex] & (1 << bitIndex)) != 0; } } return result; } #endregion #region 读取保持寄存器(D寄存器) /// <summary> /// 读取类型 /// </summary> public enum ReadTypeEnum : short { 字 = 1, 双字 = 2 } /// <summary> /// 读取保持寄存器 /// </summary> /// <param name="ID">客户端ID</param> /// <param name="addr">地址</param> /// <param name="length">长度</param> /// <param name="type">类型</param> /// <returns>值</returns> public long[] ReadHoldingRegister(byte ID, string addr, ushort length, ReadTypeEnum type = ReadTypeEnum.字) { byte[] addrBytes = AddrResolver(addr); ushort registerCount = (ushort)(length * (short)type); ushort transactionId = GetNextTransactionId(); // 构建请求 byte[] request = new byte[12]; BitConverter.GetBytes(transactionId).CopyTo(request, 0); BitConverter.GetBytes((ushort)0).CopyTo(request, 2); BitConverter.GetBytes((ushort)6).CopyTo(request, 4); request[6] = ID; request[7] = (byte)Command.读保存寄存器; addrBytes.CopyTo(request, 8); BitConverter.GetBytes(registerCount).Reverse().ToArray().CopyTo(request, 10); byte[] response = SendRequest(request, 9 + registerCount * 2); if (response == null || response.Length < 9) return new long[length]; return ParseRegisterResponse(response, length, type); } /// <summary> /// 解析寄存器响应 /// </summary> private long[] ParseRegisterResponse(byte[] response, ushort length, ReadTypeEnum type) { long[] result = new long[length]; int byteCount = response[8]; if (type == ReadTypeEnum.字) { for (int i = 0; i < length; i++) { int dataIndex = 9 + i * 2; if (dataIndex + 1 < response.Length) { result[i] = (response[dataIndex] << 8) | response[dataIndex + 1]; // 处理负数(16位有符号) if (result[i] > 32767) result[i] -= 65536; } } } else //双字 { for (int i = 0; i < length; i++) { int dataIndex = 9 + i * 4; if (dataIndex + 3 < response.Length) { result[i] = ((long)response[dataIndex] << 24) | ((long)response[dataIndex + 1] << 16) | ((long)response[dataIndex + 2] << 8) | response[dataIndex + 3]; // 处理负数(32位有符号) if (result[i] > 2147483647) result[i] -= 4294967296; } } } return result; } #endregion #region 写入单个线圈寄存器值(M寄存器) /// <summary> /// 写入单个线圈寄存器值 /// </summary> /// <param name="ID">客户端ID</param> /// <param name="addr">地址</param> /// <param name="value">值</param> public bool WriteSingleCoils(byte ID, string addr, bool value) { byte[] addrBytes = AddrResolver(addr); ushort transactionId = GetNextTransactionId(); ushort writeValue = value ? (ushort)0xFF00 : (ushort)0x0000; byte[] request = new byte[12]; BitConverter.GetBytes(transactionId).CopyTo(request, 0); BitConverter.GetBytes((ushort)0).CopyTo(request, 2); BitConverter.GetBytes((ushort)6).CopyTo(request, 4); request[6] = ID; request[7] = (byte)Command.写单个线圈寄存器; addrBytes.CopyTo(request, 8); BitConverter.GetBytes(writeValue).Reverse().ToArray().CopyTo(request, 10); byte[] response = SendRequest(request, 12); return response != null && response.Length >= 12; } #endregion #region 写入单个保持寄存器值(D寄存器) /// <summary> /// 写入单个保持寄存器值(支持16位和32位) /// </summary> /// <param name="ID">设备ID</param> /// <param name="addr">地址</param> /// <param name="value">值(16位或32位)</param> /// <param name="dataType">数据类型(字=16位,双字=32位)</param> /// <returns>是否写入成功</returns> public bool WriteSingleRegister(byte ID, string addr, int value, ReadTypeEnum dataType = ReadTypeEnum.字) { if (!connecte) { info.msg = "未连接,无法写入数据"; LogInfoDisplay?.Invoke(info); return false; } if (dataType == ReadTypeEnum.字) { // 16位写入 if (value < -32768 || value > 65535) { info.msg = "16位写入值超出范围(-32768~65535)"; LogInfoDisplay?.Invoke(info); return false; } ushort writeValue = (ushort)value; // 直接转换,保持二进制表示 return WriteSingleRegister16(ID, addr, writeValue); } else { // 32位写入 - 写入两个连续寄存器 return WriteDoubleWord(ID, addr, value); } } /// <summary> /// 写入单个保持寄存器值(字符串版本) /// </summary> public bool WriteSingleRegister(byte ID, string addr, string value, ReadTypeEnum dataType = ReadTypeEnum.字) { if (int.TryParse(value, out int intValue)) { return WriteSingleRegister(ID, addr, intValue, dataType); } else { info.msg = "写入值格式错误,必须为有效的整数"; LogInfoDisplay?.Invoke(info); return false; } } /// <summary> /// 16位写入实现(功能码06) /// </summary> private bool WriteSingleRegister16(byte ID, string addr, ushort value) { byte[] addrBytes = AddrResolver(addr); ushort transactionId = GetNextTransactionId(); // 构建16位写入请求 byte[] request = new byte[12]; BitConverter.GetBytes(transactionId).CopyTo(request, 0); // 事务ID BitConverter.GetBytes((ushort)0).CopyTo(request, 2); // 协议ID BitConverter.GetBytes((ushort)6).CopyTo(request, 4); // 长度 request[6] = ID; // 设备ID request[7] = (byte)Command.写单个保存寄存器; // 功能码06 addrBytes.CopyTo(request, 8); // 地址 BitConverter.GetBytes(value).Reverse().ToArray().CopyTo(request, 10); // 16位值 byte[] response = SendRequest(request, 12); return response != null && response.Length >= 12; } /// <summary> /// 32位写入实现(功能码16) /// </summary> private bool WriteDoubleWord(byte ID, string addr, int value) { byte[] addrBytes = AddrResolver(addr); ushort transactionId = GetNextTransactionId(); // 将32位整数拆分为两个16位寄存器值 ushort highWord = (ushort)(value >> 16); // 高16位 ushort lowWord = (ushort)(value & 0xFFFF); // 低16位 // 构建32位写入请求(写入两个寄存器) byte[] request = new byte[17]; // 12 + 5 = 17字节 BitConverter.GetBytes(transactionId).CopyTo(request, 0); BitConverter.GetBytes((ushort)0).CopyTo(request, 2); BitConverter.GetBytes((ushort)11).CopyTo(request, 4); // 长度=11 request[6] = ID; request[7] = (byte)Command.写多个保持寄存器; // 功能码16 addrBytes.CopyTo(request, 8); BitConverter.GetBytes((ushort)2).Reverse().ToArray().CopyTo(request, 10); // 寄存器数量=2 request[12] = 4; // 字节数=4 // 写入两个寄存器值(注意字节序) BitConverter.GetBytes(highWord).Reverse().ToArray().CopyTo(request, 13); // 高16位 BitConverter.GetBytes(lowWord).Reverse().ToArray().CopyTo(request, 15); // 低16位 byte[] response = SendRequest(request, 12); return response != null && response.Length >= 12; } /// <summary> /// 写入浮点数(32位) /// </summary> public bool WriteSingleRegister(byte ID, string addr, float value) { if (!connecte) { info.msg = "未连接,无法写入数据"; LogInfoDisplay?.Invoke(info); return false; } byte[] addrBytes = AddrResolver(addr); ushort transactionId = GetNextTransactionId(); // 将float转换为4字节,再拆分为两个16位寄存器 byte[] floatBytes = BitConverter.GetBytes(value); ushort highWord = (ushort)((floatBytes[3] << 8) | floatBytes[2]); // 高16位(大端序) ushort lowWord = (ushort)((floatBytes[1] << 8) | floatBytes[0]); // 低16位(大端序) // 构建32位写入请求 byte[] request = new byte[17]; BitConverter.GetBytes(transactionId).CopyTo(request, 0); BitConverter.GetBytes((ushort)0).CopyTo(request, 2); BitConverter.GetBytes((ushort)11).CopyTo(request, 4); request[6] = ID; request[7] = (byte)Command.写多个保持寄存器; addrBytes.CopyTo(request, 8); BitConverter.GetBytes((ushort)2).Reverse().ToArray().CopyTo(request, 10); request[12] = 4; // 写入浮点数的两个寄存器值 BitConverter.GetBytes(highWord).Reverse().ToArray().CopyTo(request, 13); BitConverter.GetBytes(lowWord).Reverse().ToArray().CopyTo(request, 15); byte[] response = SendRequest(request, 12); return response != null && response.Length >= 12; } #endregion #region 写入多个线圈寄存器值(M寄存器) /// <summary> /// 写入多个线圈寄存器值(功能码15)- 所有线圈写入相同值 /// </summary> /// <param name="ID">设备ID</param> /// <param name="addr">起始地址(如"M100")</param> /// <param name="length">写入的线圈数量</param> /// <param name="value">所有线圈的状态(true=ON, false=OFF)</param> /// <returns>是否写入成功</returns> /// <example> /// WriteMultipleCoils(1, "M100", 2, true); /// // 写入M100=true, M101=true /// WriteMultipleCoils(1, "M100", 5, false); /// // 写入M100~M104全部为false /// </example> public bool WriteMultipleCoils(byte ID, string addr, int length, bool value) { if (!connecte) { info.msg = "未连接,无法写入数据"; LogInfoDisplay?.Invoke(info); return false; } // 参数验证 if (length <= 0 || length > 1968) { info.msg = "写入线圈数量必须在1-1968之间"; LogInfoDisplay?.Invoke(info); return false; } // 创建所有线圈值相同的数组 bool[] coilValues = new bool[length]; for (int i = 0; i < length; i++) { coilValues[i] = value; } return WriteMultipleCoilsInternal(ID, addr, (ushort)length, coilValues); } /// <summary> /// 写入多个线圈寄存器值(bool数组版本)- 每个线圈独立控制 /// </summary> /// <param name="ID">设备ID</param> /// <param name="addr">起始地址</param> /// <param name="values">每个线圈的状态数组</param> /// <returns>是否写入成功</returns> /// <example> /// WriteMultipleCoils(1, "M100", new bool[] { true, false, true }); /// // 写入M100=true, M101=false, M102=true /// </example> public bool WriteMultipleCoils(byte ID, string addr, bool[] values) { if (!connecte) { info.msg = "未连接,无法写入数据"; LogInfoDisplay?.Invoke(info); return false; } if (values == null || values.Length == 0 || values.Length > 1968) { info.msg = "写入线圈数量必须在1-1968之间"; LogInfoDisplay?.Invoke(info); return false; } return WriteMultipleCoilsInternal(ID, addr, (ushort)values.Length, values); } /// <summary> /// 内部实现:写入多个线圈寄存器 /// </summary> private bool WriteMultipleCoilsInternal(byte ID, string addr, ushort length, bool[] values) { byte[] addrBytes = AddrResolver(addr); ushort transactionId = GetNextTransactionId(); // 计算字节数 int byteCount = (length + 7) / 8; byte[] coilBytes = ConvertBoolsToBytes(values, length); // 构建请求 byte[] request = new byte[13 + byteCount]; BitConverter.GetBytes(transactionId).CopyTo(request, 0); BitConverter.GetBytes((ushort)0).CopyTo(request, 2); BitConverter.GetBytes((ushort)(7 + byteCount)).CopyTo(request, 4); // 长度 request[6] = ID; request[7] = (byte)Command.写多个线圈寄存器; // 功能码15 addrBytes.CopyTo(request, 8); BitConverter.GetBytes(length).Reverse().ToArray().CopyTo(request, 10); request[12] = (byte)byteCount; // 字节数 coilBytes.CopyTo(request, 13); // 线圈数据 byte[] response = SendRequest(request, 12); // 写入响应长度固定为12 return response != null && response.Length >= 12; } /// <summary> /// 将bool数组转换为字节数组 /// </summary> private byte[] ConvertBoolsToBytes(bool[] values, ushort length) { int byteCount = (length + 7) / 8; byte[] result = new byte[byteCount]; for (int i = 0; i < length; i++) { if (values[i]) { int byteIndex = i / 8; int bitIndex = i % 8; result[byteIndex] |= (byte)(1 << bitIndex); } } return result; } #endregion #region 写入多个保持寄存器值(D寄存器) /// <summary> /// 写入多个保持寄存器值 - 所有寄存器写入相同值 /// </summary> /// <param name="ID">设备ID</param> /// <param name="addr">起始地址(如"D100")</param> /// <param name="length">写入的寄存器数量</param> /// <param name="value">所有寄存器的值</param> /// <param name="dataType">数据类型(字=16位,双字=32位)</param> /// <returns>是否写入成功</returns> /// <example> /// WriteMultipleRegister(1, "D100", 3, 100); /// // 写入D100=100, D101=100, D102=100 /// WriteMultipleRegister(1, "D200", 2, 123456789, ReadTypeEnum.双字); /// // 写入D200-D201=123456789, D202-D203=123456789 /// </example> public bool WriteMultipleRegister(byte ID, string addr, int length, int value, ReadTypeEnum dataType = ReadTypeEnum.字) { if (!connecte) { info.msg = "未连接,无法写入数据"; LogInfoDisplay?.Invoke(info); return false; } // 参数验证 if (length <= 0 || length > 123) { info.msg = "写入寄存器数量必须在1-123之间"; LogInfoDisplay?.Invoke(info); return false; } if (dataType == ReadTypeEnum.字) { // 16位写入验证 if (value < -32768 || value > 65535) { info.msg = "16位写入值超出范围(-32768~65535)"; LogInfoDisplay?.Invoke(info); return false; } ushort[] registers = new ushort[length]; ushort writeValue = (ushort)value; for (int i = 0; i < length; i++) { registers[i] = writeValue; } return WriteMultipleRegisters16(ID, addr, registers); } else { // 32位写入 - 每个32位值占用2个寄存器 int actualRegisterCount = length * 2; if (actualRegisterCount > 123) { info.msg = $"32位写入需要{actualRegisterCount}个寄存器,超过最大限制123"; LogInfoDisplay?.Invoke(info); return false; } ushort[] registers = new ushort[actualRegisterCount]; for (int i = 0; i < length; i++) { int startIndex = i * 2; registers[startIndex] = (ushort)(value >> 16); // 高16位 registers[startIndex + 1] = (ushort)(value & 0xFFFF); // 低16位 } return WriteMultipleRegisters16(ID, addr, registers); } } /// <summary> /// 写入多个保持寄存器值(字符串版本) /// </summary> public bool WriteMultipleRegister(byte ID, string addr, int length, string value, ReadTypeEnum dataType = ReadTypeEnum.字) { if (int.TryParse(value, out int intValue)) { return WriteMultipleRegister(ID, addr, length, intValue, dataType); } else { info.msg = "写入值格式错误,必须为有效的整数"; LogInfoDisplay?.Invoke(info); return false; } } /// <summary> /// 写入多个保持寄存器值(独立值数组版本) /// </summary> public bool WriteMultipleRegister(byte ID, string addr, ushort[] values) { if (!connecte) { info.msg = "未连接,无法写入数据"; LogInfoDisplay?.Invoke(info); return false; } if (values == null || values.Length == 0 || values.Length > 123) { info.msg = "写入寄存器数量必须在1-123之间"; LogInfoDisplay?.Invoke(info); return false; } return WriteMultipleRegisters16(ID, addr, values); } /// <summary> /// 写入多个保持寄存器值(int数组版本) /// </summary> public bool WriteMultipleRegister(byte ID, string addr, int[] values) { if (!connecte) { info.msg = "未连接,无法写入数据"; LogInfoDisplay?.Invoke(info); return false; } if (values == null || values.Length == 0 || values.Length > 123) { info.msg = "写入寄存器数量必须在1-123之间"; LogInfoDisplay?.Invoke(info); return false; } // 转换为ushort数组 ushort[] shortValues = new ushort[values.Length]; for (int i = 0; i < values.Length; i++) { if (values[i] < 0 || values[i] > ushort.MaxValue) { info.msg = $"第{i + 1}个写入值超出范围,必须为0-{ushort.MaxValue}"; LogInfoDisplay?.Invoke(info); return false; } shortValues[i] = (ushort)values[i]; } return WriteMultipleRegisters16(ID, addr, shortValues); } /// <summary> /// 16位写入多个保持寄存器实现(功能码16) /// </summary> private bool WriteMultipleRegisters16(byte ID, string addr, ushort[] values) { byte[] addrBytes = AddrResolver(addr); ushort transactionId = GetNextTransactionId(); ushort length = (ushort)values.Length; // 构建请求 byte[] request = new byte[13 + length * 2]; BitConverter.GetBytes(transactionId).CopyTo(request, 0); BitConverter.GetBytes((ushort)0).CopyTo(request, 2); BitConverter.GetBytes((ushort)(7 + length * 2)).CopyTo(request, 4); // 长度 request[6] = ID; request[7] = (byte)Command.写多个保持寄存器; // 功能码16 addrBytes.CopyTo(request, 8); BitConverter.GetBytes(length).Reverse().ToArray().CopyTo(request, 10); request[12] = (byte)(length * 2); // 字节数 // 写入寄存器数据(注意字节序:高位在前) for (int i = 0; i < length; i++) { byte[] valueBytes = BitConverter.GetBytes(values[i]).Reverse().ToArray(); valueBytes.CopyTo(request, 13 + i * 2); } byte[] response = SendRequest(request, 12); return response != null && response.Length >= 12; } #endregion #region 数据接收 /// <summary> /// 数据接收 /// </summary> private void Receive() { while (connecte && clientSocket != null && !disposed) { try { int bytesRead = clientSocket.Receive(receiveBuffer); if (bytesRead > 0) { ProcessReceivedData(receiveBuffer, bytesRead); } else if (bytesRead == 0) { // 连接关闭 connecte = false; info.msg = "连接被远程关闭"; LogInfoDisplay?.Invoke(info); HandleReconnection(); break; } } catch (SocketException ex) { if (ex.SocketErrorCode == SocketError.ConnectionReset || ex.SocketErrorCode == SocketError.ConnectionAborted) { connecte = false; info.msg = "连接异常断开"; LogInfoDisplay?.Invoke(info); HandleReconnection(); break; } } catch (Exception) { connecte = false; break; } } } /// <summary> /// 处理接收到的数据 /// </summary> private void ProcessReceivedData(byte[] data, int length) { lock (responseLock) { currentResponse = new byte[length]; Array.Copy(data, 0, currentResponse, 0, length); responseReceived.Set(); // 触发接收事件 if (returnReceiveEvent != null) { string hexString = BitConverter.ToString(data, 0, length).Replace("-", " "); returnReceiveEvent(hexString); } } } #endregion #region 客户端关闭 /*客户端关闭*/ /// <summary> /// 客户端关闭 /// </summary> public void Close() { Disconnect(); } protected virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { autoReconnect = false; // 禁用自动重连 Disconnect(); responseReceived?.Dispose(); } disposed = true; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion } #endregion
10-09
#region ModbusTCP客户端 public class ModbusTCP : IDisposable { #region 字段与属性 private TcpClient tcpClient; //----------------------------------------TCP 客户端实例 private NetworkStream stream; //---------------------------------------网络流,用于收发数据 private volatile bool connected = false; //----------------------------当前连接状态 private readonly object socketLock = new object(); //------------------锁,保证线程安全 private static ushort transId = 1; //----------------------------------Modbus 事务ID,每次递增 string nameClient; //--------------------------------------------------客户端名称 private string currentIp; //-------------------------------------------当前连接IP private int currentPort; //--------------------------------------------当前连接端口 private volatile bool disposed = false; //-----------------------------是否已释放资源 private readonly object responseLock = new object(); //----------------响应锁 public del LogInfoDisplay; //-------------------------------------------委托变量-信息显示 public InfoMsg info = new InfoMsg("", ""); //---------------------------显示信息 public event Action<string> returnReceiveEvent;//-----------------------数据接收事件 //自动重连 private bool autoReconnect = true; //----------------------------------自动重连,默认开启 private int maxReconnectAttempts = 5; //-------------------------------重连次数 public bool Connected => connected; //---------------------------------连接状态 public bool AutoReconnect { get => autoReconnect; set { lock (socketLock) autoReconnect = value; } } public int MaxReconnectAttempts { get => maxReconnectAttempts; set { lock (socketLock) maxReconnectAttempts = value; } } public int ReconnectInterval { get; set; } = 5000; //------------------重连间隔 public int Timeout { get; set; } = 5000; //----------------------------超时时间(毫秒) public int AddressOffset { get; set; } = 0; //-------------------------地址偏移,默认M0对应0(如 D100 实际地址为100) private static readonly object idLock = new object(); private static ushort GenerateTransactionId() { lock (idLock) { return transId++; } } private System.Timers.Timer heartbeatTimer; //-------------------------心跳定时器 #endregion #region 数据类型枚举 /// <summary> /// 读写数据类型 /// 字 = 16位(short),双字 = 32位(int) /// </summary> public enum DataType : short { 字 = 1, 双字 = 2 } /// <summary> /// 寄存器内字节顺序(用于16位数据) /// </summary> public enum ByteOrder { BigEndian, // 高字节在前(AB) LittleEndian // 低字节在前(BA) } /// <summary> /// 定义32位数据在两个16位寄存器中的存储顺序 /// </summary> public enum WordOrder { /// <summary> /// 高字在前,低字在后(如西门子、标准 Modbus) /// 示例:0x12345678 → [0x1234][0x5678] /// </summary> HighFirst = 1, // 高位寄存器在前(ABCD) /// <summary> /// 低字在前,高字在后(如三菱、欧姆龙常见) /// 示例:0x12345678 → [0x5678][0x1234] /// </summary> LowFirst = 2 // 低位寄存器在前(CDAB) } #endregion #region 客户端初始化 /// <summary> /// 客户端初始化 /// </summary> /// <param name="name">客户端名称</param> /// <param name="receiveData">接收数据事件</param> /// <param name="info">消息显示事件</param> /// <param name="addressOffset">地址偏移量(如M0对应0,则为0;若PLC从1开始则设为-1)</param> public void Initialize(string name = "客户端", Action<string> receiveData = null, del info = null,int addressOffset = 0) { nameClient = name; //-----------------------客户端名称 returnReceiveEvent += receiveData; //-------接收数据事件 LogInfoDisplay += info; //------------------消息事件 this.info.msgType = nameClient; //----------log消息类型 this.AddressOffset = addressOffset; //------地址偏移设置 } /// <summary> /// 配置自动重连 /// </summary> /// <param name="enabled">开启重连true/false</param> /// <param name="intervalMs">重连间隔</param> /// <param name="maxAttempts">重连次数</param> public void ConfigureAutoReconnect(bool enabled, int intervalMs = 5000, int maxAttempts = 5) { AutoReconnect = enabled; ReconnectInterval = intervalMs; MaxReconnectAttempts = maxAttempts; } /// <summary> /// 配置并启用心跳功能 /// 通过定时器周期性向指定地址写入0/1交替信号,用于PLC监控连接状态 /// </summary> /// <param name="enabled">是否启用心跳功能,true=启用,false=禁用</param> /// <param name="address">心跳信号写入的PLC地址,如"D750"</param> /// <param name="intervalMs">心跳间隔时间,单位毫秒,默认500ms</param> /// <example> /// // 启用心跳,每500ms向D750地址写入0/1交替信号 /// modbusTCP.ConfigureHeartbeat(true, "D750", 500); /// /// // 禁用心跳功能 /// modbusTCP.ConfigureHeartbeat(false); /// </example> public void ConfigureHeartbeat(bool enabled = true, string address = "D700", int intervalMs = 500) { // 停止并释放之前的心跳定时器,避免多个定时器同时运行 heartbeatTimer?.Stop(); // 停止定时器 heartbeatTimer?.Dispose(); // 释放定时器资源 heartbeatTimer = null; // 如果禁用心跳,直接返回 if (!enabled) return; // 创建新的心跳定时器 heartbeatTimer = new System.Timers.Timer(intervalMs); bool state = false; // 心跳状态标志,用于0/1交替 heartbeatTimer.Elapsed += async (s, e) => { try { if (!connected) return; state = !state; await WriteSingleRegistersAsync(1, address, state ? 1 : 0); } catch (Exception ex) { // 异常必须在 async void 中捕获,否则会崩溃整个程序 Console.WriteLine("[Heartbeat] Exception: " + ex.Message); } }; heartbeatTimer.Start(); } #endregion #region 连接与断开 /// <summary> /// 连接到 Modbus TCP 服务器 /// </summary> /// <param name="ip">PLC IP 地址</param> /// <param name="port">端口,默认 502</param> /// <returns>是否连接成功</returns> public bool Connect(string ip, int port = 502) { lock (socketLock) { if (connected) return true; // 已连接则直接返回 try { tcpClient = new TcpClient(); tcpClient.SendTimeout = Timeout; tcpClient.ReceiveTimeout = Timeout; var result = tcpClient.BeginConnect(IPAddress.Parse(ip), port, null, null); var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(Timeout)); if (!success) { info.msg = $"连接超时 ({ip}:{port})"; LogInfoDisplay?.Invoke(info); return false; } tcpClient.EndConnect(result); stream = tcpClient.GetStream(); connected = true; currentIp = ip; currentPort = port; info.msg = $"连接成功 {ip}:{port}"; LogInfoDisplay?.Invoke(info); return true; } catch (Exception ex) { info.msg = $"连接失败: {ex.Message}"; LogInfoDisplay?.Invoke(info); Disconnect(); HandleReconnection(); return false; } } } /// <summary> /// 断开连接 /// </summary> public void Disconnect() { lock (socketLock) { if (!connected) return; try { connected = false; stream?.Close(); tcpClient?.Close(); stream = null; tcpClient = null; info.msg = "已断开连接"; LogInfoDisplay?.Invoke(info); } catch (Exception ex) { info.msg = $"断开异常: {ex.Message}"; LogInfoDisplay?.Invoke(info); } } } /// <summary> /// 处理自动重连逻辑(避免阻塞主线程) /// </summary> private void HandleReconnection() { if (!AutoReconnect || string.IsNullOrEmpty(currentIp)) return; Task.Run(() => { int attempts = 0; while (!connected && !disposed && attempts < MaxReconnectAttempts) { Thread.Sleep(ReconnectInterval); try { if (Connect(currentIp, currentPort)) { info.msg = "自动重连成功"; LogInfoDisplay?.Invoke(info); break; } attempts++; } catch { } } }); } #endregion #region 发送请求 /// <summary> /// 发送 Modbus 请求并等待响应 /// </summary> /// <param name="request">请求报文</param> /// <param name="expectedResponseLength">期望响应长度(用于校验)</param> /// <returns>响应字节数组,失败返回 null</returns> private async Task<byte[]> SendRequestAsync(byte[] request, int expectedResponseLength) { CancellationTokenSource cts = new CancellationTokenSource(); try { cts.CancelAfter(Timeout); // 建议 Timeout >= 3000 // 假设 tcpClient 已连接,networkStream = tcpClient.GetStream() if (stream == null || !stream.CanWrite) return null; // 设置事务ID(大端字节序) ushort transactionId = GenerateTransactionId(); // 自增或随机生成 request[0] = (byte)(transactionId >> 8); request[1] = (byte)(transactionId & 0xFF); // 发送请求 await stream.WriteAsync(request, 0, request.Length).ConfigureAwait(false); byte[] header = new byte[6]; // 先读6字节获取 Length int read = await ReadExactAsync(stream, header, 0, 6, cts.Token).ConfigureAwait(false); if (read != 6) return null; // 校验 Protocol ID short protocolId = (short)((header[2] << 8) | header[3]); if (protocolId != 0) { Console.WriteLine($"协议ID错误: {protocolId}"); return null; } // 解析后续长度(第5-6字节) int pduLength = (header[4] << 8) | header[5]; int totalResponseLength = 6 + pduLength; byte[] response = new byte[totalResponseLength]; Array.Copy(header, 0, response, 0, 6); if (pduLength > 0) { read = await ReadExactAsync(stream, response, 6, pduLength, cts.Token).ConfigureAwait(false); if (read != pduLength) return null; if (returnReceiveEvent != null) { string hexString = BitConverter.ToString(response).Replace("-", " "); returnReceiveEvent(hexString); } } // 校验 Transaction ID ushort responseTid = (ushort)((response[0] << 8) | response[1]); if (responseTid != transactionId) { Console.WriteLine("事务ID不匹配"); return null; } // 检查异常码 if (pduLength >= 2 && (response[7] & 0x80) == 0x80) { Console.WriteLine($"Modbus 异常码: 0x{response[8]:X2}"); return null; } // 最终长度校验 if (response.Length < expectedResponseLength) { Console.WriteLine("响应长度不足"); return null; } return response; } catch (TimeoutException) { Console.WriteLine("通信超时:等待响应时间过长"); return null; } catch (Exception ex) { Console.WriteLine("通信异常: " + ex.Message); connected = false; return null; } finally { // 必须手动 Dispose cts?.Dispose(); } } /// <summary> /// 确保异步读取指定数量的字节 /// </summary> private async Task<int> ReadExactAsync(NetworkStream stream, byte[] buffer, int offset, int count, CancellationToken ct = default) { int totalRead = 0; while (totalRead < count) { try { int read = await stream.ReadAsync(buffer, offset + totalRead, count - totalRead, ct).ConfigureAwait(false); if (read == 0) break; // 连接关闭 totalRead += read; } catch (OperationCanceledException) when (!ct.IsCancellationRequested) { throw new TimeoutException("读取超时"); } } return totalRead; } #endregion #region 地址解析 /// <summary> /// 如 PLC 地址从 1 开始编号,则设置 AddressOffset = -1 /// 解析地址字符串如 "D100" -> 实际地址 99(若偏移-1) /// </summary> private ushort ParseAddress(string address) { if (string.IsNullOrEmpty(address) || address.Length < 2) throw new ArgumentException("地址格式错误"); char type = char.ToUpper(address[0]); string numPart = address.Substring(1).Split('.')[0]; // 忽略位索引 if (!int.TryParse(numPart, out int rawAddr)) throw new ArgumentException("地址数值无效"); int baseAddress; switch (type) { case 'D': case 'M': case 'X': case 'Y': baseAddress = 0x0000; break; default: throw new ArgumentException($"不支持的地址类型: {type}"); } int finalAddr = baseAddress + rawAddr + AddressOffset; if (finalAddr < 0 || finalAddr > 65535) throw new ArgumentException("地址超出范围"); return (ushort)finalAddr; } #endregion #region 读取线圈寄存器(M寄存器) /// <summary> /// 读取线圈寄存器(功能码01) /// </summary> /// <param name="ID">客户端ID</param> /// <param name="address">地址</param> /// <param name="length">长度</param> /// <returns>值</returns> public async Task<bool[]> ReadCoilsAsync(byte ID, string address, int length) { if (length <= 0) length = 1; if (length > 2048) length = 2048; ushort addr = ParseAddress(address); int byteCount = (length + 7) / 8; byte[] request = new byte[12]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = 0x00; request[5] = 0x06; //----后续长度:6字节(PDU) request[6] = ID; //-------------------------单元标识符ID request[7] = 0x01; //-----------------------功能码(读线圈) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(length >> 8); //-------数量高字节 request[11] = (byte)(length & 0xFF); //-----数量低字节 byte[] response = await SendRequestAsync(request, 9 + byteCount).ConfigureAwait(false); if (response == null || response.Length < 9 + byteCount) return null; bool[] result = new bool[length]; for (int i = 0; i < length; i++) { int byteIdx = i / 8; int bitIdx = i % 8; result[i] = (response[9 + byteIdx] & (1 << bitIdx)) != 0; } return result; } #endregion #region 读取输入状态 /// <summary> /// 读取输入状态(功能码02) /// </summary> /// <param name="ID">客户端ID</param> /// <param name="address">地址</param> /// <param name="length">长度</param> /// <returns>值</returns> public async Task<bool[]> ReadDiscreteInputsAsync(byte ID, string address, int length) { if (length <= 0) length = 1; if (length > 2048) length = 2048; ushort addr = ParseAddress(address); int byteCount = (length + 7) / 8; byte[] request = new byte[12]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = 0x00; request[5] = 0x06; //----后续长度:6字节(PDU) request[6] = ID; //-------------------------单元标识符ID request[7] = 0x02; //-----------------------功能码(读离散输入) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(length >> 8); //-------数量高字节 request[11] = (byte)(length & 0xFF); //-----数量低字节 byte[] response = await SendRequestAsync(request, 9 + byteCount).ConfigureAwait(false); if (response == null || response.Length < 9 + byteCount) return null; bool[] result = new bool[length]; for (int i = 0; i < length; i++) { int byteIdx = i / 8; int bitIdx = i % 8; result[i] = (response[9 + byteIdx] & (1 << bitIdx)) != 0; } return result; } #endregion #region 读取保持寄存器(D寄存器) /// <summary> /// 读取保持寄存器(功能码03) /// </summary> /// <param name="ID">客户端ID</param> /// <param name="address">地址</param> /// <param name="length">长度</param> /// <param name="type">类型</param> /// <returns>值</returns> public async Task<int[]> ReadHoldingRegistersAsync(byte ID, string address, int length, DataType type = DataType.字) { if (length <= 0) length = 1; if (length > 125) length = 125; ushort addr = ParseAddress(address); ushort regCount = (ushort)(length * (ushort)type); byte[] request = new byte[12]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = 0x00; request[5] = 0x06; //----后续长度:6字节(PDU) request[6] = ID; //-------------------------单元标识符ID request[7] = 0x03; //-----------------------功能码(读保持寄存器) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(regCount >> 8); //-----数量高字节 request[11] = (byte)(regCount & 0xFF); //---数量低字节 byte[] response = await SendRequestAsync(request, 9 + regCount * 2).ConfigureAwait(false); if (response == null || response.Length < 9 + regCount * 2) return null; return ParseRegisterResponse(response, length, type); } /// <summary> /// 解析寄存器响应(16位和32位) /// </summary> private int[] ParseRegisterResponse(byte[] response, int length, DataType type, ByteOrder byteOrder = ByteOrder.BigEndian, WordOrder wordOrder = WordOrder.HighFirst) { int[] result = new int[length]; if (type == DataType.字) { for (int i = 0; i < length; i++) { int dataIndex = 9 + i * 2; if (dataIndex + 1 < response.Length) { ushort value; if (byteOrder == ByteOrder.BigEndian) { value = (ushort)(response[dataIndex] << 8 | response[dataIndex + 1]); // AB } else { value = (ushort)(response[dataIndex] | response[dataIndex + 1] << 8); // BA } result[i] = (short)value; // 直接转换为有符号short } } } else //双字 { for (int i = 0; i < length; i++) { int dataIndex = 9 + i * 4; if (dataIndex + 3 < response.Length) { // Step 1: 先按字节序读两个16位寄存器 ushort reg1, reg2; if (byteOrder == ByteOrder.BigEndian) { reg1 = (ushort)(response[dataIndex] << 8 | response[dataIndex + 1]); reg2 = (ushort)(response[dataIndex + 2] << 8 | response[dataIndex + 3]); } else { reg1 = (ushort)(response[dataIndex + 1] << 8 | response[dataIndex]); reg2 = (ushort)(response[dataIndex + 3] << 8 | response[dataIndex + 2]); } // Step 2: 再按字序组合 uint value; if (wordOrder == WordOrder.HighFirst) { value = ((uint)reg1 << 16) | reg2; } else { value = ((uint)reg2 << 16) | reg1; } result[i] = (int)value; // 自动处理补码 } } } return result; } #endregion #region 读取输入寄存器 /// <summary> /// 读取输入寄存器(功能码04) /// </summary> /// <param name="ID">客户端ID</param> /// <param name="address">地址</param> /// <param name="length">长度</param> /// <param name="type">类型</param> /// <returns>值</returns> public async Task<int[]> ReadInputRegistersAsync(byte ID, string address, int length, DataType type = DataType.字) { if (length <= 0) length = 1; if (length > 125) length = 125; ushort addr = ParseAddress(address); ushort regCount = (ushort)(length * (ushort)type); byte[] request = new byte[12]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = 0x00; request[5] = 0x06; //----后续长度:6字节(PDU) request[6] = ID; //-------------------------单元标识符ID request[7] = 0x04; //-----------------------功能码(读输入寄存器) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(regCount >> 8); //-----数量高字节 request[11] = (byte)(regCount & 0xFF); //---数量低字节 byte[] response = await SendRequestAsync(request, 9 + regCount * 2).ConfigureAwait(false); if (response == null || response.Length < 9 + regCount * 2) return null; return ParseRegisterResponse(response, length, type); } #endregion #region 写入单个线圈寄存器值(M寄存器) /// <summary> /// 写入单个线圈寄存器值(功能码05) /// </summary> /// <param name="ID">客户端ID</param> /// <param name="address">地址</param> /// <param name="value">值</param> public async Task<bool> WriteSingleCoilsAsync(byte ID, string address, bool value) { if (!connected) return false; ushort addr = ParseAddress(address); byte[] request = new byte[12]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = 0x00; request[5] = 0x06; //----后续长度:6字节(PDU) request[6] = ID; //-------------------------单元标识符ID request[7] = 0x05; //-----------------------功能码(写单个线圈) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(value ? 0xFF : 0x00); //数据(FF表示ON,00表示OFF) request[11] = 0x00; //----------------------数据(固定0x00) try { byte[] response = await SendRequestAsync(request, 12).ConfigureAwait(false); return response != null && response.Length >= 12 && response[7] == 0x05 && (response[8] << 8 | response[9]) == addr; } catch { return false; } } #endregion #region 写入单个保持寄存器值(D寄存器) /// <summary> /// 写入单个保持寄存器值(功能码06)(功能码16) /// </summary> /// <param name="ID">设备ID</param> /// <param name="address">地址</param> /// <param name="value">值(16位或32位)</param> /// <param name="type">数据类型(字=16位,双字=32位)</param> /// <returns>是否写入成功</returns> public async Task<bool> WriteSingleRegistersAsync(byte ID, string address, int value, DataType type = DataType.字) { if (!connected) return false; if (type == DataType.字) { return await WriteSingleRegister16(ID, address, (short)value); } else { return await WriteDoubleRegister32(ID, address, (int)value); } } /// <summary> /// 16位写入实现(功能码06) /// </summary> /// <param name="ID"></param> /// <param name="address"></param> /// <param name="value"></param> /// <returns></returns> private async Task<bool> WriteSingleRegister16(byte ID, string address, short value) { ushort addr = ParseAddress(address); ushort uvalue = (ushort)value; byte[] request = new byte[12]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = 0x00; request[5] = 0x06; //----后续长度:6字节(PDU) request[6] = ID; //-------------------------单元标识符ID request[7] = 0x06; //-----------------------功能码(写16位保存寄存器) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(uvalue >> 8); //-------数量高字节 request[11] = (byte)(uvalue & 0xFF); //-----数量低字节 byte[] response = await SendRequestAsync(request, 12).ConfigureAwait(false); return response != null && response.Length >= 12 && response[7] == 0x06 && (response[8] << 8 | response[9]) == addr; } /// <summary> /// 32位写入实现(功能码16) /// </summary> /// <param name="ID"></param> /// <param name="address"></param> /// <param name="value"></param> /// <returns></returns> private async Task<bool> WriteDoubleRegister32(byte ID, string address, int value, WordOrder wordOrder = WordOrder.HighFirst) { short hiWord = (short)(value >> 16); short loWord = (short)(value & 0xFFFF); short[] words = wordOrder == WordOrder.HighFirst ? new[] { hiWord, loWord } : new[] { loWord, hiWord }; return await WriteMultipleRegistersAsync(ID, address, words); } #endregion #region 写入多个线圈寄存器值(M寄存器) /// <summary> /// 写入多个线圈寄存器值(功能码15)- 所有线圈写入相同值 /// </summary> /// <param name="ID">设备ID</param> /// <param name="address">起始地址(如"M100")</param> /// <param name="length">写入的线圈数量</param> /// <param name="value">所有线圈的状态(true=ON, false=OFF)</param> /// <returns>是否写入成功</returns> /// <example> /// WriteMultipleCoils(1, "M100", 2, true); /// // 写入M100=true, M101=true /// WriteMultipleCoils(1, "M100", 5, false); /// // 写入M100~M104全部为false /// </example> public async Task<bool> WriteMultipleCoilsAsync(byte ID, string address, int length, bool value) { if (!connected) return false; bool[] values = new bool[length]; for (int i = 0; i < length; i++) values[i] = value; return await WriteMultipleCoilsAsync(ID, address, values); } /// <summary> /// 写入多个线圈寄存器值(bool数组版本)- 每个线圈独立控制 /// </summary> /// <param name="ID">设备ID</param> /// <param name="address">起始地址</param> /// <param name="values">每个线圈的状态数组</param> /// <returns>是否写入成功</returns> /// <example> /// WriteMultipleCoils(1, "M100", new bool[] { true, false, true }); /// // 写入M100=true, M101=false, M102=true /// </example> public async Task<bool> WriteMultipleCoilsAsync(byte ID, string address, bool[] values) { if (!connected) return false; ushort addr = ParseAddress(address); int byteCount = (values.Length + 7) / 8; byte[] coilBytes = new byte[byteCount]; for (int i = 0; i < values.Length; i++) { if (values[i]) coilBytes[i / 8] |= (byte)(1 << (i % 8)); } byte[] request = new byte[13 + byteCount]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = (byte)((request.Length - 6) >> 8); request[5] = (byte)((request.Length - 6) & 0xFF); request[6] = ID; //-------------------------单元标识符ID request[7] = 0x0F; //-----------------------功能码(写多线圈寄存器) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(values.Length >> 8); //数量高字节 request[11] = (byte)(values.Length & 0xFF);//数量低字节 request[12] = (byte)byteCount; Array.Copy(coilBytes, 0, request, 13, byteCount); byte[] response = await SendRequestAsync(request, 12).ConfigureAwait(false); return response != null && response.Length >= 12; } #endregion #region 写入多个保持寄存器值(D寄存器) /// <summary> /// 写入多个保持寄存器值(功能码16) - 所有寄存器写入相同值 /// </summary> /// <param name="ID">设备ID</param> /// <param name="address">起始地址(如"D100")</param> /// <param name="length">写入的寄存器数量</param> /// <param name="value">所有寄存器的值</param> /// <param name="type">数据类型(字=16位,双字=32位)</param> /// <returns>是否写入成功</returns> /// <example> /// WriteMultipleRegister(1, "D100", 3, 100); /// // 写入D100=100, D101=100, D102=100 /// WriteMultipleRegister(1, "D200", 2, 123456789, ReadTypeEnum.双字); /// // 写入D200-D201=123456789, D202-D203=123456789 /// </example> public async Task<bool> WriteMultipleRegistersAsync(byte ID, string address, int length, int value, DataType type = DataType.字) { if (!connected) return false; if (type == DataType.字) { short[] arr = new short[length]; for (int i = 0; i < length; i++) arr[i] = (short)value; return await WriteMultipleRegistersAsync(ID, address, arr); } else { short[] arr = new short[length * 2]; for (int i = 0; i < length; i++) { arr[i * 2] = (short)(value >> 16); arr[i * 2 + 1] = (short)(value & 0xFFFF); } return await WriteMultipleRegistersAsync(ID, address, arr); } } /// <summary> /// 写入多个保持寄存器值(功能码16) /// </summary> /// <param name="ID"></param> /// <param name="address"></param> /// <param name="values"></param> /// <returns></returns> private async Task<bool> WriteMultipleRegistersAsync(byte ID, string address, short[] values) { ushort addr = ParseAddress(address); byte[] request = new byte[13 + values.Length * 2]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = (byte)((request.Length - 6) >> 8); request[5] = (byte)((request.Length - 6) & 0xFF); request[6] = ID; //-------------------------单元标识符ID request[7] = 0x10; //-----------------------功能码(写多保存寄存器) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(values.Length >> 8); //数量高字节 request[11] = (byte)(values.Length & 0xFF);//数量低字节 request[12] = (byte)(values.Length * 2); for (int i = 0; i < values.Length; i++) { request[13 + i * 2] = (byte)(values[i] >> 8); request[13 + i * 2 + 1] = (byte)(values[i] & 0xFF); } byte[] response = await SendRequestAsync(request, 12).ConfigureAwait(false); return response != null && response.Length >= 12 && response[7] == 0x10 && (response[8] << 8 | response[9]) == addr && (response[10] << 8 | response[11]) == values.Length; } #endregion #region 同步支持 /// <summary> /// 读取线圈寄存器(功能码01) /// </summary> public bool[] ReadCoils(byte ID, string address, int length) { return ReadCoilsAsync(ID, address, length).GetAwaiter().GetResult(); } /// <summary> /// 读取输入状态(功能码02) /// </summary> public bool[] ReadDiscreteInputs(byte ID, string address, int length) { return ReadDiscreteInputsAsync(ID, address, length).GetAwaiter().GetResult(); } /// <summary> /// 读取保持寄存器(功能码03) /// </summary> public int[] ReadHoldingRegisters(byte ID, string address, int length, DataType type = DataType.字) { return ReadHoldingRegistersAsync(ID, address, length, type).GetAwaiter().GetResult(); } /// <summary> /// 读取输入寄存器(功能码04) /// </summary> public int[] ReadInputRegisters(byte ID, string address, int length, DataType type = DataType.字) { return ReadInputRegistersAsync(ID, address, length, type).GetAwaiter().GetResult(); } /// <summary> /// 写入单个线圈(功能码05) /// </summary> public bool WriteSingleCoils(byte ID, string address, bool value) { return WriteSingleCoilsAsync(ID, address, value).GetAwaiter().GetResult(); } /// <summary> /// 写入单个寄存器(功能码06 或 16) /// </summary> public bool WriteSingleRegisters(byte ID, string address, int value, DataType type = DataType.字) { return WriteSingleRegistersAsync(ID, address, value, type).GetAwaiter().GetResult(); } /// <summary> /// 写入多个线圈(相同值)(功能码15) /// </summary> public bool WriteMultipleCoils(byte ID, string address, int length, bool value) { return WriteMultipleCoilsAsync(ID, address, length, value).GetAwaiter().GetResult(); } /// <summary> /// 写入多个线圈(数组)(功能码15) /// </summary> public bool WriteMultipleCoils(byte ID, string address, bool[] values) { return WriteMultipleCoilsAsync(ID, address, values).GetAwaiter().GetResult(); } /// <summary> /// 写入多个寄存器(相同值)(功能码16) /// </summary> public bool WriteMultipleRegisters(byte ID, string address, int length, int value, DataType type = DataType.字) { return WriteMultipleRegistersAsync(ID, address, length, value, type).GetAwaiter().GetResult(); } #endregion #region 客户端关闭 /// <summary> /// 释放所有资源 /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposed) return; disposed = true; if (disposing) { // 释放托管资源 heartbeatTimer?.Dispose(); stream?.Dispose(); tcpClient?.Dispose(); } } #endregion } #endregion这是我的modbus代码。private void btn_桌盘_Click(object sender, EventArgs e) { automation.modbusTCP.WriteSingleCoilsAsync(1, "M16", true); //automation.modbusTCP.WriteSingleRegistersAsync(1, "D0", 100); //automation.modbusTCP.WriteMultipleRegistersAsync(1, "D1" , 4, 50); //bool[] result = await automation.modbusTCP.ReadCoilsAsync(1, "M0", 1); }这是一个写入寄存器代码。private void btn_自动切屏_Click(object sender, EventArgs e) { //automation.modbusTCP.WriteSingleCoilsAsync(1, "M1000", true); bool[] result = automation.modbusTCP.ReadCoils(1, "M16", 1); if (result != null && result.Length > 0) { bool m16Value = result[0]; MessageBox.Show("M16 = " + m16Value); } else { MessageBox.Show("读取失败"); } }这是读寄存器代码。用modbusSlave测试读写都可以正常实现。但是有这么一种情况。private void btn_桌盘_Click(object sender, EventArgs e) { automation.modbusTCP.WriteSingleCoilsAsync(1, "M16", true); automation.modbusTCP.WriteSingleRegistersAsync(1, "D0", 100); automation.modbusTCP.WriteMultipleRegistersAsync(1, "D1" , 4, 50); //bool[] result = await automation.modbusTCP.ReadCoilsAsync(1, "M0", 1); }这个代码写多个。private void btn_自动切屏_Click(object sender, EventArgs e) { //automation.modbusTCP.WriteSingleCoilsAsync(1, "M1000", true); bool[] result = automation.modbusTCP.ReadCoils(1, "M16", 1); if (result != null && result.Length > 0) { bool m16Value = result[0]; MessageBox.Show("M16 = " + m16Value); } else { MessageBox.Show("读取失败"); } }这个保持不变。这时候如果先写入寄存器,能写入成功,但是读寄存器总是失败。如果先读就没事
最新发布
10-14
#region ModbusTCP客户端 public class ModbusTCP : IDisposable { #region 字段与属性 private TcpClient tcpClient; //----------------------------------------TCP 客户端实例 private NetworkStream stream; //---------------------------------------网络流,用于收发数据 private Thread receiveTh; //-------------------------------------------接收数据线程 private bool connected = false; //-------------------------------------当前连接状态 private readonly object socketLock = new object(); //------------------锁,保证线程安全 private ManualResetEvent receiveEvent = new ManualResetEvent(false); private byte[] receiveBuffer = new byte[256]; //-----------------------接收缓冲区 private byte[] responseBytes; //---------------------------------------存储完整响应报文 private byte[] currentResponse; //-------------------------------------当前接收到的原始数据 private static ushort transId = 1; //----------------------------------Modbus 事务ID,每次递增 string nameClient; //--------------------------------------------------客户端名称 private string currentIp; //-------------------------------------------当前连接IP private int currentPort; //--------------------------------------------当前连接端口 private volatile bool disposed = false; //-----------------------------是否已释放资源 private readonly object responseLock = new object(); //----------------响应锁 public del LogInfoDisplay; //-------------------------------------------委托变量-信息显示 public InfoMsg info = new InfoMsg("", ""); //---------------------------显示信息 public event Action<string> returnReceiveEvent;//-----------------------数据接收事件 //自动重连 private bool autoReconnect = true; //----------------------------------自动重连,默认开启 private int reconnectInterval = 5000; //-------------------------------重连间隔 private int maxReconnectAttempts = 5; //-------------------------------重连次数 public bool AutoReconnect { get => autoReconnect; set { lock (socketLock) autoReconnect = value; } } public int ReconnectInterval { get; set; } = 3000; //------------------重连间隔 public int Timeout { get; set; } = 5000; //----------------------------超时时间(毫秒) public int AddressOffset { get; set; } = 0; //-------------------------地址偏移,默认M0对应0(如 D100 实际地址为100) private static readonly object idLock = new object(); private static ushort GenerateTransactionId() { lock (idLock) { return transId++; } } private System.Timers.Timer heartbeatTimer; //-------------------------心跳定时器 #endregion #region 数据类型枚举 /// <summary> /// 读写数据类型 /// 字 = 16位(short),双字 = 32位(int) /// </summary> public enum DataType : short { 字 = 1, 双字 = 2 } /// <summary> /// 定义32位数据在两个16位寄存器中的存储顺序 /// </summary> public enum WordOrder { /// <summary> /// 高字在前,低字在后(如西门子、标准 Modbus) /// 示例:0x12345678 → [0x1234][0x5678] /// </summary> HighFirst = 1, /// <summary> /// 低字在前,高字在后(如三菱、欧姆龙常见) /// 示例:0x12345678 → [0x5678][0x1234] /// </summary> LowFirst = 2 } #endregion #region 客户端初始化 /// <summary> /// 客户端初始化 /// </summary> /// <param name="name">客户端名称</param> /// <param name="receiveData">接收数据事件</param> /// <param name="info">消息显示事件</param> /// <param name="addressOffset">地址偏移量(如M0对应0,则为0;若PLC从1开始则设为-1)</param> public void Initialize(string name = "客户端", Action<string> receiveData = null, del info = null, int addressOffset = 0) { nameClient = name; //-----------------------客户端名称 returnReceiveEvent += receiveData; //-------接收数据事件 LogInfoDisplay += info; //------------------消息事件 this.info.msgType = nameClient; //----------log消息类型 this.AddressOffset = addressOffset; //------地址偏移设置 } /// <summary> /// 配置自动重连 /// </summary> /// <param name="enabled">开启重连true/false</param> /// <param name="intervalMs">重连间隔</param> /// <param name="maxAttempts">重连次数</param> public void ConfigureAutoReconnect(bool enabled, int intervalMs = 5000, int maxAttempts = 5) { autoReconnect = enabled; reconnectInterval = intervalMs; maxReconnectAttempts = maxAttempts; AutoReconnect = autoReconnect; ReconnectInterval = reconnectInterval; } /// <summary> /// 配置并启用心跳功能 /// 通过定时器周期性向指定地址写入0/1交替信号,用于PLC监控连接状态 /// </summary> /// <param name="enabled">是否启用心跳功能,true=启用,false=禁用</param> /// <param name="address">心跳信号写入的PLC地址,如"D750"</param> /// <param name="intervalMs">心跳间隔时间,单位毫秒,默认500ms</param> /// <example> /// // 启用心跳,每500ms向D750地址写入0/1交替信号 /// modbusTCP.ConfigureHeartbeat(true, "D750", 500); /// /// // 禁用心跳功能 /// modbusTCP.ConfigureHeartbeat(false); /// </example> public void ConfigureHeartbeat(bool enabled = true, string address = "D700", int intervalMs = 500) { // 停止并释放之前的心跳定时器,避免多个定时器同时运行 heartbeatTimer?.Stop(); // 停止定时器 heartbeatTimer?.Dispose(); // 释放定时器资源 heartbeatTimer = null; // 如果禁用心跳,直接返回 if (!enabled) return; // 创建新的心跳定时器 heartbeatTimer = new System.Timers.Timer(intervalMs); bool state = false; // 心跳状态标志,用于0/1交替 heartbeatTimer.Elapsed += async (s, e) => { try { if (!connected) return; state = !state; await WriteSingleRegistersAsync(1, address, state ? 1 : 0); } catch (Exception ex) { // 异常必须在 async void 中捕获,否则会崩溃整个程序 Console.WriteLine("[Heartbeat] Exception: " + ex.Message); } }; heartbeatTimer.Start(); } #endregion #region 连接与断开 /// <summary> /// 连接到 Modbus TCP 服务器 /// </summary> /// <param name="ip">PLC IP 地址</param> /// <param name="port">端口,默认 502</param> /// <returns>是否连接成功</returns> public bool Connect(string ip, int port = 502) { lock (socketLock) { if (connected) return true; // 已连接则直接返回 try { tcpClient = new TcpClient(); tcpClient.SendTimeout = Timeout; tcpClient.ReceiveTimeout = Timeout; var result = tcpClient.BeginConnect(IPAddress.Parse(ip), port, null, null); var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(Timeout)); if (!success) { info.msg = $"连接超时 ({ip}:{port})"; LogInfoDisplay?.Invoke(info); return false; } tcpClient.EndConnect(result); stream = tcpClient.GetStream(); connected = true; currentIp = ip; currentPort = port; StartReceiveThread(); // 启动接收线程 info.msg = $"连接成功 {ip}:{port}"; LogInfoDisplay?.Invoke(info); return true; } catch (Exception ex) { info.msg = $"连接失败: {ex.Message}"; LogInfoDisplay?.Invoke(info); Disconnect(); HandleReconnection(); return false; } } } /// <summary> /// 断开连接 /// </summary> public void Disconnect() { lock (socketLock) { if (!connected) return; try { connected = false; receiveEvent.Set(); receiveTh?.Interrupt(); receiveTh = null; stream?.Close(); tcpClient?.Close(); stream = null; tcpClient = null; info.msg = "已断开连接"; LogInfoDisplay?.Invoke(info); } catch (Exception ex) { info.msg = $"断开异常: {ex.Message}"; LogInfoDisplay?.Invoke(info); } } } /// <summary> /// 处理自动重连逻辑(避免阻塞主线程) /// </summary> private void HandleReconnection() { if (!AutoReconnect || string.IsNullOrEmpty(currentIp)) return; Task.Run(() => { int attempts = 0; while (!connected && !disposed && attempts < maxReconnectAttempts) { Thread.Sleep(ReconnectInterval); try { if (Connect(currentIp, currentPort)) { info.msg = "自动重连成功"; LogInfoDisplay?.Invoke(info); break; } attempts++; } catch { } } }); } #endregion #region 接收线程 /// <summary> /// 启动后台接收线程 /// </summary> private void StartReceiveThread() { receiveTh = new Thread(ReceiveThreadProc) { IsBackground = true, Name = "ModbusTCP接收线程" }; receiveTh.Start(); } /// <summary> /// 接收线程主体:持续监听数据 /// </summary> private void ReceiveThreadProc() { while (connected && !disposed) { try { if (stream.DataAvailable) { // 先读取 MBAP 头(前6字节) int len = stream.Read(receiveBuffer, 0, 6); if (len < 6) continue; // 解析协议数据单元长度(大端) int pduLength = (receiveBuffer[4] << 8) | receiveBuffer[5]; int totalLength = 6 + pduLength; // 读取剩余部分 int read = 0; while (read < pduLength) { int n = stream.Read(receiveBuffer, 6 + read, pduLength - read); if (n <= 0) throw new IOException("连接中断"); read += n; } ProcessReceivedData(receiveBuffer, totalLength); } else { Thread.Sleep(10); // 小延时避免CPU空转 } } catch (SocketException ex) { // 连接被对端关闭或中断 if (ex.SocketErrorCode == SocketError.ConnectionReset || ex.SocketErrorCode == SocketError.ConnectionAborted) { info.msg = "连接被远程关闭或异常断开"; LogInfoDisplay?.Invoke(info); connected = false; HandleReconnection(); } else { info.msg = $"Socket异常: {ex.Message}"; LogInfoDisplay?.Invoke(info); } } catch (Exception ex) { if (connected) { info.msg = $"接收数据异常: {ex.Message}"; LogInfoDisplay?.Invoke(info); connected = false; HandleReconnection(); } break; } } } /// <summary> /// 处理接收到的数据包 /// </summary> private void ProcessReceivedData(byte[] data, int length) { lock (responseLock) { currentResponse = new byte[length]; Array.Copy(data, 0, currentResponse, 0, length); responseBytes = currentResponse; receiveEvent.Set(); // 通知等待响应的线程 // 触发接收事件(Hex字符串格式) if (returnReceiveEvent != null) { string hexString = BitConverter.ToString(data, 0, length).Replace("-", " "); returnReceiveEvent(hexString); } } } #endregion #region 发送请求 /// <summary> /// 发送 Modbus 请求并等待响应 /// </summary> /// <param name="request">请求报文</param> /// <param name="expectedResponseLength">期望响应长度(用于校验)</param> /// <returns>响应字节数组,失败返回 null</returns> private async Task<byte[]> SendRequestAsync(byte[] request, int expectedResponseLength) { try { // 假设 tcpClient 已连接,networkStream = tcpClient.GetStream() if (stream == null || !stream.CanWrite) return null; // 设置事务ID(大端字节序) ushort transactionId = GenerateTransactionId(); // 自增或随机生成 request[0] = (byte)(transactionId >> 8); request[1] = (byte)(transactionId & 0xFF); // 发送请求 await stream.WriteAsync(request, 0, request.Length).ConfigureAwait(false); // 读取响应头(至少 9 字节 MBAP + PDU header) byte[] header = new byte[9]; int read = await ReadExactAsync(stream, header, 0, 9).ConfigureAwait(false); if (read != 9) return null; // 解析后续长度(第5-6字节) int pduLength = (header[4] << 8) | header[5]; // 后续数据长度 int totalResponseLength = 6 + pduLength; // MBAP(6) + PDU if (totalResponseLength > expectedResponseLength) { // 扩展缓冲区以接收完整响应 byte[] fullResponse = new byte[totalResponseLength]; Array.Copy(header, 0, fullResponse, 0, 9); int remaining = totalResponseLength - 9; read = await ReadExactAsync(stream, fullResponse, 9, remaining).ConfigureAwait(false); if (read != remaining) return null; return fullResponse; } return header; // 如果已足够 } catch (Exception ex) { Console.WriteLine("通信异常: " + ex.Message); connected = false; return null; } } /// <summary> /// 确保异步读取指定数量的字节 /// </summary> private async Task<int> ReadExactAsync(NetworkStream stream, byte[] buffer, int offset, int count) { int totalRead = 0; while (totalRead < count) { int read = await stream.ReadAsync(buffer, offset + totalRead, count - totalRead).ConfigureAwait(false); if (read == 0) break; // 连接关闭 totalRead += read; } return totalRead; } #endregion #region 地址解析 /// <summary> /// 解析地址字符串如 "D100" -> 实际地址 99(若偏移-1) /// </summary> private ushort ParseAddress(string address) { if (string.IsNullOrEmpty(address) || address.Length < 2) throw new ArgumentException("地址格式错误"); char type = char.ToUpper(address[0]); string numPart = address.Substring(1).Split('.')[0]; // 忽略位索引 if (!int.TryParse(numPart, out int rawAddr)) throw new ArgumentException("地址数值无效"); int baseAddress; switch (type) { case 'D': case 'M': case 'X': case 'Y': baseAddress = 0x0000; break; default: throw new ArgumentException($"不支持的地址类型: {type}"); } int finalAddr = baseAddress + rawAddr + AddressOffset; if (finalAddr < 0 || finalAddr > 65535) throw new ArgumentException("地址超出范围"); return (ushort)finalAddr; } #endregion #region 读取线圈寄存器(M寄存器) /// <summary> /// 读取线圈寄存器(功能码01) /// </summary> /// <param name="ID">客户端ID</param> /// <param name="address">地址</param> /// <param name="length">长度</param> /// <returns>值</returns> public async Task<bool[]> ReadCoilsAsync(byte ID, string address, int length) { if (length <= 0) length = 1; if (length > 2048) length = 2048; ushort addr = ParseAddress(address); int byteCount = (length + 7) / 8; byte[] request = new byte[12]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = 0x00; request[5] = 0x06; //----后续长度:6字节(PDU) request[6] = ID; //-------------------------单元标识符ID request[7] = 0x01; //-----------------------功能码(读线圈) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(length >> 8); //-------数量高字节 request[11] = (byte)(length & 0xFF); //-----数量低字节 byte[] response = await SendRequestAsync(request, 9 + byteCount).ConfigureAwait(false); if (response == null || response.Length < 9 + byteCount) return null; bool[] result = new bool[length]; for (int i = 0; i < length; i++) { int byteIdx = i / 8; int bitIdx = i % 8; result[i] = (response[9 + byteIdx] & (1 << bitIdx)) != 0; } return result; } #endregion #region 读取输入状态 /// <summary> /// 读取输入状态(功能码02) /// </summary> /// <param name="ID">客户端ID</param> /// <param name="address">地址</param> /// <param name="length">长度</param> /// <returns>值</returns> public async Task<bool[]> ReadDiscreteInputsAsync(byte ID, string address, int length) { if (length <= 0) length = 1; if (length > 2048) length = 2048; ushort addr = ParseAddress(address); int byteCount = (length + 7) / 8; byte[] request = new byte[12]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = 0x00; request[5] = 0x06; //----后续长度:6字节(PDU) request[6] = ID; //-------------------------单元标识符ID request[7] = 0x02; //-----------------------功能码(读离散输入) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(length >> 8); //-------数量高字节 request[11] = (byte)(length & 0xFF); //-----数量低字节 byte[] response = await SendRequestAsync(request, 9 + byteCount).ConfigureAwait(false); if (response == null || response.Length < 9 + byteCount) return null; bool[] result = new bool[length]; for (int i = 0; i < length; i++) { int byteIdx = i / 8; int bitIdx = i % 8; result[i] = (response[9 + byteIdx] & (1 << bitIdx)) != 0; } return result; } #endregion #region 读取保持寄存器(D寄存器) /// <summary> /// 读取保持寄存器(功能码03) /// </summary> /// <param name="ID">客户端ID</param> /// <param name="address">地址</param> /// <param name="length">长度</param> /// <param name="type">类型</param> /// <returns>值</returns> public async Task<int[]> ReadHoldingRegistersAsync(byte ID, string address, int length, DataType type = DataType.字) { if (length <= 0) length = 1; if (length > 125) length = 125; ushort addr = ParseAddress(address); ushort regCount = (ushort)(length * (ushort)type); byte[] request = new byte[12]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = 0x00; request[5] = 0x06; //----后续长度:6字节(PDU) request[6] = ID; //-------------------------单元标识符ID request[7] = 0x03; //-----------------------功能码(读保持寄存器) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(regCount >> 8); //-----数量高字节 request[11] = (byte)(regCount & 0xFF); //---数量低字节 byte[] response = await SendRequestAsync(request, 9 + regCount * 2).ConfigureAwait(false); if (response == null || response.Length < 9 + regCount * 2) return null; return ParseRegisterResponse(response, length, type); } /// <summary> /// 解析寄存器响应(16位和32位) /// </summary> private int[] ParseRegisterResponse(byte[] response, int length, DataType type) { int[] result = new int[length]; if (type == DataType.字) { for (int i = 0; i < length; i++) { int dataIndex = 9 + i * 2; if (dataIndex + 1 < response.Length) { ushort value = (ushort)((response[dataIndex] << 8) | response[dataIndex + 1]); result[i] = (short)value; // 直接转换为有符号short } } } else //双字 { for (int i = 0; i < length; i++) { int dataIndex = 9 + i * 4; if (dataIndex + 3 < response.Length) { uint value = ((uint)response[dataIndex] << 24) | ((uint)response[dataIndex + 1] << 16) | ((uint)response[dataIndex + 2] << 8) | response[dataIndex + 3]; result[i] = (int)value; // 自动处理补码 } } } return result; } #endregion #region 读取输入寄存器 /// <summary> /// 读取输入寄存器(功能码04) /// </summary> /// <param name="ID">客户端ID</param> /// <param name="address">地址</param> /// <param name="length">长度</param> /// <param name="type">类型</param> /// <returns>值</returns> public async Task<int[]> ReadInputRegistersAsync(byte ID, string address, int length, DataType type = DataType.字) { if (length <= 0) length = 1; if (length > 125) length = 125; ushort addr = ParseAddress(address); ushort regCount = (ushort)(length * (ushort)type); byte[] request = new byte[12]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = 0x00; request[5] = 0x06; //----后续长度:6字节(PDU) request[6] = ID; //-------------------------单元标识符ID request[7] = 0x04; //-----------------------功能码(读输入寄存器) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(regCount >> 8); //-----数量高字节 request[11] = (byte)(regCount & 0xFF); //---数量低字节 byte[] response = await SendRequestAsync(request, 9 + regCount * 2).ConfigureAwait(false); if (response == null || response.Length < 9 + regCount * 2) return null; return ParseRegisterResponse(response, length, type); } #endregion #region 写入单个线圈寄存器值(M寄存器) /// <summary> /// 写入单个线圈寄存器值(功能码05) /// </summary> /// <param name="ID">客户端ID</param> /// <param name="address">地址</param> /// <param name="value">值</param> public async Task<bool> WriteSingleCoilsAsync(byte ID, string address, bool value) { if (!connected) return false; ushort addr = ParseAddress(address); byte[] request = new byte[12]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = 0x00; request[5] = 0x06; //----后续长度:6字节(PDU) request[6] = ID; //-------------------------单元标识符ID request[7] = 0x05; //-----------------------功能码(写单个线圈) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(value ? 0xFF : 0x00); //数据(FF表示ON,00表示OFF) request[11] = 0x00; //----------------------数据(固定0x00) try { byte[] response = await SendRequestAsync(request, 12).ConfigureAwait(false); return response != null && response.Length >= 12 && response[7] == 0x05 && (response[8] << 8 | response[9]) == addr; } catch { return false; } } #endregion #region 写入单个保持寄存器值(D寄存器) /// <summary> /// 写入单个保持寄存器值(功能码06)(功能码16) /// </summary> /// <param name="ID">设备ID</param> /// <param name="address">地址</param> /// <param name="value">值(16位或32位)</param> /// <param name="type">数据类型(字=16位,双字=32位)</param> /// <returns>是否写入成功</returns> public async Task<bool> WriteSingleRegistersAsync(byte ID, string address, int value, DataType type = DataType.字) { if (!connected) return false; if (type == DataType.字) { return await WriteSingleRegister16(ID, address, (short)value); } else { return await WriteDoubleRegister32(ID, address, (int)value); } } /// <summary> /// 16位写入实现(功能码06) /// </summary> /// <param name="ID"></param> /// <param name="address"></param> /// <param name="value"></param> /// <returns></returns> private async Task<bool> WriteSingleRegister16(byte ID, string address, short value) { ushort addr = ParseAddress(address); ushort uvalue = (ushort)value; byte[] request = new byte[12]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = 0x00; request[5] = 0x06; //----后续长度:6字节(PDU) request[6] = ID; //-------------------------单元标识符ID request[7] = 0x06; //-----------------------功能码(写16位保存寄存器) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(uvalue >> 8); //-------数量高字节 request[11] = (byte)(uvalue & 0xFF); //-----数量低字节 byte[] response = await SendRequestAsync(request, 12).ConfigureAwait(false); return response != null && response.Length >= 12 && response[7] == 0x06 && (response[8] << 8 | response[9]) == addr; } /// <summary> /// 32位写入实现(功能码16) /// </summary> /// <param name="ID"></param> /// <param name="address"></param> /// <param name="value"></param> /// <returns></returns> private async Task<bool> WriteDoubleRegister32(byte ID, string address, int value, WordOrder wordOrder = WordOrder.HighFirst) { short hiWord = (short)(value >> 16); short loWord = (short)(value & 0xFFFF); short[] words = wordOrder == WordOrder.HighFirst ? new[] { hiWord, loWord } : new[] { loWord, hiWord }; return await WriteMultipleRegistersAsync(ID, address, words); } #endregion #region 写入多个线圈寄存器值(M寄存器) /// <summary> /// 写入多个线圈寄存器值(功能码15)- 所有线圈写入相同值 /// </summary> /// <param name="ID">设备ID</param> /// <param name="address">起始地址(如"M100")</param> /// <param name="length">写入的线圈数量</param> /// <param name="value">所有线圈的状态(true=ON, false=OFF)</param> /// <returns>是否写入成功</returns> /// <example> /// WriteMultipleCoils(1, "M100", 2, true); /// // 写入M100=true, M101=true /// WriteMultipleCoils(1, "M100", 5, false); /// // 写入M100~M104全部为false /// </example> public async Task<bool> WriteMultipleCoilsAsync(byte ID, string address, int length, bool value) { if (!connected) return false; bool[] values = new bool[length]; for (int i = 0; i < length; i++) values[i] = value; return await WriteMultipleCoilsAsync(ID, address, values); } /// <summary> /// 写入多个线圈寄存器值(bool数组版本)- 每个线圈独立控制 /// </summary> /// <param name="ID">设备ID</param> /// <param name="address">起始地址</param> /// <param name="values">每个线圈的状态数组</param> /// <returns>是否写入成功</returns> /// <example> /// WriteMultipleCoils(1, "M100", new bool[] { true, false, true }); /// // 写入M100=true, M101=false, M102=true /// </example> public async Task<bool> WriteMultipleCoilsAsync(byte ID, string address, bool[] values) { if (!connected) return false; ushort addr = ParseAddress(address); int byteCount = (values.Length + 7) / 8; byte[] coilBytes = new byte[byteCount]; for (int i = 0; i < values.Length; i++) { if (values[i]) coilBytes[i / 8] |= (byte)(1 << (i % 8)); } byte[] request = new byte[13 + byteCount]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = (byte)((request.Length - 6) >> 8); request[5] = (byte)((request.Length - 6) & 0xFF); request[6] = ID; //-------------------------单元标识符ID request[7] = 0x0F; //-----------------------功能码(写多线圈寄存器) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(values.Length >> 8); //数量高字节 request[11] = (byte)(values.Length & 0xFF);//数量低字节 request[12] = (byte)(values.Length * 2); request[12] = (byte)byteCount; Array.Copy(coilBytes, 0, request, 13, byteCount); byte[] response = await SendRequestAsync(request, 12).ConfigureAwait(false); return response != null && response.Length >= 12; } #endregion #region 写入多个保持寄存器值(D寄存器) /// <summary> /// 写入多个保持寄存器值(功能码16) - 所有寄存器写入相同值 /// </summary> /// <param name="ID">设备ID</param> /// <param name="address">起始地址(如"D100")</param> /// <param name="length">写入的寄存器数量</param> /// <param name="value">所有寄存器的值</param> /// <param name="type">数据类型(字=16位,双字=32位)</param> /// <returns>是否写入成功</returns> /// <example> /// WriteMultipleRegister(1, "D100", 3, 100); /// // 写入D100=100, D101=100, D102=100 /// WriteMultipleRegister(1, "D200", 2, 123456789, ReadTypeEnum.双字); /// // 写入D200-D201=123456789, D202-D203=123456789 /// </example> public async Task<bool> WriteMultipleRegistersAsync(byte ID, string address, int length, int value, DataType type = DataType.字) { if (!connected) return false; if (type == DataType.字) { short[] arr = new short[length]; for (int i = 0; i < length; i++) arr[i] = (short)value; return await WriteMultipleRegistersAsync(ID, address, arr); } else { short[] arr = new short[length * 2]; for (int i = 0; i < length; i++) { arr[i * 2] = (short)(value >> 16); arr[i * 2 + 1] = (short)(value & 0xFFFF); } return await WriteMultipleRegistersAsync(ID, address, arr); } } /// <summary> /// 写入多个保持寄存器值(功能码16) /// </summary> /// <param name="ID"></param> /// <param name="address"></param> /// <param name="values"></param> /// <returns></returns> private async Task<bool> WriteMultipleRegistersAsync(byte ID, string address, short[] values) { ushort addr = ParseAddress(address); byte[] request = new byte[13 + values.Length * 2]; // 构建 Modbus TCP 请求报文 request[0] = 0; request[1] = 0; //----------事务ID(由 SendRequestAsync 填充或自动生成) request[2] = 0x00; request[3] = 0x00; //----协议ID request[4] = (byte)((request.Length - 6) >> 8); request[5] = (byte)((request.Length - 6) & 0xFF); request[6] = ID; //-------------------------单元标识符ID request[7] = 0x10; //-----------------------功能码(写多保存寄存器) request[8] = (byte)(addr >> 8); //----------起始地址高字节 request[9] = (byte)(addr & 0xFF); //--------起始地址低字节 request[10] = (byte)(values.Length >> 8); //数量高字节 request[11] = (byte)(values.Length & 0xFF);//数量低字节 request[12] = (byte)(values.Length * 2); for (int i = 0; i < values.Length; i++) { request[13 + i * 2] = (byte)(values[i] >> 8); request[13 + i * 2 + 1] = (byte)(values[i] & 0xFF); } byte[] response = await SendRequestAsync(request, 12).ConfigureAwait(false); return response != null && response.Length >= 12 && response[7] == 0x10 && (response[8] << 8 | response[9]) == addr && (response[10] << 8 | response[11]) == values.Length; } #endregion #region 客户端关闭 /// <summary> /// 释放所有资源 /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposed) return; disposed = true; if (disposing) { // 释放托管资源 heartbeatTimer?.Dispose(); receiveEvent?.Dispose(); stream?.Dispose(); tcpClient?.Dispose(); } } #endregion } #endregion用这种方法配置下整个代码,写出完整程序,标出更改的地方
10-14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值