#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