Add an alternative NodeLocator that doesn't rely on Redis for smaller multi-server setups with a single Master.

This commit is contained in:
Michael Biggins committed 2020-09-28 16:29:21 +01:00
1 parent 5284f0c2fe
commit 2f58fc0671
5 files changed
+156 -8

No files matched your search

+1
View File
@@ -3,3 +3,4 @@
################################################################################
/.vscode/tasks.json
/.vscode/launch.json
@@ -8,7 +8,9 @@ namespace Impostor.Server.Data
public bool Enabled { get; set; }
public bool Master { get; set; }
public bool UseRedis { get; set; }
public string Redis { get; set; }
public string UDPMasterEndpoint { get; set; }
public List<ServerRedirectorNode> Nodes { get; set; }
}
}
@@ -0,0 +1,136 @@
using Impostor.Server.Data;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Impostor.Server.Net.Redirector
{
public class NodeLocatorUDPSockets : INodeLocator, IDisposable
{
readonly UdpClient client;
private bool disposedValue;
private class AvailableNode {
public IPEndPoint Endpoint { get; set; }
public DateTime LastUpdated { get; set; }
public bool Expired => LastUpdated < DateTime.Now.AddHours(-1);
}
private Dictionary<string, AvailableNode> AvailableNodes;
internal NodeLocatorUDPSockets(IOptions<ServerRedirectorConfig> config)
{
if (!Uri.TryCreate(config.Value.UDPMasterEndpoint, UriKind.Absolute, out Uri ServerAddress) || ServerAddress.Scheme.ToLower() != "udp")
{
throw new Exception($"UDPMasterEndpoint has an invalid value of '{config.Value.UDPMasterEndpoint}'. Expected udp://ip:port/ schema.");
}
if (config.Value.Master)
{
if (!IPAddress.TryParse(ServerAddress.Host, out var LocalIPBinding)) {
throw new Exception("$UDPMasterEndpoint must use an IP address rather than a hostname when acting as a master.");
}
client = new UdpClient(new IPEndPoint(LocalIPBinding, ServerAddress.Port));
AvailableNodes = new Dictionary<string, AvailableNode>();
Task.Run(HandleClientPackets);
}
else
{
client = new UdpClient(ServerAddress.Host, ServerAddress.Port);
}
}
public IPEndPoint Find(string gameCode)
{
lock (AvailableNodes)
{
return AvailableNodes.ContainsKey(gameCode) ? AvailableNodes[gameCode].Endpoint : null;
}
}
private void HandleClientPackets()
{
try
{
while (true)
{
var data = client.ReceiveAsync().GetAwaiter().GetResult();
//TODO: Log that we got an update from the given remote EP.
string message = Encoding.UTF8.GetString(data.Buffer);
var parts = message.Split(',', 2);
if (parts.Length != 2) { continue; }
if (!IPEndPoint.TryParse(parts[1], out var Endpoint)) { continue; }
HandleUpdate(parts[0], Endpoint);
}
}
catch (ObjectDisposedException)
{
return; //Bail out and don't try anything else.
}
catch
{
//Something else went wrong but we're not shutting down, give up and try again.
if (!disposedValue) { Task.Run(HandleClientPackets); }
}
}
private void HandleUpdate(string gameCode, IPEndPoint endpoint)
{
lock (AvailableNodes)
{
var node = AvailableNodes.ContainsKey(gameCode) ? AvailableNodes[gameCode] : new AvailableNode();
node.Endpoint = endpoint;
node.LastUpdated = DateTime.Now;
AvailableNodes[gameCode] = node;
var KeysToRemove = AvailableNodes.Where(kvp => kvp.Value.Expired).Select(kvp => kvp.Key).ToList();
KeysToRemove.ForEach(n => AvailableNodes.Remove(n));
}
}
public void Remove(string gameCode)
{
lock (AvailableNodes)
{
AvailableNodes.Remove(gameCode);
}
}
public void Save(string gameCode, IPEndPoint endPoint)
{
byte[] data = Encoding.UTF8.GetBytes($"{endPoint},{gameCode}");
client.Send(data, data.Length);
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
client.Close();
client.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
+15 -8
View File
@@ -66,18 +66,25 @@ namespace Impostor.Server
{
// When joining a game, it retrieves the game server ip from redis.
// When a game has been created on this node, it stores the game code with its ip in redis.
services.AddSingleton<INodeLocator, NodeLocatorRedis>();
if (redirector.UseRedis)
{
services.AddSingleton<INodeLocator, NodeLocatorRedis>();
// Dependency for the NodeLocatorRedis.
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = redirector.Redis;
options.InstanceName = "ImpostorRedis";
});
}
else
{
services.AddSingleton<INodeLocator, NodeLocatorUDPSockets>();
}
// Use the configuration as source for the list of nodes to provide
// when creating a game.
services.AddSingleton<INodeProvider, NodeProviderConfig>();
// Dependency for the NodeLocatorRedis.
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = redirector.Redis;
options.InstanceName = "ImpostorRedis";
});
}
else
{
+2
View File
@@ -8,7 +8,9 @@
"ServerRedirector": {
"Enabled": false,
"Master": true,
"UseRedis": true,
"Redis": "127.0.0.1:6379",
"UDPMasterEndpoint": "127.0.0.1:32320",
"Nodes": [
{
"Ip": "127.0.0.1",