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.
436 lines
12 KiB
436 lines
12 KiB
using EC.Util.Common;
|
|
using System.Collections.Concurrent;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
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();
|
|
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();
|
|
}
|
|
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)
|
|
{
|
|
listener.BeginAcceptTcpClient(HandleTcpClientAccepted, listener);
|
|
}
|
|
|
|
private void HandleTcpClientAccepted(IAsyncResult ar)
|
|
{
|
|
if (!IsRunning()) return;
|
|
if (ar.AsyncState is not TcpListener listener) return;
|
|
|
|
TcpClient? client;
|
|
try
|
|
{
|
|
client = listener.EndAcceptTcpClient(ar);
|
|
if (client == null || !client.Connected)
|
|
{
|
|
client?.Client.Disconnect(false);
|
|
return;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
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);
|
|
}
|
|
|
|
private void ReadBuffer(TcpClientState clientState)
|
|
{
|
|
try
|
|
{
|
|
NetworkStream stream = clientState.GetStream;
|
|
if (clientState.IsRead) return;
|
|
lock (clientState.IsReadLock)
|
|
{
|
|
if (clientState.IsRead) return;
|
|
clientState.IsRead = true;
|
|
stream.BeginRead(clientState.Buffer, 0, clientState.Buffer.Length, HandleDatagramReceived, clientState);
|
|
}
|
|
}
|
|
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 (!IsRunning()) return;
|
|
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);
|
|
|
|
lock (clientState.IsReadLock)
|
|
{
|
|
clientState.IsRead = false;
|
|
}
|
|
// continue listening for tcp datagram packets
|
|
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)
|
|
{
|
|
if (!IsRunning()) return;
|
|
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)
|
|
{
|
|
if (!IsRunning()) return;
|
|
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);
|
|
}
|
|
}
|
|
|
|
#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
|
|
}
|