/// <summary> /// Main entry point /// </summary> /// <param name="args">Command line arguments</param> private static void Main(string[] args) { try { commandLineCommands.AddCommand("-help", "Show help topics", "This command shows help topics.", HelpCommandLineCommandExecutedEvent, CommandArgument.Optional("helpTopic")); commandLineCommands.AddCommand("-port", "Set port", "This command sets the port the server should be available from.", PortCommandLineCommandExecutedEvent, "port"); commandLineCommands.AddCommand("-tick-rate", "Set tick rate", "This command sets the tick rate the server should run on.", TickRateCommandLineCommandExecutedEvent, "tickRate"); commandLineCommands.AddAlias("h", "-help"); commandLineCommands.AddAlias("p", "-port"); commandLineCommands.AddAlias("t", "-tick-rate"); consoleCommands.AddCommand("help", "Show help topic", "This command shows help topics.", HelpConsoleCommmandExecutedEvent, CommandArgument.Optional("helpTopic")); consoleCommands.AddCommand("connectors", "List connectors", "This command lists all connectors.", ConnectorsConsoleCommmandExecutedEvent); consoleCommands.AddCommand("gameresources", "List game resources", "This command lists all game resources.", GameResourcesConsoleCommmandExecutedEvent); consoleCommands.AddCommand("lobbies", "List lobbies", "This command lists all lobbies.", LobbiesConsoleCommmandExecutedEvent); consoleCommands.AddCommand("users", "List users", "This command lists all users.", UsersConsoleCommmandExecutedEvent, CommandArgument.Optional("lobbyGUID")); consoleCommands.AddCommand("kick", "Kick user", "This command kicks the specified user.", KickConsoleCommmandExecutedEvent, "userGUID"); consoleCommands.AddCommand("ban", "Ban user", "This command bans the specified user.", BanConsoleCommmandExecutedEvent, "userGUID"); consoleCommands.AddCommand("bansecret", "Ban IP address", "This command bans the specified peer secret.", BanSecretConsoleCommmandExecutedEvent, "secret"); consoleCommands.AddCommand("unbansecret", "Unban IP address", "This command unbans the specified peer secret.", UnbanSecretConsoleCommmandExecutedEvent, "secret"); consoleCommands.AddCommand("reloadbans", $"Reload bans from \"{ bansJSONFilePath }\"", $"This command reloads the bans from \"{ bansJSONFilePath }\".", ReloadBansConsoleCommmandExecutedEvent); consoleCommands.AddCommand("closelobby", "Close lobby", "This command closes the specified lobby.", CloseLobbyConsoleCommmandExecutedEvent, "lobbyCode"); consoleCommands.AddCommand("exit", "Exit process", "This command exists the current process.", ExitConsoleCommmandExecutedEvent); consoleCommands.AddAlias("commands", "help"); consoleCommands.AddAlias("cmds", "commands"); consoleCommands.AddAlias("cmd", "cmds"); consoleCommands.AddAlias("h", "help"); consoleCommands.AddAlias("?", "help"); consoleCommands.AddAlias("c", "connectors"); consoleCommands.AddAlias("resources", "gameresources"); consoleCommands.AddAlias("gr", "gameresources"); consoleCommands.AddAlias("r", "resources"); consoleCommands.AddAlias("l", "lobbies"); consoleCommands.AddAlias("u", "users"); consoleCommands.AddAlias("k", "kick"); consoleCommands.AddAlias("b", "ban"); consoleCommands.AddAlias("bsecret", "bansecret"); consoleCommands.AddAlias("ubsecret", "unbansecret"); consoleCommands.AddAlias("cl", "closelobby"); consoleCommands.AddAlias("quit", "exit"); consoleCommands.AddAlias("q", "quit"); if (File.Exists(serverJSONFilePath)) { try { using FileStream file_stream = File.OpenRead(serverJSONFilePath); using StreamReader stream_reader = new StreamReader(file_stream); serverConfigurationData = JsonConvert.DeserializeObject <ServerConfigurationData>(stream_reader.ReadToEnd()); } catch (Exception e) { Console.Error.WriteLine(e); } } else { serverConfigurationData = new ServerConfigurationData(); try { using FileStream file_stream = File.Open(serverJSONFilePath, FileMode.Create, FileAccess.Write); using StreamWriter stream_writer = new StreamWriter(file_stream); stream_writer.Write(JsonConvert.SerializeObject(serverConfigurationData)); } catch (Exception e) { Console.Error.WriteLine(e); } } if (serverConfigurationData == null) { serverConfigurationData = new ServerConfigurationData(); } if (serverConfigurationData.NetworkPort == 0) { Console.Error.WriteLine($"Port is set to \"0\". Port will be set to default port \"{ ServerConfigurationData.defaultNetworkPort }\"."); serverConfigurationData.NetworkPort = ServerConfigurationData.defaultNetworkPort; } if ((serverConfigurationData.TickRate <= 0) || (serverConfigurationData.TickRate > 1000)) { Console.Error.WriteLine($"Tick rate is set to \"{ serverConfigurationData.TickRate }\". Tick rate will be set to default tick rate \"{ ServerConfigurationData.defaultTickRate }\"."); serverConfigurationData.TickRate = ServerConfigurationData.defaultTickRate; } if (string.IsNullOrWhiteSpace(serverConfigurationData.OutputLogPath)) { Console.Error.WriteLine($"Output log path is not defined. Output log path will be set to default output log path \"{ ServerConfigurationData.defaultOutputLogPath }\"."); serverConfigurationData.OutputLogPath = ServerConfigurationData.defaultOutputLogPath; } if (string.IsNullOrWhiteSpace(serverConfigurationData.ErrorLogPath)) { Console.Error.WriteLine($"Error log path is not defined. Error log path will be set to default error log path \"{ ServerConfigurationData.defaultErrorLogPath }\"."); serverConfigurationData.ErrorLogPath = ServerConfigurationData.defaultErrorLogPath; } StringBuilder command_line_arguments_string_builder = new StringBuilder(); bool first = true; foreach (string arg in args) { if (first) { first = false; } else { command_line_arguments_string_builder.Append(' '); } command_line_arguments_string_builder.Append(arg); } if (commandLineCommands.ParseCommands(command_line_arguments_string_builder.ToString(), "-")) { outputLogger = Logger.Open(serverConfigurationData.OutputLogPath); if (serverConfigurationData.OutputLogPath == serverConfigurationData.ErrorLogPath) { errorLogger = outputLogger; } else { errorLogger = Logger.Open(serverConfigurationData.ErrorLogPath); } if (keepServerRunning) { using (server = Servers.Create(serverConfigurationData.NetworkPort, serverConfigurationData.TimeoutTime)) { if (server == null) { WriteErrorLogLine($"Failed to create server at port { serverConfigurationData.NetworkPort }."); } else { TimeSpan tick_time_span = TimeSpan.FromMilliseconds(1000 / serverConfigurationData.TickRate); Stopwatch stopwatch = new Stopwatch(); uint lag_count = 0U; ConsoleKeyInfo console_key_information; StringBuilder input_string_builder = new StringBuilder(); foreach (IConnector connector in server.Connectors) { connector.OnPeerConnectionAttempted += PeerConnectionAttemptedEvent; connector.OnPeerConnected += PeerConnectedEvent; connector.OnPeerDisconnected += PeerDisconnectedEvent; } server.Bans.AppendFromFile(bansJSONFilePath); try { foreach (string game_resources_file_path in Directory.GetFiles("./GameResources/", "*.dll")) { WriteOutputLogLine($"Loading game resources file \"{ game_resources_file_path }\"..."); try { Assembly assembly = Assembly.LoadFrom(game_resources_file_path); if (assembly == null) { WriteErrorLogLine($"Failed to load game resource assembly from \"{ game_resources_file_path }\""); } else { foreach (Type type in assembly.GetTypes()) { if (type.IsClass && !type.IsAbstract && typeof(IGameResource).IsAssignableFrom(type)) { bool has_default_constructor = true; foreach (ConstructorInfo constructor in type.GetConstructors(BindingFlags.Public)) { has_default_constructor = false; if (constructor.GetParameters().Length <= 0) { has_default_constructor = true; break; } } if (has_default_constructor) { if (server.AddGameResource(type)) { WriteOutputLogLine($"Loaded game resource \"{ type.FullName }\" from \"{ game_resources_file_path }\"."); } else { WriteErrorLogLine($"Failed to load game resource \"{ type.FullName }\" from \"{ game_resources_file_path }\"."); } } } } } } catch (Exception e) { WriteErrorLogLine(e); } } } catch (Exception e) { WriteErrorLogLine(e); } WriteOutputLogLine($"Starting server at port { serverConfigurationData.NetworkPort }..."); while (keepServerRunning) { stopwatch.Restart(); server.ProcessEvents(); if (!Console.IsInputRedirected) { while (Console.KeyAvailable) { console_key_information = Console.ReadKey(); if (console_key_information.KeyChar != '\0') { if (console_key_information.Key == ConsoleKey.Enter) { string input = input_string_builder.ToString(); WriteOutputLogLine(input); if (!consoleCommands.ParseCommand(input)) { WriteErrorLogLine("Failed to execute command."); } input_string_builder.Clear(); } else { input_string_builder.Append(console_key_information.KeyChar); } } } } stopwatch.Stop(); if (tick_time_span > stopwatch.Elapsed) { lag_count = (lag_count > 0U) ? (lag_count - 1U) : 0U; Thread.Sleep(tick_time_span - stopwatch.Elapsed); } else { ++lag_count; if (lag_count >= serverConfigurationData.TickRate) { lag_count = 0U; WriteErrorLogLine($"Server can't keep up. Try lowering the tick rate in \"{ serverJSONFilePath }\" or run the server with a lower tick rate."); } } } server.Bans.WriteToFile(bansJSONFilePath); WriteOutputLogLine("Server has shut down."); } } } } else { PrintHelpTopic(commandLineCommands, "-"); } command_line_arguments_string_builder.Clear(); if (outputLogger != null) { outputLogger.Dispose(); if (outputLogger == errorLogger) { outputLogger = null; errorLogger = null; } } if (errorLogger != null) { errorLogger.Dispose(); errorLogger = null; } } catch (Exception e) { Console.Error.WriteLine(e); } }
/// <summary> /// Tests connections between a server and clients /// </summary> /// <param name="port">Network port</param> /// <param name="onCreateConnection">On create connection</param> public void TestConnections(ushort port, CreateClientConnectionDelegate onCreateConnection) { if (port == 0) { throw new ArgumentException("Port can't be zero.", nameof(port)); } if (onCreateConnection == null) { throw new ArgumentNullException(nameof(onCreateConnection)); } UnitTestsConfigurationData unit_tests_configuration = null; if (File.Exists(unitTestsConfigurationPath)) { using FileStream file_stream = File.OpenRead(unitTestsConfigurationPath); using StreamReader file_stream_reader = new StreamReader(file_stream); unit_tests_configuration = JsonConvert.DeserializeObject <UnitTestsConfigurationData>(file_stream_reader.ReadToEnd()); } unit_tests_configuration ??= new UnitTestsConfigurationData(); Assert.IsTrue(unit_tests_configuration.IsValid); bool is_running = true; uint perform_ticks = unit_tests_configuration.PerformTicks; using IServerSynchronizer server = Servers.Create(port, Defaults.timeoutTime); Assert.IsNotNull(server); server.AddGameResource <ExampleGameResource>(); server.OnPeerConnectionAttempted += (peer) => Console.WriteLine($"[SERVER] Peer GUID \"{ peer.GUID }\" with secret \"{ peer.Secret }\" attempted to connect."); server.OnPeerConnected += (peer) => Console.WriteLine($"[SERVER] Peer GUID \"{ peer.GUID }\" with secret \"{ peer.Secret }\" is connect."); server.OnPeerDisconnected += (peer) => Console.WriteLine($"[SERVER] Peer GUID \"{ peer.GUID }\" with secret \"{ peer.Secret }\" has been disconnect."); server.OnUnknownMessageReceived += (message, json) => { Console.Error.WriteLine($"[SERVER] Message type: \"{ message.MessageType }\""); Console.Error.WriteLine("[SERVER] JSON:"); Console.Error.WriteLine(); Console.Error.WriteLine(json); Assert.Fail("Server has received an unknown message.", message, json); }; server.OnPeerMessageReceived += (peer, message) => Console.WriteLine($"[SERVER] Peer GUID \"{ peer.GUID }\" sent a message of length \"{ message.Length }\"."); IClientSynchronizer[] clients = new IClientSynchronizer[Defaults.maximalUserCount + 1U]; IUser[] users = new IUser[clients.Length]; ILobby[] lobbies = new ILobby[clients.Length]; for (int index = 0; index < clients.Length; index++) { int current_index = index; IClientSynchronizer client = onCreateConnection(server); Assert.IsNotNull(client); clients[index] = client; client.OnPeerConnectionAttempted += (peer) => Console.WriteLine($"[CLIENT] Peer GUID \"{ peer.GUID }\" with secret \"{ peer.Secret }\" attempted to connect."); client.OnPeerConnected += (peer) => Console.WriteLine($"[CLIENT] Peer GUID \"{ peer.GUID }\" with secret \"{ peer.Secret }\" is connect."); client.OnPeerDisconnected += (peer) => Console.WriteLine($"[CLIENT] Peer GUID \"{ peer.GUID }\" with secret \"{ peer.Secret }\" has been disconnect."); client.OnUnknownMessageReceived += (message, json) => { Console.Error.WriteLine($"Message type: \"{ message.MessageType }\""); Console.Error.WriteLine("JSON:"); Console.Error.WriteLine(); Console.Error.WriteLine(json); Assert.Fail($"Client index { current_index } has received an unknown message.", message, json, current_index); }; client.OnPeerMessageReceived += (peer, message) => Console.WriteLine($"[CLIENT] Peer GUID \"{ peer.GUID }\" sent a message of length \"{ message.Length }\"."); client.OnLobbyJoinAcknowledged += (lobby) => lobbies[current_index] = lobby; client.OnAuthentificationAcknowledged += (user) => { Assert.IsNotNull(user); users[current_index] = user; if (current_index == 0) { client.OnLobbyJoinAcknowledged += (lobby) => { for (int client_index = 1; client_index < clients.Length; client_index++) { clients[client_index].JoinLobby(lobby.LobbyCode, $"Client_{ client_index }"); } }; client.CreateAndJoinLobby($"Client_{ current_index }", "Test lobby", typeof(ExampleGameMode).FullName); } }; client.OnErrorMessageReceived += (errorType, message) => Console.Error.WriteLine($"Error at client index { current_index }: [{ errorType }] { message }"); } ; TimeSpan tick_time_span = TimeSpan.FromSeconds(1.0 / unit_tests_configuration.TickRate); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); while (is_running && (perform_ticks > 0U)) { stopwatch.Restart(); server.ProcessEvents(); foreach (IClientSynchronizer client in clients) { client.ProcessEvents(); } stopwatch.Stop(); TimeSpan elapsed_time = stopwatch.Elapsed; if (elapsed_time > tick_time_span) { Console.Error.WriteLine($"Can't keep up server and clients. Please lower the tick rate. Current tick rate: { unit_tests_configuration.TickRate }"); is_running = false; } else if (elapsed_time < tick_time_span) { Thread.Sleep(tick_time_span - elapsed_time); } --perform_ticks; } server.Dispose(); foreach (IClientSynchronizer client in clients) { client.Dispose(); } Assert.AreEqual(0U, perform_ticks); uint no_lobby_client_count = 0U; for (int index = 0; index < clients.Length; index++) { Assert.IsNotNull(users[index], $"User at client index { index } was not authentificated.", index); if (lobbies[index] == null) { ++no_lobby_client_count; } } if (no_lobby_client_count != 1U) { if (no_lobby_client_count == 0U) { Assert.Fail("Every client is in a lobby, which is wrong."); } else { Assert.Fail($"{ no_lobby_client_count } clients are not in a lobby."); } } Assert.Pass(); }