Dreamine.PLC.Mitsubishi.MC 1.0.1
Dreamine.PLC.Mitsubishi.MC 산업 자동화 및 I/O 기능을 제공합니다.
로딩중...
검색중...
일치하는것 없음
MitsubishiMcTcpSimulatorServer.cs
이 파일의 문서화 페이지로 가기
1using System.Net;
2using System.Net.Sockets;
3using Dreamine.PLC.Core.Memory;
4
6
15public sealed class MitsubishiMcTcpSimulatorServer : IAsyncDisposable
16{
43 private readonly List<Task> _clientTasks = [];
52 private readonly object _syncRoot = new();
61 private CancellationTokenSource? _cts;
70 private TcpListener? _listener;
79 private Task? _acceptTask;
80
105 : this(options, new InMemoryPlcMemory())
106 {
107 }
108
140 {
141 _options = options ?? throw new ArgumentNullException(nameof(options));
142 _protocol = new MitsubishiMcBinary3ESimulatorProtocol(memory ?? throw new ArgumentNullException(nameof(memory)), options);
143 _protocol.StatusChanged += OnProtocolStatusChanged;
144 }
145
154 public event EventHandler<string>? StatusChanged;
155
164 public bool IsRunning => _listener is not null;
165
189 public Task StartAsync(CancellationToken cancellationToken = default)
190 {
191 if (_listener is not null)
192 {
193 return Task.CompletedTask;
194 }
195
196 var address = ParseAddress(_options.Host);
197 _listener = new TcpListener(address, _options.Port);
198 _listener.Start();
199 _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
200 _acceptTask = Task.Run(() => AcceptLoopAsync(_cts.Token), CancellationToken.None);
201 StatusChanged?.Invoke(this, $"MC TCP simulator server started. {_options.Host}:{_options.Port}");
202 return Task.CompletedTask;
203 }
204
221 public async Task StopAsync()
222 {
223 if (_listener is null)
224 {
225 return;
226 }
227
228 _cts?.Cancel();
229 _listener.Stop();
230 _listener = null;
231
232 if (_acceptTask is not null)
233 {
234 try
235 {
236 await _acceptTask.ConfigureAwait(false);
237 }
238 catch (OperationCanceledException)
239 {
240 }
241 catch (ObjectDisposedException)
242 {
243 }
244 }
245
246 Task[] clientTasks;
247 lock (_syncRoot)
248 {
249 clientTasks = _clientTasks.ToArray();
250 _clientTasks.Clear();
251 }
252
253 try
254 {
255 await Task.WhenAll(clientTasks).ConfigureAwait(false);
256 }
257 catch
258 {
259 }
260
261 _cts?.Dispose();
262 _cts = null;
263 StatusChanged?.Invoke(this, "MC TCP simulator server stopped.");
264 }
265
281 public async ValueTask DisposeAsync()
282 {
283 _protocol.StatusChanged -= OnProtocolStatusChanged;
284 await StopAsync().ConfigureAwait(false);
285 }
286
309 private async Task AcceptLoopAsync(CancellationToken cancellationToken)
310 {
311 while (!cancellationToken.IsCancellationRequested && _listener is not null)
312 {
313 TcpClient client;
314
315 try
316 {
317 client = await _listener.AcceptTcpClientAsync(cancellationToken).ConfigureAwait(false);
318 }
319 catch (OperationCanceledException)
320 {
321 break;
322 }
323 catch (ObjectDisposedException)
324 {
325 break;
326 }
327
328 var task = Task.Run(() => HandleClientAsync(client, cancellationToken), CancellationToken.None);
329 lock (_syncRoot)
330 {
331 _clientTasks.RemoveAll(static item => item.IsCompleted);
332 _clientTasks.Add(task);
333 }
334 }
335 }
336
366 private async Task HandleClientAsync(TcpClient client, CancellationToken cancellationToken)
367 {
368 using (client)
369 using (var stream = client.GetStream())
370 {
371 StatusChanged?.Invoke(this, "MC TCP simulator client connected.");
372
373 while (!cancellationToken.IsCancellationRequested)
374 {
375 byte[] request;
376 try
377 {
378 request = await ReceiveBinary3EFrameAsync(stream, cancellationToken).ConfigureAwait(false);
379 }
380 catch (OperationCanceledException)
381 {
382 break;
383 }
384 catch (IOException)
385 {
386 break;
387 }
388 catch (ObjectDisposedException)
389 {
390 break;
391 }
392
393 var response = _protocol.Execute(request);
394 await stream.WriteAsync(response, cancellationToken).ConfigureAwait(false);
395 await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
396 }
397 }
398
399 StatusChanged?.Invoke(this, "MC TCP simulator client disconnected.");
400 }
401
445 private static async Task<byte[]> ReceiveBinary3EFrameAsync(NetworkStream stream, CancellationToken cancellationToken)
446 {
447 var header = new byte[9];
448 await ReadExactlyAsync(stream, header, cancellationToken).ConfigureAwait(false);
449
450 var requestDataLength = header[7] | (header[8] << 8);
451 if (requestDataLength < 2)
452 {
453 throw new InvalidOperationException($"Invalid MC request data length: {requestDataLength}");
454 }
455
456 var body = new byte[requestDataLength];
457 await ReadExactlyAsync(stream, body, cancellationToken).ConfigureAwait(false);
458
459 var frame = new byte[header.Length + body.Length];
460 Buffer.BlockCopy(header, 0, frame, 0, header.Length);
461 Buffer.BlockCopy(body, 0, frame, header.Length, body.Length);
462 return frame;
463 }
464
508 private static async Task ReadExactlyAsync(NetworkStream stream, byte[] buffer, CancellationToken cancellationToken)
509 {
510 var offset = 0;
511 while (offset < buffer.Length)
512 {
513 var read = await stream.ReadAsync(buffer.AsMemory(offset, buffer.Length - offset), cancellationToken).ConfigureAwait(false);
514 if (read == 0)
515 {
516 throw new IOException("The MC TCP simulator client closed the connection.");
517 }
518
519 offset += read;
520 }
521 }
522
545 private static IPAddress ParseAddress(string host)
546 {
547 if (string.IsNullOrWhiteSpace(host) || host == "*" || host == "+")
548 {
549 return IPAddress.Any;
550 }
551
552 return IPAddress.TryParse(host, out var address) ? address : IPAddress.Any;
553 }
554
577 private void OnProtocolStatusChanged(object? sender, string e)
578 {
579 StatusChanged?.Invoke(this, e);
580 }
581}
async Task HandleClientAsync(TcpClient client, CancellationToken cancellationToken)
static async Task< byte[]> ReceiveBinary3EFrameAsync(NetworkStream stream, CancellationToken cancellationToken)
static async Task ReadExactlyAsync(NetworkStream stream, byte[] buffer, CancellationToken cancellationToken)
MitsubishiMcTcpSimulatorServer(MitsubishiMcSimulatorServerOptions options, InMemoryPlcMemory memory)