Dreamine.PLC.Mitsubishi.MC 1.0.1
Dreamine.PLC.Mitsubishi.MC 산업 자동화 및 I/O 기능을 제공합니다.
로딩중...
검색중...
일치하는것 없음
TcpMitsubishiMcTransport.cs
이 파일의 문서화 페이지로 가기
1using System.Net.Sockets;
2using Dreamine.PLC.Abstractions.Results;
3
5
15{
24 private readonly SemaphoreSlim _syncLock = new(1, 1);
33 private TcpClient? _tcpClient;
42 private NetworkStream? _stream;
51 private bool _disposed;
52
61 public bool IsConnected => _tcpClient?.Connected == true && _stream is not null;
62
120 public async Task<PlcResult> ConnectAsync(
121 string host,
122 int port,
123 int timeoutMs,
124 CancellationToken cancellationToken = default)
125 {
126 ArgumentException.ThrowIfNullOrWhiteSpace(host);
127
128 if (port is <= 0 or > 65535)
129 {
130 return PlcResult.Failure($"Invalid TCP port: {port}");
131 }
132
133 if (timeoutMs <= 0)
134 {
135 return PlcResult.Failure("The connection timeout must be greater than zero.");
136 }
137
138 await _syncLock.WaitAsync(cancellationToken).ConfigureAwait(false);
139
140 try
141 {
143
144 if (IsConnected)
145 {
146 return PlcResult.Success();
147 }
148
149 await CloseCoreAsync().ConfigureAwait(false);
150
151 _tcpClient = new TcpClient
152 {
153 NoDelay = true
154 };
155
156 using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
157 timeoutCts.CancelAfter(timeoutMs);
158
159 await _tcpClient.ConnectAsync(host, port, timeoutCts.Token).ConfigureAwait(false);
160 _stream = _tcpClient.GetStream();
161
162 return PlcResult.Success();
163 }
164 catch (OperationCanceledException ex)
165 {
166 await CloseCoreAsync().ConfigureAwait(false);
167 return PlcResult.Failure(ex.Message);
168 }
169 catch (Exception ex)
170 {
171 await CloseCoreAsync().ConfigureAwait(false);
172 return PlcResult.Failure(ex.Message);
173 }
174 finally
175 {
176 _syncLock.Release();
177 }
178 }
179
209 public async Task<PlcResult> DisconnectAsync(CancellationToken cancellationToken = default)
210 {
211 await _syncLock.WaitAsync(cancellationToken).ConfigureAwait(false);
212
213 try
214 {
215 await CloseCoreAsync().ConfigureAwait(false);
216 return PlcResult.Success();
217 }
218 catch (Exception ex)
219 {
220 return PlcResult.Failure(ex.Message);
221 }
222 finally
223 {
224 _syncLock.Release();
225 }
226 }
227
285 public async Task<PlcResult<byte[]>> SendAndReceiveAsync(
286 IReadOnlyList<byte> requestFrame,
287 int receiveTimeoutMs,
288 int retryCount,
289 CancellationToken cancellationToken = default)
290 {
291 ArgumentNullException.ThrowIfNull(requestFrame);
292
293 if (requestFrame.Count == 0)
294 {
295 return PlcResult<byte[]>.Failure("The request frame must not be empty.");
296 }
297
298 if (receiveTimeoutMs <= 0)
299 {
300 return PlcResult<byte[]>.Failure("The receive timeout must be greater than zero.");
301 }
302
303 await _syncLock.WaitAsync(cancellationToken).ConfigureAwait(false);
304
305 try
306 {
308
309 if (_stream is null || _tcpClient is null || !_tcpClient.Connected)
310 {
311 return PlcResult<byte[]>.Failure("The TCP transport is not connected.");
312 }
313
314 var requestBuffer = requestFrame as byte[] ?? requestFrame.ToArray();
315
316 using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
317 timeoutCts.CancelAfter(receiveTimeoutMs);
318
319 await _stream.WriteAsync(requestBuffer, timeoutCts.Token).ConfigureAwait(false);
320 await _stream.FlushAsync(timeoutCts.Token).ConfigureAwait(false);
321
322 var response = await ReceiveBinary3EFrameAsync(_stream, timeoutCts.Token).ConfigureAwait(false);
323
324 return PlcResult<byte[]>.Success(response);
325 }
326 catch (OperationCanceledException ex)
327 {
328 return PlcResult<byte[]>.Failure(ex.Message);
329 }
330 catch (Exception ex)
331 {
332 return PlcResult<byte[]>.Failure(ex.Message);
333 }
334 finally
335 {
336 _syncLock.Release();
337 }
338 }
339
355 public async ValueTask DisposeAsync()
356 {
357 if (_disposed)
358 {
359 return;
360 }
361
362 await DisconnectAsync().ConfigureAwait(false);
363
364 _syncLock.Dispose();
365 _disposed = true;
366
367 GC.SuppressFinalize(this);
368 }
369
413 private static async Task<byte[]> ReceiveBinary3EFrameAsync(
414 NetworkStream stream,
415 CancellationToken cancellationToken)
416 {
417 var header = new byte[9];
418
419 await ReadExactlyAsync(stream, header, cancellationToken).ConfigureAwait(false);
420
421 var responseDataLength = header[7] | (header[8] << 8);
422 if (responseDataLength < 2)
423 {
424 throw new InvalidOperationException(
425 $"Invalid Mitsubishi MC response data length: {responseDataLength}");
426 }
427
428 var body = new byte[responseDataLength];
429
430 await ReadExactlyAsync(stream, body, cancellationToken).ConfigureAwait(false);
431
432 var frame = new byte[header.Length + body.Length];
433
434 Buffer.BlockCopy(header, 0, frame, 0, header.Length);
435 Buffer.BlockCopy(body, 0, frame, header.Length, body.Length);
436
437 return frame;
438 }
439
483 private static async Task ReadExactlyAsync(
484 NetworkStream stream,
485 byte[] buffer,
486 CancellationToken cancellationToken)
487 {
488 var offset = 0;
489
490 while (offset < buffer.Length)
491 {
492 var read = await stream.ReadAsync(
493 buffer.AsMemory(offset, buffer.Length - offset),
494 cancellationToken).ConfigureAwait(false);
495
496 if (read == 0)
497 {
498 throw new IOException("The remote PLC closed the TCP connection.");
499 }
500
501 offset += read;
502 }
503 }
504
520 private Task CloseCoreAsync()
521 {
522 try
523 {
524 _stream?.Close();
525 _stream?.Dispose();
526
527 _tcpClient?.Close();
528 _tcpClient?.Dispose();
529 }
530 finally
531 {
532 _stream = null;
533 _tcpClient = null;
534 }
535
536 return Task.CompletedTask;
537 }
538
554 private void ThrowIfDisposed()
555 {
556 ObjectDisposedException.ThrowIf(_disposed, GetType().Name);
557 }
558}
async Task< PlcResult< byte[]> > SendAndReceiveAsync(IReadOnlyList< byte > requestFrame, int receiveTimeoutMs, int retryCount, CancellationToken cancellationToken=default)
async Task< PlcResult > DisconnectAsync(CancellationToken cancellationToken=default)
static async Task ReadExactlyAsync(NetworkStream stream, byte[] buffer, CancellationToken cancellationToken)
static async Task< byte[]> ReceiveBinary3EFrameAsync(NetworkStream stream, CancellationToken cancellationToken)
async Task< PlcResult > ConnectAsync(string host, int port, int timeoutMs, CancellationToken cancellationToken=default)