Skip to content

Commit 34b5f6e

Browse files
author
Christoph Seiler
committed
feat: Introduce TcpClientFactory
Allows injecting custom TcpClient for monitoring and logging
1 parent ab6308e commit 34b5f6e

7 files changed

Lines changed: 81 additions & 12 deletions

File tree

S7.Net.UnitTest/ConnectionCloseTest.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
using Microsoft.VisualStudio.TestTools.UnitTesting;
22
using System;
33
using System.IO;
4-
using System.Linq;
5-
using System.Net.Sockets;
64
using System.Reflection;
75
using System.Threading;
86
using System.Threading.Tasks;
7+
using S7.Net.Tcp;
98

109
namespace S7.Net.UnitTest
1110
{
@@ -92,7 +91,7 @@ public async Task Test_CancellationDuringTransmission()
9291
}
9392

9493
// Set a value to tcpClient field so we can later ensure that it has been closed.
95-
tcpClientField.SetValue(plc, new TcpClient());
94+
tcpClientField.SetValue(plc, new TcpClientWrapper());
9695
var tcpClientValue = tcpClientField.GetValue(plc);
9796
Assert.IsNotNull(tcpClientValue);
9897

@@ -147,7 +146,7 @@ public async Task Test_CancellationBeforeTransmission()
147146
}
148147

149148
// Set a value to tcpClient field so we can later ensure that it has been closed.
150-
tcpClientField.SetValue(plc, new TcpClient());
149+
tcpClientField.SetValue(plc, new TcpClientWrapper());
151150
var tcpClientValue = tcpClientField.GetValue(plc);
152151
Assert.IsNotNull(tcpClientValue);
153152

S7.Net/PLC.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Net.Sockets;
66
using S7.Net.Internal;
77
using S7.Net.Protocol;
8+
using S7.Net.Tcp;
89
using S7.Net.Types;
910

1011

@@ -27,8 +28,11 @@ public partial class Plc : IDisposable
2728

2829
private readonly TaskQueue queue = new TaskQueue();
2930

31+
private readonly ITcpClientFactory _tcpClientFactory;
32+
3033
//TCP connection to device
31-
private TcpClient? tcpClient;
34+
private ITcpClient? tcpClient;
35+
3236
private NetworkStream? _stream;
3337

3438
private int readTimeout = DefaultTimeout; // default no timeout
@@ -124,8 +128,9 @@ public int WriteTimeout
124128
/// <param name="rack">rack of the PLC, usually it's 0, but check in the hardware configuration of Step7 or TIA portal</param>
125129
/// <param name="slot">slot of the CPU of the PLC, usually it's 2 for S7300-S7400, 0 for S7-1200 and S7-1500.
126130
/// If you use an external ethernet card, this must be set accordingly.</param>
127-
public Plc(CpuType cpu, string ip, Int16 rack, Int16 slot)
128-
: this(cpu, ip, DefaultPort, rack, slot)
131+
/// <param name="tcpClientFactory">Factory to provide the underlying <see cref="ITcpClient"/> for network communication.</param>
132+
public Plc(CpuType cpu, string ip, Int16 rack, Int16 slot, ITcpClientFactory? tcpClientFactory = null)
133+
: this(cpu, ip, DefaultPort, rack, slot, tcpClientFactory)
129134
{
130135
}
131136

@@ -141,8 +146,9 @@ public Plc(CpuType cpu, string ip, Int16 rack, Int16 slot)
141146
/// <param name="rack">rack of the PLC, usually it's 0, but check in the hardware configuration of Step7 or TIA portal</param>
142147
/// <param name="slot">slot of the CPU of the PLC, usually it's 2 for S7300-S7400, 0 for S7-1200 and S7-1500.
143148
/// If you use an external ethernet card, this must be set accordingly.</param>
144-
public Plc(CpuType cpu, string ip, int port, Int16 rack, Int16 slot)
145-
: this(ip, port, TsapPair.GetDefaultTsapPair(cpu, rack, slot))
149+
/// <param name="tcpClientFactory">Factory to provide the underlying <see cref="ITcpClient"/> for network communication.</param>
150+
public Plc(CpuType cpu, string ip, int port, Int16 rack, Int16 slot, ITcpClientFactory? tcpClientFactory = null)
151+
: this(ip, port, TsapPair.GetDefaultTsapPair(cpu, rack, slot), tcpClientFactory)
146152
{
147153
if (!Enum.IsDefined(typeof(CpuType), cpu))
148154
throw new ArgumentException(
@@ -162,7 +168,9 @@ public Plc(CpuType cpu, string ip, int port, Int16 rack, Int16 slot)
162168
/// </summary>
163169
/// <param name="ip">Ip address of the PLC</param>
164170
/// <param name="tsapPair">The TSAP addresses used for the connection request.</param>
165-
public Plc(string ip, TsapPair tsapPair) : this(ip, DefaultPort, tsapPair)
171+
/// <param name="tcpClientFactory">Factory to provide the underlying <see cref="ITcpClient"/> for network communication.</param>
172+
public Plc(string ip, TsapPair tsapPair, ITcpClientFactory? tcpClientFactory = null)
173+
: this(ip, DefaultPort, tsapPair, tcpClientFactory)
166174
{
167175
}
168176

@@ -173,7 +181,8 @@ public Plc(string ip, TsapPair tsapPair) : this(ip, DefaultPort, tsapPair)
173181
/// <param name="ip">Ip address of the PLC</param>
174182
/// <param name="port">Port number used for the connection, default 102.</param>
175183
/// <param name="tsapPair">The TSAP addresses used for the connection request.</param>
176-
public Plc(string ip, int port, TsapPair tsapPair)
184+
/// <param name="tcpClientFactory">Factory to provide the underlying <see cref="ITcpClient"/> for network communication.</param>
185+
public Plc(string ip, int port, TsapPair tsapPair, ITcpClientFactory? tcpClientFactory = null)
177186
{
178187
if (string.IsNullOrEmpty(ip))
179188
throw new ArgumentException("IP address must valid.", nameof(ip));
@@ -182,6 +191,7 @@ public Plc(string ip, int port, TsapPair tsapPair)
182191
Port = port;
183192
MaxPDUSize = 240;
184193
TsapPair = tsapPair;
194+
_tcpClientFactory = tcpClientFactory ?? new TcpClientWrapperFactory();
185195
}
186196

187197
/// <summary>

S7.Net/PlcAsynchronous.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ await queue.Enqueue(async () =>
4646

4747
private async Task<NetworkStream> ConnectAsync(CancellationToken cancellationToken)
4848
{
49-
tcpClient = new TcpClient();
49+
tcpClient = _tcpClientFactory.Create();
5050
ConfigureConnection();
5151

5252
#if NET5_0_OR_GREATER

S7.Net/Tcp/ITcpClient.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Net.Sockets;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
5+
namespace S7.Net.Tcp
6+
{
7+
public interface ITcpClient
8+
{
9+
public int ReceiveTimeout { get; set; }
10+
11+
public int SendTimeout { get; set; }
12+
13+
public bool Connected { get; }
14+
15+
public void Close();
16+
17+
public Task ConnectAsync(string ip, int port, CancellationToken cancellationToken = default);
18+
19+
public NetworkStream GetStream();
20+
}
21+
}

S7.Net/Tcp/ITcpClientFactory.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace S7.Net.Tcp;
2+
3+
public interface ITcpClientFactory
4+
{
5+
ITcpClient Create();
6+
}

S7.Net/Tcp/TcpClientWrapper.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Net.Sockets;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
5+
namespace S7.Net.Tcp;
6+
7+
internal class TcpClientWrapper : TcpClient, ITcpClient
8+
{
9+
public async Task ConnectAsync(string ip, int port, CancellationToken cancellationToken)
10+
{
11+
#if NET5_0_OR_GREATER
12+
await base.ConnectAsync(ip, port, cancellationToken).ConfigureAwait(false);
13+
#else
14+
await base.ConnectAsync(ip, port).ConfigureAwait(false);
15+
#endif
16+
}
17+
18+
public void Close()
19+
{
20+
#if NET20_OR_GREATER
21+
base.Close();
22+
#endif
23+
}
24+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace S7.Net.Tcp;
2+
3+
internal class TcpClientWrapperFactory : ITcpClientFactory
4+
{
5+
public ITcpClient Create()
6+
{
7+
return new TcpClientWrapper();
8+
}
9+
}

0 commit comments

Comments
 (0)