/// <summary> Sends signal to all components of Server to stop, then waits for everything to shut down. </summary> public static void Stop() { Log.Output(Log.Severity.DEBUG, Log.Source.NETWORK, "Stopping Server."); Stopping = true; WatchdogManager.Stop(); // This is a meh solution to the WaitForClientsTCP thread not ending until the next client connects. TcpClient Dummy = new TcpClient(); Dummy.Connect(new IPEndPoint(IPAddress.Loopback, ((IPEndPoint)TCPListener.LocalEndpoint).Port)); Dummy.Close(); while (Initialized) { Thread.Sleep(50); } // Wait for all threads to stop. }
/// <summary> Prepares the server for use, and starts listening for clients. </summary> /// <param name="PortTCP"> The port to listen on for clients communicating via TCP. </param> /// <param name="PortUDP"> The port to listen on for clients communicating via UDP. </param> /// <param name="ReceiveBufferSize"> The size, in bytes, of the receive data buffers. Increase this if your packets are longer than the default. </param> /// <param name="OperationPeriod"> The time, in ms, between network operations. If you are sending/receiving a lot of packets, and notice delays, lower this. </param> /// <param name="UsePriorityQueue"> If it is ture, packet priority control will be enabled. </param> public static void Start(int PortTCP, int PortUDP, int ReceiveBufferSize = 64, int OperationPeriod = 20, bool UsePriorityQueue = false) { Server.ReceiveBufferSize = ReceiveBufferSize; Server.OperationPeriod = OperationPeriod; Stopping = false; if (!Initialized) { Log.Output(Log.Severity.DEBUG, Log.Source.NETWORK, "Initializing Server."); Log.Output(Log.Severity.DEBUG, Log.Source.NETWORK, "Listening on ports " + PortTCP + " (TCP), and " + PortUDP + " (UDP)."); Clients = new Dictionary <string, ScarletClient>(); SendQueues = new Dictionary <string, PacketBuffer>(); ReceiveQueue = new QueueBuffer(); PacketsSent = new List <Packet>(); PacketsReceived = new List <Packet>(); Initialized = true; PriorityQueueEnabled = UsePriorityQueue; // Initialize default priority if (PriorityQueueEnabled) { DefaultPriority = PacketPriority.MEDIUM; } else { DefaultPriority = 0; } // Start Handler and listener PacketHandler.Start(); new Thread(new ParameterizedThreadStart(StartThreads)).Start(new Tuple <int, int>(PortTCP, PortUDP)); // Start watchdog WatchdogManager.Start(false); WatchdogManager.ConnectionChanged += WatchdogStatusUpdate; } else { Log.Output(Log.Severity.WARNING, Log.Source.NETWORK, "Attempted to start Server when already started."); } }
/// <summary> Immediately sends a packet. Blocks until sending is complete, regardless of protocol. </summary> public static void SendNow(Packet ToSend) { if (!Initialized) { throw new InvalidOperationException("Cannot use Server before initialization. Call Server.Start()."); } if (ToSend.Data.ID != Constants.WATCHDOG_PING || OutputWatchdogDebug) { Log.Output(Log.Severity.DEBUG, Log.Source.NETWORK, "Sending packet: " + ToSend); } if (!Clients.ContainsKey(ToSend.Endpoint)) { WatchdogManager.RemoveWatchdog(ToSend.Endpoint); Log.Output(Log.Severity.WARNING, Log.Source.NETWORK, "Tried to send packet to unknown client. Dropping."); return; } if (!Clients[ToSend.Endpoint].Connected) { Log.Output(Log.Severity.WARNING, Log.Source.NETWORK, "Cannot send packet to client that is not connected."); return; } try { if (ToSend.IsUDP) { if (!Clients.ContainsKey(ToSend.Endpoint)) { throw new InvalidOperationException("Cannot send packet to client that is not connected."); } lock (Clients[ToSend.Endpoint]) { byte[] Data = ToSend.GetForSend(); UDPListener.Send(Data, Data.Length, Clients[ToSend.Endpoint].EndpointUDP); if (StorePackets) { PacketsSent.Add(ToSend); } } } else { if (!Clients.ContainsKey(ToSend.Endpoint)) { throw new InvalidOperationException("Cannot send packet to client that is not connected."); } lock (Clients[ToSend.Endpoint]) { byte[] Data = ToSend.GetForSend(); Clients[ToSend.Endpoint].TCP.GetStream().Write(Data, 0, Data.Length); if (StorePackets) { PacketsSent.Add(ToSend); } } } } catch (Exception Exc) { Log.Output(Log.Severity.WARNING, Log.Source.NETWORK, "Failed to send packet."); Log.Exception(Log.Source.NETWORK, Exc); } }
/// <summary> /// Waits for, and receives data from a connected TCP client. /// This must be started on a thread, as it will block until CommHandler.Stopping is true, or the client disconnects. /// </summary> /// <param name="ClientObj"> The client to receive data from. Must be TcpClient. </param> private static void HandleTCPClient(object ClientObj) { TcpClient Client = (TcpClient)ClientObj; NetworkStream Receive = Client.GetStream(); if (!Receive.CanRead) { Log.Output(Log.Severity.ERROR, Log.Source.NETWORK, "Client connection does not permit reading."); throw new Exception("NetworkStream does not support reading"); } // Receive client name. String ClientName; byte[] DataBuffer = new byte[Math.Max(ReceiveBufferSize, 64)]; try { int DataSize = Receive.Read(DataBuffer, 0, DataBuffer.Length); if (DataSize == 0) { Receive?.Close(); if (!Stopping) { Log.Output(Log.Severity.WARNING, Log.Source.NETWORK, "Client disconnected before sending name."); } return; } else { ClientName = null; try { ClientName = UtilData.ToString(DataBuffer.Take(DataSize).ToArray()); } catch { } if (ClientName != null && ClientName.Length > 0) { Log.Output(Log.Severity.INFO, Log.Source.NETWORK, "TCP Client connected with name \"" + ClientName + "\"."); lock (Clients) { if (Clients.ContainsKey(ClientName)) { Clients[ClientName].TCP = Client; Clients[ClientName].Connected = true; } else { ScarletClient NewClient = new ScarletClient() { TCP = Client, Name = ClientName, Connected = true }; Clients.Add(ClientName, NewClient); } } // Create buffer for the client CreateBufferIfClientIsNew(ClientName); } else { Log.Output(Log.Severity.WARNING, Log.Source.NETWORK, "Invalid TCP client name received. Dropping connection."); } } } catch (Exception Exc) { Log.Output(Log.Severity.WARNING, Log.Source.NETWORK, "Failed to read name from incoming client. Dropping connection."); Log.Exception(Log.Source.NETWORK, Exc); Receive?.Close(); return; } ClientConnChange(new EventArgs()); WatchdogManager.AddWatchdog(ClientName); // Receive data from client. DataBuffer = new byte[ReceiveBufferSize]; while (!Stopping && Clients[ClientName].Connected) { try { int DataSize = Receive.Read(DataBuffer, 0, DataBuffer.Length); Log.Output(Log.Severity.DEBUG, Log.Source.NETWORK, "Received data from client (TCP)."); if (DataSize == 0) { Log.Output(Log.Severity.INFO, Log.Source.NETWORK, "Client has disconnected."); lock (Clients[ClientName]) { Clients[ClientName].Connected = false; } break; } if (DataSize >= 5) { byte[] Data = DataBuffer.Take(DataSize).ToArray(); IPEndPoint ClientEndpoint = (IPEndPoint)Client.Client.RemoteEndPoint; Packet ReceivedPack = new Packet(new Message(Data), false, ClientName); ReceiveQueue.Enqueue(ReceivedPack); if (StorePackets) { PacketsReceived.Add(ReceivedPack); } } else { Log.Output(Log.Severity.WARNING, Log.Source.NETWORK, "Data received from client was too short. Discarding."); } } catch (IOException IOExc) { if (IOExc.InnerException is SocketException) { int Error = ((SocketException)IOExc.InnerException).ErrorCode; Log.Output(Log.Severity.WARNING, Log.Source.NETWORK, "Failed to read data from connected client with SocketExcpetion code " + Error); Log.Exception(Log.Source.NETWORK, IOExc); if (Error == 10054) { Clients[ClientName].Connected = false; } } else { Log.Output(Log.Severity.WARNING, Log.Source.NETWORK, "Failed to read data from connected client because of IO exception."); Log.Exception(Log.Source.NETWORK, IOExc); } } catch (Exception OtherExc) { Log.Output(Log.Severity.WARNING, Log.Source.NETWORK, "Failed to read data from connected client."); Log.Exception(Log.Source.NETWORK, OtherExc); } Thread.Sleep(OperationPeriod); } lock (Clients) { Clients.Remove(ClientName); } Client.Client.Disconnect(true); Receive.Close(); Client.Close(); ClientConnChange(new EventArgs()); }
/// <summary> Watchdog parse handler </summary> /// <param name="WatchdogPacket"> Packet to parse </param> public static void ParseWatchdogPacket(Packet WatchdogPacket) { WatchdogManager.FoundWatchdog(UtilData.ToString(WatchdogPacket.Data.Payload)); }