init commit
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user