]> git.deb.at Git - rhonda/impostor.git/commitdiff
Add an alternative NodeLocator that doesn't rely on Redis for smaller multi-server...
authorMichael Biggins <mike@cubecoders.com>
Mon, 28 Sep 2020 15:29:21 +0000 (16:29 +0100)
committerMichael Biggins <mike@cubecoders.com>
Mon, 28 Sep 2020 15:29:21 +0000 (16:29 +0100)
.gitignore
src/Impostor.Server/Data/ServerRedirectorConfig.cs
src/Impostor.Server/Net/Redirector/NodeLocatorUDPSockets.cs [new file with mode: 0644]
src/Impostor.Server/Program.cs
src/Impostor.Server/config.json

index 2da2c0613d0895027de97a7cf4f2c13ac44c4616..33eefb43e466a20e30937b2a28ef810f546ff094 100644 (file)
@@ -3,3 +3,4 @@
 ################################################################################
 
 /.vscode/tasks.json
+/.vscode/launch.json
index cd5b4d729eab0406e42fc35af46bedc02ee09a59..188ea781d5058354f4c2cda221a5fa09cb5041ff 100644 (file)
@@ -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; }
     }
 }
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Redirector/NodeLocatorUDPSockets.cs b/src/Impostor.Server/Net/Redirector/NodeLocatorUDPSockets.cs
new file mode 100644 (file)
index 0000000..8f07e20
--- /dev/null
@@ -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);
+        }
+    }
+}
index 7a665f1db62f46495edf69feb012b6d7fd8da406..03e80c7da67b158888f2e119de65bc863e8180aa 100644 (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
                     {
index 6c8f49a8be2810ede26d6f2452ceaac1940bccdd..dd8b41f4a4f3967b8ecf52dbf6763688a09ff568 100644 (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",