| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- using System;
- using System.IO;
- using System.Net;
- using System.Net.Sockets;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using TeamAAS.Robot.Enums;
- using TeamAAS.Communication.Enums;
- namespace TeamAAS.Robot.Core
- {
- /// <summary>
- /// 机器人 TCP 通讯封装类。
- /// 支持 Client/Server 双模式、结束符分割、请求-响应、断线重连。
- /// </summary>
- public class RobotTcpClient : IDisposable
- {
- private readonly string _ip;
- private readonly int _port;
- private readonly TCPConnectType _connectType;
- private readonly Terminator _terminator;
- private readonly DataEncoding _dataEncoding;
- private TcpClient _tcpClient;
- private TcpListener _tcpListener;
- private TcpClient _serverSession;
- private NetworkStream _serverStream;
- private readonly AutoResetEvent _receiveEvent = new AutoResetEvent(false);
- private byte[] _receivedData;
- private readonly object _sendLock = new object();
- private readonly object _sessionLock = new object();
- private Task _reconnectTask;
- private CancellationTokenSource _reconnectCts;
- private bool _isDisposed;
- private string _terminatorString;
- private byte[] _terminatorBytes;
- public bool IsConnected
- {
- get
- {
- if (_connectType == TCPConnectType.Client)
- return _tcpClient?.Connected ?? false;
- else
- return _serverSession?.Connected ?? false;
- }
- }
- public RobotTcpClient(
- string ip,
- int port,
- TCPConnectType connectType,
- Terminator terminator,
- DataEncoding dataEncoding)
- {
- _ip = ip;
- _port = port;
- _connectType = connectType;
- _terminator = terminator;
- _dataEncoding = dataEncoding;
- _terminatorString = terminator switch
- {
- Terminator.CR => "\r",
- Terminator.LF => "\n",
- Terminator.CRLF => "\r\n",
- _ => "\r\n"
- };
- _terminatorBytes = GetEncoding().GetBytes(_terminatorString);
- }
- public void Connect()
- {
- if (_connectType == TCPConnectType.Client)
- ConnectClient();
- else
- ConnectServer();
- }
- public async Task ConnectAsync()
- {
- if (_connectType == TCPConnectType.Client)
- {
- await ConnectClientAsync();
- }
- else
- {
- ConnectServer();
- }
- }
- private void ConnectClient()
- {
- _tcpClient = new TcpClient();
- _tcpClient.Connect(IPAddress.Parse(_ip), _port);
- StartReconnectLoop();
- StartReceiveLoop();
- OnConnected?.Invoke(null);
- }
- private async Task ConnectClientAsync()
- {
- _tcpClient = new TcpClient();
- await _tcpClient.ConnectAsync(IPAddress.Parse(_ip), _port);
- StartReconnectLoop();
- StartReceiveLoop();
- OnConnected?.Invoke(null);
- }
- private void ConnectServer()
- {
- _tcpListener = new TcpListener(IPAddress.Any, _port);
- _tcpListener.Start();
- AcceptClientLoop();
- }
- private async void AcceptClientLoop()
- {
- while (!_isDisposed && _tcpListener != null)
- {
- try
- {
- var client = await _tcpListener.AcceptTcpClientAsync();
- lock (_sessionLock)
- {
- _serverSession = client;
- _serverStream = client.GetStream();
- }
- StartServerReceiveLoop(client);
- OnConnected?.Invoke(null);
- }
- catch (ObjectDisposedException) { break; }
- catch (Exception)
- {
- if (!_isDisposed)
- await Task.Delay(1000);
- }
- }
- }
- private void StartReceiveLoop()
- {
- var stream = _tcpClient.GetStream();
- Task.Run(() => ReceiveDataLoop(stream));
- }
- private void StartServerReceiveLoop(TcpClient client)
- {
- var stream = client.GetStream();
- Task.Run(() => ReceiveDataLoop(stream));
- }
- private async void ReceiveDataLoop(NetworkStream stream)
- {
- byte[] buffer = new byte[4096];
- MemoryStream ms = new MemoryStream();
- while (!_isDisposed)
- {
- try
- {
- int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
- if (bytesRead == 0) break;
- ms.Write(buffer, 0, bytesRead);
- while (ContainsTerminator(ms))
- {
- byte[] message = ExtractMessage(ms);
- ProcessReceivedData(message);
- }
- }
- catch (IOException)
- {
- break;
- }
- catch (Exception)
- {
- if (!_isDisposed)
- await Task.Delay(100);
- else
- break;
- }
- }
- if (_connectType == TCPConnectType.Client)
- {
- OnDisconnected?.Invoke(null);
- }
- else
- {
- lock (_sessionLock)
- {
- if (_serverSession != null && _serverSession == GetCurrentSession())
- {
- _serverSession = null;
- _serverStream = null;
- }
- }
- OnDisconnected?.Invoke(null);
- }
- }
- private TcpClient GetCurrentSession()
- {
- return _serverSession;
- }
- private bool ContainsTerminator(MemoryStream ms)
- {
- if (ms.Length < _terminatorBytes.Length) return false;
- byte[] data = ms.ToArray();
- for (int i = 0; i <= data.Length - _terminatorBytes.Length; i++)
- {
- bool match = true;
- for (int j = 0; j < _terminatorBytes.Length; j++)
- {
- if (data[i + j] != _terminatorBytes[j])
- {
- match = false;
- break;
- }
- }
- if (match) return true;
- }
- return false;
- }
- private byte[] ExtractMessage(MemoryStream ms)
- {
- byte[] data = ms.ToArray();
- int endIndex = -1;
- for (int i = 0; i <= data.Length - _terminatorBytes.Length; i++)
- {
- bool match = true;
- for (int j = 0; j < _terminatorBytes.Length; j++)
- {
- if (data[i + j] != _terminatorBytes[j])
- {
- match = false;
- break;
- }
- }
- if (match)
- {
- endIndex = i + _terminatorBytes.Length;
- break;
- }
- }
- byte[] message;
- if (endIndex > 0)
- {
- message = new byte[endIndex];
- Array.Copy(data, message, endIndex);
- ms.Position = 0;
- ms.SetLength(0);
- if (endIndex < data.Length)
- {
- ms.Write(data, endIndex, data.Length - endIndex);
- }
- }
- else
- {
- message = data;
- ms.Position = 0;
- ms.SetLength(0);
- }
- return message;
- }
- private void ProcessReceivedData(byte[] data)
- {
- _receivedData = data;
- OnReceived?.Invoke(data);
- _receiveEvent.Set();
- }
- private void StartReconnectLoop()
- {
- _reconnectCts = new CancellationTokenSource();
- _reconnectTask = Task.Run(async () =>
- {
- while (!_reconnectCts.Token.IsCancellationRequested)
- {
- await Task.Delay(1000, _reconnectCts.Token);
- if (_reconnectCts.Token.IsCancellationRequested) break;
- if (_connectType == TCPConnectType.Client && !IsConnected)
- {
- try
- {
- _tcpClient?.Close();
- _tcpClient = new TcpClient();
- _tcpClient.Connect(IPAddress.Parse(_ip), _port);
- StartReceiveLoop();
- }
- catch { }
- }
- }
- });
- }
- public void Disconnect()
- {
- _isDisposed = true;
- _reconnectCts?.Cancel();
- _tcpClient?.Close();
- _tcpListener?.Stop();
- _serverSession?.Close();
- }
- public string SendAndReceive(string message, int timeoutMs = 5000)
- {
- byte[] data = SendAndReceiveBytes(GetEncoding().GetBytes(message + _terminatorString), timeoutMs);
- return GetEncoding().GetString(data).TrimEnd('\r', '\n');
- }
- public async Task<string> SendAndReceiveAsync(string message, int timeoutMs = 5000)
- {
- byte[] data = await SendAndReceiveBytesAsync(GetEncoding().GetBytes(message + _terminatorString), timeoutMs);
- return GetEncoding().GetString(data).TrimEnd('\r', '\n');
- }
- private byte[] SendAndReceiveBytes(byte[] data, int timeoutMs)
- {
- lock (_sendLock)
- {
- SendBytesInternal(data);
- OnSent?.Invoke(GetEncoding().GetString(data));
- if (!_receiveEvent.WaitOne(timeoutMs))
- throw new TimeoutException($"等待机器人响应超时({timeoutMs}ms):已发送 \"{GetEncoding().GetString(data).Trim()}\"");
- return _receivedData ?? Array.Empty<byte>();
- }
- }
- private async Task<byte[]> SendAndReceiveBytesAsync(byte[] data, int timeoutMs)
- {
- await Task.Run(() =>
- {
- lock (_sendLock)
- {
- SendBytesInternal(data);
- OnSent?.Invoke(GetEncoding().GetString(data));
- if (!_receiveEvent.WaitOne(timeoutMs))
- throw new TimeoutException($"等待机器人响应超时({timeoutMs}ms):已发送 \"{GetEncoding().GetString(data).Trim()}\"");
- }
- });
- return _receivedData ?? Array.Empty<byte>();
- }
- public void Send(string message)
- {
- byte[] data = GetEncoding().GetBytes(message + _terminatorString);
- SendBytesInternal(data);
- OnSent?.Invoke(message);
- }
- public async Task SendAsync(string message)
- {
- byte[] data = GetEncoding().GetBytes(message + _terminatorString);
- await SendBytesInternalAsync(data);
- OnSent?.Invoke(message);
- }
- private void SendBytesInternal(byte[] data)
- {
- if (_connectType == TCPConnectType.Client)
- {
- if (_tcpClient != null && _tcpClient.Connected)
- {
- var stream = _tcpClient.GetStream();
- stream.Write(data, 0, data.Length);
- stream.Flush();
- }
- }
- else
- {
- lock (_sessionLock)
- {
- if (_serverStream != null && _serverSession != null && _serverSession.Connected)
- {
- _serverStream.Write(data, 0, data.Length);
- _serverStream.Flush();
- }
- }
- }
- }
- private async Task SendBytesInternalAsync(byte[] data)
- {
- if (_connectType == TCPConnectType.Client)
- {
- if (_tcpClient != null && _tcpClient.Connected)
- {
- var stream = _tcpClient.GetStream();
- await stream.WriteAsync(data, 0, data.Length);
- await stream.FlushAsync();
- }
- }
- else
- {
- lock (_sessionLock)
- {
- if (_serverStream != null && _serverSession != null && _serverSession.Connected)
- {
- _serverStream.Write(data, 0, data.Length);
- _serverStream.Flush();
- }
- }
- }
- }
- public Encoding GetEncoding()
- {
- return _dataEncoding switch
- {
- DataEncoding.ASCII => Encoding.ASCII,
- DataEncoding.UTF7 => Encoding.UTF7,
- DataEncoding.UTF8 => Encoding.UTF8,
- DataEncoding.UTF32 => Encoding.UTF32,
- DataEncoding.Unicode => Encoding.Unicode,
- _ => Encoding.Default
- };
- }
- public void Dispose()
- {
- Disconnect();
- _tcpClient?.Dispose();
- _serverSession?.Dispose();
- _serverStream?.Dispose();
- _receiveEvent.Dispose();
- _reconnectCts?.Dispose();
- }
- public event Action<byte[]> OnConnected;
- public event Action<byte[]> OnDisconnected;
- public event Action<byte[]> OnReceived;
- public event Action<string> OnSent;
- }
- }
|