Class
Sealed
public sealed class TcpTransport : SharpMeter.Transport.ITransport
Namespace: SharpMeter.Transport
TCP transport implementation for PSEM communication.
Inheritance
Implements: SharpMeter.Transport.ITransport
Constructors
| Name | Description |
|---|---|
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
| Name | Type | Description |
|---|---|---|
options | SharpMeter.Transport.TransportOptions | The transport configuration options. |
logger | ILogger<SharpMeter.Transport.TcpTransport> | The logger instance. |
Properties
| Name | Description |
|---|---|
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
| Name | Description |
|---|---|
ConnectAsync(CancellationToken cancellationToken) |
Opens the transport connection. |
DisconnectAsync(CancellationToken cancellationToken) |
Closes the transport connection. |
DisposeAsync() |
|
ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken) |
Receives raw bytes from the transport. |
SendAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken) |
Sends raw bytes over the transport. |
SendControlAsync(byte controlByte, CancellationToken cancellationToken) |
Sends a single control byte (ACK, NAK, CAN). |
ConnectAsync(CancellationToken cancellationToken)
ValueTask<Result<bool>> TcpTransport.ConnectAsync(CancellationToken cancellationToken = null)
Opens the transport connection.
Parameters
| Name | Type | Description |
|---|---|---|
cancellationToken | CancellationToken | Cancellation token. |
Returns: A result indicating success or transport error.
DisconnectAsync(CancellationToken cancellationToken)
ValueTask TcpTransport.DisconnectAsync(CancellationToken cancellationToken = null)
Closes the transport connection.
Parameters
| Name | Type | Description |
|---|---|---|
cancellationToken | CancellationToken | Cancellation token. |
ReceiveAsync(Memory buffer, CancellationToken cancellationToken)
ValueTask<Result<int>> TcpTransport.ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken = null)
Receives raw bytes from the transport.
Parameters
| Name | Type | Description |
|---|---|---|
buffer | Memory<byte> | The buffer to receive into. |
cancellationToken | CancellationToken | Cancellation 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
| Name | Type | Description |
|---|---|---|
data | ReadOnlyMemory<byte> | The data to send. |
cancellationToken | CancellationToken | Cancellation 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
| Name | Type | Description |
|---|---|---|
controlByte | byte | The control byte to send. |
cancellationToken | CancellationToken | Cancellation 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
}