mirror of
https://github.com/CommonLoon102/NBloodServerSupervisor.git
synced 2025-01-03 08:32:45 +01:00
fix port ranges (#6)
This commit is contained in:
parent
4f96ad89db
commit
c1b385c41d
12
Common/CommandLineUtils.cs
Normal file
12
Common/CommandLineUtils.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
public static class CommandLineUtils
|
||||
{
|
||||
public static string GetClientLaunchCommand(string host, int port, string modCommandLine) =>
|
||||
$"nblood -client {host} -port {port} {modCommandLine}";
|
||||
}
|
||||
}
|
@ -7,7 +7,6 @@ namespace Common
|
||||
{
|
||||
public class Constants
|
||||
{
|
||||
public const string NBloodExecutable = "nblood_server";
|
||||
public static readonly IReadOnlyDictionary<string, Mod> SupportedMods = new Dictionary<string, Mod>()
|
||||
{
|
||||
{ "BLOOD", new Mod("BLOOD", "Blood", "") },
|
||||
|
@ -1,37 +0,0 @@
|
||||
using Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
public static class NBloodServerStartInfo
|
||||
{
|
||||
private static readonly string workingDir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "blood");
|
||||
|
||||
public static ProcessStartInfo Get(int maxPlayers, int port, Mod mod)
|
||||
{
|
||||
var psi = new ProcessStartInfo(GetExecutable(), $"-server {maxPlayers} -port {port} -pname Server {mod.CommandLine}")
|
||||
{
|
||||
UseShellExecute = true,
|
||||
WorkingDirectory = workingDir
|
||||
};
|
||||
|
||||
return psi;
|
||||
}
|
||||
|
||||
private static string GetExecutable()
|
||||
{
|
||||
string nbloodServer = Constants.NBloodExecutable;
|
||||
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
if (isWindows)
|
||||
nbloodServer += ".exe";
|
||||
|
||||
return nbloodServer;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Text;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
public class PortUtils
|
||||
{
|
||||
public static int GetPort()
|
||||
{
|
||||
IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
|
||||
var usedPorts = ipGlobalProperties.GetActiveTcpConnections().Select(c => c.LocalEndPoint.Port).ToList();
|
||||
usedPorts.AddRange(ipGlobalProperties.GetActiveTcpListeners().Select(c => c.Port).ToList());
|
||||
usedPorts.AddRange(ipGlobalProperties.GetActiveUdpListeners().Select(c => c.Port).ToList());
|
||||
|
||||
var availablePorts = Enumerable.Range(1025, ushort.MaxValue).ToList().Except(usedPorts).ToList();
|
||||
|
||||
Random rnd = new Random();
|
||||
int index = rnd.Next(0, availablePorts.Count() - 1);
|
||||
int port = availablePorts[index];
|
||||
return port;
|
||||
}
|
||||
}
|
||||
}
|
86
Common/ProcessSpawner.cs
Normal file
86
Common/ProcessSpawner.cs
Normal file
@ -0,0 +1,86 @@
|
||||
using Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
public static class ProcessSpawner
|
||||
{
|
||||
private const int minPort = 23581;
|
||||
private const int maxPort = 23700;
|
||||
private const int maximumServers = 80;
|
||||
private const string nBloodExecutable = "nblood_server";
|
||||
|
||||
private static readonly string workingDir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "blood");
|
||||
private static readonly Random rnd = new Random();
|
||||
|
||||
public static SpawnedServerInfo SpawnServer(int players, string modName)
|
||||
{
|
||||
int serversRunning = Process.GetProcessesByName(nBloodExecutable).Count();
|
||||
if (serversRunning >= maximumServers)
|
||||
throw new Exception("The maximum number of servers are already running.");
|
||||
|
||||
Mod mod = GetModByName(modName);
|
||||
int port = GetPort();
|
||||
|
||||
var process = Process.Start(GetProcessStartInfo(players, port, mod));
|
||||
return new SpawnedServerInfo(process, port, mod);
|
||||
}
|
||||
|
||||
private static Mod GetModByName(string modName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(modName))
|
||||
return Constants.SupportedMods["BLOOD"];
|
||||
|
||||
if (!Constants.SupportedMods.ContainsKey(modName.ToUpper()))
|
||||
throw new Exception("This mod is not supported: " + modName);
|
||||
|
||||
return Constants.SupportedMods[modName.ToUpper()];
|
||||
}
|
||||
|
||||
private static int GetPort()
|
||||
{
|
||||
IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
|
||||
var usedPorts = ipGlobalProperties.GetActiveTcpConnections().Select(c => c.LocalEndPoint.Port).ToList();
|
||||
usedPorts.AddRange(ipGlobalProperties.GetActiveTcpListeners().Select(c => c.Port).ToList());
|
||||
usedPorts.AddRange(ipGlobalProperties.GetActiveUdpListeners().Select(c => c.Port).ToList());
|
||||
|
||||
var availablePorts = Enumerable.Range(minPort, maxPort - minPort + 1).ToList().Except(usedPorts).ToList();
|
||||
|
||||
if (availablePorts.Count == 0)
|
||||
throw new Exception($"Cannot obtain free port in range {minPort}-{maxPort}.");
|
||||
|
||||
int index = rnd.Next(0, availablePorts.Count() - 1);
|
||||
int port = availablePorts[index];
|
||||
return port;
|
||||
}
|
||||
|
||||
private static ProcessStartInfo GetProcessStartInfo(int maxPlayers, int port, Mod mod)
|
||||
{
|
||||
var psi = new ProcessStartInfo(GetExecutableName(), $"-server {maxPlayers} -port {port} -pname Server {mod.CommandLine}")
|
||||
{
|
||||
UseShellExecute = true,
|
||||
WorkingDirectory = workingDir
|
||||
};
|
||||
|
||||
return psi;
|
||||
}
|
||||
|
||||
private static string GetExecutableName()
|
||||
{
|
||||
string nbloodServer = nBloodExecutable;
|
||||
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
if (isWindows)
|
||||
nbloodServer += ".exe";
|
||||
|
||||
return nbloodServer;
|
||||
}
|
||||
}
|
||||
}
|
22
Common/SpawnedServerInfo.cs
Normal file
22
Common/SpawnedServerInfo.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
public class SpawnedServerInfo
|
||||
{
|
||||
public Process Process { get; }
|
||||
public int Port { get; }
|
||||
public Mod Mod { get; }
|
||||
|
||||
public SpawnedServerInfo(Process process, int port, Mod mod)
|
||||
{
|
||||
Process = process;
|
||||
Port = port;
|
||||
Mod = mod;
|
||||
}
|
||||
}
|
||||
}
|
@ -89,3 +89,5 @@ http://your.ip.goes.here:23580/nblood/api/startserver?players=3&modName=cryptic&
|
||||
The number of players must be at least 3 and maximum 8. The servers started with this URL won't be visible publicly via the `listservers` URL.
|
||||
The modName parameter can be `cryptic`, `dw`, `fo`, `twoira` or it can be missing.
|
||||
You can see the port and the command line command to join in the response.
|
||||
|
||||
Port range used: 23580-23700
|
||||
|
@ -25,6 +25,8 @@ namespace Supervisor
|
||||
}
|
||||
|
||||
private static void ProcessPacket(byte[] buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
string message = Encoding.ASCII.GetString(buffer);
|
||||
switch (message[0])
|
||||
@ -45,6 +47,11 @@ namespace Supervisor
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Log...
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessPlayerCountsPacket(string message)
|
||||
{
|
||||
|
@ -23,6 +23,8 @@ namespace Supervisor
|
||||
}
|
||||
|
||||
private static void KillUnusedServers()
|
||||
{
|
||||
try
|
||||
{
|
||||
var killables = Program.State.Servers.Values.Where(s =>
|
||||
s.IsPrivate
|
||||
@ -35,5 +37,10 @@ namespace Supervisor
|
||||
Process.GetProcessById(server.ProcessId).Kill();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
//Log...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ namespace Supervisor
|
||||
}
|
||||
|
||||
private static void RemoveCrashedServers()
|
||||
{
|
||||
try
|
||||
{
|
||||
var crashedServers = State.Servers.Values
|
||||
.Where(s => s.IsStarted && DateTime.UtcNow - s.LastHeartBeatUtc < TimeSpan.FromMinutes(15))
|
||||
@ -35,5 +37,10 @@ namespace Supervisor
|
||||
State.Servers.TryRemove(port, out _);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Log...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,25 +46,34 @@ namespace Supervisor
|
||||
{
|
||||
if (IsNewServerNeeded(i, mod))
|
||||
{
|
||||
int port = PortUtils.GetPort();
|
||||
var process = Process.Start(NBloodServerStartInfo.Get(i, port, mod));
|
||||
try
|
||||
{
|
||||
var spawnedServer = ProcessSpawner.SpawnServer(i, mod.Name);
|
||||
int port = spawnedServer.Port;
|
||||
int processId = spawnedServer.Process.Id;
|
||||
Program.State.Servers.AddOrUpdate(port, new Server()
|
||||
{
|
||||
Port = port,
|
||||
ProcessId = process.Id,
|
||||
ProcessId = processId,
|
||||
MaximumPlayers = i,
|
||||
CurrentPlayers = 1,
|
||||
Mod = mod,
|
||||
},
|
||||
(prt, server) =>
|
||||
{
|
||||
server.ProcessId = process.Id;
|
||||
server.ProcessId = processId;
|
||||
server.MaximumPlayers = i;
|
||||
server.CurrentPlayers = 1;
|
||||
server.Mod = mod;
|
||||
return server;
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
// No free ports, cannot create process
|
||||
// Log...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,8 @@ namespace Supervisor
|
||||
}
|
||||
|
||||
private static void ProcessWebApiMessage(byte[] buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
string message = Encoding.ASCII.GetString(buffer);
|
||||
switch (message[0])
|
||||
@ -44,6 +46,11 @@ namespace Supervisor
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Log...
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessGetCurrentStateRequest()
|
||||
{
|
||||
|
@ -1,17 +0,0 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebInterface
|
||||
{
|
||||
public static class CommandLineUtils
|
||||
{
|
||||
public static string GetLaunchCommand(string host, int port, Mod mod)
|
||||
{
|
||||
return $"nblood -client {host} -port {port} {mod.CommandLine}";
|
||||
}
|
||||
}
|
||||
}
|
@ -58,23 +58,20 @@ namespace WebInterface.Controllers
|
||||
if (parameters.ApiKey != _config.GetValue<string>("ApiKey"))
|
||||
return new StartServerResponse("Invalid ApiKey.");
|
||||
|
||||
string processName = Constants.NBloodExecutable;
|
||||
int serversRunning = Process.GetProcessesByName(processName).Count();
|
||||
if (serversRunning >= _config.GetValue<int>("MaximumServers"))
|
||||
return new StartServerResponse("The maximum number of servers are already running.");
|
||||
|
||||
Mod mod = GetMod(parameters.ModName);
|
||||
int port = PortUtils.GetPort();
|
||||
|
||||
var process = Process.Start(NBloodServerStartInfo.Get(parameters.Players, port, mod));
|
||||
byte[] payload = Encoding.ASCII.GetBytes($"B{port}\t{process.Id}\0");
|
||||
SpawnedServerInfo serverProcess = ProcessSpawner.SpawnServer(parameters.Players, parameters.ModName);
|
||||
byte[] payload = Encoding.ASCII.GetBytes($"B{serverProcess.Port}\t{serverProcess.Process.Id}\0");
|
||||
socket.SendTo(payload, webApiListenerEndPoint);
|
||||
|
||||
_logger.LogInformation("Server started waiting for {0} players on port {1}.",
|
||||
parameters.Players, port);
|
||||
parameters.Players, serverProcess.Port);
|
||||
|
||||
Thread.Sleep(TimeSpan.FromSeconds(2));
|
||||
return new StartServerResponse(port) { CommandLine = CommandLineUtils.GetLaunchCommand(HttpContext.Request.Host.Host, port, mod) };
|
||||
return new StartServerResponse(serverProcess.Port)
|
||||
{
|
||||
CommandLine = CommandLineUtils.GetClientLaunchCommand(HttpContext.Request.Host.Host,
|
||||
serverProcess.Port,
|
||||
serverProcess.Mod.CommandLine)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -108,16 +105,5 @@ namespace WebInterface.Controllers
|
||||
return new ListServersResponse("Unhandled exception has been occured. Check the logs for details.");
|
||||
}
|
||||
}
|
||||
|
||||
private Mod GetMod(string modName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(modName))
|
||||
return Constants.SupportedMods["BLOOD"];
|
||||
|
||||
if (!Constants.SupportedMods.ContainsKey(modName.ToUpper()))
|
||||
throw new Exception("This mod is not supported: " + modName);
|
||||
|
||||
return Constants.SupportedMods[modName.ToUpper()];
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using Model;
|
||||
using Common;
|
||||
using Model;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@ -28,7 +29,7 @@ namespace WebInterface.Services
|
||||
{
|
||||
Port = s.Port,
|
||||
IsStarted = s.IsStarted,
|
||||
CommandLine = s.CurrentPlayers == s.MaximumPlayers ? "Sorry, the game is already started." : CommandLineUtils.GetLaunchCommand(host, s.Port, s.Mod),
|
||||
CommandLine = GetCommandLine(s, host),
|
||||
GameType = s.GameType,
|
||||
Mod = s.Mod.FriendlyName,
|
||||
CurrentPlayers = s.CurrentPlayers,
|
||||
@ -41,6 +42,14 @@ namespace WebInterface.Services
|
||||
return serversResponse;
|
||||
}
|
||||
|
||||
public string GetCommandLine(Model.Server server, string host)
|
||||
{
|
||||
if (server.CurrentPlayers == server.MaximumPlayers)
|
||||
return "Sorry, the game is already started.";
|
||||
|
||||
return CommandLineUtils.GetClientLaunchCommand(host, server.Port, server.Mod.CommandLine);
|
||||
}
|
||||
|
||||
public GetStatisticsResponse GetStatistics()
|
||||
{
|
||||
StateResponse stateResponse = RequestState();
|
||||
|
@ -159,7 +159,7 @@
|
||||
</details>
|
||||
<br />
|
||||
<hr />
|
||||
<p>Do you think this page is ugly? You are right! If you want to make it better, PRs are welcomed (but please, don't use any JavaScript, thanks): <a href="https://github.com/CommonLoon102/NBloodServerSupervisor">https://github.com/CommonLoon102/NBloodServerSupervisor</a></p>
|
||||
<p>Do you think this page is ugly? You are right! If you want to make it look better, PRs are welcomed (but please, don't use any JavaScript, thanks): <a href="https://github.com/CommonLoon102/NBloodServerSupervisor">https://github.com/CommonLoon102/NBloodServerSupervisor</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -7,6 +7,5 @@
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"MaximumServers": 80,
|
||||
"ApiKey": "CHANGEME"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user