Dreamine.PLC.Omron.Fins 1.0.1
Dreamine.PLC.Omron.Fins 산업 자동화 및 I/O 기능을 제공합니다.
로딩중...
검색중...
일치하는것 없음
OmronFinsTcpSimulatorServer.cs
이 파일의 문서화 페이지로 가기
1using System.Net;
2using System.Net.Sockets;
3using Dreamine.PLC.Core.Memory;
5
7
16public sealed class OmronFinsTcpSimulatorServer : IAsyncDisposable
17{
35 private readonly InMemoryPlcMemory _memory;
53 private readonly List<TcpClient> _clients = [];
62 private readonly object _syncRoot = new();
71 private TcpListener? _listener;
80 private CancellationTokenSource? _cts;
89 private Task? _acceptTask;
90
116 : this(options, new InMemoryPlcMemory())
117 {
118 }
119
152 public OmronFinsTcpSimulatorServer(OmronFinsSimulatorServerOptions options, InMemoryPlcMemory memory)
153 {
154 _options = options ?? throw new ArgumentNullException(nameof(options));
155 _memory = memory ?? throw new ArgumentNullException(nameof(memory));
157 _protocol.StatusChanged += (_, message) => StatusChanged?.Invoke(this, message);
158 }
159
168 public event EventHandler<string>? StatusChanged;
169
178 public bool IsRunning => _listener is not null;
179
212 public Task StartAsync()
213 {
214 if (IsRunning)
215 {
216 return Task.CompletedTask;
217 }
218
219 var address = ParseAddress(_options.Host);
220
221 _cts = new CancellationTokenSource();
222 _listener = new TcpListener(address, _options.Port);
223 _listener.Start();
225 StatusChanged?.Invoke(this, $"FINS TCP simulator listening on {_options.Host}:{_options.Port}.");
226 return Task.CompletedTask;
227 }
228
245 public async Task StopAsync()
246 {
247 if (!IsRunning)
248 {
249 return;
250 }
251
252 _cts?.Cancel();
253 _listener?.Stop();
254 _listener = null;
255
256 TcpClient[] clients;
257 lock (_syncRoot)
258 {
259 clients = _clients.ToArray();
260 _clients.Clear();
261 }
262
263 foreach (var client in clients)
264 {
265 client.Dispose();
266 }
267
268 if (_acceptTask is not null)
269 {
270 try
271 {
272 await _acceptTask.ConfigureAwait(false);
273 }
274 catch (OperationCanceledException)
275 {
276 }
277 catch (ObjectDisposedException)
278 {
279 }
280 catch (SocketException)
281 {
282 }
283 }
284
285 _cts?.Dispose();
286 _cts = null;
287 _acceptTask = null;
288 StatusChanged?.Invoke(this, "FINS TCP simulator stopped.");
289 }
290
307 public async ValueTask DisposeAsync()
308 {
309 await StopAsync().ConfigureAwait(false);
310 }
311
312
337 private static IPAddress ParseAddress(string host)
338 {
339 if (string.IsNullOrWhiteSpace(host) || host == "0.0.0.0" || host == "*" || host == "+")
340 {
341 return IPAddress.Any;
342 }
343
344 return IPAddress.TryParse(host, out var address) ? address : IPAddress.Any;
345 }
346
379 private async Task AcceptLoopAsync(CancellationToken cancellationToken)
380 {
381 while (!cancellationToken.IsCancellationRequested && _listener is not null)
382 {
383 TcpClient client;
384
385 try
386 {
387 client = await _listener.AcceptTcpClientAsync(cancellationToken).ConfigureAwait(false);
388 }
389 catch (OperationCanceledException)
390 {
391 break;
392 }
393 catch (ObjectDisposedException)
394 {
395 break;
396 }
397 catch (SocketException) when (cancellationToken.IsCancellationRequested)
398 {
399 break;
400 }
401
402 lock (_syncRoot)
403 {
404 _clients.Add(client);
405 }
406
407 _ = Task.Run(() => ClientLoopAsync(client, cancellationToken), cancellationToken);
408 }
409 }
410
443 private async Task ClientLoopAsync(TcpClient client, CancellationToken cancellationToken)
444 {
445 try
446 {
447 using (client)
448 {
449 var stream = client.GetStream();
450 while (!cancellationToken.IsCancellationRequested && client.Connected)
451 {
452 var packet = await ReceiveFinsTcpPacketAsync(stream, cancellationToken).ConfigureAwait(false);
453 var request = OmronFinsTcpPacket.Extract(packet);
454 var response = _protocol.HandleRequest(request);
455 var responsePacket = OmronFinsTcpPacket.Wrap(response);
456 await stream.WriteAsync(responsePacket, cancellationToken).ConfigureAwait(false);
457 await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
458 }
459 }
460 }
461 catch (OperationCanceledException)
462 {
463 }
464 catch (IOException)
465 {
466 }
467 catch (SocketException)
468 {
469 }
470 catch (ObjectDisposedException)
471 {
472 }
473 catch (Exception ex)
474 {
475 StatusChanged?.Invoke(this, $"FINS TCP client error: {ex.Message}");
476 }
477 finally
478 {
479 lock (_syncRoot)
480 {
481 _clients.Remove(client);
482 }
483 }
484 }
485
542 private static async Task<byte[]> ReceiveFinsTcpPacketAsync(NetworkStream stream, CancellationToken cancellationToken)
543 {
544 var header = new byte[16];
545 await ReadExactlyAsync(stream, header, cancellationToken).ConfigureAwait(false);
546 var length = OmronFinsEndian.ReadInt32(header, 4);
547 if (length < 8)
548 {
549 throw new InvalidOperationException($"Invalid FINS/TCP packet length: {length}.");
550 }
551
552 var body = new byte[length - 8];
553 await ReadExactlyAsync(stream, body, cancellationToken).ConfigureAwait(false);
554 var packet = new byte[header.Length + body.Length];
555 Buffer.BlockCopy(header, 0, packet, 0, header.Length);
556 Buffer.BlockCopy(body, 0, packet, header.Length, body.Length);
557 return packet;
558 }
559
616 private static async Task ReadExactlyAsync(NetworkStream stream, byte[] buffer, CancellationToken cancellationToken)
617 {
618 var offset = 0;
619 while (offset < buffer.Length)
620 {
621 var read = await stream.ReadAsync(buffer.AsMemory(offset, buffer.Length - offset), cancellationToken).ConfigureAwait(false);
622 if (read == 0)
623 {
624 throw new IOException("The FINS TCP client closed the connection.");
625 }
626
627 offset += read;
628 }
629 }
630}
static int ReadInt32(ReadOnlySpan< byte > buffer, int offset)
static byte[] Extract(ReadOnlySpan< byte > packet)
static byte[] Wrap(IReadOnlyList< byte > finsFrame)
static async Task< byte[]> ReceiveFinsTcpPacketAsync(NetworkStream stream, CancellationToken cancellationToken)
static async Task ReadExactlyAsync(NetworkStream stream, byte[] buffer, CancellationToken cancellationToken)
async Task ClientLoopAsync(TcpClient client, CancellationToken cancellationToken)
OmronFinsTcpSimulatorServer(OmronFinsSimulatorServerOptions options, InMemoryPlcMemory memory)