From 017b75579ff299079bb89c345869be3572bbd906 Mon Sep 17 00:00:00 2001 From: Malloc of Kuzkycyziklistan Date: Mon, 24 Apr 2017 16:04:52 -0500 Subject: [PATCH] websock refactor changed from a static delegate system to a managed instance template system --- server/ActiveConnection.cs | 12 +++++ server/CircleScape.csproj | 5 +- server/{server.sln => CircleScape.sln} | 0 server/PendingConnection.cs | 12 +++++ server/PoolManager.cs | 25 ++++++++++ server/Utilities.cs | 64 ++++++++++++++++++++++++++ server/Websocket/Connection.cs | 29 +++++++++++- server/Websocket/Frame.cs | 11 +++++ server/Websocket/Pool.cs | 58 +++++++++++++++++++---- server/Websocket/Stack.cs | 31 ++++++++++--- 10 files changed, 229 insertions(+), 18 deletions(-) create mode 100644 server/ActiveConnection.cs rename server/{server.sln => CircleScape.sln} (100%) create mode 100644 server/PendingConnection.cs create mode 100644 server/PoolManager.cs create mode 100644 server/Utilities.cs create mode 100644 server/Websocket/Frame.cs diff --git a/server/ActiveConnection.cs b/server/ActiveConnection.cs new file mode 100644 index 0000000..0d114e4 --- /dev/null +++ b/server/ActiveConnection.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace CircleScape { + class ActiveConnection : Websocket.Connection { + public ActiveConnection(TcpClient sock) : base(sock) { } + } +} diff --git a/server/CircleScape.csproj b/server/CircleScape.csproj index 8477976..d1b2c21 100644 --- a/server/CircleScape.csproj +++ b/server/CircleScape.csproj @@ -7,7 +7,7 @@ {438DBAC1-BA37-40BB-9CCE-0FE1F23C6DC5} Exe Properties - server + CircleScape server v4.5.2 512 @@ -57,8 +57,11 @@ + + + diff --git a/server/server.sln b/server/CircleScape.sln similarity index 100% rename from server/server.sln rename to server/CircleScape.sln diff --git a/server/PendingConnection.cs b/server/PendingConnection.cs new file mode 100644 index 0000000..cbac48e --- /dev/null +++ b/server/PendingConnection.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace CircleScape { + class PendingConnection : ActiveConnection { + public PendingConnection(TcpClient sock) : base(sock) { } + } +} diff --git a/server/PoolManager.cs b/server/PoolManager.cs new file mode 100644 index 0000000..dee7c56 --- /dev/null +++ b/server/PoolManager.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CircleScape.Websocket; + +namespace CircleScape { + static class PoolManager { + private static Pool PendingConnectionsPool; + private static Pool ActiveConnectionsPool; + + static PoolManager() { + PendingConnectionsPool = new Pool { + InitialCount = 1, + InitialSize = 10, + SizeGrowth = 10, + MaxSize = 50, + MaxCount = 5 + }; + + ActiveConnectionsPool = new Pool(); + } + } +} diff --git a/server/Utilities.cs b/server/Utilities.cs new file mode 100644 index 0000000..1d6e2cf --- /dev/null +++ b/server/Utilities.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace CircleScape { + public static class StringExtensions { + public enum kHashReturnType { + RAW, HEX, BASE64 + } + + public static string Base64Encode(this string str, bool isUtf8 = true) { + var raw = + isUtf8 ? Encoding.UTF8.GetBytes(str) + : Encoding.ASCII.GetBytes(str); + return Convert.ToBase64String(raw); + } + + public static string Base64Decode(this string str, bool isUtf8 = true) { + var raw = Convert.FromBase64String(str); + return isUtf8 ? Encoding.UTF8.GetString(raw) + : Encoding.ASCII.GetString(raw); + } + + public static string SHA1(this string str, kHashReturnType type = kHashReturnType.RAW) { + using(var hasher = System.Security.Cryptography.SHA1.Create()) { + return ParseRawHash( + hasher.ComputeHash(Encoding.ASCII.GetBytes(str)), + type + ); + } + } + + public static string MD5(this string str, kHashReturnType type = kHashReturnType.RAW) { + using(var hasher = System.Security.Cryptography.MD5.Create()) { + return ParseRawHash( + hasher.ComputeHash(Encoding.ASCII.GetBytes(str)), + type + ); + } + } + + private static string ParseRawHash(byte[] hash, kHashReturnType type) { + var raw = Encoding.ASCII.GetString(hash); + + switch(type) { + case kHashReturnType.BASE64: + return Base64Encode(raw, false); + case kHashReturnType.HEX: + return BitConverter.ToString(hash).Replace("-", ""); + case kHashReturnType.RAW: + default: + return raw; + } + } + } + + public static class NumericExtensions { + + } +} diff --git a/server/Websocket/Connection.cs b/server/Websocket/Connection.cs index 5cae9c1..498d26c 100644 --- a/server/Websocket/Connection.cs +++ b/server/Websocket/Connection.cs @@ -6,13 +6,40 @@ using System.Text; using System.Threading.Tasks; namespace CircleScape.Websocket { - class Connection { + abstract class Connection { private TcpClient Socket; + private NetworkStream Stream; + + public bool Disconnected { get; private set; } = false; + public string DisconnectReason { get; private set; } = null; + + public bool Handshaked { get; private set; } = false; + private string RawClientHandshake = ""; + private Dictionary Headers = + new Dictionary(StringComparer.OrdinalIgnoreCase); public Connection(TcpClient sock) { Socket = sock; + Socket.ReceiveTimeout = 1; + Stream = sock.GetStream(); } + public void Disconnect(string reason = null) { + Disconnected = true; + DisconnectReason = reason; + } + // called after the client successfully handshakes + public virtual void OnOpen() { } + + // called when the thread manager iterates through + // the thread list and stops on this thread + public virtual void OnParse() { } + + // called when data has been received + public virtual void OnReceive(byte[] data) { } + + // called when the connection is disconnected + public virtual void OnClose() { } } } diff --git a/server/Websocket/Frame.cs b/server/Websocket/Frame.cs new file mode 100644 index 0000000..7563ad3 --- /dev/null +++ b/server/Websocket/Frame.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CircleScape.Websocket { + class Frame { + + } +} diff --git a/server/Websocket/Pool.cs b/server/Websocket/Pool.cs index 3d04699..e507a5b 100644 --- a/server/Websocket/Pool.cs +++ b/server/Websocket/Pool.cs @@ -5,11 +5,25 @@ using System.Text; using System.Threading; namespace CircleScape.Websocket { - class Pool { - private const int InitialCount = 3; - private const int InitialSize = 3; - private const int SizeGrowth = 1; - private const int MaxSize = 10; + class Pool where T : Connection { + // number of threads that should be started when the pool is created + // these threads will run for as long as the pool exists + public int InitialCount { get; set; } = 3; + // amount of connections that should initially be allowed per thread + public int InitialSize { get; set; } = 3; + // amount of additional connections that each thread can handle after + // a new thread is created + public int SizeGrowth { get; set; } = 1; + // maximum amount of connections that a single thread will be assigned + public int MaxSize { get; set; } = 10; + // maximum number of threads that will be spawned + // 0 means no limit + public int MaxCount { get; set; } = 0; + // maximum number of connections in a thread that exceeds the calculated + // amount for the pool's thread count before the connection redistribution + // function is called + // 0 means never redistribute + public int Tolerance { get; set; } = 0; private int _fullThreadCount; private bool updateFullThreadCount = true; @@ -19,21 +33,47 @@ namespace CircleScape.Websocket { private Dictionary Connections = new Dictionary(); + private List InvalidThreads + = new List(); + private Mutex InvalidThreadsMutex = new Mutex(); + public Pool() { for(var i = 0; i < InitialCount; ++i) CreateThread(); } public bool AddConnection(Connection connection) { - foreach(var thread in Threads) { + if(InvalidThreads.Count > 0) { + foreach(var invalidThread in InvalidThreads) + Threads.RemoveAll(x => Object.ReferenceEquals(invalidThread, x)); + updateFullThreadCount = true; + InvalidThreads.RemoveAll(x => true); + } + + foreach(var thread in Threads) { + if(thread.Stack.Count < FullThreadSize) { + thread.Stack.AddClient(connection); + return true; + } + } + + if(MaxCount == 0 || Threads.Count < MaxCount) { + CreateThread(connection); + return true; } return false; } + public void InvalidateThread(Stack stackRef) { + var ctx = Threads.FirstOrDefault(x => Object.ReferenceEquals(x.Stack, stackRef)); + if(ctx != null) + InvalidThreads.Add(ctx); + } + private ThreadContext CreateThread(Connection initialConnection = null, bool runWithNoClients = false) { - var stack = new Stack(runWithNoClients, initialConnection); + var stack = new Stack(this, runWithNoClients, initialConnection); var ctx = new ThreadContext { Stack = stack, Thread = new Thread(new ThreadStart(stack.ManageStack)) @@ -44,7 +84,7 @@ namespace CircleScape.Websocket { return ctx; } - private int FullThreadCount { + private int FullThreadSize { get { if(updateFullThreadCount) { _fullThreadCount = Math.Min( @@ -59,7 +99,7 @@ namespace CircleScape.Websocket { class ThreadContext { public Thread Thread { get; set; } - public Stack Stack { get; set; } + public Stack Stack { get; set; } } } } diff --git a/server/Websocket/Stack.cs b/server/Websocket/Stack.cs index 968431e..06ebc1a 100644 --- a/server/Websocket/Stack.cs +++ b/server/Websocket/Stack.cs @@ -6,18 +6,22 @@ using System.Text; using System.Threading; namespace CircleScape.Websocket { - class Stack { + class Stack where T : Connection { + private Pool PoolRef = null; private List Clients = new List(); private Mutex ClientsMutex = new Mutex(); private bool RunWithNoClients = false; + private bool Running = true; + private bool _finished = false; - public Stack(Connection initialConnection = null) { + public Stack(Pool poolRef, Connection initialConnection = null) { + PoolRef = poolRef; if(initialConnection != null) Clients.Add(initialConnection); } - public Stack(bool runWithNoClients, Connection initialConnection = null) - : this(initialConnection) + public Stack(Pool poolRef, bool runWithNoClients, Connection initialConnection = null) + : this(poolRef, initialConnection) { RunWithNoClients = runWithNoClients; } @@ -32,17 +36,30 @@ namespace CircleScape.Websocket { return true; } - public int ClientCount { + public int Count { get { return Clients.Count; } } + public void StopThread() { + Running = false; + } + + public bool Finished { get; private set; } + // USED FOR THREADING -- DO NOT CALL public void ManageStack() { - int clientCount = ClientCount; + while(Running && (Count > 0 || RunWithNoClients)) { + for(var i = Count - 1; i >= 0 && Running; ++i) { + PoolRef.OnConnectionParse(Clients[i]); - for(var i = 0; i < clientCount; ++i) + + } + } + + Finished = true; + PoolRef.InvalidateThread(this); } } }