using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace Shared.TCP
{
public class TcpServer
{
private readonly ILogger<TcpServer> _logger;
private readonly IServerConfiguration _serverConfiguration;
private readonly ConcurrentDictionary<Guid, Socket> _clients = new ConcurrentDictionary<Guid, Socket>();
private object _startLocker = new object();
private bool _isStarted;
public TcpServer(ILogger<TcpServer> logger, IServerConfiguration serverConfiguration)
{
if (logger is null)
{
throw new ArgumentNullException(nameof(logger));
}
if (serverConfiguration is null)
{
throw new ArgumentNullException(nameof(serverConfiguration));
}
_logger = logger;
_serverConfiguration = serverConfiguration;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
lock (_startLocker)
{
if (_isStarted)
{
_logger.LogWarning("Server alrady started");
return;
}
_isStarted = true;
}
Thread handleConnected = new Thread(async () =>
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
//Проверим на связи ли клиент пинганув
foreach(var c in _clients)
{
try
{
await SendToClient(c.Key, Array.Empty<byte>());
}
catch(Exception ex)
{
_logger.LogError(ex.ToString());
}
}
//Чистим отвалившихся
var disconnected = _clients.Where(x => x.Value.Connected);
foreach (var it in disconnected)
{
var removed = _clients.TryRemove(it.Key, out var client);
if (!removed)
{
_logger.LogWarning($"Cant remove disconnected client with GUID {it.Key}");
continue;
}
client.Shutdown(SocketShutdown.Both);
client.Close();
}
await Task.Delay(1000, cancellationToken);
}
}
catch (Exception ex)
{
_logger.LogDebug(ex.ToString());
}
});
Thread acceptClientThread = new Thread(
async () =>
{
try
{
cancellationToken.ThrowIfCancellationRequested();
TcpListener listener = new TcpListener(_serverConfiguration.ServerLicalAddress, _serverConfiguration.ServerPort);
listener.Start(_serverConfiguration.Backlog);
while (!cancellationToken.IsCancellationRequested)
{
if (_serverConfiguration.MaxConnections <= _clients.Count)
{
await Task.Delay(500);
continue;
}
var client = await listener.AcceptSocketAsync();
Guid clientGuid = Guid.NewGuid();
if (_clients.TryAdd(clientGuid, client))
{
_logger.LogInformation("Client accepted");
}
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Server stopped");
}
});
acceptClientThread.Start();
handleConnected.Start();
}
public async Task SendToClient(Guid clientGuid, ReadOnlyMemory<byte> message, CancellationToken cancellationToken = default)
{
try
{
using var stream = new NetworkStream(_clients[clientGuid], false);
{
await stream.WriteAsync(message.ToArray(), 0, message.Length);
}
}
catch (SocketException sex)
{
_logger.LogError(sex.ToString());
}
catch (Exception ex)
{
_logger.LogDebug(ex.ToString());
}
}
public async Task BrodcastMessage(ReadOnlyMemory<byte> message, CancellationToken cancellationToken = default)
{
try
{
cancellationToken.ThrowIfCancellationRequested();
var tasks = _clients.Where(x => x.Value.Connected).Select(x => SendToClient(x.Key, message, cancellationToken));
await Task.WhenAll(tasks).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex.ToString());
}
}
}
}