1using System.Collections.Concurrent;
4using System.Net.Sockets;
5using Dreamine.Communication.Abstractions.Enums;
6using Dreamine.Communication.Abstractions.Interfaces;
7using Dreamine.Communication.Abstractions.Models;
8using Dreamine.Communication.Core.Framing;
9using Dreamine.Communication.Core.Protocols;
68 private readonly ConcurrentDictionary<Guid, TcpClientConnectionEntry>
_clients =
new();
105 private int _state = (int)ConnectionState.Disconnected;
126 new DreamineEnvelopeProtocolAdapter(),
127 new LengthPrefixedMessageFrameCodec())
173 IMessageProtocolAdapter protocolAdapter,
174 IMessageFrameCodec frameCodec)
176 _options = options ??
throw new ArgumentNullException(nameof(options));
177 _protocolAdapter = protocolAdapter ??
throw new ArgumentNullException(nameof(protocolAdapter));
178 _frameCodec = frameCodec ??
throw new ArgumentNullException(nameof(frameCodec));
191 public ConnectionState
State => (ConnectionState)Volatile.Read(ref
_state);
201 public TransportKind
Kind => TransportKind.Tcp;
224 set => _options.SendTargetMode = value;
271 public Task
ConnectAsync(CancellationToken cancellationToken =
default)
273 if (
State is ConnectionState.Listening or ConnectionState.Connecting)
275 return Task.CompletedTask;
278 cancellationToken.ThrowIfCancellationRequested();
280 SetState(ConnectionState.Connecting);
291 _serverCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
293 SetState(ConnectionState.Listening);
299 return Task.CompletedTask;
340 if (
State == ConnectionState.Disconnected)
345 SetState(ConnectionState.Disconnecting);
358 catch (OperationCanceledException)
361 catch (ObjectDisposedException)
364 catch (SocketException)
373 cancellationToken.ThrowIfCancellationRequested();
375 SetState(ConnectionState.Disconnected);
411 MessageEnvelope message,
412 CancellationToken cancellationToken =
default)
475 MessageEnvelope message,
476 CancellationToken cancellationToken =
default)
478 ArgumentNullException.ThrowIfNull(message);
480 if (
State != ConnectionState.Listening)
482 throw new InvalidOperationException(
"TCP server is not listening.");
488 if (targets.Length == 0)
490 throw new InvalidOperationException(
"No TCP client is connected to the server.");
493 var sendTasks = targets
497 await Task.WhenAll(sendTasks).ConfigureAwait(
false);
533 MessageEnvelope message,
534 CancellationToken cancellationToken =
default)
566 var entries = _clients.Values
567 .OrderBy(x => x.ConnectedAt)
570 return targetMode
switch
643 CancellationToken cancellationToken)
645 cancellationToken.ThrowIfCancellationRequested();
647 if (!target.
Client.Connected)
653 await target.
SendLock.WaitAsync(cancellationToken).ConfigureAwait(
false);
657 if (!target.
Client.Connected)
663 var stream = target.
Client.GetStream();
665 await
_frameCodec.WriteFrameAsync(stream, payload, cancellationToken)
666 .ConfigureAwait(
false);
711 while (!cancellationToken.IsCancellationRequested &&
712 State == ConnectionState.Listening)
714 var client = await
_listener.AcceptTcpClientAsync(cancellationToken)
715 .ConfigureAwait(
false);
717 client.ReceiveBufferSize =
_options.ReceiveBufferSize;
718 client.SendBufferSize =
_options.SendBufferSize;
720 var clientId = Guid.NewGuid();
724 DateTimeOffset.UtcNow);
734 catch (OperationCanceledException)
737 catch (ObjectDisposedException)
740 catch (SocketException)
742 if (!cancellationToken.IsCancellationRequested)
751 if (!cancellationToken.IsCancellationRequested)
760 if (!cancellationToken.IsCancellationRequested)
812 CancellationToken cancellationToken)
816 var stream = client.GetStream();
818 while (!cancellationToken.IsCancellationRequested &&
819 State == ConnectionState.Listening &&
822 var payload = await
_frameCodec.ReadFrameAsync(stream, cancellationToken)
823 .ConfigureAwait(
false);
834 catch (OperationCanceledException)
837 catch (ObjectDisposedException)
840 catch (SocketException)
885 foreach (var pair
in _clients.ToArray())
917 var removed =
_clients.TryRemove(clientId, out _);
953 Interlocked.Exchange(ref
_state, (
int)state);
1003 if (host ==
"0.0.0.0")
1005 return IPAddress.Any;
1008 if (host ==
"127.0.0.1" ||
1009 host.Equals(
"localhost", StringComparison.OrdinalIgnoreCase))
1011 return IPAddress.Loopback;
1014 if (IPAddress.TryParse(host, out var ipAddress))
1019 throw new ArgumentException($
"Invalid TCP server host: {host}", nameof(host));
1056 ArgumentException.ThrowIfNullOrWhiteSpace(options.
Host);
1058 if (options.
Port <= 0 || options.
Port > 65535)
1060 throw new ArgumentOutOfRangeException(nameof(options.
Port));
1065 throw new ArgumentOutOfRangeException(nameof(options.
Backlog));
1075 throw new ArgumentOutOfRangeException(nameof(options.
SendBufferSize));
1132 DateTimeOffset connectedAt)
1135 Client = client ??
throw new ArgumentNullException(nameof(client));
TcpClientConnectionEntry[] GetTargetClients(TcpServerSendTargetMode targetMode)
Task ConnectAsync(CancellationToken cancellationToken=default)
readonly TcpServerTransportOptions _options
async ValueTask DisposeAsync()
Task SendAsync(MessageEnvelope message, CancellationToken cancellationToken=default)
EventHandler< MessageEnvelope >? MessageReceived
readonly IMessageProtocolAdapter _protocolAdapter
readonly IMessageFrameCodec _frameCodec
static IPAddress ParseHost(string host)
TcpServerTransport(TcpServerTransportOptions options)
async Task SendAsync(TcpServerSendTargetMode targetMode, MessageEnvelope message, CancellationToken cancellationToken=default)
static void ValidateOptions(TcpServerTransportOptions options)
async Task SendToClientAsync(TcpClientConnectionEntry target, byte[] payload, CancellationToken cancellationToken)
async Task AcceptLoopAsync(CancellationToken cancellationToken)
void SetState(ConnectionState state)
async Task ReceiveLoopAsync(Guid clientId, TcpClient client, CancellationToken cancellationToken)
readonly ConcurrentDictionary< Guid, TcpClientConnectionEntry > _clients
CancellationTokenSource? _serverCts
async Task DisconnectAsync(CancellationToken cancellationToken=default)
TcpServerTransport(TcpServerTransportOptions options, IMessageProtocolAdapter protocolAdapter, IMessageFrameCodec frameCodec)
void RemoveClient(Guid clientId, TcpClient client)
TcpServerSendTargetMode SendTargetMode
EventHandler< int >? ConnectedClientCountChanged
Task BroadcastAsync(MessageEnvelope message, CancellationToken cancellationToken=default)
void NotifyConnectedClientCountChanged()
TcpClientConnectionEntry(Guid clientId, TcpClient client, DateTimeOffset connectedAt)
DateTimeOffset ConnectedAt