134 CancellationToken cancellationToken =
default)
136 ArgumentException.ThrowIfNullOrWhiteSpace(host);
138 if (port is <= 0 or > 65535)
140 return PlcResult.Failure($
"Invalid UDP port: {port}");
145 return PlcResult.Failure(
"The connection timeout must be greater than zero.");
148 await
_syncLock.WaitAsync(cancellationToken).ConfigureAwait(
false);
156 var addresses = await Dns.GetHostAddressesAsync(host, cancellationToken).ConfigureAwait(
false);
157 var address = addresses.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork)
158 ?? addresses.FirstOrDefault();
162 return PlcResult.Failure($
"Failed to resolve UDP host: {host}");
166 _udpClient =
new UdpClient(address.AddressFamily);
169 return PlcResult.Success();
171 catch (OperationCanceledException ex)
174 return PlcResult.Failure(ex.Message);
179 return PlcResult.Failure(ex.Message);
293 IReadOnlyList<byte> requestFrame,
294 int receiveTimeoutMs,
296 CancellationToken cancellationToken =
default)
298 ArgumentNullException.ThrowIfNull(requestFrame);
300 if (requestFrame.Count == 0)
302 return PlcResult<byte[]>.Failure(
"The request frame must not be empty.");
305 if (receiveTimeoutMs <= 0)
307 return PlcResult<byte[]>.Failure(
"The receive timeout must be greater than zero.");
310 await
_syncLock.WaitAsync(cancellationToken).ConfigureAwait(
false);
318 return PlcResult<byte[]>.Failure(
"The UDP transport is not ready.");
321 var attempts = Math.Max(1, retryCount);
322 var requestBuffer = requestFrame as
byte[] ?? requestFrame.ToArray();
323 Exception? lastException =
null;
325 for (var attempt = 1; attempt <= attempts; attempt++)
327 cancellationToken.ThrowIfCancellationRequested();
329 using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
330 timeoutCts.CancelAfter(receiveTimeoutMs);
334 await
_udpClient.SendAsync(requestBuffer, requestBuffer.Length).WaitAsync(timeoutCts.Token).ConfigureAwait(
false);
335 var response = await
_udpClient.ReceiveAsync().WaitAsync(timeoutCts.Token).ConfigureAwait(
false);
337 if (response.Buffer.Length == 0)
339 lastException =
new IOException(
"The UDP PLC response was empty.");
345 return PlcResult<byte[]>.Failure(
346 $
"The UDP PLC response frame is too large. Length: {response.Buffer.Length}");
349 return PlcResult<byte[]>.Success(response.Buffer);
351 catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)
355 catch (SocketException ex)
359 catch (IOException ex)
365 return PlcResult<byte[]>.Failure(
366 lastException?.Message ??
"The UDP PLC request timed out.");
368 catch (OperationCanceledException ex)
370 return PlcResult<byte[]>.Failure(ex.Message);
374 return PlcResult<byte[]>.Failure(ex.Message);