// This file is provided under The MIT License as part of RiptideNetworking.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/RiptideNetworking/Riptide/blob/main/LICENSE.md
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
namespace Riptide.Transports.Tcp
{
/// A server which can accept connections from s.
public class TcpServer : TcpPeer, IServer
{
///
public event EventHandler Connected;
///
public event EventHandler DataReceived;
///
public ushort Port { get; private set; }
/// The maximum number of pending connections to allow at any given time.
public int MaxPendingConnections { get; private set; } = 5;
/// Whether or not the server is running.
private bool isRunning = false;
/// The currently open connections, accessible by their endpoints.
private Dictionary connections;
/// Connections that have been closed and need to be removed from .
private readonly List closedConnections = new List();
/// The IP address to bind the socket to.
private readonly IPAddress listenAddress;
///
public TcpServer(int socketBufferSize = DefaultSocketBufferSize) : this(IPAddress.IPv6Any, socketBufferSize) { }
/// Initializes the transport, binding the socket to a specific IP address.
/// The IP address to bind the socket to.
/// How big the socket's send and receive buffers should be.
public TcpServer(IPAddress listenAddress, int socketBufferSize = DefaultSocketBufferSize) : base(socketBufferSize)
{
this.listenAddress = listenAddress;
}
///
public void Start(ushort port)
{
Port = port;
connections = new Dictionary();
StartListening(port);
}
/// Starts listening for connections on the given port.
/// The port to listen on.
private void StartListening(ushort port)
{
if (isRunning)
StopListening();
IPEndPoint localEndPoint = new IPEndPoint(listenAddress, port);
socket = new Socket(SocketType.Stream, ProtocolType.Tcp)
{
SendBufferSize = socketBufferSize,
ReceiveBufferSize = socketBufferSize,
NoDelay = true,
};
socket.Bind(localEndPoint);
socket.Listen(MaxPendingConnections);
isRunning = true;
}
///
public void Poll()
{
if (!isRunning)
return;
Accept();
foreach (TcpConnection connection in connections.Values)
connection.Receive();
foreach (IPEndPoint endPoint in closedConnections)
connections.Remove(endPoint);
closedConnections.Clear();
}
/// Accepts any pending connections.
private void Accept()
{
if (socket.Poll(0, SelectMode.SelectRead))
{
Socket acceptedSocket = socket.Accept();
IPEndPoint fromEndPoint = (IPEndPoint)acceptedSocket.RemoteEndPoint;
if (!connections.ContainsKey(fromEndPoint))
{
TcpConnection newConnection = new TcpConnection(acceptedSocket, fromEndPoint, this);
connections.Add(fromEndPoint, newConnection);
OnConnected(newConnection);
}
else
acceptedSocket.Close();
}
}
/// Stops listening for connections.
private void StopListening()
{
if (!isRunning)
return;
isRunning = false;
socket.Close();
}
///
public void Close(Connection connection)
{
if (connection is TcpConnection tcpConnection)
{
closedConnections.Add(tcpConnection.RemoteEndPoint);
tcpConnection.Close();
}
}
///
public void Shutdown()
{
StopListening();
connections.Clear();
}
/// Invokes the event.
/// The successfully established connection.
protected virtual void OnConnected(Connection connection)
{
Connected?.Invoke(this, new ConnectedEventArgs(connection));
}
///
protected internal override void OnDataReceived(int amount, TcpConnection fromConnection)
{
if ((MessageHeader)(ReceiveBuffer[0] & Message.HeaderBitmask) == MessageHeader.Connect)
{
if (fromConnection.DidReceiveConnect)
return;
fromConnection.DidReceiveConnect = true;
}
DataReceived?.Invoke(this, new DataReceivedEventArgs(ReceiveBuffer, amount, fromConnection));
}
}
}