using SimpleCore.Library; using System; using System.Diagnostics; using System.Globalization; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Text; using System.Threading; using static System.Windows.Forms.VisualStyles.VisualStyleElement.ToolTip; namespace StandardScene.TCP { /// /// 异步 TCP 客户端 /// public class AsyncTcpClient : IDisposable { private sealed class DatagramReadState { public TcpClient Client { get; set; } public byte[] Buffer { get; set; } } public event EventHandler> DatagramReceived; // 接收到数据报文事件 public event EventHandler> PlaintextReceived; // 接收到数据报文明文事件 public event EventHandler ServerConnected; // 与服务器的连接已建立事件 public event EventHandler ServerDisconnected; // 与服务器的连接已断开事件 public event EventHandler ServerExceptionOccurred; // 与服务器的连接发生异常事件 private TcpClient tcpClient; private bool disposed = false; private int retries = 0; // 重连计数 private readonly object _reconnectGate = new object(); private bool _closing = false; private bool _isConnecting = false; private bool _isReconnecting = false; private Timer _reconnectTimer; public AsyncTcpClient(IPAddress remoteIPAddress, int remotePort) { this.Addresses = remoteIPAddress; this.Port = remotePort; this.Encoding = Encoding.Default; this.Retries = 3; this.RetryInterval = 5; } /// /// 是否已与服务器建立连接 /// public bool Connected { get { try { return this.tcpClient != null && this.tcpClient.Connected; } catch { return false; } } } /// /// 远端服务器的IP地址列表 /// public IPAddress Addresses { get; private set; } /// /// 远端服务器的端口 /// public int Port { get; private set; } /// /// 连接重试次数 /// public int Retries { get; set; } /// /// 连接重试间隔 /// public int RetryInterval { get; set; } /// /// 远端服务器终结点 /// public IPEndPoint RemoteIPEndPoint { get { return new IPEndPoint(this.Addresses, this.Port); } } /// /// 通信所使用的编码 /// public Encoding Encoding { get; set; } uint on = 1; /// /// 连接到服务器 /// /// public AsyncTcpClient Connect() { lock (_reconnectGate) { _closing = false; } if (this.Connected) { return this; } this.ConnectInternal(resetRetries: true); return this; } private void ConnectInternal(bool resetRetries) { TcpClient client; lock (_reconnectGate) { if (_closing || disposed) { return; } // Prevent multiple in-flight connection attempts. if (_isConnecting || _isReconnecting) { return; } _isConnecting = true; if (resetRetries) { retries = 0; } client = new TcpClient(); this.tcpClient = client; } try { client.Client.IOControl(IOControlCode.KeepAliveValues, KeepAlive(1, 500, 500), null); client.BeginConnect(this.Addresses, this.Port, new AsyncCallback(this.HandleTcpServerConnected), client); } catch { try { client.Close(); } catch { } lock (_reconnectGate) { _isConnecting = false; } if (!_closing && !disposed) { ScheduleReconnect("connect begin failed"); } } } private void HandleRemoteDisconnect(TcpClient client, string reason) { if (!ReferenceEquals(client, this.tcpClient)) { return; } try { client.Close(); } catch { } this.RaiseServerDisconnected(this.Addresses, this.Port); if (!_closing && !disposed) { ScheduleReconnect(reason); } } private void ScheduleReconnect(string reason) { lock (_reconnectGate) { if (_closing || disposed) { return; } if (this.Connected) { return; } if (_isConnecting || _isReconnecting) { return; } // Keep reconnecting until the client is closed/disposed. // Preserve the counter only for logging (avoid int overflow by wrapping). if (retries == int.MaxValue) { retries = 0; } retries++; _isReconnecting = true; if (_reconnectTimer != null) { try { _reconnectTimer.Dispose(); } catch { } _reconnectTimer = null; } Diagnosis.Post($"[AsyncTcpClient] schedule reconnect attempt {retries}/{this.Retries} in {this.RetryInterval}s. Reason={reason}"); Timer t = null; t = new Timer(_ => { try { lock (_reconnectGate) { _isReconnecting = false; _reconnectTimer = null; } this.ConnectInternal(resetRetries: false); } catch { } finally { try { t.Dispose(); } catch { } } }, null, TimeSpan.FromSeconds((double)this.RetryInterval), Timeout.InfiniteTimeSpan); _reconnectTimer = t; } } private byte[] KeepAlive(int onOff, int keepAliveTime, int keepAliveInterval) { byte[] buffer = new byte[12]; BitConverter.GetBytes(onOff).CopyTo(buffer, 0); BitConverter.GetBytes(keepAliveTime).CopyTo(buffer, 4); BitConverter.GetBytes(keepAliveInterval).CopyTo(buffer, 8); return buffer; } /// /// 关闭与服务器的连接 /// /// 异步TCP客户端 public AsyncTcpClient Close() { TcpClient clientToClose = null; bool wasConnected = false; lock (_reconnectGate) { _closing = true; retries = 0; _isConnecting = false; _isReconnecting = false; if (_reconnectTimer != null) { try { _reconnectTimer.Dispose(); } catch { } _reconnectTimer = null; } clientToClose = this.tcpClient; wasConnected = clientToClose != null && clientToClose.Connected; this.tcpClient = null; } if (clientToClose != null) { try { clientToClose.Close(); } catch { } } if (wasConnected) { this.RaiseServerDisconnected(this.Addresses, this.Port); } return this; } private void HandleTcpServerConnected(IAsyncResult ar) { TcpClient client = (TcpClient)ar.AsyncState; try { if (!ReferenceEquals(client, this.tcpClient)) { // Stale connect callback for an old TcpClient instance. try { client.Close(); } catch { } return; } client.EndConnect(ar); this.RaiseServerConnected(this.Addresses, this.Port); lock (_reconnectGate) { this.retries = 0; _isConnecting = false; _isReconnecting = false; if (_reconnectTimer != null) { try { _reconnectTimer.Dispose(); } catch { } _reconnectTimer = null; } } byte[] buffer = new byte[client.ReceiveBufferSize]; var state = new DatagramReadState { Client = client, Buffer = buffer }; client.GetStream().BeginRead(buffer, 0, buffer.Length, new AsyncCallback(this.HandleDatagramReceived), state); } catch (Exception ex) { if (!ReferenceEquals(client, this.tcpClient)) { return; } lock (_reconnectGate) { _isConnecting = false; } if (!_closing && !disposed) { Diagnosis.Post($" HandleTcpServerConnected {this.Addresses} {this.Port} 连接断开,尝试重连..."); ScheduleReconnect("connect failed"); } } } private void HandleDatagramReceived(IAsyncResult ar) { DatagramReadState state = null; TcpClient client = null; byte[] buffer = null; try { state = (DatagramReadState)ar.AsyncState; client = state.Client; buffer = state.Buffer; if (!ReferenceEquals(client, this.tcpClient)) { // Stale read callback for an old TcpClient instance. return; } NetworkStream stream = client.GetStream(); int numberOfReadBytes = 0; try { numberOfReadBytes = stream.EndRead(ar); } catch { numberOfReadBytes = 0; } if (numberOfReadBytes == 0) { HandleRemoteDisconnect(client, "zero-byte read"); return; } byte[] receivedBytes = new byte[numberOfReadBytes]; Buffer.BlockCopy(buffer, 0, receivedBytes, 0, numberOfReadBytes); this.RaiseDatagramReceived(client, receivedBytes); this.RaisePlaintextReceived(client, receivedBytes); // then start reading from the network again stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(this.HandleDatagramReceived), state); } catch (Exception ex) { Trace.WriteLine(ex.Message); if (client != null && ReferenceEquals(client, this.tcpClient) && !_closing && !disposed) { HandleRemoteDisconnect(client, "read failed"); } } } private void RaiseDatagramReceived(TcpClient sender, byte[] datagram) { if (this.DatagramReceived != null) { this.DatagramReceived(this, new TcpDatagramReceivedEventArgs(sender, datagram)); } } private void RaisePlaintextReceived(TcpClient sender, byte[] datagram) { if (this.PlaintextReceived != null) { //this.PlaintextReceived(this, new TcpDatagramReceivedEventArgs(sender, this.Encoding.GetString(datagram, 0, datagram.Length))); this.PlaintextReceived(this, new TcpDatagramReceivedEventArgs(sender, datagram)); } } private void RaiseServerConnected(IPAddress ipAddresses, int port) { if (this.ServerConnected != null) { this.ServerConnected(this, new TcpServerConnectedEventArgs(ipAddresses, port)); } } private void RaiseServerDisconnected(IPAddress ipAddresses, int port) { if (this.ServerDisconnected != null) { this.ServerDisconnected(this, new TcpServerDisconnectedEventArgs(ipAddresses, port)); } } private void RaiseServerExceptionOccurred(IPAddress ipAddresses, int port, Exception innerException) { if (this.ServerExceptionOccurred != null) { this.ServerExceptionOccurred(this, new TcpServerExceptionOccurredEventArgs(ipAddresses, port, innerException)); } } /// /// 发送报文 /// /// public void Send(byte[] datagram) { if (datagram == null) { throw new ArgumentNullException("datagram"); } if (!this.Connected) { this.RaiseServerDisconnected(this.Addresses, this.Port); throw new InvalidProgramException("This client has not connected to server."); } this.tcpClient.GetStream().BeginWrite(datagram, 0, datagram.Length, new AsyncCallback(this.HandleDatagramWritten), this.tcpClient); } private void HandleDatagramWritten(IAsyncResult ar) { ((TcpClient)ar.AsyncState).GetStream().EndWrite(ar); } public void Send(string datagram) { this.Send(this.Encoding.GetBytes(datagram)); } /// /// 释放非托管资源 /// public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!this.disposed) { this.disposed = true; if (disposing) { try { this.Close(); } catch// (SocketException ex) { } } } } } }