Dreamine.Communication.Sockets 1.0.2
Dreamine.Communication.Sockets 통신 기능과 관련 API를 제공합니다.
로딩중...
검색중...
일치하는것 없음
TcpClientTransport.cs
이 파일의 문서화 페이지로 가기
1using System.IO;
2using System.Net.Sockets;
3using Dreamine.Communication.Abstractions.Enums;
4using Dreamine.Communication.Abstractions.Interfaces;
5using Dreamine.Communication.Abstractions.Models;
6using Dreamine.Communication.Core.Framing;
7using Dreamine.Communication.Core.Protocols;
9
11
20public sealed class TcpClientTransport : IMessageTransport
21{
39 private readonly IMessageProtocolAdapter _protocolAdapter;
48 private readonly IMessageFrameCodec _frameCodec;
49
58 private TcpClient? _client;
67 private CancellationTokenSource? _receiveLoopCts;
76 private Task? _receiveLoopTask;
85 private int _state = (int)ConnectionState.Disconnected;
86
104 : this(
105 options,
106 new DreamineEnvelopeProtocolAdapter(),
107 new LengthPrefixedMessageFrameCodec())
108 {
109 }
110
153 IMessageProtocolAdapter protocolAdapter,
154 IMessageFrameCodec frameCodec)
155 {
156 _options = options ?? throw new ArgumentNullException(nameof(options));
157 _protocolAdapter = protocolAdapter ?? throw new ArgumentNullException(nameof(protocolAdapter));
158 _frameCodec = frameCodec ?? throw new ArgumentNullException(nameof(frameCodec));
159
161 }
162
171 public ConnectionState State => (ConnectionState)Volatile.Read(ref _state);
172
181 public TransportKind Kind => TransportKind.Tcp;
182
191 public event EventHandler<MessageEnvelope>? MessageReceived;
192
225 public async Task ConnectAsync(CancellationToken cancellationToken = default)
226 {
227 if (State is ConnectionState.Connected or ConnectionState.Connecting)
228 {
229 return;
230 }
231
232 SetState(ConnectionState.Connecting);
233
234 try
235 {
237
238 _client = new TcpClient
239 {
240 ReceiveBufferSize = _options.ReceiveBufferSize,
241 SendBufferSize = _options.SendBufferSize
242 };
243
244 using var timeoutCts = new CancellationTokenSource(_options.ConnectTimeoutMs);
245 using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
246 cancellationToken,
247 timeoutCts.Token);
248
249 await _client.ConnectAsync(_options.Host, _options.Port, linkedCts.Token)
250 .ConfigureAwait(false);
251
252 SetState(ConnectionState.Connected);
253
254 _receiveLoopCts = new CancellationTokenSource();
255 _receiveLoopTask = Task.Run(
257 _receiveLoopCts.Token);
258 }
259 catch
260 {
261 SetState(ConnectionState.Faulted);
263
264 throw;
265 }
266 }
267
292 public async Task DisconnectAsync(CancellationToken cancellationToken = default)
293 {
294 if (State == ConnectionState.Disconnected)
295 {
296 return;
297 }
298
299 SetState(ConnectionState.Disconnecting);
300
301 _receiveLoopCts?.Cancel();
302
304
305 if (_receiveLoopTask is not null)
306 {
307 try
308 {
309 await _receiveLoopTask.ConfigureAwait(false);
310 }
311 catch (OperationCanceledException)
312 {
313 }
314 catch (ObjectDisposedException)
315 {
316 }
317 catch (SocketException)
318 {
319 }
320 catch (IOException)
321 {
322 }
323 }
324
325 _receiveLoopCts?.Dispose();
326 _receiveLoopCts = null;
327 _receiveLoopTask = null;
328
329 cancellationToken.ThrowIfCancellationRequested();
330
331 SetState(ConnectionState.Disconnected);
332 }
333
382 public async Task SendAsync(
383 MessageEnvelope message,
384 CancellationToken cancellationToken = default)
385 {
386 ArgumentNullException.ThrowIfNull(message);
387
388 if (_client is null || State != ConnectionState.Connected)
389 {
390 throw new InvalidOperationException("TCP client is not connected.");
391 }
392
393 try
394 {
395 var stream = _client.GetStream();
396 var payload = _protocolAdapter.Encode(message);
397
398 await _frameCodec.WriteFrameAsync(stream, payload, cancellationToken)
399 .ConfigureAwait(false);
400 }
401 catch
402 {
403 SetState(ConnectionState.Faulted);
405
406 throw;
407 }
408 }
409
426 public async ValueTask DisposeAsync()
427 {
428 await DisconnectAsync().ConfigureAwait(false);
429 }
430
455 private async Task ReceiveLoopAsync(CancellationToken cancellationToken)
456 {
457 if (_client is null)
458 {
459 return;
460 }
461
462 try
463 {
464 var stream = _client.GetStream();
465
466 while (!cancellationToken.IsCancellationRequested &&
467 State == ConnectionState.Connected)
468 {
469 var payload = await _frameCodec.ReadFrameAsync(stream, cancellationToken)
470 .ConfigureAwait(false);
471
472 if (payload is null)
473 {
474 break;
475 }
476
477 var message = _protocolAdapter.Decode(payload);
478 MessageReceived?.Invoke(this, message);
479 }
480
481 if (!cancellationToken.IsCancellationRequested &&
482 State == ConnectionState.Connected)
483 {
484 SetState(ConnectionState.Disconnected);
486 }
487 }
488 catch (OperationCanceledException)
489 {
490 }
491 catch (ObjectDisposedException)
492 {
493 }
494 catch (SocketException)
495 {
496 if (!cancellationToken.IsCancellationRequested)
497 {
498 SetState(ConnectionState.Faulted);
500 }
501 }
502 catch (IOException)
503 {
504 if (!cancellationToken.IsCancellationRequested)
505 {
506 SetState(ConnectionState.Faulted);
508 }
509 }
510 catch
511 {
512 if (!cancellationToken.IsCancellationRequested)
513 {
514 SetState(ConnectionState.Faulted);
516 }
517 }
518 }
519
528 private void CleanupClient()
529 {
530 try
531 {
532 _client?.Close();
533 }
534 catch
535 {
536 }
537
538 try
539 {
540 _client?.Dispose();
541 }
542 catch
543 {
544 }
545
546 _client = null;
547 }
548
565 private void SetState(ConnectionState state)
566 {
567 Interlocked.Exchange(ref _state, (int)state);
568 }
569
602 private static void ValidateOptions(TcpClientTransportOptions options)
603 {
604 ArgumentException.ThrowIfNullOrWhiteSpace(options.Host);
605
606 if (options.Port <= 0 || options.Port > 65535)
607 {
608 throw new ArgumentOutOfRangeException(nameof(options.Port));
609 }
610
611 if (options.ReceiveBufferSize <= 0)
612 {
613 throw new ArgumentOutOfRangeException(nameof(options.ReceiveBufferSize));
614 }
615
616 if (options.SendBufferSize <= 0)
617 {
618 throw new ArgumentOutOfRangeException(nameof(options.SendBufferSize));
619 }
620
621 if (options.ConnectTimeoutMs <= 0)
622 {
623 throw new ArgumentOutOfRangeException(nameof(options.ConnectTimeoutMs));
624 }
625 }
626}
async Task ReceiveLoopAsync(CancellationToken cancellationToken)
async Task SendAsync(MessageEnvelope message, CancellationToken cancellationToken=default)
TcpClientTransport(TcpClientTransportOptions options, IMessageProtocolAdapter protocolAdapter, IMessageFrameCodec frameCodec)
static void ValidateOptions(TcpClientTransportOptions options)
async Task DisconnectAsync(CancellationToken cancellationToken=default)
async Task ConnectAsync(CancellationToken cancellationToken=default)