init commit

This commit is contained in:
zhaowei.huang
2026-06-14 11:19:15 +08:00
parent e79a3815a5
commit c8e540d272
174 changed files with 60830 additions and 39 deletions
+502
View File
@@ -0,0 +1,502 @@
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
{
/// <summary>
/// 异步 TCP 客户端
/// </summary>
public class AsyncTcpClient : IDisposable
{
private sealed class DatagramReadState
{
public TcpClient Client { get; set; }
public byte[] Buffer { get; set; }
}
public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>> DatagramReceived; // 接收到数据报文事件
public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>> PlaintextReceived; // 接收到数据报文明文事件
public event EventHandler<TcpServerConnectedEventArgs> ServerConnected; // 与服务器的连接已建立事件
public event EventHandler<TcpServerDisconnectedEventArgs> ServerDisconnected; // 与服务器的连接已断开事件
public event EventHandler<TcpServerExceptionOccurredEventArgs> 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;
}
/// <summary>
/// 是否已与服务器建立连接
/// </summary>
public bool Connected
{
get
{
try
{
return this.tcpClient != null && this.tcpClient.Connected;
}
catch
{
return false;
}
}
}
/// <summary>
/// 远端服务器的IP地址列表
/// </summary>
public IPAddress Addresses { get; private set; }
/// <summary>
/// 远端服务器的端口
/// </summary>
public int Port { get; private set; }
/// <summary>
/// 连接重试次数
/// </summary>
public int Retries { get; set; }
/// <summary>
/// 连接重试间隔
/// </summary>
public int RetryInterval { get; set; }
/// <summary>
/// 远端服务器终结点
/// </summary>
public IPEndPoint RemoteIPEndPoint
{
get
{
return new IPEndPoint(this.Addresses, this.Port);
}
}
/// <summary>
/// 通信所使用的编码
/// </summary>
public Encoding Encoding { get; set; }
uint on = 1;
/// <summary>
/// 连接到服务器
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// 关闭与服务器的连接
/// </summary>
/// <returns>异步TCP客户端</returns>
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<byte[]>(sender, datagram));
}
}
private void RaisePlaintextReceived(TcpClient sender, byte[] datagram)
{
if (this.PlaintextReceived != null)
{
//this.PlaintextReceived(this, new TcpDatagramReceivedEventArgs<string>(sender, this.Encoding.GetString(datagram, 0, datagram.Length)));
this.PlaintextReceived(this, new TcpDatagramReceivedEventArgs<byte[]>(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));
}
}
/// <summary>
/// 发送报文
/// </summary>
/// <param name="datagram"></param>
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));
}
/// <summary>
/// 释放非托管资源
/// </summary>
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)
{
}
}
}
}
}
}
@@ -0,0 +1,22 @@
using System;
using System.Net.Sockets;
namespace StandardScene.TCP
{
/// <summary>
/// 接收到数据报文事件
/// </summary>
/// <typeparam name="T"></typeparam>
public class TcpDatagramReceivedEventArgs<T> : EventArgs
{
public TcpDatagramReceivedEventArgs(TcpClient tcpClient, T datagram)
{
this.TcpClient = tcpClient;
this.Datagram = datagram;
}
public TcpClient TcpClient { get; private set; }
public T Datagram { get; private set; }
}
}
@@ -0,0 +1,31 @@
using System;
using System.Globalization;
using System.Net;
namespace StandardScene.TCP
{
/// <summary>
/// 与服务器的连接已建立事件
/// </summary>
public class TcpServerConnectedEventArgs : EventArgs
{
public TcpServerConnectedEventArgs(IPAddress ipAddress, int port)
{
if (ipAddress == null)
{
throw new ArgumentNullException("ipAddress");
}
this.Address = ipAddress;
this.Port = port;
}
public IPAddress Address { get; private set; }
public int Port { get; private set; }
public override string ToString()
{
return this.Address + ":" + this.Port.ToString(CultureInfo.InvariantCulture);
}
}
}
@@ -0,0 +1,31 @@
using System;
using System.Globalization;
using System.Net;
namespace StandardScene.TCP
{
/// <summary>
/// 与服务器的连接已断开事件
/// </summary>
public class TcpServerDisconnectedEventArgs : EventArgs
{
public TcpServerDisconnectedEventArgs(IPAddress ipAddress, int port)
{
if (ipAddress == null)
{
throw new ArgumentNullException("ipAddress");
}
this.Address = ipAddress;
this.Port = port;
}
public IPAddress Address { get; private set; }
public int Port { get; private set; }
public override string ToString()
{
return this.Address + ":" + this.Port.ToString(CultureInfo.InvariantCulture);
}
}
}
@@ -0,0 +1,34 @@
using System;
using System.Globalization;
using System.Net;
namespace StandardScene.TCP
{
/// <summary>
/// 与服务器的连接发生异常事件
/// </summary>
public class TcpServerExceptionOccurredEventArgs : EventArgs
{
public TcpServerExceptionOccurredEventArgs(IPAddress ipAddresses, int port, Exception innerException)
{
if (ipAddresses == null)
{
throw new ArgumentNullException("ipAddress");
}
this.Address = ipAddresses;
this.Port = port;
this.Exception = innerException;
}
public IPAddress Address { get; private set; }
public int Port { get; private set; }
public Exception Exception { get; private set; }
public override string ToString()
{
return this.Address + ":" + this.Port.ToString(CultureInfo.InvariantCulture);
}
}
}