diff --git a/S7.Net.UnitTest/ConnectionCloseTest.cs b/S7.Net.UnitTest/ConnectionCloseTest.cs
index 87c9ea9..330b795 100644
--- a/S7.Net.UnitTest/ConnectionCloseTest.cs
+++ b/S7.Net.UnitTest/ConnectionCloseTest.cs
@@ -1,11 +1,10 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
-using System.Linq;
-using System.Net.Sockets;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
+using S7.Net.Tcp;
namespace S7.Net.UnitTest
{
@@ -92,7 +91,7 @@ public async Task Test_CancellationDuringTransmission()
}
// Set a value to tcpClient field so we can later ensure that it has been closed.
- tcpClientField.SetValue(plc, new TcpClient());
+ tcpClientField.SetValue(plc, new TcpClientWrapper());
var tcpClientValue = tcpClientField.GetValue(plc);
Assert.IsNotNull(tcpClientValue);
@@ -147,7 +146,7 @@ public async Task Test_CancellationBeforeTransmission()
}
// Set a value to tcpClient field so we can later ensure that it has been closed.
- tcpClientField.SetValue(plc, new TcpClient());
+ tcpClientField.SetValue(plc, new TcpClientWrapper());
var tcpClientValue = tcpClientField.GetValue(plc);
Assert.IsNotNull(tcpClientValue);
diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs
index 35d038e..b8cd5dd 100644
--- a/S7.Net/PLC.cs
+++ b/S7.Net/PLC.cs
@@ -5,6 +5,7 @@
using System.Net.Sockets;
using S7.Net.Internal;
using S7.Net.Protocol;
+using S7.Net.Tcp;
using S7.Net.Types;
@@ -27,8 +28,11 @@ public partial class Plc : IDisposable
private readonly TaskQueue queue = new TaskQueue();
+ private readonly ITcpClientFactory _tcpClientFactory;
+
//TCP connection to device
- private TcpClient? tcpClient;
+ private ITcpClient? tcpClient;
+
private NetworkStream? _stream;
private int readTimeout = DefaultTimeout; // default no timeout
@@ -124,8 +128,9 @@ public int WriteTimeout
/// rack of the PLC, usually it's 0, but check in the hardware configuration of Step7 or TIA portal
/// slot of the CPU of the PLC, usually it's 2 for S7300-S7400, 0 for S7-1200 and S7-1500.
/// If you use an external ethernet card, this must be set accordingly.
- public Plc(CpuType cpu, string ip, Int16 rack, Int16 slot)
- : this(cpu, ip, DefaultPort, rack, slot)
+ /// Factory to provide the underlying for network communication.
+ public Plc(CpuType cpu, string ip, Int16 rack, Int16 slot, ITcpClientFactory? tcpClientFactory = null)
+ : this(cpu, ip, DefaultPort, rack, slot, tcpClientFactory)
{
}
@@ -141,8 +146,9 @@ public Plc(CpuType cpu, string ip, Int16 rack, Int16 slot)
/// rack of the PLC, usually it's 0, but check in the hardware configuration of Step7 or TIA portal
/// slot of the CPU of the PLC, usually it's 2 for S7300-S7400, 0 for S7-1200 and S7-1500.
/// If you use an external ethernet card, this must be set accordingly.
- public Plc(CpuType cpu, string ip, int port, Int16 rack, Int16 slot)
- : this(ip, port, TsapPair.GetDefaultTsapPair(cpu, rack, slot))
+ /// Factory to provide the underlying for network communication.
+ public Plc(CpuType cpu, string ip, int port, Int16 rack, Int16 slot, ITcpClientFactory? tcpClientFactory = null)
+ : this(ip, port, TsapPair.GetDefaultTsapPair(cpu, rack, slot), tcpClientFactory)
{
if (!Enum.IsDefined(typeof(CpuType), cpu))
throw new ArgumentException(
@@ -162,7 +168,9 @@ public Plc(CpuType cpu, string ip, int port, Int16 rack, Int16 slot)
///
/// Ip address of the PLC
/// The TSAP addresses used for the connection request.
- public Plc(string ip, TsapPair tsapPair) : this(ip, DefaultPort, tsapPair)
+ /// Factory to provide the underlying for network communication.
+ public Plc(string ip, TsapPair tsapPair, ITcpClientFactory? tcpClientFactory = null)
+ : this(ip, DefaultPort, tsapPair, tcpClientFactory)
{
}
@@ -173,7 +181,8 @@ public Plc(string ip, TsapPair tsapPair) : this(ip, DefaultPort, tsapPair)
/// Ip address of the PLC
/// Port number used for the connection, default 102.
/// The TSAP addresses used for the connection request.
- public Plc(string ip, int port, TsapPair tsapPair)
+ /// Factory to provide the underlying for network communication.
+ public Plc(string ip, int port, TsapPair tsapPair, ITcpClientFactory? tcpClientFactory = null)
{
if (string.IsNullOrEmpty(ip))
throw new ArgumentException("IP address must valid.", nameof(ip));
@@ -182,6 +191,7 @@ public Plc(string ip, int port, TsapPair tsapPair)
Port = port;
MaxPDUSize = 240;
TsapPair = tsapPair;
+ _tcpClientFactory = tcpClientFactory ?? new TcpClientWrapperFactory();
}
///
diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs
index 8b828f3..ddb6d61 100644
--- a/S7.Net/PlcAsynchronous.cs
+++ b/S7.Net/PlcAsynchronous.cs
@@ -46,7 +46,7 @@ await queue.Enqueue(async () =>
private async Task ConnectAsync(CancellationToken cancellationToken)
{
- tcpClient = new TcpClient();
+ tcpClient = _tcpClientFactory.Create();
ConfigureConnection();
#if NET5_0_OR_GREATER
diff --git a/S7.Net/Tcp/ITcpClient.cs b/S7.Net/Tcp/ITcpClient.cs
new file mode 100644
index 0000000..600448e
--- /dev/null
+++ b/S7.Net/Tcp/ITcpClient.cs
@@ -0,0 +1,21 @@
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace S7.Net.Tcp
+{
+ public interface ITcpClient
+ {
+ public int ReceiveTimeout { get; set; }
+
+ public int SendTimeout { get; set; }
+
+ public bool Connected { get; }
+
+ public void Close();
+
+ public Task ConnectAsync(string ip, int port, CancellationToken cancellationToken = default);
+
+ public NetworkStream GetStream();
+ }
+}
diff --git a/S7.Net/Tcp/ITcpClientFactory.cs b/S7.Net/Tcp/ITcpClientFactory.cs
new file mode 100644
index 0000000..260c2cd
--- /dev/null
+++ b/S7.Net/Tcp/ITcpClientFactory.cs
@@ -0,0 +1,6 @@
+namespace S7.Net.Tcp;
+
+public interface ITcpClientFactory
+{
+ ITcpClient Create();
+}
\ No newline at end of file
diff --git a/S7.Net/Tcp/TcpClientWrapper.cs b/S7.Net/Tcp/TcpClientWrapper.cs
new file mode 100644
index 0000000..9d5419e
--- /dev/null
+++ b/S7.Net/Tcp/TcpClientWrapper.cs
@@ -0,0 +1,24 @@
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace S7.Net.Tcp;
+
+internal class TcpClientWrapper : TcpClient, ITcpClient
+{
+ public async Task ConnectAsync(string ip, int port, CancellationToken cancellationToken)
+ {
+#if NET5_0_OR_GREATER
+ await base.ConnectAsync(ip, port, cancellationToken).ConfigureAwait(false);
+#else
+ await base.ConnectAsync(ip, port).ConfigureAwait(false);
+#endif
+ }
+
+ public void Close()
+ {
+#if NET20_OR_GREATER
+ base.Close();
+#endif
+ }
+}
\ No newline at end of file
diff --git a/S7.Net/Tcp/TcpClientWrapperFactory.cs b/S7.Net/Tcp/TcpClientWrapperFactory.cs
new file mode 100644
index 0000000..65f7ea5
--- /dev/null
+++ b/S7.Net/Tcp/TcpClientWrapperFactory.cs
@@ -0,0 +1,9 @@
+namespace S7.Net.Tcp;
+
+internal class TcpClientWrapperFactory : ITcpClientFactory
+{
+ public ITcpClient Create()
+ {
+ return new TcpClientWrapper();
+ }
+}
\ No newline at end of file