public class TcpSession
{
#region Events
/// <summary>
/// Событие вызываемок при получении сообщения от клиента
/// </summary>
public event Action<object, ReceivedMessageEventArgs> MessageReceived;
/// <summary>
/// Событие вызываемое при какой-либо ожидаемой ошибке
/// </summary>
public event Action<object, ServerErrorArgs> SessionError;
#endregion
#region Private vars
private Guid _sessionID = new Guid();
private Task _sessionCommunicationTask = null;
private CancellationToken _cancellationToken;
private TcpClient _client = null;
private NetworkStream _clientStream = null;
private TcpSessionHandlerBase _handler = null;
DateTime lastConnectionTimeChecked;
private bool _isStoped = false;
private int _sessionLifetime = 60;
private ConcurrentQueue<byte[]> _sendQueue = null; //Потокобезопасная очередь отправки сообщений, нужна чтобы в теории из любого потока можно было добавить сообщение для отправки клиенту
#endregion Private vars
#region Props
/// <summary>
/// ID сессии
/// </summary>
public Guid ID => _sessionID;
/// <summary>
/// Остановленна ли текущая сессия
/// </summary>
public bool IsStoped => _isStoped;
/// <summary>
/// "Время жизни" сессии
/// Подразумевается время между приемом-отправкой, во время которого не было получено или отправленно новых сообщений
/// </summary>
public int SessionLifetime
{
get => _sessionLifetime;
set{ if(value > 0)
_sessionLifetime = value;
_sessionLifetime = 60;}
}
/// <summary>
/// Метод получения конечной точки клиента
/// </summary>
/// <returns></returns>
public EndPoint СlientEndPoint => _client.Client.RemoteEndPoint;
#endregion Props
#region ctor
/// <summary>
/// Конструктор сессии
/// </summary>
/// <param name="client"></param>
public TcpSession(TcpClient client)
{
_sessionID = Guid.NewGuid();
_client = client;
//_handler = CreateHandler();
}
#endregion ctor
#region Public
/// <summary>
/// Метод запуска сессии
/// Предполагается что будет вызываться только из сервера
/// </summary>
/// <param name="cancellationToken"></param>
public void Start(CancellationToken cancellationToken)
{
_cancellationToken = cancellationToken;
if(_client != null)
{
_handler = CreateHandler();
_sendQueue = new ConcurrentQueue<byte[]>();
_sessionCommunicationTask = new Task(Process, cancellationToken);
_sessionCommunicationTask.Start();
}
}
/// <summary>
/// Добавить сообщение в очередь отправки
/// </summary>
/// <param name="message"></param>
public void AddMessageToSendQueue(byte[] message)
{
if(_sendQueue != null)
{
if(message != null && message.Length > 0)
_sendQueue.Enqueue(message);
}
}
/// <summary>
/// Метод остановки сессии
/// </summary>
public void Stop()
{
_isStoped = true;
_client.Close();
}
#endregion Public
#region Protected
/// <summary>
/// Виртуальный метод котоый нужен чтобы создать обработчик сессии, который будет что-то делать с пришедшими сообщениями
/// </summary>
/// <returns></returns>
protected virtual TcpSessionHandlerBase CreateHandler()
{
return new TcpSessionHandlerBase(this);
}
#endregion
#region Private
/// <summary>
/// Проверка не истекло ли время жизни сессии
/// </summary>
private bool isTimoutReached => (DateTime.Now - lastConnectionTimeChecked).TotalSeconds > _sessionLifetime;
/// <summary>
/// Основной метод работы сессии
/// </summary>
private void Process()
{
#region Бесполезное объяснение того как работает сессия
#endregion
try
{
lastConnectionTimeChecked = DateTime.Now;
_clientStream = _client.GetStream();
while(!(_cancellationToken.IsCancellationRequested || IsStoped))
{
//Если истекло время жизни сессии
if(isTimoutReached)
break;
//Т.к. в теории не должно накапливаться много сообщений для отправки, отправляем по одному сообщению, в любом случае, очередь отправки должна будет заканчиваться быстрее чем приходят новые
if(_sendQueue.Count > 0)
{
byte[] msg;
if(_sendQueue.TryDequeue(out msg))
SendMessage(msg);
}
GetMessage(_clientStream);
//Thread.Sleep(100);
}
}
catch(Exception ex)
{
SessionError?.Invoke(this, new ServerErrorArgs { ErrorMessage = ex.Message });
}
finally
{
_client.Close();
}
Stop();
}
/// <summary>
/// Метод для получения сообщения из NetworkStream
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
private async Task<byte[]> GetMessage(NetworkStream stream)
{
using(MemoryStream ms = new MemoryStream())
{
byte[] data = new byte[64]; // буфер для получаемых данных
int bytes = 0;
do
{
bytes = await _clientStream.ReadAsync(data);
await ms.WriteAsync(data, 0, bytes);
} while(_clientStream.DataAvailable);
if(ms.Length > 0)
{
MessageReceived?.Invoke(this, new ReceivedMessageEventArgs() { Message = ms.ToArray() });
lastConnectionTimeChecked = DateTime.Now;
return ms.ToArray();
}
}
return null;
}
/// <summary>
/// Метод отправки сообщения клиенту
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
private async Task SendMessage(byte[] message)
{
if(_clientStream.CanWrite)
{
await _clientStream.WriteAsync(message);
lastConnectionTimeChecked = DateTime.Now;
}
}
#endregion Private
}