Class Sealed
public sealed class TcpTransport : SharpMeter.Transport.ITransport

Namespace: SharpMeter.Transport

TCP transport implementation for PSEM communication.

Inheritance

Implements: SharpMeter.Transport.ITransport

Constructors

NameDescription
TcpTransport(TransportOptions options, TcpTransport> logger) Initializes a new instance of TcpTransport.

TcpTransport(TransportOptions options, TcpTransport> logger)

TcpTransport.TcpTransport(TransportOptions options, ILogger<TcpTransport> logger)

Initializes a new instance of TcpTransport.

Parameters

NameTypeDescription
optionsSharpMeter.Transport.TransportOptionsThe transport configuration options.
loggerILogger<SharpMeter.Transport.TcpTransport>The logger instance.

Properties

NameDescription
IsConnected Gets a value indicating whether the transport connection is open.

IsConnected

bool TcpTransport.IsConnected { get; }

Gets a value indicating whether the transport connection is open.

Methods

ConnectAsync(CancellationToken cancellationToken)

ValueTask<Result<bool>> TcpTransport.ConnectAsync(CancellationToken cancellationToken = null)

Opens the transport connection.

Parameters

NameTypeDescription
cancellationTokenCancellationTokenCancellation token.

Returns: A result indicating success or transport error.

DisconnectAsync(CancellationToken cancellationToken)

ValueTask TcpTransport.DisconnectAsync(CancellationToken cancellationToken = null)

Closes the transport connection.

Parameters

NameTypeDescription
cancellationTokenCancellationTokenCancellation token.

ReceiveAsync(Memory buffer, CancellationToken cancellationToken)

ValueTask<Result<int>> TcpTransport.ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken = null)

Receives raw bytes from the transport.

Parameters

NameTypeDescription
bufferMemory<byte>The buffer to receive into.
cancellationTokenCancellationTokenCancellation token.

Returns: A result containing the number of bytes received, or a transport error.

SendAsync(ReadOnlyMemory data, CancellationToken cancellationToken)

ValueTask<Result<bool>> TcpTransport.SendAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = null)

Sends raw bytes over the transport.

Parameters

NameTypeDescription
dataReadOnlyMemory<byte>The data to send.
cancellationTokenCancellationTokenCancellation token.

Returns: A result indicating success or transport error.

SendControlAsync(byte controlByte, CancellationToken cancellationToken)

ValueTask TcpTransport.SendControlAsync(byte controlByte, CancellationToken cancellationToken = null)

Sends a single control byte (ACK, NAK, CAN).

Parameters

NameTypeDescription
controlBytebyteThe control byte to send.
cancellationTokenCancellationTokenCancellation token.
Type Relationships
classDiagram
                    style TcpTransport fill:#f9f,stroke:#333,stroke-width:2px
                    TcpTransport ..|> ITransport : implements
                
View Source
/// <summary>
///     TCP transport implementation for PSEM communication.
/// </summary>
public sealed partial class TcpTransport : ITransport
{
#region Constructor
    /// <summary>
    ///     Initializes a new instance of <see cref = "TcpTransport"/>.
    /// </summary>
    /// <param name = "options">The transport configuration options.</param>
    /// <param name = "logger">The logger instance.</param>
    public TcpTransport(TransportOptions options, ILogger<TcpTransport> logger)
    {
        _options = options;
        _logger = logger;
    }

#endregion
#region IAsyncDisposable
    /// <inheritdoc/>
    public async ValueTask DisposeAsync() => await DisconnectAsync();
#endregion
#region Log
    private static partial class Log
    {
        [LoggerMessage(Level = LogLevel.Information, Message = "TCP connected to {Host}:{Port}")]
        public static partial void Connected(ILogger logger, string host, int port);
        [LoggerMessage(Level = LogLevel.Information, Message = "TCP disconnected")]
        public static partial void Disconnected(ILogger logger);
        [LoggerMessage(Level = LogLevel.Error, Message = "Failed to connect to {Host}:{Port}")]
        public static partial void ConnectFailed(ILogger logger, Exception ex, string host, int port);
        [LoggerMessage(Level = LogLevel.Debug, Message = "TX [{Length} bytes]: {Data}")]
        public static partial void DataSent(ILogger logger, int length, string data);
        [LoggerMessage(Level = LogLevel.Debug, Message = "RX [{Length} bytes]: {Data}")]
        public static partial void DataReceived(ILogger logger, int length, string data);
        [LoggerMessage(Level = LogLevel.Error, Message = "TCP send failed")]
        public static partial void SendFailed(ILogger logger, Exception ex);
        [LoggerMessage(Level = LogLevel.Error, Message = "TCP receive failed")]
        public static partial void ReceiveFailed(ILogger logger, Exception ex);
    }

#endregion
#region Fields
    private readonly TransportOptions _options;
    private readonly ILogger<TcpTransport> _logger;
    private TcpClient? _client;
    private NetworkStream? _stream;
#endregion
#region ITransport Members
    /// <inheritdoc/>
    public bool IsConnected => _client?.Connected ?? false;

    /// <inheritdoc/>
    public async ValueTask<Result<bool>> ConnectAsync(CancellationToken cancellationToken = default)
    {
        try
        {
            if (_client is { Connected: true })
                await DisconnectAsync(cancellationToken);
            _client = new TcpClient
            {
                ReceiveTimeout = _options.ReadTimeoutMs,
                SendTimeout = _options.WriteTimeoutMs,
                ReceiveBufferSize = _options.ReceiveBufferSize
            };
            if (IPAddress.TryParse(_options.Host, out IPAddress? ipAddress))
                await _client.ConnectAsync(ipAddress, _options.Port, cancellationToken);
            else
                await _client.ConnectAsync(_options.Host, _options.Port, cancellationToken);
            _stream = _client.GetStream();
            Log.Connected(_logger, _options.Host, _options.Port);
            return true;
        }
        catch (SocketException ex)
        {
            Log.ConnectFailed(_logger, ex, _options.Host, _options.Port);
            return PsemError.Transport($"TCP connect failed to {_options.Host}:{_options.Port}: {ex.Message}");
        }
        catch (IOException ex)
        {
            Log.ConnectFailed(_logger, ex, _options.Host, _options.Port);
            return PsemError.Transport($"TCP connect failed to {_options.Host}:{_options.Port}: {ex.Message}");
        }
    }

    /// <inheritdoc/>
    public async ValueTask DisconnectAsync(CancellationToken cancellationToken = default)
    {
        if (_stream is not null)
        {
            await _stream.DisposeAsync();
            _stream = null;
        }

        _client?.Dispose();
        _client = null;
        Log.Disconnected(_logger);
    }

    /// <inheritdoc/>
    public async ValueTask<Result<bool>> SendAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
    {
        if (_stream is null)
            return PsemError.Transport("TCP stream is not connected");
        try
        {
            await _stream.WriteAsync(data, cancellationToken);
            if (_logger.IsEnabled(LogLevel.Debug))
            {
                var hex = Convert.ToHexString(data.Span);
                Log.DataSent(_logger, data.Length, hex);
            }

            return true;
        }
        catch (IOException ex)
        {
            Log.SendFailed(_logger, ex);
            return PsemError.Transport($"TCP send failed: {ex.Message}");
        }
        catch (ObjectDisposedException ex)
        {
            Log.SendFailed(_logger, ex);
            return PsemError.Transport($"TCP send failed: {ex.Message}");
        }
    }

    /// <inheritdoc/>
    public async ValueTask<Result<int>> ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
    {
        if (_stream is null)
            return PsemError.Transport("TCP stream is not connected");
        try
        {
            var bytesRead = await _stream.ReadAsync(buffer, cancellationToken);
            if (bytesRead > 0 && _logger.IsEnabled(LogLevel.Debug))
            {
                var hex = Convert.ToHexString(buffer.Span[..bytesRead]);
                Log.DataReceived(_logger, bytesRead, hex);
            }

            return bytesRead;
        }
        catch (OperationCanceledException)
        {
            return PsemError.Timeout("TCP receive");
        }
        catch (IOException ex)
        {
            Log.ReceiveFailed(_logger, ex);
            return PsemError.Transport($"TCP receive failed: {ex.Message}");
        }
        catch (ObjectDisposedException ex)
        {
            Log.ReceiveFailed(_logger, ex);
            return PsemError.Transport($"TCP receive failed: {ex.Message}");
        }
    }

    /// <inheritdoc/>
    public async ValueTask SendControlAsync(byte controlByte, CancellationToken cancellationToken = default)
    {
        var buf = new[]
        {
            controlByte
        };
        await SendAsync(buf, cancellationToken);
    }
#endregion
}
Was this page helpful?