磁导航1.0内部交管和信号交互
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
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<byte[], byte[]> _handler;
|
||||
private readonly object _clientsLock = new object();
|
||||
private readonly HashSet<TcpClient> _clients = new HashSet<TcpClient>();
|
||||
private TcpListener _listener;
|
||||
private CancellationTokenSource _cts;
|
||||
private int _clientCount;
|
||||
private bool _running;
|
||||
|
||||
public MagCarSimTcpServer(IPAddress address, int port, Func<byte[], byte[]> 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<TcpClient> clients;
|
||||
lock (_clientsLock)
|
||||
{
|
||||
clients = new List<TcpClient>(_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user