using System.Net;
namespace DTLib.Network;
///
/// Multi-threaded TCP
///
public class TCPSocketServer : IDisposable
{
public readonly IPEndPoint LocalEndPoint;
public record ConnectionParams(Socket ConnectionSocket, Thread ConnectionThread);
protected Action? _connectionHandler;
protected Action _exceptionCallback;
protected Socket? _mainSocket;
/// address and port of the server in the local network
/// this delegate is called on every incoming socket connection
/// this delegate is called on every Exception throwed in threads created by the server
public TCPSocketServer(IPEndPoint localEndpoint,
Action? incomingConnectionHandler,
Action exceptionCallback)
{
LocalEndPoint = localEndpoint;
_connectionHandler = incomingConnectionHandler;
_exceptionCallback = exceptionCallback;
}
private volatile int acceptLoopRunning = 0;
///
/// Starts server on a new thread
///
/// the server is already started
public virtual void Listen()
{
if (Interlocked.CompareExchange(ref acceptLoopRunning, 1, 1) == 1)
throw new Exception("Server is already started. Stop it to ");
_mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_mainSocket.Bind(LocalEndPoint);
_mainSocket.Listen(64);
Thread loopThread = new Thread(AcceptLoop);
loopThread.Start();
}
/// server runs this loop in special thread
private void AcceptLoop()
{
try
{
// atomic (thread-safe) comparison acceptLoopRunning==1
while (Interlocked.CompareExchange(ref acceptLoopRunning, 1, 1) == 1)
{
Socket incomingConnection = _mainSocket!.Accept();
Thread connectionThread = new Thread(HandleConnection);
connectionThread.Start(new ConnectionParams(incomingConnection, connectionThread));
}
}
catch (Exception e)
{
_exceptionCallback(e);
Stop();
}
}
private void HandleConnection(object? _p)
{
var p = (ConnectionParams)_p!;
try
{
_connectionHandler?.Invoke(p);
}
catch (Exception e)
{
_exceptionCallback(e);
}
finally
{
p.ConnectionSocket.Shutdown(SocketShutdown.Both);
p.ConnectionSocket.Close();
}
}
public virtual void Stop()
{
// atomic (thread-safe) comparison acceptLoopRunning==0 and assignement acceptLoopRunning=0
if (Interlocked.CompareExchange(ref acceptLoopRunning, 0, 1) == 0)
throw new Exception("Server isn't running");
if(_mainSocket is null)
return;
_mainSocket.Shutdown(SocketShutdown.Both);
_mainSocket.Close();
}
public void Dispose() => Stop();
}