Dreamine.IO.Fastech.Ethernet 1.0.1
Dreamine.IO.Fastech.Ethernet 산업 자동화 및 I/O 기능을 제공합니다.
로딩중...
검색중...
일치하는것 없음
UdpFastechEthernetIoTransport.cs
이 파일의 문서화 페이지로 가기
1using System.Net;
2using System.Net.Sockets;
3using Dreamine.IO.Abstractions.Results;
5
7
17{
26 private readonly SemaphoreSlim _syncLock = new(1, 1);
44 private UdpClient? _udpClient;
53 private IPEndPoint? _remoteEndPoint;
62 private bool _disposed;
63
89 {
90 _options = options ?? throw new ArgumentNullException(nameof(options));
91 }
92
101 public bool IsConnected => _udpClient is not null;
102
111 public byte[] LastRequestFrame { get; private set; } = [];
112
121 public byte[] LastResponseFrame { get; private set; } = [];
122
155 public async Task<IoResult> ConnectAsync(CancellationToken cancellationToken = default)
156 {
157 if (string.IsNullOrWhiteSpace(_options.Host))
158 {
159 return IoResult.Failure("The Fastech host must not be empty.");
160 }
161
162 if (_options.Port is <= 0 or > 65535)
163 {
164 return IoResult.Failure($"Invalid UDP port: {_options.Port}.");
165 }
166
167 if (_options.LocalPort is < 0 or > 65535)
168 {
169 return IoResult.Failure($"Invalid local UDP port: {_options.LocalPort}.");
170 }
171
172 await _syncLock.WaitAsync(cancellationToken).ConfigureAwait(false);
173
174 try
175 {
177
178 if (IsConnected)
179 {
180 return IoResult.Success();
181 }
182
183 var remoteAddress = await ResolveRemoteAddressAsync(cancellationToken).ConfigureAwait(false);
184 _remoteEndPoint = new IPEndPoint(remoteAddress, _options.Port);
185 _udpClient = _options.LocalPort == 0
186 ? new UdpClient(remoteAddress.AddressFamily)
187 : new UdpClient(_options.LocalPort, remoteAddress.AddressFamily);
188
190
191 return IoResult.Success();
192 }
193 catch (Exception ex)
194 {
195 await CloseCoreAsync().ConfigureAwait(false);
196 return IoResult.Failure(ex.Message);
197 }
198 finally
199 {
200 _syncLock.Release();
201 }
202 }
203
236 public async Task<IoResult> DisconnectAsync(CancellationToken cancellationToken = default)
237 {
238 await _syncLock.WaitAsync(cancellationToken).ConfigureAwait(false);
239
240 try
241 {
242 await CloseCoreAsync().ConfigureAwait(false);
243 return IoResult.Success();
244 }
245 catch (Exception ex)
246 {
247 return IoResult.Failure(ex.Message);
248 }
249 finally
250 {
251 _syncLock.Release();
252 }
253 }
254
319 public async Task<IoResult<byte[]>> SendAndReceiveAsync(
320 IReadOnlyList<byte> requestFrame,
321 int receiveTimeoutMs,
322 int expectedResponseLength,
323 CancellationToken cancellationToken = default)
324 {
325 ArgumentNullException.ThrowIfNull(requestFrame);
326
327 if (requestFrame.Count == 0)
328 {
329 return IoResult<byte[]>.Failure("The request frame must not be empty.");
330 }
331
332 if (receiveTimeoutMs <= 0)
333 {
334 return IoResult<byte[]>.Failure("The receive timeout must be greater than zero.");
335 }
336
337 await _syncLock.WaitAsync(cancellationToken).ConfigureAwait(false);
338
339 try
340 {
342
343 if (_udpClient is null || _remoteEndPoint is null)
344 {
345 return IoResult<byte[]>.Failure("The Fastech UDP transport is not connected.");
346 }
347
348 var requestBuffer = requestFrame as byte[] ?? requestFrame.ToArray();
349 LastRequestFrame = requestBuffer;
351
352 using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
353 timeoutCts.CancelAfter(receiveTimeoutMs);
354
355 await _udpClient.SendAsync(requestBuffer, _remoteEndPoint, timeoutCts.Token).ConfigureAwait(false);
356 var response = await _udpClient.ReceiveAsync(timeoutCts.Token).ConfigureAwait(false);
357 LastResponseFrame = response.Buffer;
358
359 return IoResult<byte[]>.Success(response.Buffer);
360 }
361 catch (OperationCanceledException ex)
362 {
363 return IoResult<byte[]>.Failure($"No UDP response from {_options.Host}:{_options.Port} within {receiveTimeoutMs} ms. {ex.Message}");
364 }
365 catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset)
366 {
367 return IoResult<byte[]>.Failure(
368 $"No UDP listener responded at {_options.Host}:{_options.Port}. Windows reported UDP reset/ICMP port unreachable. {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
426 private Task CloseCoreAsync()
427 {
428 _udpClient?.Dispose();
429 _udpClient = null;
430 _remoteEndPoint = null;
431
432 return Task.CompletedTask;
433 }
434
467 private async Task<IPAddress> ResolveRemoteAddressAsync(CancellationToken cancellationToken)
468 {
469 if (IPAddress.TryParse(_options.Host, out var address))
470 {
471 return address;
472 }
473
474 var addresses = await Dns.GetHostAddressesAsync(_options.Host, cancellationToken).ConfigureAwait(false);
475 var resolvedAddress = addresses.FirstOrDefault(x => x.AddressFamily == AddressFamily.InterNetwork)
476 ?? addresses.FirstOrDefault();
477
478 return resolvedAddress ?? throw new InvalidOperationException($"Unable to resolve UDP host: {_options.Host}.");
479 }
480
497 private static void DisableUdpConnectionReset(UdpClient client)
498 {
499 if (!OperatingSystem.IsWindows())
500 {
501 return;
502 }
503
504 try
505 {
506 const int sioUdpConnReset = -1744830452;
507 client.Client.IOControl(sioUdpConnReset, [0], null);
508 }
509 catch (SocketException)
510 {
511 }
512 }
513
530 private void ThrowIfDisposed()
531 {
532 ObjectDisposedException.ThrowIf(_disposed, this);
533 }
534}
async Task< IoResult< byte[]> > SendAndReceiveAsync(IReadOnlyList< byte > requestFrame, int receiveTimeoutMs, int expectedResponseLength, CancellationToken cancellationToken=default)
async Task< IoResult > ConnectAsync(CancellationToken cancellationToken=default)
async Task< IoResult > DisconnectAsync(CancellationToken cancellationToken=default)
async Task< IPAddress > ResolveRemoteAddressAsync(CancellationToken cancellationToken)