using JamesFrowen.SimpleWeb; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace FishNet.Transporting.Bayou.Server { public class ServerSocket : CommonSocket { #region Public. /// /// Gets the current ConnectionState of a remote client on the server. /// /// ConnectionId to get ConnectionState for. internal RemoteConnectionState GetConnectionState(int connectionId) { RemoteConnectionState state = _clients.Contains(connectionId) ? RemoteConnectionState.Started : RemoteConnectionState.Stopped; return state; } #endregion #region Private. #region Configuration. /// /// Port used by server. /// private ushort _port; /// /// Maximum number of allowed clients. /// private int _maximumClients; /// /// MTU sizes for each channel. /// private int _mtu; #endregion #region Queues. /// /// Outbound messages which need to be handled. /// private Queue _outgoing = new Queue(); /// /// Ids to disconnect next iteration. This ensures data goes through to disconnecting remote connections. This may be removed in a later release. /// private List _disconnectingNext = new List(); /// /// Ids to disconnect immediately. /// private List _disconnectingNow = new List(); /// /// ConnectionEvents which need to be handled. /// private Queue _remoteConnectionEvents = new Queue(); #endregion /// /// Currently connected clients. /// private HashSet _clients = new HashSet(); /// /// Server socket manager. /// private SimpleWebServer _server; /// /// SslConfiguration to use. /// private SslConfiguration _sslConfiguration; #endregion ~ServerSocket() { StopConnection(); } /// /// Initializes this for use. /// /// internal void Initialize(Transport t, int unreliableMTU, SslConfiguration config) { _sslConfiguration = config; base.Transport = t; _mtu = unreliableMTU; } /// /// Threaded operation to process server actions. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Socket() { TcpConfig tcpConfig = new TcpConfig(false, 5000, 20000); SslConfig config; if (!_sslConfiguration.Enabled) config = new SslConfig(); else config = new SslConfig(_sslConfiguration.Enabled, _sslConfiguration.CertificatePath, _sslConfiguration.CertificatePassword, _sslConfiguration.SslProtocol); _server = new SimpleWebServer(5000, tcpConfig, _mtu, 5000, config); _server.onConnect += _server_onConnect; _server.onDisconnect += _server_onDisconnect; _server.onData += _server_onData; _server.onError += _server_onError; base.SetConnectionState(LocalConnectionState.Starting, true); _server.Start(_port); base.SetConnectionState(LocalConnectionState.Started, true); } /// /// Called when a client connection errors. /// private void _server_onError(int clientId, Exception arg2) { StopConnection(clientId, true); } /// /// Called when receiving data. /// private void _server_onData(int clientId, ArraySegment data) { if (_server == null || !_server.Active) return; Channel channel; ArraySegment segment = base.RemoveChannel(data, out channel); ServerReceivedDataArgs dataArgs = new ServerReceivedDataArgs(segment, channel, clientId, base.Transport.Index); base.Transport.HandleServerReceivedDataArgs(dataArgs); } /// /// Called when a client connects. /// private void _server_onConnect(int clientId) { if (_server == null || !_server.Active) return; if (_clients.Count >= _maximumClients) _server.KickClient(clientId); else _remoteConnectionEvents.Enqueue(new RemoteConnectionEvent(true, clientId)); } /// /// Called when a client disconnects. /// private void _server_onDisconnect(int clientId) { StopConnection(clientId, true); } /// /// Gets the address of a remote connection Id. /// /// /// Returns string.empty if Id is not found. internal string GetConnectionAddress(int connectionId) { if (_server == null || !_server.Active) return string.Empty; return _server.GetClientAddress(connectionId); } /// /// Starts the server. /// internal bool StartConnection(ushort port, int maximumClients) { if (base.GetConnectionState() != LocalConnectionState.Stopped) return false; base.SetConnectionState(LocalConnectionState.Starting, true); //Assign properties. _port = port; _maximumClients = maximumClients; ResetQueues(); Socket(); return true; } /// /// Stops the local socket. /// internal bool StopConnection() { if (_server == null || base.GetConnectionState() == LocalConnectionState.Stopped || base.GetConnectionState() == LocalConnectionState.Stopping) return false; ResetQueues(); base.SetConnectionState(LocalConnectionState.Stopping, true); _server.Stop(); base.SetConnectionState(LocalConnectionState.Stopped, true); return true; } /// /// Stops a remote client disconnecting the client from the server. /// /// ConnectionId of the client to disconnect. internal bool StopConnection(int connectionId, bool immediately) { if (_server == null || base.GetConnectionState() != LocalConnectionState.Started) return false; //Don't disconnect immediately, wait until next command iteration. if (!immediately) { _disconnectingNext.Add(connectionId); } //Disconnect immediately. else { _server.KickClient(connectionId); _clients.Remove(connectionId); base.Transport.HandleRemoteConnectionState(new RemoteConnectionStateArgs(RemoteConnectionState.Stopped, connectionId, base.Transport.Index)); } return true; } /// /// Resets queues. /// private void ResetQueues() { _clients.Clear(); base.ClearPacketQueue(ref _outgoing); _disconnectingNext.Clear(); _disconnectingNow.Clear(); _remoteConnectionEvents.Clear(); } /// /// Dequeues and processes commands. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DequeueDisconnects() { int count; count = _disconnectingNow.Count; //If there are disconnect nows. if (count > 0) { for (int i = 0; i < count; i++) StopConnection(_disconnectingNow[i], true); _disconnectingNow.Clear(); } count = _disconnectingNext.Count; //If there are disconnect next. if (count > 0) { for (int i = 0; i < count; i++) _disconnectingNow.Add(_disconnectingNext[i]); _disconnectingNext.Clear(); } } /// /// Dequeues and processes outgoing. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DequeueOutgoing() { if (base.GetConnectionState() != LocalConnectionState.Started || _server == null) { //Not started, clear outgoing. base.ClearPacketQueue(ref _outgoing); } else { int count = _outgoing.Count; for (int i = 0; i < count; i++) { Packet outgoing = _outgoing.Dequeue(); int connectionId = outgoing.ConnectionId; AddChannel(ref outgoing); ArraySegment segment = outgoing.GetArraySegment(); //Send to all clients. if (connectionId == -1) _server.SendAll(_clients, segment); //Send to one client. else _server.SendOne(connectionId, segment); outgoing.Dispose(); } } } /// /// Allows for Outgoing queue to be iterated. /// internal void IterateOutgoing() { if (_server == null) return; DequeueOutgoing(); DequeueDisconnects(); } /// /// Iterates the Incoming queue. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void IterateIncoming() { if (_server == null) return; //Handle connection and disconnection events. while (_remoteConnectionEvents.Count > 0) { RemoteConnectionEvent connectionEvent = _remoteConnectionEvents.Dequeue(); if (connectionEvent.Connected) _clients.Add(connectionEvent.ConnectionId); RemoteConnectionState state = (connectionEvent.Connected) ? RemoteConnectionState.Started : RemoteConnectionState.Stopped; base.Transport.HandleRemoteConnectionState(new RemoteConnectionStateArgs(state, connectionEvent.ConnectionId, base.Transport.Index)); } //Read data from clients. _server.ProcessMessageQueue(); } /// /// Sends a packet to a single, or all clients. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SendToClient(byte channelId, ArraySegment segment, int connectionId) { Send(ref _outgoing, channelId, segment, connectionId); } /// /// Returns the maximum number of clients allowed to connect to the server. If the transport does not support this method the value -1 is returned. /// /// internal int GetMaximumClients() { return _maximumClients; } } }