/// <summary> /// Create a new client and start sending UDP updates and registering the timeout event. /// </summary> /// <param name="endPoint">The endpoint of the new client.</param> /// <returns>A new net server client instance.</returns> private NetServerClient CreateNewClient(IPEndPoint endPoint) { var netServerClient = new NetServerClient(_udpClient, endPoint); netServerClient.UpdateManager.StartUdpUpdates(); netServerClient.UpdateManager.OnTimeout += () => HandleClientTimeout(netServerClient); _clients.Add(netServerClient); return(netServerClient); }
/// <summary> /// Handles the event when a client times out. Disconnects the UDP client and cleans up any references /// to the client. /// </summary> /// <param name="client">The client that timed out.</param> private void HandleClientTimeout(NetServerClient client) { var id = client.Id; // Only execute the client timeout callback if the client is registered and thus has an ID if (client.IsRegistered) { ClientTimeoutEvent?.Invoke(id); } client.Disconnect(); _registeredClients.Remove(id); _clients.Remove(client); Logger.Get().Info(this, $"Client {id} timed out"); }
/// <summary> /// Handle a list of packets from a registered client. /// </summary> /// <param name="client">The registered client.</param> /// <param name="packets">The list of packets to handle.</param> private void HandlePacketsRegisteredClient(NetServerClient client, List <Packet.Packet> packets) { var id = client.Id; foreach (var packet in packets) { // Create a server update packet from the raw packet instance var serverUpdatePacket = new ServerUpdatePacket(packet); if (!serverUpdatePacket.ReadPacket()) { // If ReadPacket returns false, we received a malformed packet, which we simply ignore for now continue; } client.UpdateManager.OnReceivePacket <ServerUpdatePacket, ServerPacketId>(serverUpdatePacket); // Let the packet manager handle the received data _packetManager.HandleServerPacket(id, serverUpdatePacket); } }
/// <summary> /// Handle a list of packets from an unregistered client. /// </summary> /// <param name="client">The unregistered client.</param> /// <param name="packets">The list of packets to handle.</param> private void HandlePacketsUnregisteredClient(NetServerClient client, List <Packet.Packet> packets) { for (var i = 0; i < packets.Count; i++) { var packet = packets[i]; // Create a server update packet from the raw packet instance var serverUpdatePacket = new ServerUpdatePacket(packet); if (!serverUpdatePacket.ReadPacket()) { // If ReadPacket returns false, we received a malformed packet, which we simply ignore for now // Logger.Get().Warn(this, "Received malformed packet, ignoring"); continue; } client.UpdateManager.OnReceivePacket <ServerUpdatePacket, ServerPacketId>(serverUpdatePacket); if (!serverUpdatePacket.GetPacketData().TryGetValue( ServerPacketId.LoginRequest, out var packetData )) { continue; } var loginRequest = (LoginRequest)packetData; Logger.Get().Info(this, $"Received login request from '{loginRequest.Username}'"); // Invoke the handler of the login request and decide what to do with the client based on the result var allowClient = LoginRequestEvent?.Invoke( client.Id, client.EndPoint, loginRequest, client.UpdateManager ); if (!allowClient.HasValue) { Logger.Get().Error(this, "Login request has no handler"); return; } if (allowClient.Value) { // Logger.Get().Info(this, $"Login request from '{loginRequest.Username}' approved"); // client.UpdateManager.SetLoginResponseData(LoginResponseStatus.Success); // Register the client and add them to the dictionary client.IsRegistered = true; _registeredClients[client.Id] = client; // Now that the client is registered, we forward the rest of the packets to the other handler var leftoverPackets = packets.GetRange( i + 1, packets.Count - i - 1 ); HandlePacketsRegisteredClient(client, leftoverPackets); } else { client.Disconnect(); _clients.Remove(client); } break; } }
/// <summary> /// Callback for when UDP traffic is received. /// </summary> /// <param name="result">The async result.</param> private void OnUdpReceive(IAsyncResult result) { // Initialize default IPEndPoint for reference in data receive method var endPoint = new IPEndPoint(IPAddress.Any, 0); byte[] receivedData; try { receivedData = _udpClient.EndReceive(result, ref endPoint); } catch { // Logger.Get().Debug(this, $"UDP EndReceive exception: {e.GetType()}, message: {e.Message}"); // Return if an exception was caught, since there's no need to handle the packets then return; } finally { // Immediately start receiving data again regardless of whether there was an exception or not. // But we do this in a loop since it can throw an exception in some cases. var tries = 10; while (tries > 0) { try { _udpClient.BeginReceive(OnUdpReceive, null); break; } catch (Exception e) { Logger.Get().Warn(this, $"UDP BeginReceive exception: {e.GetType()}, message: {e.Message}"); } tries--; } // If we ran out of tries while starting the UDP receive again, we stop the server entirely if (tries == 0) { Logger.Get().Warn(this, "Could not successfully call BeginReceive, stopping server"); Stop(); } } List <Packet.Packet> packets; // Lock the leftover data array for synchronous data handling // This makes sure that from another asynchronous receive callback we don't // read/write to it in different places lock (_leftoverDataLock) { packets = PacketManager.HandleReceivedData(receivedData, ref _leftoverData); } var isRegisteredClient = false; NetServerClient client = null; // We lock while we are searching for the client that corresponds to this endpoint lock (_clientLock) { // Figure out which client this data is from or if it is a new client foreach (var existingClient in _clients.GetCopy()) { if (existingClient.HasAddress(endPoint)) { isRegisteredClient = existingClient.IsRegistered; client = existingClient; break; } } // If an existing client could not be found, we stay in the lock while creating a new client if (client == null) { Logger.Get().Info(this, $"Received packet from unknown client with address: {endPoint.Address}:{endPoint.Port}, creating new client"); // We didn't find a client with the given address, so we assume it is a new client // that wants to connect client = CreateNewClient(endPoint); } } // Outside of the lock we can handle the packets for the found/created client if (isRegisteredClient) { HandlePacketsRegisteredClient(client, packets); } else { HandlePacketsUnregisteredClient(client, packets); } }