// 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)); } } }