diff --git a/Modbus.Net/Modbus.Net.Modbus/ModbusProtocolLinkerBytesExtend.cs b/Modbus.Net/Modbus.Net.Modbus/ModbusProtocolLinkerBytesExtend.cs index 2cd46ff..6be2851 100644 --- a/Modbus.Net/Modbus.Net.Modbus/ModbusProtocolLinkerBytesExtend.cs +++ b/Modbus.Net/Modbus.Net.Modbus/ModbusProtocolLinkerBytesExtend.cs @@ -5,6 +5,14 @@ using System.Text; namespace Modbus.Net.Modbus { + /// + /// Udp字节伸缩 + /// + public class ModbusUdpProtocolLinkerBytesExtend : ModbusTcpProtocolLinkerBytesExtend + { + + } + /// /// Rtu透传字节伸缩 /// diff --git a/Modbus.Net/Modbus.Net.Modbus/ModbusUdpProtocol.cs b/Modbus.Net/Modbus.Net.Modbus/ModbusUdpProtocol.cs new file mode 100644 index 0000000..720ed02 --- /dev/null +++ b/Modbus.Net/Modbus.Net.Modbus/ModbusUdpProtocol.cs @@ -0,0 +1,45 @@ +using System.Configuration; + +namespace Modbus.Net.Modbus +{ + /// + /// Modbus/Udp协议 + /// + public class ModbusUdpProtocol : ModbusProtocol + { + /// + /// 构造函数 + /// + /// 从站号 + /// 主站号 + public ModbusUdpProtocol(byte slaveAddress, byte masterAddress) + : this(ConfigurationManager.AppSettings["IP"], slaveAddress, masterAddress) + { + } + + /// + /// 构造函数 + /// + /// ip地址 + /// 从站号 + /// 主站号 + public ModbusUdpProtocol(string ip, byte slaveAddress, byte masterAddress) + : base(slaveAddress, masterAddress) + { + ProtocolLinker = new ModbusUdpProtocolLinker(ip); + } + + /// + /// 构造函数 + /// + /// ip地址 + /// 端口 + /// 从站号 + /// 主站号 + public ModbusUdpProtocol(string ip, int port, byte slaveAddress, byte masterAddress) + : base(slaveAddress, masterAddress) + { + ProtocolLinker = new ModbusUdpProtocolLinker(ip, port); + } + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Modbus/ModbusUdpProtocolLinker.cs b/Modbus.Net/Modbus.Net.Modbus/ModbusUdpProtocolLinker.cs new file mode 100644 index 0000000..cb7e394 --- /dev/null +++ b/Modbus.Net/Modbus.Net.Modbus/ModbusUdpProtocolLinker.cs @@ -0,0 +1,47 @@ +using System.Configuration; + +namespace Modbus.Net.Modbus +{ + /// + /// Modbus/Udp协议连接器 + /// + public class ModbusUdpProtocolLinker : UdpProtocolLinker + { + /// + /// 构造函数 + /// + /// IP地址 + public ModbusUdpProtocolLinker(string ip) + : this(ip, int.Parse(ConfigurationManager.AppSettings["ModbusPort"] ?? "502")) + { + } + + /// + /// 构造函数 + /// + /// IP地址 + /// 端口 + public ModbusUdpProtocolLinker(string ip, int port) : base(ip, port) + { + ((BaseConnector)BaseConnector).AddController(new FifoController(0)); + } + + /// + /// 校验返回数据 + /// + /// 设备返回的数据 + /// 数据是否正确 + public override bool? CheckRight(byte[] content) + { + //ProtocolLinker的CheckRight不会返回null + if (base.CheckRight(content) != true) return false; + //长度校验失败 + if (content[5] != content.Length - 6) + throw new ModbusProtocolErrorException(500); + //Modbus协议错误 + if (content[7] > 127) + throw new ModbusProtocolErrorException(content[2] > 0 ? content[2] : content[8]); + return true; + } + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Modbus/ModbusUtility.cs b/Modbus.Net/Modbus.Net.Modbus/ModbusUtility.cs index c47b678..d4405db 100644 --- a/Modbus.Net/Modbus.Net.Modbus/ModbusUtility.cs +++ b/Modbus.Net/Modbus.Net.Modbus/ModbusUtility.cs @@ -33,6 +33,10 @@ namespace Modbus.Net.Modbus /// Ascii连接Tcp透传 /// AsciiInTcp = 4, + /// + /// Udp连接 + /// + Udp = 5 } /// @@ -193,6 +197,17 @@ namespace Modbus.Net.Modbus MasterAddress)); break; } + //Udp协议 + case ModbusType.Udp: + { + Wrapper = ConnectionString == null + ? new ModbusUdpProtocol(SlaveAddress, MasterAddress) + : (ConnectionStringPort == null + ? new ModbusUdpProtocol(ConnectionString, SlaveAddress, MasterAddress) + : new ModbusUdpProtocol(ConnectionStringIp, ConnectionStringPort.Value, SlaveAddress, + MasterAddress)); + break; + } } } } diff --git a/Modbus.Net/Modbus.Net/SocketMessageEventArgs.cs b/Modbus.Net/Modbus.Net/SocketMessageEventArgs.cs new file mode 100644 index 0000000..78431e1 --- /dev/null +++ b/Modbus.Net/Modbus.Net/SocketMessageEventArgs.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Modbus.Net +{ + /* + /// + /// Socket收到的数据 + /// + public class SocketMessageEventArgs : EventArgs + { + /// + /// 构造器 + /// + /// 需要返回的信息 + public SocketMessageEventArgs(byte[] message) + { + Message = message; + } + + /// + /// 返回的信息 + /// + public byte[] Message { get; } + }*/ +} diff --git a/Modbus.Net/Modbus.Net/TcpConnector.cs b/Modbus.Net/Modbus.Net/TcpConnector.cs index 174968a..cadff86 100644 --- a/Modbus.Net/Modbus.Net/TcpConnector.cs +++ b/Modbus.Net/Modbus.Net/TcpConnector.cs @@ -8,26 +8,6 @@ using Serilog; namespace Modbus.Net { - /// - /// Socket收到的数据 - /// - public class SocketMessageEventArgs : EventArgs - { - /// - /// 构造器 - /// - /// 需要返回的信息 - public SocketMessageEventArgs(byte[] message) - { - Message = message; - } - - /// - /// 返回的信息 - /// - public byte[] Message { get; } - } - /// /// Socket收发类 /// 作者:本类来源于CSDN,并由罗圣(Chris L.)根据实际需要修改 diff --git a/Modbus.Net/Modbus.Net/UdpConnector.cs b/Modbus.Net/Modbus.Net/UdpConnector.cs new file mode 100644 index 0000000..96a8623 --- /dev/null +++ b/Modbus.Net/Modbus.Net/UdpConnector.cs @@ -0,0 +1,287 @@ +using System; +using System.Linq; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using Nito.AsyncEx; +using Serilog; + +namespace Modbus.Net +{ + /// + /// Udp收发类 + /// + public class UdpConnector : BaseConnector, IDisposable + { + private readonly string _host; + private readonly int _port; + + /// + /// 1MB 的接收缓冲区 + /// + private readonly byte[] _receiveBuffer = new byte[1024]; + + private int _errorCount; + private int _receiveCount; + + private int _sendCount; + + private UdpClient _socketClient; + + private bool m_disposed; + + private Task _receiveThread; + private bool _taskCancel = false; + + /// + /// 构造器 + /// + /// Ip地址 + /// 端口 + /// 超时时间 + public UdpConnector(string ipaddress, int port, int timeoutTime = 10000) : base(timeoutTime) + { + _host = ipaddress; + _port = port; + } + + /// + public override string ConnectionToken => _host; + + /// + protected override int TimeoutTime { get; set; } + + /// + public override bool IsConnected => _socketClient?.Client != null && _socketClient.Client.Connected; + + /// + protected override AsyncLock Lock { get; } = new AsyncLock(); + + /// + public void Dispose() + { + Dispose(true); + //.NET Framework 类库 + // GC..::.SuppressFinalize 方法 + //请求系统不要调用指定对象的终结器。 + GC.SuppressFinalize(this); + } + + /// + /// 虚方法,可供子类重写 + /// + /// + protected virtual void Dispose(bool disposing) + { + if (!m_disposed) + { + if (disposing) + { + // Release managed resources + } + // Release unmanaged resources + if (_socketClient != null) + { + CloseClientSocket(); +#if NET40 || NET45 || NET451 || NET452 + _socketClient.Close(); +#else + _socketClient.Dispose(); +#endif + Log.Debug("Tcp client {ConnectionToken} Disposed", ConnectionToken); + } + m_disposed = true; + } + } + + /// + /// 析构函数 + /// 当客户端没有显示调用Dispose()时由GC完成资源回收功能 + /// + ~UdpConnector() + { + Dispose(false); + } + + /// + public override async Task ConnectAsync() + { + using (await Lock.LockAsync()) + { + if (_socketClient != null) + { + return true; + } + try + { + _socketClient = new UdpClient(); + + try + { + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeoutTime); + await Task.Run(() => _socketClient.Connect(_host, _port), cts.Token); + } + catch (Exception e) + { + Log.Error(e, "Tcp client {ConnectionToken} connect error", ConnectionToken); + } + if (_socketClient.Client.Connected) + { + _taskCancel = false; + Controller.SendStart(); + ReceiveMsgThreadStart(); + Log.Information("Tcp client {ConnectionToken} connected", ConnectionToken); + return true; + } + Log.Error("Tcp client {ConnectionToken} connect failed.", ConnectionToken); + return false; + } + catch (Exception err) + { + Log.Error(err, "Tcp client {ConnectionToken} connect exception", ConnectionToken); + return false; + } + } + } + + /// + public override bool Disconnect() + { + if (_socketClient == null) + return true; + + try + { + Dispose(); + Log.Information("Tcp client {ConnectionToken} disconnected successfully", ConnectionToken); + return true; + } + catch (Exception err) + { + Log.Error(err, "Tcp client {ConnectionToken} disconnected exception", ConnectionToken); + return false; + } + finally + { + _socketClient = null; + } + } + + /// + protected override async Task SendMsgWithoutConfirm(byte[] message) + { + var datagram = message; + + try + { + if (!IsConnected) + await ConnectAsync(); + + RefreshSendCount(); + + Log.Verbose("Tcp client {ConnectionToken} send text len = {Length}", ConnectionToken, datagram.Length); + Log.Verbose($"Tcp client {ConnectionToken} send: {String.Concat(datagram.Select(p => " " + p.ToString("X2")))}"); + await _socketClient.SendAsync(datagram, datagram.Length); + } + catch (Exception err) + { + Log.Error(err, "Tcp client {ConnectionToken} send exception", ConnectionToken); + CloseClientSocket(); + } + } + + /// + protected override void ReceiveMsgThreadStart() + { + _receiveThread = Task.Run(ReceiveMessage); + } + + /// + protected override void ReceiveMsgThreadStop() + { + _taskCancel = true; + } + + /// + /// 接收返回消息 + /// + /// 返回的消息 + protected async Task ReceiveMessage() + { + try + { + while (!_taskCancel) + { + var receive = await _socketClient.ReceiveAsync(); + + var len = receive.Buffer.Length; + // 异步接收回答 + if (len > 0) + { + if (receive.Buffer.Clone() is byte[] receiveBytes) + { + Log.Verbose("Tcp client {ConnectionToken} receive text len = {Length}", ConnectionToken, + receiveBytes.Length); + Log.Verbose( + $"Tcp client {ConnectionToken} receive: {String.Concat(receiveBytes.Select(p => " " + p.ToString("X2")))}"); + var isMessageConfirmed = Controller.ConfirmMessage(receiveBytes); + if (isMessageConfirmed == false) + { + //主动传输事件 + } + } + } + + RefreshReceiveCount(); + } + } + catch (ObjectDisposedException) + { + //ignore + } + catch (Exception err) + { + Log.Error(err, "Tcp client {ConnectionToken} receive exception", ConnectionToken); + //CloseClientSocket(); + } + } + + private void RefreshSendCount() + { + _sendCount++; + Log.Verbose("Tcp client {ConnectionToken} send count: {SendCount}", ConnectionToken, _sendCount); + } + + private void RefreshReceiveCount() + { + _receiveCount++; + Log.Verbose("Tcp client {ConnectionToken} receive count: {SendCount}", ConnectionToken, _receiveCount); + } + + private void RefreshErrorCount() + { + _errorCount++; + Log.Verbose("Tcp client {ConnectionToken} error count: {ErrorCount}", ConnectionToken, _errorCount); + } + + private void CloseClientSocket() + { + try + { + Controller.SendStop(); + Controller.Clear(); + ReceiveMsgThreadStop(); + if (_socketClient.Client.Connected) + { + _socketClient?.Client.Disconnect(false); + } + _socketClient?.Close(); + } + catch (Exception ex) + { + Log.Error(ex, "Tcp client {ConnectionToken} client close exception", ConnectionToken); + } + } + } +} \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net/UdpProtocolLinker.cs b/Modbus.Net/Modbus.Net/UdpProtocolLinker.cs new file mode 100644 index 0000000..7c74f4d --- /dev/null +++ b/Modbus.Net/Modbus.Net/UdpProtocolLinker.cs @@ -0,0 +1,48 @@ +using System.Configuration; + +namespace Modbus.Net +{ + /// + /// Udp连接对象 + /// + public abstract class UdpProtocolLinker : ProtocolLinker + { + /// + /// 构造器 + /// + protected UdpProtocolLinker(int port) + : this(ConfigurationManager.AppSettings["IP"], port) + { + } + + /// + /// 构造器 + /// + /// Ip地址 + /// 端口 + protected UdpProtocolLinker(string ip, int port) + : this(ip, port, int.Parse(ConfigurationManager.AppSettings["IPConnectionTimeout"] ?? "-1")) + { + } + + /// + /// 构造器 + /// + /// Ip地址 + /// 端口 + /// 超时时间 + protected UdpProtocolLinker(string ip, int port, int connectionTimeout) + { + if (connectionTimeout == -1) + { + //初始化连接对象 + BaseConnector = new UdpConnector(ip, port); + } + else + { + //初始化连接对象 + BaseConnector = new UdpConnector(ip, port, connectionTimeout); + } + } + } +} \ No newline at end of file