commit c00d3b14266ed2dee01bdd962b2f540145372d7c Author: Derek Holloway Date: Mon May 12 18:04:18 2025 -0700 init commit over here diff --git a/CLI/Help/Documentation.txt b/CLI/Help/Documentation.txt new file mode 100644 index 0000000..a35a7c6 --- /dev/null +++ b/CLI/Help/Documentation.txt @@ -0,0 +1,15 @@ +The servers default port is 25566 + + +TCPServer.cs + Void TCPServer() Initilizes the server object on the default game port of 25566 + Void SendAll(string Data) Send data to all the clients + Event onReceived Fires when data is received from a client + +TCPClient.cs + Void TCPClient(String ServerIP) Initilizes the client object + Void Send(String Data) Sends data to the server + Event onReceived Fires when data is received from the server + +UDPClient.cs + \ No newline at end of file diff --git a/CLI/Help/IdeaMap.jpg b/CLI/Help/IdeaMap.jpg new file mode 100644 index 0000000..ccbc839 Binary files /dev/null and b/CLI/Help/IdeaMap.jpg differ diff --git a/CLI/HelpDocumentation.cs b/CLI/HelpDocumentation.cs new file mode 100644 index 0000000..2c9c6d2 --- /dev/null +++ b/CLI/HelpDocumentation.cs @@ -0,0 +1,33 @@ +using MistoxServer; +using System; + +namespace MistoxHolePunch { + class HelpDocumentation { + +public static string HelpText = @" +-------------- Help Page for Mistox Game Server -------------- + + Usage: MistServer ServerIP [Command] [Options] + + Command Linux Style Command Meaning + /c -c Start The Client + /s -s Start The Server + + ServerOptions + /s -s [options] + /p -p The port that will be used for the server [Includes {port} + 1] + /a -a Uses the Athoratative model for the server + + ClientOptions + /c -c [options] + /h -h The Hostname or IP of the server + /p -p The port of the server + + Examples: + MistServer.exe /c 127.0.0.1 /p 25550 /u Mistox Start the client connecting to server 127.0.0.1 using port 25550 + MistServer.exe /c 127.0.0.1 Start the client connecting to server 127.0.0.1 using port 25567 + MistServer.exe /s Start the server +" + ; + } +} \ No newline at end of file diff --git a/CLI/MistServer.csproj b/CLI/MistServer.csproj new file mode 100644 index 0000000..04cf650 --- /dev/null +++ b/CLI/MistServer.csproj @@ -0,0 +1,25 @@ + + + + Exe + net8.0 + MistoxHolePunch.Program + Mistox Game Server + Mistox + Mistox.net + Mistox Game Server + Game Server for Mistox Games and partners + false + + + + none + none + false + + + + + + + diff --git a/CLI/MistServer.csproj.user b/CLI/MistServer.csproj.user new file mode 100644 index 0000000..4079ca7 --- /dev/null +++ b/CLI/MistServer.csproj.user @@ -0,0 +1,10 @@ + + + + <_LastSelectedProfileId>F:\Users\Derek\Desktop\GameServerStandard\CLI\Properties\PublishProfiles\FolderProfile.pubxml + MistServer + + + ProjectDebugger + + \ No newline at end of file diff --git a/CLI/Program.cs b/CLI/Program.cs new file mode 100644 index 0000000..de7c053 --- /dev/null +++ b/CLI/Program.cs @@ -0,0 +1,114 @@ +using System; +using System.Threading.Tasks; +using MistoxServer; + +namespace MistoxHolePunch { + class Program { + + static IMistoxServer serverObj; + static bool running = true; + + void onConnected( object sender, EventArgs e ) { + // sender and e are always null + // put connected functions in here + } + + void slowReceive( object obj, EventArgs e ) { + if ( serverObj is ServerInterface ) { + // If ServerMode is passive obj is byte[] and Send takes in byte[] + // If Servermode is Athoritiative obj is the Class type sent and Send takes in any Class type except generic object + + // Check to make sure data is correct before relaying + // Also perform server specific checks in here + // IE player didnt teleport or is shooting from 20 feet from his body + serverObj.Send( obj, SendType.SlowUpdate ); + } + Console.Write( "Received TCP : " ); + Console.WriteLine( obj ); + } + + void fastReceive( object obj, EventArgs e ) { + if( serverObj is ServerInterface ) { + // If ServerMode is passive obj is byte[] and Send takes in byte[] + // If Servermode is Athoritiative obj is the Class type sent and Send takes in any Class type except generic object + + // Check to make sure data is correct before relaying + // Also perform server specific checks in here + // IE player didnt teleport or is shooting from 20 feet from his body + serverObj.Send( obj, SendType.FastUpdate ); + } + Console.Write( "Received UDP : " ); + Console.WriteLine( obj ); + } + + void onDisconnected( object sender, EventArgs e ) { + // sender and e are always null + // put disconnected functions in here + } + + static string host = "example.com"; + static int port = 7300; + static ServerMode mode = ServerMode.Passive; + + async Task RunServer() { + serverObj = mServer.newServer( Convert.ToInt32( port ), mode ); + serverObj.onConnected += onConnected; + serverObj.onSlowReceive += slowReceive; + serverObj.onFastReceive += fastReceive; + serverObj.onDisconnected += onDisconnected; + while ( running ) { + // Stop this thread for 1 second so the CPU isnt 100% + // All the real work is on different threads + // While loop needs to exist so our main thread doesnt close + await Task.Delay( 1000 ); + } + } + + async Task RunClient() { + serverObj = mServer.newClient( host, Convert.ToInt32( port ) ); + serverObj.onConnected += onConnected; + serverObj.onSlowReceive += slowReceive; + serverObj.onFastReceive += fastReceive; + serverObj.onDisconnected += onDisconnected; + while( running ) { + // Stop this thread for 1 second so the CPU isnt 100% + // All the real work is on different threads + // While loop needs to exist so our main thread doesnt close + await Task.Delay( 1000 ); + } + } + + static async Task Main(string[] args) { + string Task = args.Length > 0 ? args[0].ToLower() : null; + + for( int i = 0; i < args.Length; i++ ) { + string cur = args[i].ToLower(); + if( cur == "/h" || cur == "-h" ) { + host = args [i + 1]; + } else if( cur == "/p" || cur == "-p" ) { + port = Convert.ToInt32( args [i + 1] ); + } else if( cur == "/a" || cur == "-a" ) { + mode = ServerMode.Authoritative; + } + } + + if (Task == "/?" || Task == "--help") { + Console.WriteLine(HelpDocumentation.HelpText); + } else if (Task == "/s" || Task == "-s") { + Program prog = new Program(); + await prog.RunServer(); + } else if (Task == "/c" || Task == "-c") { + Program prog = new Program(); + await prog.RunClient(); + } else { + host = "example.com"; + port = 1500; + Program prog = new Program(); + //prog.RunClient(); + await prog.RunServer(); + } + } + + } + +} \ No newline at end of file diff --git a/CLI/Properties/PublishProfiles/FolderProfile.pubxml b/CLI/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..5e1a8d4 --- /dev/null +++ b/CLI/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,18 @@ + + + + + Release + Any CPU + F:\Users\Derek\Desktop + FileSystem + <_TargetId>Folder + net8.0 + linux-x64 + true + true + true + + \ No newline at end of file diff --git a/CLI/Properties/PublishProfiles/FolderProfile.pubxml.user b/CLI/Properties/PublishProfiles/FolderProfile.pubxml.user new file mode 100644 index 0000000..531b631 --- /dev/null +++ b/CLI/Properties/PublishProfiles/FolderProfile.pubxml.user @@ -0,0 +1,10 @@ + + + + + True|2023-12-26T22:53:03.7982931Z;True|2023-12-25T20:43:03.3448404-08:00;True|2023-12-25T20:38:14.9173619-08:00;True|2023-12-25T15:49:11.8551513-08:00;True|2023-12-25T15:43:58.6114277-08:00;True|2023-12-25T13:38:16.4384268-08:00;True|2023-12-25T13:05:11.8207105-08:00;True|2023-12-25T12:52:06.9027395-08:00;True|2023-12-25T12:49:31.5840138-08:00;True|2023-12-25T12:44:52.1638218-08:00;True|2023-12-25T12:44:04.8184020-08:00;True|2023-12-25T12:23:48.6142003-08:00;True|2023-12-25T12:19:14.4957590-08:00;True|2023-12-25T12:15:47.1143818-08:00;True|2023-12-25T12:01:00.8637339-08:00; + + + \ No newline at end of file diff --git a/MistoxServer.sln b/MistoxServer.sln new file mode 100644 index 0000000..7e325fc --- /dev/null +++ b/MistoxServer.sln @@ -0,0 +1,71 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32519.379 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MistServer", "CLI\MistServer.csproj", "{FA40271A-DDC7-4F53-AD00-D6034A524403}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MistServerModule", "MistoxServer\MistServerModule.csproj", "{E9C6F813-3AF5-4273-9845-5D9E4D7842F1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Debug|ARM.ActiveCfg = Debug|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Debug|ARM.Build.0 = Debug|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Debug|ARM64.Build.0 = Debug|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Debug|x64.ActiveCfg = Debug|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Debug|x64.Build.0 = Debug|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Debug|x86.ActiveCfg = Debug|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Debug|x86.Build.0 = Debug|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Release|Any CPU.Build.0 = Release|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Release|ARM.ActiveCfg = Release|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Release|ARM.Build.0 = Release|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Release|ARM64.ActiveCfg = Release|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Release|ARM64.Build.0 = Release|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Release|x64.ActiveCfg = Release|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Release|x64.Build.0 = Release|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Release|x86.ActiveCfg = Release|Any CPU + {FA40271A-DDC7-4F53-AD00-D6034A524403}.Release|x86.Build.0 = Release|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Debug|ARM.ActiveCfg = Debug|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Debug|ARM.Build.0 = Debug|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Debug|ARM64.Build.0 = Debug|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Debug|x64.ActiveCfg = Debug|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Debug|x64.Build.0 = Debug|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Debug|x86.ActiveCfg = Debug|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Debug|x86.Build.0 = Debug|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Release|Any CPU.Build.0 = Release|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Release|ARM.ActiveCfg = Release|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Release|ARM.Build.0 = Release|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Release|ARM64.ActiveCfg = Release|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Release|ARM64.Build.0 = Release|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Release|x64.ActiveCfg = Release|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Release|x64.Build.0 = Release|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Release|x86.ActiveCfg = Release|Any CPU + {E9C6F813-3AF5-4273-9845-5D9E4D7842F1}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C6AB3FF2-DA2C-4A0F-8A5D-488A3D103106} + EndGlobalSection +EndGlobal diff --git a/MistoxServer/Client/TCPClient.cs b/MistoxServer/Client/TCPClient.cs new file mode 100644 index 0000000..81cd9be --- /dev/null +++ b/MistoxServer/Client/TCPClient.cs @@ -0,0 +1,71 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +//IP Range Updater + +namespace MistoxServer.Client { + public class mTCPClient : IDisposable { + + TcpClient Server; + + public event EventHandler onConnected; + public event EventHandler onReceived; + public event EventHandler onDisconnected; + + public bool Alive; + bool notified = false; + + public mTCPClient( IPEndPoint ServerAddress ) { + Server = new TcpClient( AddressFamily.InterNetwork ); + Server.NoDelay = true; + Server.Connect( ServerAddress ); + Alive = true; + Thread RThread = new Thread(async () => { await ReceiveThread(); }); + RThread.Start(); + } + + async Task ReceiveThread() { + using( NetworkStream ns = Server.GetStream() ) { + try { + while( Alive ) { + if( Server.Connected && !notified ) { + Console.WriteLine( "Connected to server" ); + onConnected?.Invoke( new object(), new EventArgs() ); + notified = true; + } else if( !Server.Connected ) { + Console.WriteLine( "Disconnected from server" ); + onDisconnected?.Invoke( new object(), new EventArgs() ); + Alive = false; + } + byte[] StreamData = new byte[1024]; + int bytesRead = await ns.ReadAsync(StreamData, 0, StreamData.Length); + if( bytesRead > 0 ) { + dynamic data = mSerialize.tReceive( StreamData.Sub( 0, bytesRead ) ); + if( data != null ) { + onReceived?.Invoke( data, new EventArgs() ); + } + } + } + } catch( Exception ) { + Console.WriteLine( "You have disconnected from the server" ); + onDisconnected?.Invoke( new object(), new EventArgs() ); + Alive = false; + } + } + } + + public async Task Send(Packet packet) { + byte[] data = mSerialize.PacketSerialize( packet ); + await Server.GetStream().WriteAsync( data, 0, data.Length ); + } + + public void Dispose() { + Alive = false; + Server.Close(); + Server = null; + } + } +} diff --git a/MistoxServer/Client/UDPClient.cs b/MistoxServer/Client/UDPClient.cs new file mode 100644 index 0000000..5ad6c25 --- /dev/null +++ b/MistoxServer/Client/UDPClient.cs @@ -0,0 +1,66 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +// Network Sync + +namespace MistoxServer.Client { + public class mUDPClient : IDisposable { + + public event EventHandler onReceived; + ServerMode Mode; + + Socket udpClient; + bool Alive; + int Port; + + public mUDPClient( IPEndPoint ServerAddress, ServerMode mode ) { + udpClient = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp ); + udpClient.SetSocketOption( SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true ); + udpClient.SetSocketOption( SocketOptionLevel.IP, SocketOptionName.IpTimeToLive, 128 ); + udpClient.Bind( new IPEndPoint( IPAddress.Any, ServerAddress.Port ) ); + Alive = true; + Port = ServerAddress.Port; + Mode = mode; + Thread Client = new Thread(async() => { await ReceiveThread(); }); + Client.Start(); + } + + async Task ReceiveThread() { + while( Alive ) { + try { + byte[] buffer = new byte[1024]; + int bytesRead = await udpClient.ReceiveAsync( buffer ); + if (Mode == ServerMode.Passive) { + onReceived?.Invoke( buffer.Sub( 0, bytesRead ), new EventArgs() ); + } else if (Mode == ServerMode.Authoritative) { + dynamic data = mSerialize.uReceive( buffer.Sub(0, bytesRead) ); + if( data != null ) { + onReceived?.Invoke( data, new EventArgs() ); + } + } + } catch( Exception ) { + + } + } + } + + public async Task Send( Packet Data, IPEndPoint remoteHost ) { + if (Mode == ServerMode.Authoritative) { + byte[] byteData = mSerialize.PacketSerialize( Data ); + await udpClient.SendToAsync( byteData, SocketFlags.None, new IPEndPoint( remoteHost.Address, Port ) ); + } else if (Mode == ServerMode.Passive) { + byte[] byteData = Data as byte[]; + await udpClient.SendToAsync( byteData, SocketFlags.None, new IPEndPoint( remoteHost.Address, Port ) ); + } + } + + public void Dispose() { + Alive = false; + udpClient.Close(); + udpClient = null; + } + } +} diff --git a/MistoxServer/Interface/ClientInterface.cs b/MistoxServer/Interface/ClientInterface.cs new file mode 100644 index 0000000..c5e63ed --- /dev/null +++ b/MistoxServer/Interface/ClientInterface.cs @@ -0,0 +1,59 @@ +using MistoxServer.Client; +using System; +using System.Net; +using System.Threading.Tasks; + +namespace MistoxServer { + public class ClientInterface : IMistoxServer { + + mTCPClient SlowUpdate; + mUDPClient FastUpdate; + IPEndPoint mPEndPoint; + + public event EventHandler onConnected; + public event EventHandler onSlowReceive; + public event EventHandler onFastReceive; + public event EventHandler onDisconnected; + + public ClientInterface( string IpOrHostName, int Port ) { + // Get Server IP + IPHostEntry host = Dns.GetHostEntry( IpOrHostName ); + + foreach( IPAddress entry in host.AddressList ) { + if ( entry.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ) { + mPEndPoint = new IPEndPoint( entry, Port ); + } + } + + Console.WriteLine( "The client is initilized and trying to connect to the server at ip : " + mPEndPoint.Address ); + // Make a UDP connection to the server + try { + FastUpdate = new mUDPClient( mPEndPoint, ServerMode.Authoritative ); + FastUpdate.onReceived += (object o, EventArgs e) => { onFastReceive?.Invoke( o, e ); }; + } catch( Exception e ) { + Console.WriteLine( "An error has occured with the connection to the server. Error { " ); + Console.WriteLine( e.ToString() ); + Console.WriteLine( "}" ); + } + // Make a TCP connection to the server + try { + SlowUpdate = new mTCPClient( mPEndPoint ); + SlowUpdate.onConnected += ( object o, EventArgs e ) => { onConnected?.Invoke( o, e ); }; + SlowUpdate.onReceived += ( object o, EventArgs e ) => { onSlowReceive?.Invoke( o, e ); }; + SlowUpdate.onDisconnected += ( object o, EventArgs e ) => { onDisconnected?.Invoke( o, e ); }; + } catch( Exception e ) { + Console.WriteLine( "An error has occured with the connection to the server. Error { " ); + Console.WriteLine( e.ToString() ); + Console.WriteLine( "}" ); + } + } + + public async Task Send(Packet data, SendType speed) { + if (SendType.SlowUpdate == speed) { + await SlowUpdate.Send( data ); + } else { + await FastUpdate.Send( data, mPEndPoint ); + } + } + } +} diff --git a/MistoxServer/Interface/Interface.cs b/MistoxServer/Interface/Interface.cs new file mode 100644 index 0000000..e8b2648 --- /dev/null +++ b/MistoxServer/Interface/Interface.cs @@ -0,0 +1,50 @@ +using System; +using System.Net; +using System.Threading.Tasks; + +namespace MistoxServer { + public partial class mServer { + public static IMistoxServer newServer(int port, ServerMode mode) { + return new ServerInterface(port, mode); + } + + public static IMistoxServer newClient(string ServerIPOrHostName, int Port) { + int index = 0; + IPAddress[] remoteAddress = Dns.GetHostAddresses(ServerIPOrHostName); + for( int i=0; i< remoteAddress.Length; i++ ) { + if( remoteAddress[i].AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork || remoteAddress [i].AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6 ) { + index = i; + } + } + string ipAddress = remoteAddress[index].ToString(); + if ( remoteAddress.Length > 0) { + return new ClientInterface(ipAddress, Port); + } else { + Console.WriteLine("The server at " + ServerIPOrHostName + " doesn't exit or cannot be found"); + return null; + } + } + } + + public interface IMistoxServer { + + public event EventHandler onConnected; + public event EventHandler onSlowReceive; + public event EventHandler onFastReceive; + public event EventHandler onDisconnected; + + public Task Send(Packet data, SendType speed); + + } + + public enum SendType { + SlowUpdate, + FastUpdate + } + + public enum ServerMode { + Passive, + Authoritative + } + +} diff --git a/MistoxServer/Interface/ServerInterface.cs b/MistoxServer/Interface/ServerInterface.cs new file mode 100644 index 0000000..3f91fa5 --- /dev/null +++ b/MistoxServer/Interface/ServerInterface.cs @@ -0,0 +1,57 @@ +using MistoxServer.Client; +using MistoxServer.Server; +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +namespace MistoxServer { + public class ServerInterface : IMistoxServer { + + mUDPClient FastUpdateServer; + mTCPListener SlowUpdateServer; + List Connections = new List(); + + public event EventHandler onConnected; + public event EventHandler onSlowReceive; + public event EventHandler onFastReceive; + public event EventHandler onDisconnected; + + public ServerInterface( int port, ServerMode mode ) { + FastUpdateServer = new mUDPClient( new IPEndPoint( IPAddress.IPv6Any, port ), mode ); + SlowUpdateServer = new mTCPListener( port, mode ); + + SlowUpdateServer.onConnected += OnConnected; + FastUpdateServer.onReceived += ( object o, EventArgs e ) => { onFastReceive?.Invoke( o, e ); }; + SlowUpdateServer.onDisconnected += OnDisconnected; + + Console.WriteLine( "The Server is initilized and waiting for clients to connect at port : " + port ); + } + + void OnConnected( object sender, EventArgs e ) { + Connection user = (Connection)sender; + onConnected?.Invoke( sender, e ); + user.slowClient.onReceived+= ( object o, EventArgs e ) => { onSlowReceive?.Invoke( o, e ); }; + user.slowClient.onDisconnected += OnDisconnected; + Connections.Add( user ); + } + + void OnDisconnected( object sender, EventArgs e ) { + Connection user = (Connection)sender; + onDisconnected?.Invoke( sender, e ); + Connections.Remove( user ); + } + + public async Task Send( Packet data, SendType speed ) { + if (speed == SendType.SlowUpdate) { + foreach( Connection cur in Connections ) { + await cur.slowClient.Send( data ); + } + } else { + foreach( Connection cur in Connections ) { + await FastUpdateServer.Send( data, cur.fastClient ); + } + } + } + } +} diff --git a/MistoxServer/MistServerModule.csproj b/MistoxServer/MistServerModule.csproj new file mode 100644 index 0000000..66c5f41 --- /dev/null +++ b/MistoxServer/MistServerModule.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + + + + + + + diff --git a/MistoxServer/MistoxServer.sln b/MistoxServer/MistoxServer.sln new file mode 100644 index 0000000..f9bb559 --- /dev/null +++ b/MistoxServer/MistoxServer.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31729.503 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MistoxServer", "MistoxServer.csproj", "{947D92F0-B737-4D1D-857B-F4EAB70D6980}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CLI", "..\CLI\CLI.csproj", "{B8EA2F11-62A3-45B2-8FDC-E734FD6B87E1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {947D92F0-B737-4D1D-857B-F4EAB70D6980}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {947D92F0-B737-4D1D-857B-F4EAB70D6980}.Debug|Any CPU.Build.0 = Debug|Any CPU + {947D92F0-B737-4D1D-857B-F4EAB70D6980}.Release|Any CPU.ActiveCfg = Release|Any CPU + {947D92F0-B737-4D1D-857B-F4EAB70D6980}.Release|Any CPU.Build.0 = Release|Any CPU + {B8EA2F11-62A3-45B2-8FDC-E734FD6B87E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8EA2F11-62A3-45B2-8FDC-E734FD6B87E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8EA2F11-62A3-45B2-8FDC-E734FD6B87E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8EA2F11-62A3-45B2-8FDC-E734FD6B87E1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {59213D9A-3888-4C3D-9EB7-BB116FAB1EB7} + EndGlobalSection +EndGlobal diff --git a/MistoxServer/Server/Connection.cs b/MistoxServer/Server/Connection.cs new file mode 100644 index 0000000..9b4dedc --- /dev/null +++ b/MistoxServer/Server/Connection.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MistoxServer.Server { + + public class Connection { + public mTCPServer slowClient { get; set; } + public IPEndPoint fastClient { get; set; } + } + +} diff --git a/MistoxServer/Server/ServerListener.cs b/MistoxServer/Server/ServerListener.cs new file mode 100644 index 0000000..b573ca3 --- /dev/null +++ b/MistoxServer/Server/ServerListener.cs @@ -0,0 +1,58 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +// Client Connections + +namespace MistoxServer.Server { + public class mTCPListener : IDisposable { + + public event EventHandler onConnected; + public event EventHandler onDisconnected; + public event EventHandler onReceive; + + TcpListener Listener; + ServerMode ServerMode; + bool Alive; + int port; + + public mTCPListener( int Port, ServerMode mode ) { + port = Port; + Alive = true; + ServerMode = mode; + Thread ConnectionThread = new Thread(async () => await ListenerThread() ); + ConnectionThread.Start(); + } + + async Task ListenerThread() { + Listener = new TcpListener( IPAddress.Any, port ); + Listener.Start(); + while( Alive ) { + TcpClient client = await Listener.AcceptTcpClientAsync(); + + Connection user = new Connection(){ + slowClient = new mTCPServer(client, ServerMode), + fastClient = new IPEndPoint( ((IPEndPoint)client.Client.RemoteEndPoint).Address, port ) + }; + + Console.WriteLine( "New User Connected" ); + + onConnected?.Invoke( user, new EventArgs() ); + user.slowClient.onDisconnected += onDisconnected; + user.slowClient.onReceived += onReceive; + + Thread receiveThread = new Thread(async () => await user.slowClient.ReceiveThread(user) ); + receiveThread.Start(); + } + } + + public void Dispose() { + Alive = false; + Listener.Stop(); + Listener = null; + } + } + +} diff --git a/MistoxServer/Server/TCPServer.cs b/MistoxServer/Server/TCPServer.cs new file mode 100644 index 0000000..607061b --- /dev/null +++ b/MistoxServer/Server/TCPServer.cs @@ -0,0 +1,58 @@ +using System; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace MistoxServer.Server { + public class mTCPServer : IDisposable { + public TcpClient slowClient; + ServerMode Mode; + + public event EventHandler onReceived; + public event EventHandler onDisconnected; + + public mTCPServer( TcpClient client, ServerMode mode ) { + slowClient = client; + slowClient.Client.NoDelay = true; + Mode = mode; + } + + bool Alive = true; + public async Task ReceiveThread( Connection Client ) { + bool connected = true; + while( Alive && connected ) { + try { + byte[] StreamData = new byte[1024]; + int bytesRead = await slowClient.GetStream().ReadAsync(StreamData, 0, StreamData.Length); + if( bytesRead > 0 ) { + if ( Mode == ServerMode.Passive) { + onReceived?.Invoke( StreamData.Sub( 0, bytesRead ), new EventArgs() ); + } else if ( Mode == ServerMode.Authoritative) { + dynamic data = mSerialize.tReceive( StreamData.Sub( 0, bytesRead ) ); + if( data != null ) { + onReceived?.Invoke( data, new EventArgs() ); + } + } + } + } catch( Exception ) { + Console.WriteLine( "User disconnected" ); + connected = false; + onDisconnected?.Invoke( Client, new EventArgs() ); + } + } + } + + public async Task Send( T packet ) { + if( Mode == ServerMode.Authoritative ) { + byte[] byteData = mSerialize.PacketSerialize( packet ); + await slowClient.GetStream().WriteAsync( byteData, 0, byteData.Length ); + } else if( Mode == ServerMode.Passive ) { + byte[] byteData = packet as byte[]; + await slowClient.GetStream().WriteAsync( byteData, 0, byteData.Length ); + } + } + + public void Dispose() { + Alive = false; + } + } +} diff --git a/MistoxServer/mExtensions.cs b/MistoxServer/mExtensions.cs new file mode 100644 index 0000000..64d4fad --- /dev/null +++ b/MistoxServer/mExtensions.cs @@ -0,0 +1,95 @@ +using System; +using System.IO; +using System.Net; +using System.Text; +using MsgPack.Serialization; + +namespace MistoxServer { + + static class Extensions { + public static T[] Join( this T[] first, T[] second ) { + T[] bytes = new T[first.Length + second.Length]; + Buffer.BlockCopy( first, 0, bytes, 0, first.Length ); + Buffer.BlockCopy( second, 0, bytes, first.Length, second.Length ); + return bytes; + } + + public static T[] Sub( this T[] data, int index, int length ) { + T[] result = new T[length]; + Array.Copy( data, index, result, 0, length ); + return result; + } + + } + + public class mSerialize { + public static byte[] PacketSerialize( T Packet ) { + MessagePackSerializer serializer = MessagePackSerializer.Get(); + using( MemoryStream stream = new MemoryStream() ) { + serializer.Pack( stream, Packet ); + byte[] typename = Encoding.UTF8.GetBytes(typeof(T).FullName + "," + typeof(T).Assembly.FullName); + byte[] typelength = BitConverter.GetBytes(typename.Length); + byte[] packetdata = stream.ToArray(); + byte[] paketlength = BitConverter.GetBytes(packetdata.Length); + return typelength.Join( typename ).Join( paketlength ).Join( packetdata ); + } + } + + public static object PacketDeserialize( string typeData, byte[] Data ) { + Type type = Type.GetType( typeData ); + MessagePackSerializer Serilizer = MessagePackSerializer.Get(type); + + using( MemoryStream ms = new MemoryStream( Data ) ) { + return (object)Serilizer.Unpack( ms ); + } + } + + static byte[] TBufferedData = new byte[0]; + public static dynamic tReceive( byte [] BytesRead ) { + TBufferedData = TBufferedData.Join( BytesRead ); + if( TBufferedData.Length > 4 ) { + int typeLength = BitConverter.ToInt32( TBufferedData.Sub(0, 4) ); + if( TBufferedData.Length > (8 + typeLength) ) { + int dataLength = BitConverter.ToInt32( TBufferedData.Sub( typeLength + 4, 4 ) ); + int TotalLength = 8 + typeLength + dataLength; + if ( TBufferedData.Length >= TotalLength ) { + string typeData = Encoding.UTF8.GetString( TBufferedData.Sub(4, typeLength) ); + if ( typeData.Substring(0, 13) != "System.Object" ) { + byte[] dataBytes = TBufferedData.Sub( (typeLength + 8), dataLength ); + dynamic data = mSerialize.PacketDeserialize( typeData, dataBytes ); + TBufferedData = TBufferedData.Sub( TotalLength, TBufferedData.Length - TotalLength ); + return data; + } else { + TBufferedData = TBufferedData.Sub( TotalLength, TBufferedData.Length - TotalLength ); + } + } + } + } + return null; + } + + static byte[] UBufferedData = new byte[0]; + public static dynamic uReceive( byte [] BytesRead ) { + UBufferedData = UBufferedData.Join( BytesRead ); + if( UBufferedData.Length > 4 ) { + int typeLength = BitConverter.ToInt32( UBufferedData.Sub(0, 4) ); + if( UBufferedData.Length > (8 + typeLength) ) { + int dataLength = BitConverter.ToInt32( UBufferedData.Sub( typeLength + 4, 4 ) ); + int TotalLength = 8 + typeLength + dataLength; + if( UBufferedData.Length >= TotalLength ) { + string typeData = Encoding.UTF8.GetString( UBufferedData.Sub(4, typeLength) ); + if( typeData.Substring( 0, 13 ) != "System.Object" ) { + byte[] dataBytes = UBufferedData.Sub( (typeLength + 8), dataLength ); + dynamic data = mSerialize.PacketDeserialize( typeData, dataBytes ); + UBufferedData = UBufferedData.Sub( TotalLength, UBufferedData.Length - TotalLength ); + return data; + } else { + UBufferedData = UBufferedData.Sub( TotalLength, UBufferedData.Length - TotalLength ); + } + } + } + } + return null; + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..2fe7343 --- /dev/null +++ b/README.md @@ -0,0 +1,111 @@ +# Server Standard + +This is a central server and client model based server. The basis of the networking is on msgpack. works on IPv4 only at the moment. In a future release I might add IPV6 but not on any roadmap. Fully threaded and asynchrous to scale easily. + +## Library + +This is a library with a CLI attached to it to show usage. You can send and receive any data type except the standard 'Object' type + +## Usage + +Follow the example listed in Program.cs inside the CLI +Note: All datatypes that are sent to the server, The server needs to be aware of when in Athouritative mode. + +Create the server +```c# +IMistoxServer serverobj = mServer.newServer( int Port, ServerMode mode ) +``` + +Create the client +```c# +IMistoxServer serverobj = mServer.newClient( string HostNameOrIP, int Port ) +``` + +Sending data +```c# +serverobj.Send( dynamic Object, SendType Speed ) +``` + +Connected and Disconnected +```c# + +serverobj.onConnected += OnConnected; +serverobj.onDisconnected += OnDisconnected; +void OnConnected( object Data, EventArgs e ){ + // Sender and e are always null + // Put on connected funtions here + // such as when connected spawn a player +} +void OnDisconnected( object Data, EventArgs e ){ + // Sender and e are always null + // Put on disconnected funtions here + // such as when disconnected return to main menu +} +``` + +Receiving data +```c# + +serverobj.onSlowReceive += SlowReceivedData; +serverobj.onFastReceive += FastReceivedData; +void SlowReceivedData( object Data, EventArgs e ){ + if (Data is Datatype dt){ // Only works in Athoritative mode on server | will always work on client + + } +} +void FastReceivedData( object Data, EventArgs e ){ + if (Data is Datatype dt){ // Only works in Athoritative mode on server | will always work on client + + } +} +``` + +SendType *Enumeration + + Where SlowUpdate is sent over TCP + + And FastUpate is sent over UDP +```c# +public enum SendType { + SlowUpdate, + FastUpdate +} +``` + +ServerMode *Enumeration + The Servermode tells the server if it should Deserialize examine then Reserialize the data passing through. + This can be useful to make sure data is + + 1 actually belonging to the server + + 2 the ability to keep track of objects within the server [ prevent cheating ] + + This requires alot of overhead on the server though. + + Passive doesnt use a serdes and instead just passes the data in and out + In this mode the server doesnt need to know data types + + Athoritative will Deserialize and Serialize the data + Refer to the CLI/Program.CS for reference + In this mode the server needs to know the data types being sent + Therefore you need to put all your data classes into a library and and the library to both the application and the server +```c# +public enum ServerMode { + Passive, + Authoritative +} +``` + +## Setup + +Place all your networked data types into a library +Copy the library to the application and to the server +reference the library for the networked data types + +## Contributing + +All Credits to Derek Holloway + +## License + +[MIT](https://choosealicense.com/licenses/mit/) \ No newline at end of file