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.

171 lines
5.4 KiB

using Apewer.Internals;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace Apewer.Network
{
/// <summary>TCP 服务端。</summary>
public sealed class TcpServer : IDisposable
{
Socket _server = null;
List<Socket> _clients = new List<Socket>(1024);
/// <summary>获取当前监听的端口。</summary>
public int Port { get => GetLocalPort(); }
/// <summary>启动服务端,并在所有接口上监听端口。</summary>
public TcpServer(int port) : this(IPAddress.Any, port) { }
/// <summary>启动服务端,并监听端口。</summary>
public TcpServer(string ip, int port) : this(IPAddress.Parse(ip), port) { }
/// <summary>启动服务端,并监听端口。</summary>
public TcpServer(IPAddress address, int port) : this(new IPEndPoint(address, port)) { }
/// <summary>启动服务端,并监听端口。</summary>
public TcpServer(IPEndPoint endpoint)
{
_server = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_server.Bind(endpoint);
_server.Listen(500);
_server.SendTimeout = 5000;
}
/// <summary>关闭连接,释放系统资源。</summary>
public void Dispose()
{
lock (_clients)
{
foreach (var client in _clients)
{
try { client.Disconnect(false); } catch { }
try { client.Close(100); } catch { }
#if !NET20
try { client.Dispose(); } catch { }
#endif
}
_clients.Clear();
}
if (_server != null)
{
try { _server.Disconnect(false); } catch { }
try { _server.Close(100); } catch { }
#if !NET20
try { _server.Dispose(); } catch { }
#endif
}
}
/// <summary>获取当前所有客户端连接。</summary>
public Socket[] GetClients()
{
lock (_clients)
{
var array = _clients.ToArray();
return array;
}
}
/// <summary>等待接收客户端连接。此方法将会阻塞当前线程直到关闭监听的端口。</summary>
/// <param name="callback">处理客户端连接,当此回调结束时将释放此连接。</param>
/// <exception cref="ArgumentNullException"></exception>
public void Accept(Action<Socket> callback)
{
if (callback == null) throw new ArgumentNullException(nameof(callback));
while (true)
{
var socket = null as Socket;
try
{
socket = _server.Accept();
if (socket != null)
{
Callback.Enqueue(this, socket, callback);
}
}
catch
{
if (socket != null)
{
try { socket.Close(); } catch { }
try { socket.Disconnect(false); } catch { }
#if !NET20
try { socket.Dispose(); } catch { }
#endif
}
}
}
}
int GetLocalPort()
{
var socket = _server;
if (socket != null)
{
var endpoint = socket.LocalEndPoint as IPEndPoint;
if (endpoint != null) return endpoint.Port;
}
return 0;
}
sealed class Callback
{
TcpServer _server;
Socket _socket;
Action<Socket> _action;
public static void Enqueue(TcpServer server, Socket socket, Action<Socket> callback)
{
var state = new Callback();
state._server = server ?? throw new ArgumentNullException(nameof(server));
state._socket = socket ?? throw new ArgumentNullException(nameof(socket));
state._action = callback ?? throw new ArgumentNullException(nameof(callback));
ThreadPool.QueueUserWorkItem(InThreadPool, state);
}
static void InThreadPool(object state)
{
var instance = state as Callback;
if (instance == null) return;
if (instance._server == null) return;
if (instance._socket == null) return;
if (instance._action == null) return;
var socket = instance._socket;
lock (instance._server._clients)
{
instance._server._clients.Add(instance._socket);
}
try
{
instance._action.Invoke(instance._socket);
}
catch
{ }
finally
{
lock (instance._server._clients)
{
instance._server._clients.Remove(instance._socket);
}
try { socket.Disconnect(false); } catch { }
try { socket.Close(100); } catch { }
#if !NET20
try { socket.Dispose(); } catch { }
#endif
}
}
}
}
}