using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; namespace StandardScene.MagCarSimulator { public sealed class MagCarSimTcpServer : IDisposable { private readonly IPAddress _address; private readonly int _port; private readonly Func _handler; private readonly object _clientsLock = new object(); private readonly HashSet _clients = new HashSet(); private TcpListener _listener; private CancellationTokenSource _cts; private int _clientCount; private bool _running; public MagCarSimTcpServer(IPAddress address, int port, Func handler) { _address = address ?? IPAddress.Any; _port = port; _handler = handler ?? throw new ArgumentNullException(nameof(handler)); } public bool IsRunning => _running; public int ClientCount => Volatile.Read(ref _clientCount); public void Start() { if (_running) { return; } _cts = new CancellationTokenSource(); _listener = new TcpListener(_address, _port); _listener.Server.NoDelay = true; _listener.Start(); _running = true; _ = Task.Run(() => AcceptLoop(_cts.Token)); } public void Dispose() { _running = false; try { _cts?.Cancel(); } catch { } try { _listener?.Stop(); } catch { } List clients; lock (_clientsLock) { clients = new List(_clients); _clients.Clear(); } foreach (var client in clients) { try { client.Close(); } catch { } } _cts?.Dispose(); _cts = null; _listener = null; } private async Task AcceptLoop(CancellationToken token) { while (!token.IsCancellationRequested) { TcpClient client; try { client = await _listener.AcceptTcpClientAsync(token).ConfigureAwait(false); } catch (OperationCanceledException) { return; } catch (ObjectDisposedException) { return; } catch (SocketException) { if (token.IsCancellationRequested) { return; } continue; } lock (_clientsLock) { _clients.Add(client); } _ = Task.Run(() => HandleClient(client, token), token); } } private void HandleClient(TcpClient client, CancellationToken token) { Interlocked.Increment(ref _clientCount); try { client.NoDelay = true; client.ReceiveTimeout = 0; using (var stream = client.GetStream()) { var buffer = new byte[MagCarSimProtocol.FrameLength]; while (!token.IsCancellationRequested) { if (!ReadExact(stream, buffer, token)) { return; } byte[] response; try { response = _handler(buffer); } catch (Exception ex) { MagCarSimLog.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] TCP 处理异常: {ex.Message}"); return; } if (response == null || response.Length != MagCarSimProtocol.FrameLength) { continue; } stream.Write(response, 0, response.Length); stream.Flush(); } } } catch (Exception ex) when (IsBenign(ex)) { } catch (Exception ex) { MagCarSimLog.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] TCP 连接异常: {ex.Message}"); } finally { lock (_clientsLock) { _clients.Remove(client); } try { client.Dispose(); } catch { } Interlocked.Decrement(ref _clientCount); } } private static bool ReadExact(NetworkStream stream, byte[] buffer, CancellationToken token) { var offset = 0; while (offset < buffer.Length) { token.ThrowIfCancellationRequested(); int read; try { read = stream.Read(buffer, offset, buffer.Length - offset); } catch (Exception ex) when (IsBenign(ex)) { return false; } if (read == 0) { return false; } offset += read; } return true; } private static bool IsBenign(Exception ex) { for (var current = ex; current != null; current = current.InnerException) { if (current is ObjectDisposedException || current is OperationCanceledException) { return true; } if (current is SocketException socketEx) { switch (socketEx.SocketErrorCode) { case SocketError.OperationAborted: case SocketError.Interrupted: case SocketError.ConnectionAborted: case SocketError.ConnectionReset: case SocketError.Shutdown: return true; } } } return false; } } }