public class TcpServer : IDisposable
{
#region Events
/// <summary>
/// Событие вызываемое в начале принятия клиента
/// </summary>
public event Action<object, ServerClientsArgs> ClientAccepting;
/// <summary>
/// Событие вызываемое в случае успешнго принятия клиента
/// </summary>
public event Action<object, ServerClientsArgs> ClientAccepted;
/// <summary>
/// Событие вызываемое при начале отключения клиента
/// </summary>
public event Action<object, ServerClientsArgs> ClientDisconnecting;
/// <summary>
/// Событие вызываемое в случае успешного отключения клиента
/// </summary>
public event Action<object, ServerClientsArgs> ClientDisconnected;
/// <summary>
/// Событие срабатывающее при каких-то ошибках
/// </summary>
public event Action<object, ServerErrorArgs> ErrorEvent;
#endregion Events
#region Default
/// <summary>
/// IP-aдрес сервреа по-умолчанию
/// Очевидный localhost
/// </summary>
private static IPAddress _defaultIPAddress = IPAddress.Parse("127.0.0.1");
/// <summary>
/// Порт сервера по-умлочанию
/// </summary>
private static int _defaultPort = 9000;
/// <summary>
/// Конечная точка сервреа по-умлочанию
/// </summary>
private static IPEndPoint _defaultEndPoint = new IPEndPoint(_defaultIPAddress, _defaultPort);
#endregion Default
#region Private vars
private TcpListener _listener = null; //Слушатель сервера для приема новых соединений
private bool _isStoped = false; //Флаг останвки приема сообщений
private bool _isPurgingStoped = false; //Флаг остановки очистки
private Task _listeningTask = null; //Таск приема входящих соединений
private Task _purgingTask = null; //Таск очистки сессий помеченых как неактивные
private ConcurrentDictionary<Guid, TcpSession> _sessions = null; //сессии сервера
private object locker = new object(); //объект синхронизации потоков
private DateTime _startTime; //Время включения сервера
private CancellationToken cancellationToken; //токен остановки, для того чтобы можно было завершить работу сервера из вне
#endregion Private vars
#region Props
public int ConnectedSessionCount => this._sessions.Where(kv => !kv.Value.IsStoped).Count();
public TimeSpan UpTime => DateTime.Now - _startTime;
public bool IsRuning => !_isStoped;
#endregion Props
#region ctors
public TcpServer() : this(_defaultEndPoint)
{
}
public TcpServer(int port) : this(new IPEndPoint(_defaultIPAddress, port))
{
}
public TcpServer(IPAddress address) : this(new IPEndPoint(address, _defaultPort))
{
}
public TcpServer(IPAddress address, int port) : this(new IPEndPoint(address, port))
{
}
public TcpServer(IPEndPoint endPoint)
{
Init(endPoint);
}
#endregion ctors
#region Public methods
/// <summary>
/// Метод запуска сессии
/// </summary>
/// <param name="cancellationToken"></param>
public void Start(CancellationToken cancellationToken)
{
this.cancellationToken = cancellationToken;
if(_listener != null)
{
_startTime = DateTime.Now;
_sessions = new ConcurrentDictionary<Guid, TcpSession>();
_listener.Start();
_listeningTask = new Task(StartListening);
_listeningTask.Start();
//Создаем таск очистки сессий
_purgingTask = new Task(Purging);
_purgingTask.Start();
}
}
/// <summary>
/// Остановка приема соединение, останока коммуникации сессий и очистка списка.
/// </summary>
public void Stop()
{
_isStoped = true;
_listener.Stop();
StopAllClientSessions();
}
public string GetServerInfo => $"|Connected clients: {ConnectedSessionCount} | Uptime: {UpTime}|";
#endregion Public methods
#region Private methods
private bool AddSession(TcpSession session)
{
ClientAccepting?.Invoke(this, new ServerClientsArgs { client = session });
var result = this._sessions.TryAdd(session.ID, session);
if(result)
ClientAccepted?.Invoke(this, new ServerClientsArgs { client = session });
return result;
}
private bool RemoveSession(TcpSession session)
{
ClientDisconnecting?.Invoke(this, new ServerClientsArgs { client = session });
var result = this._sessions.TryRemove(session.ID, out var tmp);
if(result)
ClientDisconnected?.Invoke(this, new ServerClientsArgs { client = session });
return result;
}
private void Init(IPEndPoint endPoint)
{
try
{
_listener = new TcpListener(endPoint);
_sessions = new ConcurrentDictionary<Guid, TcpSession>();
}
catch(Exception ex)
{
ErrorEvent?.Invoke(this, new ServerErrorArgs { ErrorMessage = ex.Message });
_listener = null;
}
}
private void StartListening()
{
if(_listener != null)
_listener.Start();
while(!_isStoped || !this.cancellationToken.IsCancellationRequested)
{
try
{
TcpSession clientSession = CreateSession(_listener.AcceptTcpClient());
AddSession(clientSession);
clientSession.Start(cancellationToken);
}
catch(Exception ex)
{
ErrorEvent?.Invoke(this, new ServerErrorArgs() { ErrorMessage = ex.Message });
_isStoped = true;
Stop();
break;
}
//Thread.Sleep(10);
}
StopAllClientSessions();
}
/// <summary>
/// Виртуальный метод для того чтобы наследники класса могли создавать свои сессии
/// </summary>
/// <param name="tcpClient"></param>
/// <returns></returns>
protected virtual TcpSession CreateSession(TcpClient tcpClient)
{
return new TcpSession(tcpClient);
}
/// <summary>
/// Метод очистки неактивных сессий
/// </summary>
private void Purging()
{
while(!_isPurgingStoped || !cancellationToken.IsCancellationRequested)
{
var disconnected = _sessions.Where(s => s.Value.IsStoped);
lock(locker)
{
foreach(var s in disconnected)
{
RemoveSession(s.Value);
}
}
disconnected = null;
}
//Thread.Sleep(10*1000);
}
/// <summary>
/// Метод остановки всех активных клиентов
/// </summary>
private void StopAllClientSessions()
{
foreach(var kv in _sessions)
kv.Value.Stop();
}
#endregion Private methods
#region Dispose implementation
private bool isDisposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if(isDisposed)
{
return;
}
if(disposing)
{
//TODO Сюда нужно добавить в будущем то что нужно задиспозить
}
isDisposed = true;
}
~TcpServer()
{
Dispose(false);
}
#endregion Dispose implementation
}