Fixing an issue with missing internal client on TCP (caused by AddressFamily.Unknown in default constructor)
This commit is contained in:
@@ -45,7 +45,7 @@ namespace System.Collections.Generic
|
|||||||
internalDequeueTcs = ResetToken(ref _dequeueTcs);
|
internalDequeueTcs = ResetToken(ref _dequeueTcs);
|
||||||
}
|
}
|
||||||
|
|
||||||
await WaitAsync(internalDequeueTcs, cancellationToken).ConfigureAwait(false);
|
await WaitAsync(internalDequeueTcs, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ namespace System.Collections.Generic
|
|||||||
{
|
{
|
||||||
if (await Task.WhenAny(tcs.Task, Task.Delay(-1, cancellationToken)) == tcs.Task)
|
if (await Task.WhenAny(tcs.Task, Task.Delay(-1, cancellationToken)) == tcs.Task)
|
||||||
{
|
{
|
||||||
await tcs.Task.ConfigureAwait(false);
|
await tcs.Task;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
responseBytes.AddRange(requestBytes.Take(2));
|
responseBytes.AddRange(requestBytes.Take(2));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var coils = await Client.ReadCoilsAsync(unitId, firstAddress, count, cancellationToken).ConfigureAwait(false);
|
var coils = await Client.ReadCoilsAsync(unitId, firstAddress, count, cancellationToken);
|
||||||
|
|
||||||
byte[] values = new byte[(int)Math.Ceiling(coils.Count / 8.0)];
|
byte[] values = new byte[(int)Math.Ceiling(coils.Count / 8.0)];
|
||||||
for (int i = 0; i < coils.Count; i++)
|
for (int i = 0; i < coils.Count; i++)
|
||||||
@@ -349,7 +349,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
responseBytes.AddRange(requestBytes.Take(2));
|
responseBytes.AddRange(requestBytes.Take(2));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var discreteInputs = await Client.ReadDiscreteInputsAsync(unitId, firstAddress, count, cancellationToken).ConfigureAwait(false);
|
var discreteInputs = await Client.ReadDiscreteInputsAsync(unitId, firstAddress, count, cancellationToken);
|
||||||
|
|
||||||
byte[] values = new byte[(int)Math.Ceiling(discreteInputs.Count / 8.0)];
|
byte[] values = new byte[(int)Math.Ceiling(discreteInputs.Count / 8.0)];
|
||||||
for (int i = 0; i < discreteInputs.Count; i++)
|
for (int i = 0; i < discreteInputs.Count; i++)
|
||||||
@@ -389,7 +389,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
responseBytes.AddRange(requestBytes.Take(2));
|
responseBytes.AddRange(requestBytes.Take(2));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var holdingRegisters = await Client.ReadHoldingRegistersAsync(unitId, firstAddress, count, cancellationToken).ConfigureAwait(false);
|
var holdingRegisters = await Client.ReadHoldingRegistersAsync(unitId, firstAddress, count, cancellationToken);
|
||||||
|
|
||||||
byte[] values = new byte[holdingRegisters.Count * 2];
|
byte[] values = new byte[holdingRegisters.Count * 2];
|
||||||
for (int i = 0; i < holdingRegisters.Count; i++)
|
for (int i = 0; i < holdingRegisters.Count; i++)
|
||||||
@@ -424,7 +424,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
responseBytes.AddRange(requestBytes.Take(2));
|
responseBytes.AddRange(requestBytes.Take(2));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var inputRegisters = await Client.ReadInputRegistersAsync(unitId, firstAddress, count, cancellationToken).ConfigureAwait(false);
|
var inputRegisters = await Client.ReadInputRegistersAsync(unitId, firstAddress, count, cancellationToken);
|
||||||
|
|
||||||
byte[] values = new byte[count * 2];
|
byte[] values = new byte[count * 2];
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
@@ -474,7 +474,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
LowByte = requestBytes[5],
|
LowByte = requestBytes[5],
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isSuccess = await Client.WriteSingleCoilAsync(requestBytes[0], coil, cancellationToken).ConfigureAwait(false);
|
bool isSuccess = await Client.WriteSingleCoilAsync(requestBytes[0], coil, cancellationToken);
|
||||||
if (isSuccess)
|
if (isSuccess)
|
||||||
{
|
{
|
||||||
// Response is an echo of the request
|
// Response is an echo of the request
|
||||||
@@ -514,7 +514,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
LowByte = requestBytes[5]
|
LowByte = requestBytes[5]
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isSuccess = await Client.WriteSingleHoldingRegisterAsync(requestBytes[0], register, cancellationToken).ConfigureAwait(false);
|
bool isSuccess = await Client.WriteSingleHoldingRegisterAsync(requestBytes[0], register, cancellationToken);
|
||||||
if (isSuccess)
|
if (isSuccess)
|
||||||
{
|
{
|
||||||
// Response is an echo of the request
|
// Response is an echo of the request
|
||||||
@@ -576,7 +576,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSuccess = await Client.WriteMultipleCoilsAsync(requestBytes[0], coils, cancellationToken).ConfigureAwait(false);
|
bool isSuccess = await Client.WriteMultipleCoilsAsync(requestBytes[0], coils, cancellationToken);
|
||||||
if (isSuccess)
|
if (isSuccess)
|
||||||
{
|
{
|
||||||
// Response is an echo of the request
|
// Response is an echo of the request
|
||||||
@@ -634,7 +634,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
LowByte = requestBytes[baseOffset + i * 2 + 1]
|
LowByte = requestBytes[baseOffset + i * 2 + 1]
|
||||||
});
|
});
|
||||||
|
|
||||||
bool isSuccess = await Client.WriteMultipleHoldingRegistersAsync(requestBytes[0], list, cancellationToken).ConfigureAwait(false);
|
bool isSuccess = await Client.WriteMultipleHoldingRegistersAsync(requestBytes[0], list, cancellationToken);
|
||||||
if (isSuccess)
|
if (isSuccess)
|
||||||
{
|
{
|
||||||
// Response is an echo of the request
|
// Response is an echo of the request
|
||||||
@@ -693,7 +693,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var res = await Client.ReadDeviceIdentificationAsync(requestBytes[6], category, firstObject, cancellationToken).ConfigureAwait(false);
|
var res = await Client.ReadDeviceIdentificationAsync(requestBytes[6], category, firstObject, cancellationToken);
|
||||||
|
|
||||||
var bodyBytes = new List<byte>();
|
var bodyBytes = new List<byte>();
|
||||||
|
|
||||||
|
|||||||
@@ -203,11 +203,11 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
#if NET8_0_OR_GREATER
|
#if NET8_0_OR_GREATER
|
||||||
var client = await _listener.AcceptTcpClientAsync(cancellationToken).ConfigureAwait(false);
|
var client = await _listener.AcceptTcpClientAsync(cancellationToken);
|
||||||
#else
|
#else
|
||||||
var client = await _listener.AcceptTcpClientAsync().ConfigureAwait(false);
|
var client = await _listener.AcceptTcpClientAsync();
|
||||||
#endif
|
#endif
|
||||||
await _clientListLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _clientListLock.WaitAsync(cancellationToken);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_clients.Add(client);
|
_clients.Add(client);
|
||||||
@@ -237,20 +237,20 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
using (var cts = new CancellationTokenSource(ReadWriteTimeout))
|
using (var cts = new CancellationTokenSource(ReadWriteTimeout))
|
||||||
using (cancellationToken.Register(cts.Cancel))
|
using (cancellationToken.Register(cts.Cancel))
|
||||||
{
|
{
|
||||||
byte[] headerBytes = await stream.ReadExpectedBytesAsync(6, cts.Token).ConfigureAwait(false);
|
byte[] headerBytes = await stream.ReadExpectedBytesAsync(6, cts.Token);
|
||||||
requestBytes.AddRange(headerBytes);
|
requestBytes.AddRange(headerBytes);
|
||||||
|
|
||||||
byte[] followingCountBytes = headerBytes.Skip(4).Take(2).ToArray();
|
byte[] followingCountBytes = headerBytes.Skip(4).Take(2).ToArray();
|
||||||
followingCountBytes.SwapBigEndian();
|
followingCountBytes.SwapBigEndian();
|
||||||
int followingCount = BitConverter.ToUInt16(followingCountBytes, 0);
|
int followingCount = BitConverter.ToUInt16(followingCountBytes, 0);
|
||||||
|
|
||||||
byte[] bodyBytes = await stream.ReadExpectedBytesAsync(followingCount, cts.Token).ConfigureAwait(false);
|
byte[] bodyBytes = await stream.ReadExpectedBytesAsync(followingCount, cts.Token);
|
||||||
requestBytes.AddRange(bodyBytes);
|
requestBytes.AddRange(bodyBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] responseBytes = await HandleRequestAsync([.. requestBytes], cancellationToken).ConfigureAwait(false);
|
byte[] responseBytes = await HandleRequestAsync([.. requestBytes], cancellationToken);
|
||||||
if (responseBytes != null)
|
if (responseBytes != null)
|
||||||
await stream.WriteAsync(responseBytes, 0, responseBytes.Length, cancellationToken).ConfigureAwait(false);
|
await stream.WriteAsync(responseBytes, 0, responseBytes.Length, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -259,7 +259,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
await _clientListLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _clientListLock.WaitAsync(cancellationToken);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_clients.Remove(client);
|
_clients.Remove(client);
|
||||||
@@ -334,7 +334,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
responseBytes.AddRange(requestBytes.Take(8));
|
responseBytes.AddRange(requestBytes.Take(8));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var coils = await Client.ReadCoilsAsync(unitId, firstAddress, count, cancellationToken).ConfigureAwait(false);
|
var coils = await Client.ReadCoilsAsync(unitId, firstAddress, count, cancellationToken);
|
||||||
|
|
||||||
byte[] values = new byte[(int)Math.Ceiling(coils.Count / 8.0)];
|
byte[] values = new byte[(int)Math.Ceiling(coils.Count / 8.0)];
|
||||||
for (int i = 0; i < coils.Count; i++)
|
for (int i = 0; i < coils.Count; i++)
|
||||||
@@ -373,7 +373,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
responseBytes.AddRange(requestBytes.Take(8));
|
responseBytes.AddRange(requestBytes.Take(8));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var discreteInputs = await Client.ReadDiscreteInputsAsync(unitId, firstAddress, count, cancellationToken).ConfigureAwait(false);
|
var discreteInputs = await Client.ReadDiscreteInputsAsync(unitId, firstAddress, count, cancellationToken);
|
||||||
|
|
||||||
byte[] values = new byte[(int)Math.Ceiling(discreteInputs.Count / 8.0)];
|
byte[] values = new byte[(int)Math.Ceiling(discreteInputs.Count / 8.0)];
|
||||||
for (int i = 0; i < discreteInputs.Count; i++)
|
for (int i = 0; i < discreteInputs.Count; i++)
|
||||||
@@ -412,7 +412,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
responseBytes.AddRange(requestBytes.Take(8));
|
responseBytes.AddRange(requestBytes.Take(8));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var holdingRegisters = await Client.ReadHoldingRegistersAsync(unitId, firstAddress, count, cancellationToken).ConfigureAwait(false);
|
var holdingRegisters = await Client.ReadHoldingRegistersAsync(unitId, firstAddress, count, cancellationToken);
|
||||||
|
|
||||||
byte[] values = new byte[holdingRegisters.Count * 2];
|
byte[] values = new byte[holdingRegisters.Count * 2];
|
||||||
for (int i = 0; i < holdingRegisters.Count; i++)
|
for (int i = 0; i < holdingRegisters.Count; i++)
|
||||||
@@ -446,7 +446,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
responseBytes.AddRange(requestBytes.Take(8));
|
responseBytes.AddRange(requestBytes.Take(8));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var inputRegisters = await Client.ReadInputRegistersAsync(unitId, firstAddress, count, cancellationToken).ConfigureAwait(false);
|
var inputRegisters = await Client.ReadInputRegistersAsync(unitId, firstAddress, count, cancellationToken);
|
||||||
|
|
||||||
byte[] values = new byte[count * 2];
|
byte[] values = new byte[count * 2];
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
@@ -493,7 +493,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
LowByte = requestBytes[11],
|
LowByte = requestBytes[11],
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isSuccess = await Client.WriteSingleCoilAsync(requestBytes[6], coil, cancellationToken).ConfigureAwait(false);
|
bool isSuccess = await Client.WriteSingleCoilAsync(requestBytes[6], coil, cancellationToken);
|
||||||
if (isSuccess)
|
if (isSuccess)
|
||||||
{
|
{
|
||||||
// Response is an echo of the request
|
// Response is an echo of the request
|
||||||
@@ -533,7 +533,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
LowByte = requestBytes[11]
|
LowByte = requestBytes[11]
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isSuccess = await Client.WriteSingleHoldingRegisterAsync(requestBytes[6], register, cancellationToken).ConfigureAwait(false);
|
bool isSuccess = await Client.WriteSingleHoldingRegisterAsync(requestBytes[6], register, cancellationToken);
|
||||||
if (isSuccess)
|
if (isSuccess)
|
||||||
{
|
{
|
||||||
// Response is an echo of the request
|
// Response is an echo of the request
|
||||||
@@ -592,7 +592,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSuccess = await Client.WriteMultipleCoilsAsync(requestBytes[6], coils, cancellationToken).ConfigureAwait(false);
|
bool isSuccess = await Client.WriteMultipleCoilsAsync(requestBytes[6], coils, cancellationToken);
|
||||||
if (isSuccess)
|
if (isSuccess)
|
||||||
{
|
{
|
||||||
// Response is an echo of the request
|
// Response is an echo of the request
|
||||||
@@ -647,7 +647,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
LowByte = requestBytes[baseOffset + i * 2 + 1]
|
LowByte = requestBytes[baseOffset + i * 2 + 1]
|
||||||
});
|
});
|
||||||
|
|
||||||
bool isSuccess = await Client.WriteMultipleHoldingRegistersAsync(requestBytes[6], list, cancellationToken).ConfigureAwait(false);
|
bool isSuccess = await Client.WriteMultipleHoldingRegistersAsync(requestBytes[6], list, cancellationToken);
|
||||||
if (isSuccess)
|
if (isSuccess)
|
||||||
{
|
{
|
||||||
// Response is an echo of the request
|
// Response is an echo of the request
|
||||||
@@ -699,7 +699,7 @@ namespace AMWD.Protocols.Modbus.Proxy
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var res = await Client.ReadDeviceIdentificationAsync(requestBytes[6], category, firstObject, cancellationToken).ConfigureAwait(false);
|
var res = await Client.ReadDeviceIdentificationAsync(requestBytes[6], category, firstObject, cancellationToken);
|
||||||
|
|
||||||
var bodyBytes = new List<byte>();
|
var bodyBytes = new List<byte>();
|
||||||
|
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ namespace AMWD.Protocols.Modbus.Serial
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get next request to process
|
// Get next request to process
|
||||||
var item = await _requestQueue.DequeueAsync(cancellationToken).ConfigureAwait(false);
|
var item = await _requestQueue.DequeueAsync(cancellationToken);
|
||||||
|
|
||||||
// Remove registration => already removed from queue
|
// Remove registration => already removed from queue
|
||||||
item.CancellationTokenRegistration.Dispose();
|
item.CancellationTokenRegistration.Dispose();
|
||||||
@@ -267,13 +267,13 @@ namespace AMWD.Protocols.Modbus.Serial
|
|||||||
// Build combined cancellation token
|
// Build combined cancellation token
|
||||||
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, item.CancellationTokenSource.Token);
|
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, item.CancellationTokenSource.Token);
|
||||||
// Wait for exclusive access
|
// Wait for exclusive access
|
||||||
await _portLock.WaitAsync(linkedCts.Token).ConfigureAwait(false);
|
await _portLock.WaitAsync(linkedCts.Token);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Ensure connection is up
|
// Ensure connection is up
|
||||||
await AssertConnection(linkedCts.Token).ConfigureAwait(false);
|
await AssertConnection(linkedCts.Token);
|
||||||
|
|
||||||
await _serialPort.WriteAsync(item.Request, linkedCts.Token).ConfigureAwait(false);
|
await _serialPort.WriteAsync(item.Request, linkedCts.Token);
|
||||||
|
|
||||||
linkedCts.Token.ThrowIfCancellationRequested();
|
linkedCts.Token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
@@ -282,7 +282,7 @@ namespace AMWD.Protocols.Modbus.Serial
|
|||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
int readCount = await _serialPort.ReadAsync(buffer, 0, buffer.Length, linkedCts.Token).ConfigureAwait(false);
|
int readCount = await _serialPort.ReadAsync(buffer, 0, buffer.Length, linkedCts.Token);
|
||||||
if (readCount < 1)
|
if (readCount < 1)
|
||||||
throw new EndOfStreamException();
|
throw new EndOfStreamException();
|
||||||
|
|
||||||
@@ -313,7 +313,7 @@ namespace AMWD.Protocols.Modbus.Serial
|
|||||||
_portLock.Release();
|
_portLock.Release();
|
||||||
_idleTimer.Change(IdleTimeout, Timeout.InfiniteTimeSpan);
|
_idleTimer.Change(IdleTimeout, Timeout.InfiniteTimeSpan);
|
||||||
|
|
||||||
await Task.Delay(InterRequestDelay, cancellationToken).ConfigureAwait(false);
|
await Task.Delay(InterRequestDelay, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||||
@@ -370,7 +370,7 @@ namespace AMWD.Protocols.Modbus.Serial
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromSeconds(delay), cancellationToken).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromSeconds(delay), cancellationToken);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{ /* keep it quiet */ }
|
{ /* keep it quiet */ }
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ namespace AMWD.Protocols.Modbus.Serial.Utils
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await _serialPort.BaseStream.ReadAsync(buffer, offset, count, cts.Token).ConfigureAwait(false);
|
return await _serialPort.BaseStream.ReadAsync(buffer, offset, count, cts.Token);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -195,9 +195,9 @@ namespace AMWD.Protocols.Modbus.Serial.Utils
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
#if NET6_0_OR_GREATER
|
#if NET6_0_OR_GREATER
|
||||||
await _serialPort.BaseStream.WriteAsync(buffer, cts.Token).ConfigureAwait(false);
|
await _serialPort.BaseStream.WriteAsync(buffer, cts.Token);
|
||||||
#else
|
#else
|
||||||
await _serialPort.BaseStream.WriteAsync(buffer, 0, buffer.Length, cts.Token).ConfigureAwait(false);
|
await _serialPort.BaseStream.WriteAsync(buffer, 0, buffer.Length, cts.Token);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace System.IO
|
|||||||
int offset = 0;
|
int offset = 0;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
int count = await stream.ReadAsync(buffer, offset, expectedBytes - offset, cancellationToken).ConfigureAwait(false);
|
int count = await stream.ReadAsync(buffer, offset, expectedBytes - offset, cancellationToken);
|
||||||
if (count < 1)
|
if (count < 1)
|
||||||
throw new EndOfStreamException();
|
throw new EndOfStreamException();
|
||||||
|
|
||||||
|
|||||||
@@ -26,13 +26,17 @@ namespace AMWD.Protocols.Modbus.Tcp
|
|||||||
private bool _isDisposed;
|
private bool _isDisposed;
|
||||||
private readonly CancellationTokenSource _disposeCts = new();
|
private readonly CancellationTokenSource _disposeCts = new();
|
||||||
|
|
||||||
|
private readonly TcpClientWrapperFactory _tcpClientFactory = new();
|
||||||
private readonly SemaphoreSlim _clientLock = new(1, 1);
|
private readonly SemaphoreSlim _clientLock = new(1, 1);
|
||||||
private readonly TcpClientWrapper _tcpClient = new();
|
private TcpClientWrapper _tcpClient = null;
|
||||||
private readonly Timer _idleTimer;
|
private readonly Timer _idleTimer;
|
||||||
|
|
||||||
private readonly Task _processingTask;
|
private readonly Task _processingTask;
|
||||||
private readonly AsyncQueue<RequestQueueItem> _requestQueue = new();
|
private readonly AsyncQueue<RequestQueueItem> _requestQueue = new();
|
||||||
|
|
||||||
|
private TimeSpan _readTimeout = TimeSpan.FromMilliseconds(1);
|
||||||
|
private TimeSpan _writeTimeout = TimeSpan.FromMilliseconds(1);
|
||||||
|
|
||||||
#endregion Fields
|
#endregion Fields
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -58,15 +62,33 @@ namespace AMWD.Protocols.Modbus.Tcp
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual TimeSpan ReadTimeout
|
public virtual TimeSpan ReadTimeout
|
||||||
{
|
{
|
||||||
get => TimeSpan.FromMilliseconds(_tcpClient.ReceiveTimeout);
|
get => _readTimeout;
|
||||||
set => _tcpClient.ReceiveTimeout = (int)value.TotalMilliseconds;
|
set
|
||||||
|
{
|
||||||
|
if (value < TimeSpan.Zero)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||||||
|
|
||||||
|
_readTimeout = value;
|
||||||
|
|
||||||
|
if (_tcpClient != null)
|
||||||
|
_tcpClient.ReceiveTimeout = (int)value.TotalMilliseconds;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual TimeSpan WriteTimeout
|
public virtual TimeSpan WriteTimeout
|
||||||
{
|
{
|
||||||
get => TimeSpan.FromMilliseconds(_tcpClient.SendTimeout);
|
get => _writeTimeout;
|
||||||
set => _tcpClient.SendTimeout = (int)value.TotalMilliseconds;
|
set
|
||||||
|
{
|
||||||
|
if (value < TimeSpan.Zero)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||||||
|
|
||||||
|
_writeTimeout = value;
|
||||||
|
|
||||||
|
if (_tcpClient != null)
|
||||||
|
_tcpClient.SendTimeout = (int)value.TotalMilliseconds;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -116,7 +138,6 @@ namespace AMWD.Protocols.Modbus.Tcp
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_processingTask.Wait();
|
|
||||||
_processingTask.Dispose();
|
_processingTask.Dispose();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -124,7 +145,7 @@ namespace AMWD.Protocols.Modbus.Tcp
|
|||||||
|
|
||||||
OnIdleTimer(null);
|
OnIdleTimer(null);
|
||||||
|
|
||||||
_tcpClient.Dispose();
|
_tcpClient?.Dispose();
|
||||||
_clientLock.Dispose();
|
_clientLock.Dispose();
|
||||||
|
|
||||||
while (_requestQueue.TryDequeue(out var item))
|
while (_requestQueue.TryDequeue(out var item))
|
||||||
@@ -164,7 +185,7 @@ namespace AMWD.Protocols.Modbus.Tcp
|
|||||||
{
|
{
|
||||||
Request = [.. request],
|
Request = [.. request],
|
||||||
ValidateResponseComplete = validateResponseComplete,
|
ValidateResponseComplete = validateResponseComplete,
|
||||||
TaskCompletionSource = new(),
|
TaskCompletionSource = new TaskCompletionSource<IReadOnlyList<byte>>(),
|
||||||
CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)
|
CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -187,7 +208,7 @@ namespace AMWD.Protocols.Modbus.Tcp
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get next request to process
|
// Get next request to process
|
||||||
var item = await _requestQueue.DequeueAsync(cancellationToken).ConfigureAwait(false);
|
var item = await _requestQueue.DequeueAsync(cancellationToken);
|
||||||
|
|
||||||
// Remove registration => already removed from queue
|
// Remove registration => already removed from queue
|
||||||
item.CancellationTokenRegistration.Dispose();
|
item.CancellationTokenRegistration.Dispose();
|
||||||
@@ -195,19 +216,19 @@ namespace AMWD.Protocols.Modbus.Tcp
|
|||||||
// Build combined cancellation token
|
// Build combined cancellation token
|
||||||
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, item.CancellationTokenSource.Token);
|
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, item.CancellationTokenSource.Token);
|
||||||
// Wait for exclusive access
|
// Wait for exclusive access
|
||||||
await _clientLock.WaitAsync(linkedCts.Token).ConfigureAwait(false);
|
await _clientLock.WaitAsync(linkedCts.Token);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Ensure connection is up
|
// Ensure connection is up
|
||||||
await AssertConnection(linkedCts.Token).ConfigureAwait(false);
|
await AssertConnection(linkedCts.Token);
|
||||||
|
|
||||||
var stream = _tcpClient.GetStream();
|
var stream = _tcpClient.GetStream();
|
||||||
await stream.FlushAsync(linkedCts.Token).ConfigureAwait(false);
|
await stream.FlushAsync(linkedCts.Token);
|
||||||
|
|
||||||
#if NET6_0_OR_GREATER
|
#if NET6_0_OR_GREATER
|
||||||
await stream.WriteAsync(item.Request, linkedCts.Token).ConfigureAwait(false);
|
await stream.WriteAsync(item.Request, linkedCts.Token);
|
||||||
#else
|
#else
|
||||||
await stream.WriteAsync(item.Request, 0, item.Request.Length, linkedCts.Token).ConfigureAwait(false);
|
await stream.WriteAsync(item.Request, 0, item.Request.Length, linkedCts.Token);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
linkedCts.Token.ThrowIfCancellationRequested();
|
linkedCts.Token.ThrowIfCancellationRequested();
|
||||||
@@ -218,9 +239,9 @@ namespace AMWD.Protocols.Modbus.Tcp
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
#if NET6_0_OR_GREATER
|
#if NET6_0_OR_GREATER
|
||||||
int readCount = await stream.ReadAsync(buffer, linkedCts.Token).ConfigureAwait(false);
|
int readCount = await stream.ReadAsync(buffer, linkedCts.Token);
|
||||||
#else
|
#else
|
||||||
int readCount = await stream.ReadAsync(buffer, 0, buffer.Length, linkedCts.Token).ConfigureAwait(false);
|
int readCount = await stream.ReadAsync(buffer, 0, buffer.Length, linkedCts.Token);
|
||||||
#endif
|
#endif
|
||||||
if (readCount < 1)
|
if (readCount < 1)
|
||||||
throw new EndOfStreamException();
|
throw new EndOfStreamException();
|
||||||
@@ -267,7 +288,7 @@ namespace AMWD.Protocols.Modbus.Tcp
|
|||||||
// Has to be called within _clientLock!
|
// Has to be called within _clientLock!
|
||||||
private async Task AssertConnection(CancellationToken cancellationToken)
|
private async Task AssertConnection(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_tcpClient.Connected)
|
if (_tcpClient?.Connected == true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int delay = 1;
|
int delay = 1;
|
||||||
@@ -284,14 +305,16 @@ namespace AMWD.Protocols.Modbus.Tcp
|
|||||||
{
|
{
|
||||||
foreach (var ipAddress in ipAddresses)
|
foreach (var ipAddress in ipAddresses)
|
||||||
{
|
{
|
||||||
_tcpClient.Close();
|
_tcpClient?.Close();
|
||||||
|
_tcpClient?.Dispose();
|
||||||
|
|
||||||
|
_tcpClient = _tcpClientFactory.Create(ipAddress.AddressFamily, _readTimeout, _writeTimeout);
|
||||||
#if NET6_0_OR_GREATER
|
#if NET6_0_OR_GREATER
|
||||||
using var connectTask = _tcpClient.ConnectAsync(ipAddress, Port, cancellationToken);
|
var connectTask = _tcpClient.ConnectAsync(ipAddress, Port, cancellationToken);
|
||||||
#else
|
#else
|
||||||
using var connectTask = _tcpClient.ConnectAsync(ipAddress, Port);
|
var connectTask = _tcpClient.ConnectAsync(ipAddress, Port);
|
||||||
#endif
|
#endif
|
||||||
if (await Task.WhenAny(connectTask, Task.Delay(ReadTimeout, cancellationToken)) == connectTask)
|
if (await Task.WhenAny(connectTask, Task.Delay(_readTimeout, cancellationToken)) == connectTask)
|
||||||
{
|
{
|
||||||
await connectTask;
|
await connectTask;
|
||||||
if (_tcpClient.Connected)
|
if (_tcpClient.Connected)
|
||||||
@@ -309,7 +332,7 @@ namespace AMWD.Protocols.Modbus.Tcp
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromSeconds(delay), cancellationToken).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromSeconds(delay), cancellationToken);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{ /* keep it quiet */ }
|
{ /* keep it quiet */ }
|
||||||
|
|||||||
@@ -218,11 +218,11 @@ namespace AMWD.Protocols.Modbus.Tcp
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
#if NET8_0_OR_GREATER
|
#if NET8_0_OR_GREATER
|
||||||
var client = await _listener.AcceptTcpClientAsync(cancellationToken).ConfigureAwait(false);
|
var client = await _listener.AcceptTcpClientAsync(cancellationToken);
|
||||||
#else
|
#else
|
||||||
var client = await _listener.AcceptTcpClientAsync().ConfigureAwait(false);
|
var client = await _listener.AcceptTcpClientAsync();
|
||||||
#endif
|
#endif
|
||||||
await _clientListLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _clientListLock.WaitAsync(cancellationToken);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_clients.Add(client);
|
_clients.Add(client);
|
||||||
@@ -252,20 +252,20 @@ namespace AMWD.Protocols.Modbus.Tcp
|
|||||||
using (var cts = new CancellationTokenSource(ReadWriteTimeout))
|
using (var cts = new CancellationTokenSource(ReadWriteTimeout))
|
||||||
using (cancellationToken.Register(cts.Cancel))
|
using (cancellationToken.Register(cts.Cancel))
|
||||||
{
|
{
|
||||||
byte[] headerBytes = await stream.ReadExpectedBytesAsync(6, cts.Token).ConfigureAwait(false);
|
byte[] headerBytes = await stream.ReadExpectedBytesAsync(6, cts.Token);
|
||||||
requestBytes.AddRange(headerBytes);
|
requestBytes.AddRange(headerBytes);
|
||||||
|
|
||||||
byte[] followingCountBytes = headerBytes.Skip(4).Take(2).ToArray();
|
byte[] followingCountBytes = headerBytes.Skip(4).Take(2).ToArray();
|
||||||
followingCountBytes.SwapBigEndian();
|
followingCountBytes.SwapBigEndian();
|
||||||
int followingCount = BitConverter.ToUInt16(followingCountBytes, 0);
|
int followingCount = BitConverter.ToUInt16(followingCountBytes, 0);
|
||||||
|
|
||||||
byte[] bodyBytes = await stream.ReadExpectedBytesAsync(followingCount, cts.Token).ConfigureAwait(false);
|
byte[] bodyBytes = await stream.ReadExpectedBytesAsync(followingCount, cts.Token);
|
||||||
requestBytes.AddRange(bodyBytes);
|
requestBytes.AddRange(bodyBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] responseBytes = HandleRequest([.. requestBytes]);
|
byte[] responseBytes = HandleRequest([.. requestBytes]);
|
||||||
if (responseBytes != null)
|
if (responseBytes != null)
|
||||||
await stream.WriteAsync(responseBytes, 0, responseBytes.Length, cancellationToken).ConfigureAwait(false);
|
await stream.WriteAsync(responseBytes, 0, responseBytes.Length, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -274,7 +274,7 @@ namespace AMWD.Protocols.Modbus.Tcp
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
await _clientListLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _clientListLock.WaitAsync(cancellationToken);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_clients.Remove(client);
|
_clients.Remove(client);
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ namespace AMWD.Protocols.Modbus.Tcp.Utils
|
|||||||
{
|
{
|
||||||
/// <inheritdoc cref="TcpClient" />
|
/// <inheritdoc cref="TcpClient" />
|
||||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||||
internal class TcpClientWrapper : IDisposable
|
internal class TcpClientWrapper(AddressFamily addressFamily) : IDisposable
|
||||||
{
|
{
|
||||||
#region Fields
|
#region Fields
|
||||||
|
|
||||||
private readonly TcpClient _client = new();
|
private readonly TcpClient _client = new(addressFamily);
|
||||||
|
|
||||||
#endregion Fields
|
#endregion Fields
|
||||||
|
|
||||||
|
|||||||
26
AMWD.Protocols.Modbus.Tcp/Utils/TcpClientWrapperFactory.cs
Normal file
26
AMWD.Protocols.Modbus.Tcp/Utils/TcpClientWrapperFactory.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace AMWD.Protocols.Modbus.Tcp.Utils
|
||||||
|
{
|
||||||
|
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||||
|
internal class TcpClientWrapperFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="TcpClientWrapper"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addressFamily">The <see cref="AddressFamily"/> of the <see cref="TcpClient"/> to use.</param>
|
||||||
|
/// <param name="readTimeout">The read timeout.</param>
|
||||||
|
/// <param name="writeTimeout">The write timeout.</param>
|
||||||
|
/// <returns>A new <see cref="TcpClientWrapper"/> instance.</returns>
|
||||||
|
public virtual TcpClientWrapper Create(AddressFamily addressFamily, TimeSpan readTimeout, TimeSpan writeTimeout)
|
||||||
|
{
|
||||||
|
var client = new TcpClientWrapper(addressFamily)
|
||||||
|
{
|
||||||
|
ReceiveTimeout = (int)readTimeout.TotalMilliseconds,
|
||||||
|
SendTimeout = (int)writeTimeout.TotalMilliseconds
|
||||||
|
};
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -16,6 +17,7 @@ namespace AMWD.Protocols.Modbus.Tests.Tcp
|
|||||||
private readonly string _hostname = "127.0.0.1";
|
private readonly string _hostname = "127.0.0.1";
|
||||||
|
|
||||||
private Mock<TcpClientWrapper> _tcpClientMock;
|
private Mock<TcpClientWrapper> _tcpClientMock;
|
||||||
|
private Mock<TcpClientWrapperFactory> _tcpClientFactoryMock;
|
||||||
private Mock<NetworkStreamWrapper> _networkStreamMock;
|
private Mock<NetworkStreamWrapper> _networkStreamMock;
|
||||||
|
|
||||||
private bool _alwaysConnected;
|
private bool _alwaysConnected;
|
||||||
@@ -40,10 +42,19 @@ namespace AMWD.Protocols.Modbus.Tests.Tcp
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void ShouldGetAndSetPropertiesOfBaseClient()
|
public async Task ShouldSetPropertiesOfBaseClient()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
byte[] request = [1, 2, 3];
|
||||||
|
byte[] expectedResponse = [9, 8, 7];
|
||||||
|
var validation = new Func<IReadOnlyList<byte>, bool>(_ => true);
|
||||||
|
_networkResponseQueue.Enqueue(expectedResponse);
|
||||||
|
|
||||||
var connection = GetTcpConnection();
|
var connection = GetTcpConnection();
|
||||||
|
await connection.InvokeAsync(request, validation);
|
||||||
|
|
||||||
|
_tcpClientMock.Invocations.Clear();
|
||||||
|
_networkStreamMock.Invocations.Clear();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
connection.ReadTimeout = TimeSpan.FromSeconds(123);
|
connection.ReadTimeout = TimeSpan.FromSeconds(123);
|
||||||
@@ -51,8 +62,8 @@ namespace AMWD.Protocols.Modbus.Tests.Tcp
|
|||||||
|
|
||||||
// Assert - part 1
|
// Assert - part 1
|
||||||
Assert.AreEqual("TCP", connection.Name);
|
Assert.AreEqual("TCP", connection.Name);
|
||||||
Assert.AreEqual(1, connection.ReadTimeout.TotalSeconds);
|
Assert.AreEqual(123, connection.ReadTimeout.TotalSeconds);
|
||||||
Assert.AreEqual(1, connection.WriteTimeout.TotalSeconds);
|
Assert.AreEqual(456, connection.WriteTimeout.TotalSeconds);
|
||||||
|
|
||||||
Assert.AreEqual(_hostname, connection.Hostname);
|
Assert.AreEqual(_hostname, connection.Hostname);
|
||||||
Assert.AreEqual(502, connection.Port);
|
Assert.AreEqual(502, connection.Port);
|
||||||
@@ -61,9 +72,6 @@ namespace AMWD.Protocols.Modbus.Tests.Tcp
|
|||||||
_tcpClientMock.VerifySet(c => c.ReceiveTimeout = 123000, Times.Once);
|
_tcpClientMock.VerifySet(c => c.ReceiveTimeout = 123000, Times.Once);
|
||||||
_tcpClientMock.VerifySet(c => c.SendTimeout = 456000, Times.Once);
|
_tcpClientMock.VerifySet(c => c.SendTimeout = 456000, Times.Once);
|
||||||
|
|
||||||
_tcpClientMock.VerifyGet(c => c.ReceiveTimeout, Times.Once);
|
|
||||||
_tcpClientMock.VerifyGet(c => c.SendTimeout, Times.Once);
|
|
||||||
|
|
||||||
_tcpClientMock.VerifyNoOtherCalls();
|
_tcpClientMock.VerifyNoOtherCalls();
|
||||||
_networkStreamMock.VerifyNoOtherCalls();
|
_networkStreamMock.VerifyNoOtherCalls();
|
||||||
}
|
}
|
||||||
@@ -173,6 +181,7 @@ namespace AMWD.Protocols.Modbus.Tests.Tcp
|
|||||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||||
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
||||||
|
|
||||||
|
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
_tcpClientMock.Verify(c => c.Connected, Times.Once);
|
_tcpClientMock.Verify(c => c.Connected, Times.Once);
|
||||||
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
||||||
|
|
||||||
@@ -211,11 +220,10 @@ namespace AMWD.Protocols.Modbus.Tests.Tcp
|
|||||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||||
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
||||||
|
|
||||||
_tcpClientMock.VerifyGet(c => c.ReceiveTimeout, Times.Once);
|
|
||||||
|
|
||||||
_tcpClientMock.Verify(c => c.Connected, Times.Exactly(3));
|
_tcpClientMock.Verify(c => c.Connected, Times.Exactly(3));
|
||||||
_tcpClientMock.Verify(c => c.Close(), Times.Exactly(2));
|
_tcpClientMock.Verify(c => c.Close(), Times.Exactly(2));
|
||||||
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
_tcpClientMock.Verify(c => c.Dispose(), Times.Once);
|
||||||
|
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Exactly(2));
|
||||||
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
||||||
|
|
||||||
_networkStreamMock.Verify(ns => ns.FlushAsync(It.IsAny<CancellationToken>()), Times.Once);
|
_networkStreamMock.Verify(ns => ns.FlushAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||||
@@ -289,11 +297,10 @@ namespace AMWD.Protocols.Modbus.Tests.Tcp
|
|||||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||||
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
||||||
|
|
||||||
_tcpClientMock.VerifyGet(c => c.ReceiveTimeout, Times.Once);
|
|
||||||
|
|
||||||
_tcpClientMock.Verify(c => c.Connected, Times.Exactly(3));
|
_tcpClientMock.Verify(c => c.Connected, Times.Exactly(3));
|
||||||
_tcpClientMock.Verify(c => c.Close(), Times.Once);
|
_tcpClientMock.Verify(c => c.Close(), Times.Once);
|
||||||
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
_tcpClientMock.Verify(c => c.Dispose(), Times.Once);
|
||||||
|
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Exactly(2));
|
||||||
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
||||||
|
|
||||||
_networkStreamMock.Verify(ns => ns.FlushAsync(It.IsAny<CancellationToken>()), Times.Once);
|
_networkStreamMock.Verify(ns => ns.FlushAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||||
@@ -329,11 +336,10 @@ namespace AMWD.Protocols.Modbus.Tests.Tcp
|
|||||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||||
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
||||||
|
|
||||||
_tcpClientMock.VerifyGet(c => c.ReceiveTimeout, Times.Exactly(2));
|
|
||||||
|
|
||||||
_tcpClientMock.Verify(c => c.Connected, Times.Exactly(3));
|
_tcpClientMock.Verify(c => c.Connected, Times.Exactly(3));
|
||||||
_tcpClientMock.Verify(c => c.Close(), Times.Exactly(2));
|
_tcpClientMock.Verify(c => c.Close(), Times.Exactly(2));
|
||||||
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Exactly(2));
|
_tcpClientMock.Verify(c => c.Dispose(), Times.Exactly(2));
|
||||||
|
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Exactly(3));
|
||||||
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
||||||
|
|
||||||
_networkStreamMock.Verify(ns => ns.FlushAsync(It.IsAny<CancellationToken>()), Times.Once);
|
_networkStreamMock.Verify(ns => ns.FlushAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||||
@@ -426,6 +432,7 @@ namespace AMWD.Protocols.Modbus.Tests.Tcp
|
|||||||
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
CollectionAssert.AreEqual(expectedResponse, response.ToArray());
|
||||||
|
|
||||||
_tcpClientMock.Verify(c => c.Connected, Times.Once);
|
_tcpClientMock.Verify(c => c.Connected, Times.Once);
|
||||||
|
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
||||||
|
|
||||||
_networkStreamMock.Verify(ns => ns.FlushAsync(It.IsAny<CancellationToken>()), Times.Once);
|
_networkStreamMock.Verify(ns => ns.FlushAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||||
@@ -475,6 +482,7 @@ namespace AMWD.Protocols.Modbus.Tests.Tcp
|
|||||||
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
CollectionAssert.AreEqual(request, _networkRequestCallbacks.First());
|
||||||
|
|
||||||
_tcpClientMock.Verify(c => c.Connected, Times.Once);
|
_tcpClientMock.Verify(c => c.Connected, Times.Once);
|
||||||
|
_tcpClientMock.Verify(c => c.ConnectAsync(It.IsAny<IPAddress>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
_tcpClientMock.Verify(c => c.GetStream(), Times.Once);
|
||||||
_tcpClientMock.Verify(c => c.Dispose(), Times.Once);
|
_tcpClientMock.Verify(c => c.Dispose(), Times.Once);
|
||||||
|
|
||||||
@@ -508,7 +516,7 @@ namespace AMWD.Protocols.Modbus.Tests.Tcp
|
|||||||
return ValueTask.FromResult(0);
|
return ValueTask.FromResult(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
_tcpClientMock = new Mock<TcpClientWrapper>();
|
_tcpClientMock = new Mock<TcpClientWrapper>(AddressFamily.Unknown);
|
||||||
_tcpClientMock.Setup(c => c.Connected).Returns(() => _alwaysConnected || _connectedQueue.Dequeue());
|
_tcpClientMock.Setup(c => c.Connected).Returns(() => _alwaysConnected || _connectedQueue.Dequeue());
|
||||||
_tcpClientMock.Setup(c => c.ReceiveTimeout).Returns(() => _clientReceiveTimeout);
|
_tcpClientMock.Setup(c => c.ReceiveTimeout).Returns(() => _clientReceiveTimeout);
|
||||||
_tcpClientMock.Setup(c => c.SendTimeout).Returns(() => _clientSendTimeout);
|
_tcpClientMock.Setup(c => c.SendTimeout).Returns(() => _clientSendTimeout);
|
||||||
@@ -521,6 +529,11 @@ namespace AMWD.Protocols.Modbus.Tests.Tcp
|
|||||||
.Setup(c => c.GetStream())
|
.Setup(c => c.GetStream())
|
||||||
.Returns(() => _networkStreamMock.Object);
|
.Returns(() => _networkStreamMock.Object);
|
||||||
|
|
||||||
|
_tcpClientFactoryMock = new Mock<TcpClientWrapperFactory>();
|
||||||
|
_tcpClientFactoryMock
|
||||||
|
.Setup(c => c.Create(It.IsAny<AddressFamily>(), It.IsAny<TimeSpan>(), It.IsAny<TimeSpan>()))
|
||||||
|
.Returns(_tcpClientMock.Object);
|
||||||
|
|
||||||
var connection = new ModbusTcpConnection
|
var connection = new ModbusTcpConnection
|
||||||
{
|
{
|
||||||
Hostname = _hostname,
|
Hostname = _hostname,
|
||||||
@@ -528,9 +541,9 @@ namespace AMWD.Protocols.Modbus.Tests.Tcp
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Replace real connection with mock
|
// Replace real connection with mock
|
||||||
var connectionField = connection.GetType().GetField("_tcpClient", BindingFlags.NonPublic | BindingFlags.Instance);
|
var factoryField = connection.GetType().GetField("_tcpClientFactory", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
(connectionField.GetValue(connection) as TcpClientWrapper)?.Dispose();
|
(factoryField.GetValue(connection) as TcpClientWrapper)?.Dispose();
|
||||||
connectionField.SetValue(connection, _tcpClientMock.Object);
|
factoryField.SetValue(connection, _tcpClientFactoryMock.Object);
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AMWD.Protocols.Modbus.Tcp",
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AMWD.Protocols.Modbus.Serial", "AMWD.Protocols.Modbus.Serial\AMWD.Protocols.Modbus.Serial.csproj", "{D966826F-EE6C-4BC0-9185-C2A9A50FD586}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AMWD.Protocols.Modbus.Serial", "AMWD.Protocols.Modbus.Serial\AMWD.Protocols.Modbus.Serial.csproj", "{D966826F-EE6C-4BC0-9185-C2A9A50FD586}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AMWD.Protocols.Modbus.Proxy", "AMWD.Protocols.Modbus.Proxy\AMWD.Protocols.Modbus.Proxy.csproj", "{C30EBE45-E3B8-4997-95DF-8F94B31C8E1A}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AMWD.Protocols.Modbus.Proxy", "AMWD.Protocols.Modbus.Proxy\AMWD.Protocols.Modbus.Proxy.csproj", "{C30EBE45-E3B8-4997-95DF-8F94B31C8E1A}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Renamed `ModbusSerialServer` to `ModbusRtuServer` to clearify the protocol that is used.
|
- Renamed `ModbusSerialServer` to `ModbusRtuServer` to clearify the protocol that is used.
|
||||||
- Made `Protocol` property of `ModbusClientBase` non-abstract.
|
- Made `Protocol` property of `ModbusClientBase` non-abstract.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Issue with missing client on TCP connection when using default constructor (`AddressFamily.Unknown`)
|
||||||
|
|
||||||
|
|
||||||
## [v0.2.0] (2024-04-02)
|
## [v0.2.0] (2024-04-02)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user