208 lines
6.0 KiB
C#
208 lines
6.0 KiB
C#
using System.Net;
|
|
using System.Net.Sockets;
|
|
using Sick.SafetyScanners.DataStructures;
|
|
using Sick.SafetyScanners.Exceptions;
|
|
using Sick.SafetyScanners.Interfaces;
|
|
using Sick.SafetyScanners.Types;
|
|
|
|
namespace Sick.SafetyScanners.Communication;
|
|
|
|
/// <summary>
|
|
/// Synchronous TCP client for COLA2 communication
|
|
/// Thread-safe implementation
|
|
/// </summary>
|
|
public sealed class TcpClient : ITcpClient
|
|
{
|
|
private readonly IpAddress _serverIp;
|
|
private readonly Port _serverPort;
|
|
private System.Net.Sockets.TcpClient? _tcpClient;
|
|
private NetworkStream? _stream;
|
|
private readonly object _lock = new();
|
|
private bool _disposed;
|
|
|
|
public IpAddress ServerIp => _serverIp;
|
|
public Port ServerPort => _serverPort;
|
|
|
|
public bool IsConnected
|
|
{
|
|
get
|
|
{
|
|
lock (_lock)
|
|
{
|
|
return _tcpClient?.Connected == true && _stream != null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new TCP client
|
|
/// </summary>
|
|
public TcpClient(IpAddress serverIp, Port serverPort)
|
|
{
|
|
_serverIp = serverIp;
|
|
_serverPort = serverPort;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new TCP client from string IP and port
|
|
/// </summary>
|
|
public TcpClient(string serverIp, ushort serverPort)
|
|
: this(new IpAddress(serverIp), new Port(serverPort))
|
|
{
|
|
}
|
|
|
|
public void Connect(TimeDuration? timeout = null)
|
|
{
|
|
if (_disposed)
|
|
throw new ObjectDisposedException(nameof(TcpClient));
|
|
|
|
var timeoutDuration = timeout?.ToTimeSpan() ?? TimeSpan.FromSeconds(5);
|
|
|
|
lock (_lock)
|
|
{
|
|
if (IsConnected)
|
|
{
|
|
// Already connected
|
|
return;
|
|
}
|
|
|
|
_tcpClient?.Dispose();
|
|
_tcpClient = new System.Net.Sockets.TcpClient();
|
|
}
|
|
|
|
try
|
|
{
|
|
var connectResult = _tcpClient!.BeginConnect(_serverIp.ToIPAddress(), _serverPort.Value, null, null);
|
|
var success = connectResult.AsyncWaitHandle.WaitOne(timeoutDuration);
|
|
|
|
if (!success)
|
|
{
|
|
_tcpClient.Dispose();
|
|
_tcpClient = null;
|
|
throw new Cola2TimeoutException("Connect", timeoutDuration,
|
|
$"Timeout exceeded while connecting to {_serverIp}:{_serverPort}");
|
|
}
|
|
|
|
_tcpClient.EndConnect(connectResult);
|
|
|
|
lock (_lock)
|
|
{
|
|
_stream = _tcpClient.GetStream();
|
|
}
|
|
}
|
|
catch (Exception ex) when (!(ex is Cola2TimeoutException || ex is TcpCommunicationException))
|
|
{
|
|
lock (_lock)
|
|
{
|
|
_tcpClient?.Dispose();
|
|
_tcpClient = null;
|
|
}
|
|
throw new TcpCommunicationException(_serverIp.ToString(), _serverPort.Value,
|
|
"Connection failed", ex);
|
|
}
|
|
}
|
|
|
|
public void Disconnect()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (!IsConnected)
|
|
return;
|
|
|
|
_stream?.Dispose();
|
|
_stream = null;
|
|
_tcpClient?.Close();
|
|
_tcpClient?.Dispose();
|
|
_tcpClient = null;
|
|
}
|
|
}
|
|
|
|
public void Send(ReadOnlyMemory<byte> data)
|
|
{
|
|
if (_disposed)
|
|
throw new ObjectDisposedException(nameof(TcpClient));
|
|
|
|
NetworkStream? stream;
|
|
lock (_lock)
|
|
{
|
|
if (!IsConnected)
|
|
throw new TcpCommunicationException(_serverIp.ToString(), _serverPort.Value,
|
|
"Cannot send data: not connected");
|
|
|
|
stream = _stream;
|
|
}
|
|
|
|
if (stream == null)
|
|
throw new InvalidOperationException("Stream is null");
|
|
|
|
try
|
|
{
|
|
stream.Write(data.Span);
|
|
stream.Flush();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new TcpCommunicationException(_serverIp.ToString(), _serverPort.Value,
|
|
"Failed to send data", ex);
|
|
}
|
|
}
|
|
|
|
public PacketBuffer Receive(TimeDuration? timeout = null)
|
|
{
|
|
if (_disposed)
|
|
throw new ObjectDisposedException(nameof(TcpClient));
|
|
|
|
NetworkStream? stream;
|
|
lock (_lock)
|
|
{
|
|
if (!IsConnected)
|
|
throw new TcpCommunicationException(_serverIp.ToString(), _serverPort.Value,
|
|
"Cannot receive data: not connected");
|
|
|
|
stream = _stream;
|
|
}
|
|
|
|
if (stream == null)
|
|
throw new InvalidOperationException("Stream is null");
|
|
|
|
var timeoutDuration = timeout?.ToTimeSpan() ?? TimeSpan.FromSeconds(5);
|
|
// Use MaxSize to match C++ implementation (MAXSIZE = 10000)
|
|
var buffer = new byte[PacketBuffer.MaxSize];
|
|
|
|
try
|
|
{
|
|
// Set read timeout
|
|
stream.ReadTimeout = (int)timeoutDuration.TotalMilliseconds;
|
|
|
|
var bytesRead = stream.Read(buffer, 0, buffer.Length);
|
|
|
|
if (bytesRead == 0)
|
|
{
|
|
throw new TcpCommunicationException(_serverIp.ToString(), _serverPort.Value,
|
|
"Connection closed by remote host");
|
|
}
|
|
|
|
return new PacketBuffer(buffer, bytesRead);
|
|
}
|
|
catch (Exception ex) when (!(ex is Cola2TimeoutException || ex is TcpCommunicationException))
|
|
{
|
|
throw new TcpCommunicationException(_serverIp.ToString(), _serverPort.Value,
|
|
"Failed to receive data", ex);
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
|
|
lock (_lock)
|
|
{
|
|
_stream?.Dispose();
|
|
_tcpClient?.Dispose();
|
|
_disposed = true;
|
|
}
|
|
}
|
|
}
|
|
|