|
|
|
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
|
|
|
|
|
|
|
|
private byte[] InOptionValues { get; set; }
|
|
|
|
|
|
|
|
/// <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)
|
|
|
|
{
|
|
|
|
uint dummy = 0;
|
|
|
|
InOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
|
|
|
|
BitConverter.GetBytes((uint)1).CopyTo(InOptionValues, 0);
|
|
|
|
BitConverter.GetBytes((uint)1000).CopyTo(InOptionValues, Marshal.SizeOf(dummy));
|
|
|
|
BitConverter.GetBytes((uint)1000).CopyTo(InOptionValues, Marshal.SizeOf(dummy) * 2);
|
|
|
|
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.TcpClient.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(TcpClient client)
|
|
|
|
{
|
|
|
|
ClientConnected?.Invoke(this, new TcpClientConnectedEventArgs(client));
|
|
|
|
}
|
|
|
|
|
|
|
|
private void RaiseClientDisconnected(TcpClient client)
|
|
|
|
{
|
|
|
|
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 ReadBuffer(TcpClientState internalClient, NetworkStream networkStream)
|
|
|
|
{
|
|
|
|
networkStream.BeginRead(internalClient.Buffer, 0, internalClient.Buffer.Length, HandleDatagramReceived, internalClient);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void HandleTcpClientAccepted(IAsyncResult ar)
|
|
|
|
{
|
|
|
|
if (!IsRunning()) return;
|
|
|
|
TcpListener? listener;
|
|
|
|
TcpClient? client;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
listener = ar.AsyncState as TcpListener;
|
|
|
|
client = listener?.EndAcceptTcpClient(ar);
|
|
|
|
client?.Client.IOControl(IOControlCode.KeepAliveValues, InOptionValues, null);
|
|
|
|
}
|
|
|
|
catch (Exception)
|
|
|
|
{
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
if (listener == null || client == null || !client.Connected) return;
|
|
|
|
|
|
|
|
byte[] buffer = new byte[client.ReceiveBufferSize];
|
|
|
|
TcpClientState internalClient = new(client, buffer);
|
|
|
|
|
|
|
|
// add client connection to cache
|
|
|
|
string clientKey = client.Client.RemoteEndPoint?.ToString() ?? "";
|
|
|
|
if (clientKey == "") return;
|
|
|
|
Clients.AddOrUpdate(clientKey, internalClient, (n, o) => { return internalClient; });
|
|
|
|
RaiseClientConnected(client);
|
|
|
|
|
|
|
|
// begin to read data
|
|
|
|
try
|
|
|
|
{
|
|
|
|
NetworkStream networkStream = internalClient.NetworkStream;
|
|
|
|
ReadBuffer(internalClient, networkStream);
|
|
|
|
}
|
|
|
|
catch (Exception)
|
|
|
|
{
|
|
|
|
Clients.TryRemove(clientKey, out _);
|
|
|
|
RaiseClientDisconnected(internalClient.TcpClient);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// keep listening to accept next connection
|
|
|
|
AcceptTcpClient(listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void HandleDatagramReceived(IAsyncResult ar)
|
|
|
|
{
|
|
|
|
if (!IsRunning()) return;
|
|
|
|
TcpClientState? internalClient = ar.AsyncState as TcpClientState;
|
|
|
|
if (internalClient == null) return;
|
|
|
|
string clientKey = internalClient.TcpClient.Client.RemoteEndPoint?.ToString() ?? "";
|
|
|
|
if (clientKey == "") return;
|
|
|
|
if (!internalClient.TcpClient.Connected)
|
|
|
|
{
|
|
|
|
// connection has been closed
|
|
|
|
Clients.TryRemove(clientKey, out _);
|
|
|
|
RaiseClientDisconnected(internalClient.TcpClient);
|
|
|
|
}
|
|
|
|
|
|
|
|
NetworkStream networkStream;
|
|
|
|
int readBytesNum;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
networkStream = internalClient.NetworkStream;
|
|
|
|
// if the remote host has shutdown its connection,
|
|
|
|
// read will immediately return with zero bytes.
|
|
|
|
readBytesNum = networkStream.EndRead(ar);
|
|
|
|
}
|
|
|
|
catch (Exception)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (readBytesNum == 0)
|
|
|
|
{
|
|
|
|
// connection has been closed
|
|
|
|
Clients.TryRemove(clientKey, out _);
|
|
|
|
RaiseClientDisconnected(internalClient.TcpClient);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// received byte and trigger event notification
|
|
|
|
byte[] receivedBytes = new byte[readBytesNum];
|
|
|
|
Buffer.BlockCopy(internalClient.Buffer, 0, receivedBytes, 0, readBytesNum);
|
|
|
|
RaiseDatagramReceived(internalClient, receivedBytes);
|
|
|
|
|
|
|
|
// continue listening for tcp datagram packets
|
|
|
|
ReadBuffer(internalClient, networkStream);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion Receive
|
|
|
|
|
|
|
|
#region Send
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 发送报文至指定的客户端
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="tcpClient">客户端</param>
|
|
|
|
/// <param name="datagram">报文</param>
|
|
|
|
public void Send(TcpClient tcpClient, byte[] datagram)
|
|
|
|
{
|
|
|
|
if (!IsRunning()) return;
|
|
|
|
if (tcpClient == null || !tcpClient.Connected || datagram == null) return;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
NetworkStream stream = tcpClient.GetStream();
|
|
|
|
if (stream.CanWrite)
|
|
|
|
{
|
|
|
|
stream.Write(datagram, 0, datagram.Length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 发送报文至指定的客户端
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="tcpClient">客户端</param>
|
|
|
|
/// <param name="datagram">报文</param>
|
|
|
|
public void Send(TcpClient tcpClient, string datagram)
|
|
|
|
{
|
|
|
|
Send(tcpClient, 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.TcpClient, datagram);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 发送报文至所有客户端
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="datagram">报文</param>
|
|
|
|
public void SendToAll(string datagram)
|
|
|
|
{
|
|
|
|
if (!IsRunning()) return;
|
|
|
|
SendToAll(Encoding.GetBytes(datagram));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 发送报文至指定的客户端
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="tcpClient">客户端</param>
|
|
|
|
/// <param name="datagram">报文</param>
|
|
|
|
public void SendAsync(TcpClient tcpClient, byte[] datagram)
|
|
|
|
{
|
|
|
|
if (!IsRunning()) return;
|
|
|
|
if (tcpClient == null || !tcpClient.Connected || datagram == null) return;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
NetworkStream stream = tcpClient.GetStream();
|
|
|
|
if (stream.CanWrite)
|
|
|
|
{
|
|
|
|
stream.BeginWrite(datagram, 0, datagram.Length, HandleDatagramWritten, tcpClient);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 发送报文至指定的客户端
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="tcpClient">客户端</param>
|
|
|
|
/// <param name="datagram">报文</param>
|
|
|
|
public void SendAsync(TcpClient tcpClient, string datagram)
|
|
|
|
{
|
|
|
|
SendAsync(tcpClient, 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.TcpClient, datagram);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 发送报文至所有客户端
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="datagram">报文</param>
|
|
|
|
public void SendToAllAsync(string datagram)
|
|
|
|
{
|
|
|
|
if (!IsRunning()) return;
|
|
|
|
SendToAllAsync(Encoding.GetBytes(datagram));
|
|
|
|
}
|
|
|
|
|
|
|
|
private void HandleDatagramWritten(IAsyncResult ar)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
(ar.AsyncState as TcpClient)?.GetStream().EndWrite(ar);
|
|
|
|
}
|
|
|
|
catch (Exception)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#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
|
|
|
|
}
|