websock refactor
changed from a static delegate system to a managed instance template system
This commit is contained in:
parent
664d4220a9
commit
017b75579f
10 changed files with 229 additions and 18 deletions
12
server/ActiveConnection.cs
Normal file
12
server/ActiveConnection.cs
Normal file
|
@ -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) { }
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
<ProjectGuid>{438DBAC1-BA37-40BB-9CCE-0FE1F23C6DC5}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>server</RootNamespace>
|
||||
<RootNamespace>CircleScape</RootNamespace>
|
||||
<AssemblyName>server</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
|
@ -57,8 +57,11 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Entrypoint.cs" />
|
||||
<Compile Include="PoolManager.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Utilities.cs" />
|
||||
<Compile Include="Websocket\Connection.cs" />
|
||||
<Compile Include="Websocket\Frame.cs" />
|
||||
<Compile Include="Websocket\Pool.cs" />
|
||||
<Compile Include="Websocket\Server.cs" />
|
||||
<Compile Include="Websocket\Stack.cs" />
|
||||
|
|
12
server/PendingConnection.cs
Normal file
12
server/PendingConnection.cs
Normal file
|
@ -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) { }
|
||||
}
|
||||
}
|
25
server/PoolManager.cs
Normal file
25
server/PoolManager.cs
Normal file
|
@ -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<PendingConnection> PendingConnectionsPool;
|
||||
private static Pool<ActiveConnection> ActiveConnectionsPool;
|
||||
|
||||
static PoolManager() {
|
||||
PendingConnectionsPool = new Pool<PendingConnection> {
|
||||
InitialCount = 1,
|
||||
InitialSize = 10,
|
||||
SizeGrowth = 10,
|
||||
MaxSize = 50,
|
||||
MaxCount = 5
|
||||
};
|
||||
|
||||
ActiveConnectionsPool = new Pool<ActiveConnection>();
|
||||
}
|
||||
}
|
||||
}
|
64
server/Utilities.cs
Normal file
64
server/Utilities.cs
Normal file
|
@ -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 {
|
||||
|
||||
}
|
||||
}
|
|
@ -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<string, string> Headers =
|
||||
new Dictionary<string, string>(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() { }
|
||||
}
|
||||
}
|
||||
|
|
11
server/Websocket/Frame.cs
Normal file
11
server/Websocket/Frame.cs
Normal file
|
@ -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 {
|
||||
|
||||
}
|
||||
}
|
|
@ -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<T> 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<UInt64, Connection> Connections
|
||||
= new Dictionary<UInt64, Connection>();
|
||||
|
||||
private List<ThreadContext> InvalidThreads
|
||||
= new List<ThreadContext>();
|
||||
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<T> 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<T>(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<T> Stack { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,18 +6,22 @@ using System.Text;
|
|||
using System.Threading;
|
||||
|
||||
namespace CircleScape.Websocket {
|
||||
class Stack {
|
||||
class Stack<T> where T : Connection {
|
||||
private Pool<T> PoolRef = null;
|
||||
private List<Connection> Clients = new List<Connection>();
|
||||
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<T> poolRef, Connection initialConnection = null) {
|
||||
PoolRef = poolRef;
|
||||
if(initialConnection != null)
|
||||
Clients.Add(initialConnection);
|
||||
}
|
||||
|
||||
public Stack(bool runWithNoClients, Connection initialConnection = null)
|
||||
: this(initialConnection)
|
||||
public Stack(Pool<T> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue