You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

431 lines
12 KiB

using EC.Util.Common;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
namespace JiLinApp.Docking.VibrateAlarm;
public class AsyncTcpServer : IDisposable
{
#region Fields
private TcpListener Listener { get; set; }
private ConcurrentDictionary<string, TcpClientState> Clients { get; }
#region Properties
/// <summary>
/// 监听的IP地址
/// </summary>
public IPAddress Address { get; private set; }
/// <summary>
/// 监听的端口
/// </summary>
public int Port { get; private set; }
/// <summary>
/// 通信使用的编码
/// </summary>
public Encoding Encoding { get; set; }
public bool Disposed { get; private set; }
#endregion Properties
#endregion Fields
#region Ctors
/// <summary>
/// 异步TCP服务器
/// </summary>
/// <param name="port">监听的端口</param>
public AsyncTcpServer(int port) : this(IPAddress.Any, port)
{
}
/// <summary>
/// 异步TCP服务器
/// </summary>
/// <param name="ipep">监听的终结点</param>
public AsyncTcpServer(IPEndPoint ipep) : this(ipep.Address, ipep.Port)
{
}
/// <summary>
/// 异步TCP服务器
/// </summary>
/// <param name="address">监听的IP地址</param>
/// <param name="port">监听的端口</param>
public AsyncTcpServer(IPAddress address, int port)
{
Address = address;
Port = port;
Encoding = Encoding.Default;
Listener = new(address, port);
Clients = new();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) Listener.AllowNatTraversal(true);
}
~AsyncTcpServer()
{
Stop();
}
#endregion Ctors
#region Server
/// <summary>
/// 启动服务器
/// </summary>
/// <returns>异步TCP服务器</returns>
public void Start()
{
Start(30);
}
/// <summary>
/// 启动服务器
/// </summary>
/// <param name="backlog">服务器所允许的挂起连接序列的最大长度</param>
/// <returns>异步TCP服务器</returns>
public void Start(int backlog)
{
if (IsRunning()) return;
Listener.Start(backlog);
AcceptTcpClient(Listener);
}
/// <summary>
/// 停止服务器
/// </summary>
/// <returns>异步TCP服务器</returns>
public void Stop()
{
if (!IsRunning()) return;
try
{
Listener.Stop();
}
catch
{
}
finally
{
foreach (var client in Clients.Values)
{
client.Client.Client.Disconnect(false);
}
Clients.Clear();
}
}
public bool IsRunning()
{
return Listener != null && Listener.Server.IsBound;
}
public ICollection<TcpClientState> GetAllClient()
{
return Clients.Values;
}
#endregion Server
#region Events
/// <summary>
/// 与客户端的连接已建立事件
/// </summary>
public event EventHandler<TcpClientConnectedEventArgs>? ClientConnected;
/// <summary>
/// 与客户端的连接已断开事件
/// </summary>
public event EventHandler<TcpClientDisconnectedEventArgs>? ClientDisconnected;
/// <summary>
/// 接收到数据报文事件
/// </summary>
public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>>? DatagramReceived;
private void RaiseClientConnected(string clientAddr, TcpClientState clientState)
{
if (string.IsNullOrEmpty(clientAddr) || clientAddr.Equals(":")) return;
Clients.AddOrUpdate(clientAddr, clientState, (n, o) => { return clientState; });
ClientConnected?.Invoke(this, new TcpClientConnectedEventArgs(clientState.Client));
}
private void RaiseClientDisconnected(string clientAddr, TcpClient client)
{
if (string.IsNullOrEmpty(clientAddr) || clientAddr.Equals(":")) return;
client.Client.Disconnect(false);
if (Clients.TryRemove(clientAddr, out _))
{
ClientDisconnected?.Invoke(this, new TcpClientDisconnectedEventArgs(client));
}
}
private void RaiseDatagramReceived(TcpClientState sender, byte[] datagram)
{
DatagramReceived?.Invoke(this, new TcpDatagramReceivedEventArgs<byte[]>(sender, datagram));
}
#endregion Events
#region Receive
private void AcceptTcpClient(TcpListener listener)
{
TaskUtil.RunCatch(() => listener.BeginAcceptTcpClient(HandleTcpClientAccepted, listener));
}
private void HandleTcpClientAccepted(IAsyncResult ar)
{
try
{
if (ar.AsyncState is not TcpListener listener) return;
TcpClient? client = listener.EndAcceptTcpClient(ar);
if (client == null || !client.Connected)
{
client?.Client.Disconnect(false);
return;
}
// add client connection to cache
string clientAddr = client.ClientAddr();
byte[] buffer = new byte[client.ReceiveBufferSize];
TcpClientState clientState = new(client, buffer);
RaiseClientConnected(clientAddr, clientState);
// begin to read data
ReadBuffer(clientState);
// keep listening to accept next connection
AcceptTcpClient(listener);
}
catch (Exception)
{
}
}
private void ReadBuffer(TcpClientState clientState)
{
try
{
NetworkStream stream = clientState.GetStream;
int tryTime = 3, tryInterval = 200;
bool sleepFlag = false;
for (int i = 0; i < tryTime; i++)
{
if (sleepFlag) Thread.Sleep(tryInterval);
lock (clientState.IsReadLock)
{
sleepFlag = clientState.IsRead;
if (sleepFlag) continue;
clientState.IsRead = true;
stream.BeginRead(clientState.Buffer, 0, clientState.Buffer.Length, HandleDatagramReceived, clientState);
break;
}
}
}
catch (IOException e)
{
LogUnit.Error(e);
return;
}
catch (Exception e)
{
LogUnit.Error(e);
string clientAddr = clientState.Client.ClientAddr();
RaiseClientDisconnected(clientAddr, clientState.Client);
}
}
private void HandleDatagramReceived(IAsyncResult ar)
{
if (ar.AsyncState is not TcpClientState clientState) return;
int readNum;
string clientAddr = clientState.Client.ClientAddr();
try
{
NetworkStream networkStream = clientState.GetStream;
// if the remote host has shutdown its connection, read will immediately return with zero bytes.
readNum = networkStream.EndRead(ar);
if (readNum == 0)
{
RaiseClientDisconnected(clientAddr, clientState.Client);
return;
}
}
catch (Exception)
{
RaiseClientDisconnected(clientAddr, clientState.Client);
return;
}
// received byte and trigger event notification
byte[] receivedBytes = new byte[readNum];
Buffer.BlockCopy(clientState.Buffer, 0, receivedBytes, 0, readNum);
RaiseDatagramReceived(clientState, receivedBytes);
// continue listening for tcp datagram packets
lock (clientState.IsReadLock) clientState.IsRead = false;
ReadBuffer(clientState);
}
#endregion Receive
#region Send
/// <summary>
/// 发送报文至指定的客户端
/// </summary>
/// <param name="client">客户端</param>
/// <param name="datagram">报文</param>
public void Send(TcpClient client, byte[] datagram)
{
if (!IsRunning()) return;
if (client == null || datagram == null) return;
try
{
NetworkStream stream = client.GetStream();
stream.Write(datagram, 0, datagram.Length);
}
catch (Exception)
{
string clientAddr = client.ClientAddr();
RaiseClientDisconnected(clientAddr, client);
}
}
/// <summary>
/// 发送报文至指定的客户端
/// </summary>
/// <param name="client">客户端</param>
/// <param name="datagram">报文</param>
public void Send(TcpClient client, string datagram)
{
Send(client, Encoding.GetBytes(datagram));
}
/// <summary>
/// 发送报文至所有客户端
/// </summary>
/// <param name="datagram">报文</param>
public void SendToAll(byte[] datagram)
{
if (!IsRunning()) return;
foreach (var client in Clients.Values) Send(client.Client, datagram);
}
/// <summary>
/// 发送报文至所有客户端
/// </summary>
/// <param name="datagram">报文</param>
public void SendToAll(string datagram)
{
SendToAll(Encoding.GetBytes(datagram));
}
/// <summary>
/// 发送报文至指定的客户端
/// </summary>
/// <param name="client">客户端</param>
/// <param name="datagram">报文</param>
public bool SendAsync(TcpClient client, byte[] datagram)
{
if (!IsRunning()) return false;
if (client == null || datagram == null) return false;
try
{
NetworkStream stream = client.GetStream();
IAsyncResult result = stream.BeginWrite(datagram, 0, datagram.Length, HandleDatagramWritten, client);
return result.IsCompleted;
}
catch (Exception)
{
string clientAddr = client.ClientAddr();
RaiseClientDisconnected(clientAddr, client);
}
return false;
}
/// <summary>
/// 发送报文至指定的客户端
/// </summary>
/// <param name="client">客户端</param>
/// <param name="datagram">报文</param>
public bool SendAsync(TcpClient client, string datagram)
{
return SendAsync(client, Encoding.GetBytes(datagram));
}
/// <summary>
/// 发送报文至所有客户端
/// </summary>
/// <param name="datagram">报文</param>
public void SendToAllAsync(byte[] datagram)
{
if (!IsRunning()) return;
foreach (var client in Clients.Values) SendAsync(client.Client, datagram);
}
/// <summary>
/// 发送报文至所有客户端
/// </summary>
/// <param name="datagram">报文</param>
public void SendToAllAsync(string datagram)
{
SendToAllAsync(Encoding.GetBytes(datagram));
}
private void HandleDatagramWritten(IAsyncResult ar)
{
if (ar.AsyncState is not TcpClient client) return;
try
{
client.GetStream().EndWrite(ar);
}
catch (Exception)
{
string clientAddr = client.ClientAddr();
RaiseClientDisconnected(clientAddr, client);
return;
}
}
#endregion Send
#region IDisposable Members
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources;
/// <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (!Disposed)
{
if (disposing) Stop();
Disposed = true;
}
}
#endregion IDisposable Members
}