Files
BQP/srcs/RobotNet10/RobotApp/Communication/Sick.SafetyScanners/Communication/TcpClient.cs
2026-07-13 09:25:40 +07:00

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;
}
}
}