Dreamine.IO.Fastech.Ethernet 1.0.1
Dreamine.IO.Fastech.Ethernet 산업 자동화 및 I/O 기능을 제공합니다.
로딩중...
검색중...
일치하는것 없음
TcpFastechEthernetIoTransport.cs
이 파일의 문서화 페이지로 가기
1using System.Net.Sockets;
2using Dreamine.IO.Abstractions.Results;
4
6
16{
25 private const int DefaultReceiveBufferSize = 4096;
26
35 private readonly SemaphoreSlim _syncLock = new(1, 1);
53 private TcpClient? _tcpClient;
62 private NetworkStream? _stream;
71 private bool _disposed;
72
98 {
99 _options = options ?? throw new ArgumentNullException(nameof(options));
100 }
101
110 public bool IsConnected => _tcpClient?.Connected == true && _stream is not null;
111
144 public async Task<IoResult> ConnectAsync(CancellationToken cancellationToken = default)
145 {
146 if (string.IsNullOrWhiteSpace(_options.Host))
147 {
148 return IoResult.Failure("The Fastech host must not be empty.");
149 }
150
151 if (_options.Port is <= 0 or > 65535)
152 {
153 return IoResult.Failure($"Invalid TCP port: {_options.Port}.");
154 }
155
156 if (_options.ConnectTimeoutMs <= 0)
157 {
158 return IoResult.Failure("The connection timeout must be greater than zero.");
159 }
160
161 await _syncLock.WaitAsync(cancellationToken).ConfigureAwait(false);
162
163 try
164 {
166
167 if (IsConnected)
168 {
169 return IoResult.Success();
170 }
171
172 await CloseCoreAsync().ConfigureAwait(false);
173
174 _tcpClient = new TcpClient
175 {
176 NoDelay = true
177 };
178
179 using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
180 timeoutCts.CancelAfter(_options.ConnectTimeoutMs);
181
182 await _tcpClient.ConnectAsync(_options.Host, _options.Port, timeoutCts.Token).ConfigureAwait(false);
183 _stream = _tcpClient.GetStream();
184
185 return IoResult.Success();
186 }
187 catch (OperationCanceledException ex)
188 {
189 await CloseCoreAsync().ConfigureAwait(false);
190 return IoResult.Failure(ex.Message);
191 }
192 catch (Exception ex)
193 {
194 await CloseCoreAsync().ConfigureAwait(false);
195 return IoResult.Failure(ex.Message);
196 }
197 finally
198 {
199 _syncLock.Release();
200 }
201 }
202
235 public async Task<IoResult> DisconnectAsync(CancellationToken cancellationToken = default)
236 {
237 await _syncLock.WaitAsync(cancellationToken).ConfigureAwait(false);
238
239 try
240 {
241 await CloseCoreAsync().ConfigureAwait(false);
242 return IoResult.Success();
243 }
244 catch (Exception ex)
245 {
246 return IoResult.Failure(ex.Message);
247 }
248 finally
249 {
250 _syncLock.Release();
251 }
252 }
253
318 public async Task<IoResult<byte[]>> SendAndReceiveAsync(
319 IReadOnlyList<byte> requestFrame,
320 int receiveTimeoutMs,
321 int expectedResponseLength,
322 CancellationToken cancellationToken = default)
323 {
324 ArgumentNullException.ThrowIfNull(requestFrame);
325
326 if (requestFrame.Count == 0)
327 {
328 return IoResult<byte[]>.Failure("The request frame must not be empty.");
329 }
330
331 if (receiveTimeoutMs <= 0)
332 {
333 return IoResult<byte[]>.Failure("The receive timeout must be greater than zero.");
334 }
335
336 if (expectedResponseLength < 0)
337 {
338 return IoResult<byte[]>.Failure("The expected response length must not be negative.");
339 }
340
341 await _syncLock.WaitAsync(cancellationToken).ConfigureAwait(false);
342
343 try
344 {
346
347 if (_stream is null || _tcpClient is null || !_tcpClient.Connected)
348 {
349 return IoResult<byte[]>.Failure("The Fastech TCP transport is not connected.");
350 }
351
352 var requestBuffer = requestFrame as byte[] ?? requestFrame.ToArray();
353
354 using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
355 timeoutCts.CancelAfter(receiveTimeoutMs);
356
357 await _stream.WriteAsync(requestBuffer, timeoutCts.Token).ConfigureAwait(false);
358 await _stream.FlushAsync(timeoutCts.Token).ConfigureAwait(false);
359
360 var response = expectedResponseLength > 0
361 ? await ReadExactlyAsync(_stream, expectedResponseLength, timeoutCts.Token).ConfigureAwait(false)
362 : await ReadAvailableAsync(_stream, timeoutCts.Token).ConfigureAwait(false);
363
364 return IoResult<byte[]>.Success(response);
365 }
366 catch (OperationCanceledException ex)
367 {
368 return IoResult<byte[]>.Failure(ex.Message);
369 }
370 catch (Exception ex)
371 {
372 return IoResult<byte[]>.Failure(ex.Message);
373 }
374 finally
375 {
376 _syncLock.Release();
377 }
378 }
379
396 public async ValueTask DisposeAsync()
397 {
398 if (_disposed)
399 {
400 return;
401 }
402
403 await DisconnectAsync().ConfigureAwait(false);
404 _syncLock.Dispose();
405 _disposed = true;
406
407 GC.SuppressFinalize(this);
408 }
409
458 private static async Task<byte[]> ReadExactlyAsync(
459 NetworkStream stream,
460 int length,
461 CancellationToken cancellationToken)
462 {
463 var buffer = new byte[length];
464 var offset = 0;
465
466 while (offset < buffer.Length)
467 {
468 var read = await stream.ReadAsync(
469 buffer.AsMemory(offset, buffer.Length - offset),
470 cancellationToken).ConfigureAwait(false);
471
472 if (read == 0)
473 {
474 throw new IOException("The remote Fastech endpoint closed the TCP connection.");
475 }
476
477 offset += read;
478 }
479
480 return buffer;
481 }
482
523 private static async Task<byte[]> ReadAvailableAsync(
524 NetworkStream stream,
525 CancellationToken cancellationToken)
526 {
527 var buffer = new byte[DefaultReceiveBufferSize];
528 var read = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
529
530 if (read == 0)
531 {
532 throw new IOException("The remote Fastech endpoint closed the TCP connection.");
533 }
534
535 return buffer[..read];
536 }
537
554 private Task CloseCoreAsync()
555 {
556 try
557 {
558 _stream?.Dispose();
559 _tcpClient?.Dispose();
560 }
561 finally
562 {
563 _stream = null;
564 _tcpClient = null;
565 }
566
567 return Task.CompletedTask;
568 }
569
586 private void ThrowIfDisposed()
587 {
588 ObjectDisposedException.ThrowIf(_disposed, this);
589 }
590}
async Task< IoResult< byte[]> > SendAndReceiveAsync(IReadOnlyList< byte > requestFrame, int receiveTimeoutMs, int expectedResponseLength, CancellationToken cancellationToken=default)
static async Task< byte[]> ReadAvailableAsync(NetworkStream stream, CancellationToken cancellationToken)
static async Task< byte[]> ReadExactlyAsync(NetworkStream stream, int length, CancellationToken cancellationToken)
async Task< IoResult > DisconnectAsync(CancellationToken cancellationToken=default)
async Task< IoResult > ConnectAsync(CancellationToken cancellationToken=default)