Dreamine.PLC.Mitsubishi.MC 1.0.1
Dreamine.PLC.Mitsubishi.MC 산업 자동화 및 I/O 기능을 제공합니다.
로딩중...
검색중...
일치하는것 없음
UdpMitsubishiMcTransport.cs
이 파일의 문서화 페이지로 가기
1using System.Net;
2using System.Net.Sockets;
3using Dreamine.PLC.Abstractions.Results;
4
6
16{
25 private const int MaximumMcResponseFrameLength = 8192;
34 private readonly SemaphoreSlim _syncLock = new(1, 1);
43 private UdpClient? _udpClient;
52 private IPEndPoint? _remoteEndPoint;
61 private bool _disposed;
62
71 public bool IsConnected => _udpClient is not null && _remoteEndPoint is not null;
72
130 public async Task<PlcResult> ConnectAsync(
131 string host,
132 int port,
133 int timeoutMs,
134 CancellationToken cancellationToken = default)
135 {
136 ArgumentException.ThrowIfNullOrWhiteSpace(host);
137
138 if (port is <= 0 or > 65535)
139 {
140 return PlcResult.Failure($"Invalid UDP port: {port}");
141 }
142
143 if (timeoutMs <= 0)
144 {
145 return PlcResult.Failure("The connection timeout must be greater than zero.");
146 }
147
148 await _syncLock.WaitAsync(cancellationToken).ConfigureAwait(false);
149
150 try
151 {
153
154 await CloseCoreAsync().ConfigureAwait(false);
155
156 var addresses = await Dns.GetHostAddressesAsync(host, cancellationToken).ConfigureAwait(false);
157 var address = addresses.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork)
158 ?? addresses.FirstOrDefault();
159
160 if (address is null)
161 {
162 return PlcResult.Failure($"Failed to resolve UDP host: {host}");
163 }
164
165 _remoteEndPoint = new IPEndPoint(address, port);
166 _udpClient = new UdpClient(address.AddressFamily);
168
169 return PlcResult.Success();
170 }
171 catch (OperationCanceledException ex)
172 {
173 await CloseCoreAsync().ConfigureAwait(false);
174 return PlcResult.Failure(ex.Message);
175 }
176 catch (Exception ex)
177 {
178 await CloseCoreAsync().ConfigureAwait(false);
179 return PlcResult.Failure(ex.Message);
180 }
181 finally
182 {
183 _syncLock.Release();
184 }
185 }
186
216 public async Task<PlcResult> DisconnectAsync(CancellationToken cancellationToken = default)
217 {
218 await _syncLock.WaitAsync(cancellationToken).ConfigureAwait(false);
219
220 try
221 {
222 await CloseCoreAsync().ConfigureAwait(false);
223 return PlcResult.Success();
224 }
225 catch (Exception ex)
226 {
227 return PlcResult.Failure(ex.Message);
228 }
229 finally
230 {
231 _syncLock.Release();
232 }
233 }
234
292 public async Task<PlcResult<byte[]>> SendAndReceiveAsync(
293 IReadOnlyList<byte> requestFrame,
294 int receiveTimeoutMs,
295 int retryCount,
296 CancellationToken cancellationToken = default)
297 {
298 ArgumentNullException.ThrowIfNull(requestFrame);
299
300 if (requestFrame.Count == 0)
301 {
302 return PlcResult<byte[]>.Failure("The request frame must not be empty.");
303 }
304
305 if (receiveTimeoutMs <= 0)
306 {
307 return PlcResult<byte[]>.Failure("The receive timeout must be greater than zero.");
308 }
309
310 await _syncLock.WaitAsync(cancellationToken).ConfigureAwait(false);
311
312 try
313 {
315
316 if (_udpClient is null || _remoteEndPoint is null)
317 {
318 return PlcResult<byte[]>.Failure("The UDP transport is not ready.");
319 }
320
321 var attempts = Math.Max(1, retryCount);
322 var requestBuffer = requestFrame as byte[] ?? requestFrame.ToArray();
323 Exception? lastException = null;
324
325 for (var attempt = 1; attempt <= attempts; attempt++)
326 {
327 cancellationToken.ThrowIfCancellationRequested();
328
329 using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
330 timeoutCts.CancelAfter(receiveTimeoutMs);
331
332 try
333 {
334 await _udpClient.SendAsync(requestBuffer, requestBuffer.Length).WaitAsync(timeoutCts.Token).ConfigureAwait(false);
335 var response = await _udpClient.ReceiveAsync().WaitAsync(timeoutCts.Token).ConfigureAwait(false);
336
337 if (response.Buffer.Length == 0)
338 {
339 lastException = new IOException("The UDP PLC response was empty.");
340 continue;
341 }
342
343 if (response.Buffer.Length > MaximumMcResponseFrameLength)
344 {
345 return PlcResult<byte[]>.Failure(
346 $"The UDP PLC response frame is too large. Length: {response.Buffer.Length}");
347 }
348
349 return PlcResult<byte[]>.Success(response.Buffer);
350 }
351 catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)
352 {
353 lastException = ex;
354 }
355 catch (SocketException ex)
356 {
357 lastException = ex;
358 }
359 catch (IOException ex)
360 {
361 lastException = ex;
362 }
363 }
364
365 return PlcResult<byte[]>.Failure(
366 lastException?.Message ?? "The UDP PLC request timed out.");
367 }
368 catch (OperationCanceledException ex)
369 {
370 return PlcResult<byte[]>.Failure(ex.Message);
371 }
372 catch (Exception ex)
373 {
374 return PlcResult<byte[]>.Failure(ex.Message);
375 }
376 finally
377 {
378 _syncLock.Release();
379 }
380 }
381
397 public async ValueTask DisposeAsync()
398 {
399 if (_disposed)
400 {
401 return;
402 }
403
404 await DisconnectAsync().ConfigureAwait(false);
405
406 _syncLock.Dispose();
407 _disposed = true;
408
409 GC.SuppressFinalize(this);
410 }
411
427 private Task CloseCoreAsync()
428 {
429 try
430 {
431 _udpClient?.Close();
432 _udpClient?.Dispose();
433 }
434 finally
435 {
436 _udpClient = null;
437 _remoteEndPoint = null;
438 }
439
440 return Task.CompletedTask;
441 }
442
458 private void ThrowIfDisposed()
459 {
460 ObjectDisposedException.ThrowIf(_disposed, GetType().Name);
461 }
462}
async Task< PlcResult< byte[]> > SendAndReceiveAsync(IReadOnlyList< byte > requestFrame, int receiveTimeoutMs, int retryCount, CancellationToken cancellationToken=default)
async Task< PlcResult > DisconnectAsync(CancellationToken cancellationToken=default)
async Task< PlcResult > ConnectAsync(string host, int port, int timeoutMs, CancellationToken cancellationToken=default)