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