diff --git a/MistoxServer/Client/MistoxClient.cs b/MistoxServer/Client/MistoxClient.cs index a3db325..0189b31 100644 --- a/MistoxServer/Client/MistoxClient.cs +++ b/MistoxServer/Client/MistoxClient.cs @@ -2,35 +2,51 @@ using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; +using System.Threading.Tasks; using Mistox.ResevedPackets; namespace Mistox.Client { public class MistoxClient { - public string ServerName { get; set; } = ""; // The Name of the server + public string ServerName { get; private set; } = ""; // The Name of the server + public string UserName { get; private set; } = ""; // My UserName public bool Alive { get; set; } = true; // Is the server running - public mTCPClient ServerConnection; - public UdpClient ClientSocket; - public UDPReceiver UdpReceiver; - public ConcurrentDictionary clientConnections; // IP Address, Client + public bool isAuthenticated { get; set; } = false; // Is the server authenticated + public ConcurrentDictionary clientConnections; // UserName, Connection + + public mTCPClient ServerConnection {get; private set; } // Global TCP Server Connection + public UdpClient ClientSocket { get; private set; } // Global UDP Sender + public UDPReceiver UdpReceiver { get; private set; } // Global UDP Receiver + private readonly string ConnectionKey = ""; // Server Connection Key - public MistoxClient(IPEndPoint Server) { + public MistoxClient(string userName, string connectionKey, IPEndPoint Server) { // Create and listen on UDP ClientSocket = new UdpClient( Server.Port ); UdpReceiver = new UDPReceiver( this ); + UserName = userName; + ConnectionKey = connectionKey; // Create and connect to server on TCP Console.WriteLine( "The client is initilized and trying to connect to the server at ip : " + Server.Address ); try { ServerConnection = new mTCPClient( this, Server ); Console.WriteLine( "Connected to the server sucessfully" ); + Alive = true; } catch( Exception e ) { Console.WriteLine( "An error has occured with the connection to the server. Error { " ); Console.WriteLine( e.ToString() ); Console.WriteLine( "}" ); + Alive = false; } - Alive = true; + } + + public async Task TryAuth() { + Console.WriteLine("Attempting to authenticate against the server..."); + await ServerConnection.Send(new UserAuthPacket { + UserName = UserName, + ConnectionKey = ConnectionKey + }); } ///////////////////////////// @@ -38,27 +54,33 @@ namespace Mistox.Client { ///////////////////////////// public void UserConnected(UserConnectedPacket packet) { - mUDPClient user = new mUDPClient(this, packet.ClientIPAddress, packet.ClientPort) { + IPEndPoint remoteUser = new IPEndPoint(packet.ClientIPAddress, packet.ClientPort); + mUDPClient user = new mUDPClient(this, remoteUser) { UserName = packet.UserName }; - clientConnections[user.GetConnectionKey()] = user; + clientConnections[remoteUser] = user; InvokeOnUserConnected(user); } public void UserDisconnected(UserDisconnectedPacket packet) { - mUDPClient client; - bool found = clientConnections.TryGetValue(packet.ConnectionKey, out client); - if (found) { + mUDPClient client = null; + foreach(mUDPClient cur in clientConnections.Values) { + if (cur.UserName.ToLower() == packet.UserName.ToLower()) { + client = cur; + break; + } + } + if (client != null) { InvokeOnUserDisconnected( client ); client.Alive = false; client.Dispose(); - clientConnections[packet.ConnectionKey] = null; + clientConnections[client._remoteHost] = null; } } - ///////////////////////////// - // Events - ///////////////////////////// +///////////////////////////// +// Events +///////////////////////////// public event Action onDataReceived; public void InvokeOnReceived(dynamic DataPacket, mUDPClient remote = null){ @@ -81,6 +103,11 @@ namespace Mistox.Client { public event Action onServerConnected; public void InvokeOnServerConnected(){ Alive = true; + Task.Run(async () => { + try { + await TryAuth(); + } catch {} + }); onServerConnected?.Invoke(); } diff --git a/MistoxServer/Client/TCPClient.cs b/MistoxServer/Client/TCPClient.cs index fa449a8..7259361 100644 --- a/MistoxServer/Client/TCPClient.cs +++ b/MistoxServer/Client/TCPClient.cs @@ -9,16 +9,17 @@ using System.Threading.Tasks; namespace Mistox.Client { public class mTCPClient : IDisposable { - TcpClient _connection; - MistoxClient _parent; + private readonly TcpClient _connection; + private readonly MistoxClient _parent; + private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1); public mTCPClient( MistoxClient parent, IPEndPoint ServerAddress ) { _connection = new TcpClient( AddressFamily.InterNetwork ); _connection.NoDelay = true; _connection.Connect( ServerAddress ); _parent.InvokeOnServerConnected(); - Thread RThread = new Thread(async () => { await ReceiveThread(); }); - RThread.Start(); + // Start the receive thread + Task.Run(ReceiveThread); } async Task ReceiveThread() { @@ -32,19 +33,21 @@ namespace Mistox.Client { await ns.ReadExactlyAsync(headerBuffer, 0, 3); // Parse the header - byte packetbyte = headerBuffer[0]; - ushort dataLength = (ushort)((headerBuffer[1] << 8) | headerBuffer[2]); + DataPacketHeader Header = new DataPacketHeader { + Type = headerBuffer[0], + DataLength = (ushort)((headerBuffer[1] << 8) | headerBuffer[2]) + }; // Make sure the data looks valid - if (dataLength > payloadBuffer.Length) { + if (Header.DataLength > payloadBuffer.Length) { throw new Exception("Incoming TCP payload is too large for the pre-allocated buffer."); } // Read off the data - await ns.ReadExactlyAsync(payloadBuffer, 0, dataLength); + await ns.ReadExactlyAsync(payloadBuffer, 0, Header.DataLength); // Create the dataobject - dynamic dataobject = DataPacket.Deserialize( packetbyte, dataLength, payloadBuffer.AsMemory(DataPacket.HeaderLength, dataLength) ); + dynamic dataobject = DataPacket.Deserialize( Header, payloadBuffer.AsMemory(DataPacket.HeaderLength, Header.DataLength) ); // Return the object _parent.InvokeOnReceived(dataobject); @@ -60,13 +63,18 @@ namespace Mistox.Client { public async Task Send(T packet) { byte[] data = DataPacket.Serialize( packet ); - await _connection.GetStream().WriteAsync( data, 0, data.Length ); + await _sendLock.WaitAsync(); + try { + await _connection.GetStream().WriteAsync( data, 0, data.Length ); + } finally { + _sendLock.Release(); + } } public void Dispose() { _parent.InvokeOnServerDisconnected(); _connection.Close(); - _connection = null; + _connection.Dispose(); } } } diff --git a/MistoxServer/Client/UDPClient.cs b/MistoxServer/Client/UDPClient.cs index 0cf5a62..add5ff9 100644 --- a/MistoxServer/Client/UDPClient.cs +++ b/MistoxServer/Client/UDPClient.cs @@ -1,5 +1,6 @@ using System; using System.Net; +using System.Threading; using System.Threading.Tasks; namespace Mistox.Client { @@ -7,38 +8,31 @@ namespace Mistox.Client { public string UserName { get; set; } = ""; // The remote players UserName public bool Alive { get; set; } = true; // Is the connection alive - public IPEndPoint IPAddress { get; set; } // The remote IP Address + public IPEndPoint _remoteHost { get; set; } // The remote IP Address - IPEndPoint _remoteHost; - MistoxClient _parent; + private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1); + private readonly MistoxClient _parent; - public mUDPClient( MistoxClient parent, IPAddress RemoteIP, int Port ) { + public mUDPClient( MistoxClient parent, IPEndPoint remoteHost ) { _parent = parent; - _remoteHost = new IPEndPoint( RemoteIP, Port ); + _remoteHost = remoteHost; _parent.Alive = true; } public async Task Send( T Data ) { byte[] byteData = DataPacket.Serialize( Data ); - await _parent.ClientSocket.SendAsync( byteData, byteData.Length, _remoteHost ); - } - - public static UInt128 GetConnectionKey(IPAddress ipAddress) { - byte[] bytes = ipAddress.GetAddressBytes(); - if (bytes.Length == 4) { - bytes = ipAddress.MapToIPv6().GetAddressBytes(); + await _sendLock.WaitAsync(); + try { + await _parent.ClientSocket.SendAsync( byteData, byteData.Length, _remoteHost ); + } finally { + _sendLock.Release(); } - return BitConverter.ToUInt128(bytes, 0); - } - - public UInt128 GetConnectionKey() { - return GetConnectionKey( IPAddress.Address ); } public void Dispose() { _parent.Alive = false; _parent.ClientSocket.Close(); - _parent.ClientSocket = null; + _parent.ClientSocket.Dispose(); } } } \ No newline at end of file diff --git a/MistoxServer/Client/UDPReceiver.cs b/MistoxServer/Client/UDPReceiver.cs index 5243dec..c8b776f 100644 --- a/MistoxServer/Client/UDPReceiver.cs +++ b/MistoxServer/Client/UDPReceiver.cs @@ -1,17 +1,16 @@ using System; using System.Net.Sockets; -using System.Threading; using System.Threading.Tasks; namespace Mistox.Client { public class UDPReceiver : IDisposable { - MistoxClient _parent; + private readonly MistoxClient _parent; public UDPReceiver( MistoxClient parent) { _parent = parent; - Thread Client = new Thread(async() => { await ReceiveThread(); }); - Client.Start(); + // Start the receive thread + Task.Run(ReceiveThread); } async Task ReceiveThread() { @@ -21,25 +20,26 @@ namespace Mistox.Client { // As UDP reads the whole next packet then drops. Im guaranteed to receive 1 packet at a time UdpReceiveResult udpPacket = await _parent.ClientSocket.ReceiveAsync(); - // Compute the connectionKey - UInt128 ConnectionKey = mUDPClient.GetConnectionKey(udpPacket.RemoteEndPoint.Address); - // Try find the user mUDPClient client = null; - bool clientExists = _parent.clientConnections.TryGetValue(ConnectionKey, out client); + bool clientExists = _parent.clientConnections.TryGetValue(udpPacket.RemoteEndPoint, out client); if (clientExists) { // Create the data readers ReadOnlyMemory dataMemory = udpPacket.Buffer.AsMemory(); ReadOnlySpan dataPacket = dataMemory.Span; - // Split apart the packet - byte dataType = dataPacket[0]; - ushort DataLength = (ushort)((dataPacket[1] << 8) | dataPacket[2]); - ReadOnlyMemory data = dataMemory.Slice(DataPacket.HeaderLength, DataLength); + // Create the packet header + DataPacketHeader Header = new DataPacketHeader { + Type = dataPacket[0], + DataLength = (ushort)((dataPacket[1] << 8) | dataPacket[2]) + }; + + // Slice out the data part of the packet + ReadOnlyMemory data = dataMemory.Slice(DataPacket.HeaderLength, Header.DataLength); // Extract the object - dynamic buildObj = DataPacket.Deserialize( dataType, DataLength, data ); + dynamic buildObj = DataPacket.Deserialize( Header, data ); // Return the object _parent.InvokeOnReceived( buildObj, client ); diff --git a/MistoxServer/Interface/ClientInterface.cs b/MistoxServer/Interface/ClientInterface.cs index bf17ebc..3a560bd 100644 --- a/MistoxServer/Interface/ClientInterface.cs +++ b/MistoxServer/Interface/ClientInterface.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using Mistox.Client; @@ -15,7 +14,7 @@ namespace Mistox { public event Action onUserDisconnected; public event Action onServerDisconnected; - public ClientInterface( string IpOrHostName, int Port ) { + public ClientInterface( string UserName, string ConnectionKey, string IpOrHostName, int Port ) { // Get Server IP IPHostEntry host = Dns.GetHostEntry( IpOrHostName ); @@ -27,7 +26,7 @@ namespace Mistox { } // Start and wire up the Mistox server client - serverInstance = new MistoxClient( ServerDenstination ); + serverInstance = new MistoxClient( UserName, ConnectionKey, ServerDenstination ); serverInstance.onServerConnected += onServerConnected; serverInstance.onUserConnected += (mUDPClient endpoint) => { onUserConnected?.Invoke(endpoint); }; serverInstance.onDataReceived += (dynamic data) => { onDataReceived?.Invoke(data); }; @@ -36,15 +35,15 @@ namespace Mistox { } public async Task SendAllClients(Packet data) { - foreach( KeyValuePair cur in serverInstance.clientConnections ){ - await cur.Value.Send( data ); + foreach( mUDPClient cur in serverInstance.clientConnections.Values ){ + await cur.Send( data ); } } public async Task SendClient(string UserName, Packet data) { - foreach( KeyValuePair cur in serverInstance.clientConnections) { - if (cur.Value.UserName.ToLower() == UserName.ToLower()) { - await cur.Value.Send( data ); + foreach( mUDPClient cur in serverInstance.clientConnections.Values ) { + if (cur.UserName.ToLower() == UserName.ToLower()) { + await cur.Send( data ); break; } } diff --git a/MistoxServer/Interface/Interface.cs b/MistoxServer/Interface/Interface.cs index 40e6194..8ea34f5 100644 --- a/MistoxServer/Interface/Interface.cs +++ b/MistoxServer/Interface/Interface.cs @@ -10,7 +10,7 @@ namespace Mistox { return new ServerInterface(port, serverName); } - public static ClientInterface newClient( string ServerIPOrHostName, int Port ) { + public static ClientInterface newClient( string UserName, string ServerIPOrHostName, int Port, string ConnectionKey = "" ) { NetworkPacketDictionary.BuildNetworkPacketList(); int index = 0; IPAddress[] remoteAddress = Dns.GetHostAddresses(ServerIPOrHostName); @@ -21,7 +21,7 @@ namespace Mistox { } string ipAddress = remoteAddress[index].ToString(); if ( remoteAddress.Length > 0) { - return new ClientInterface(ipAddress, Port); + return new ClientInterface(UserName, ConnectionKey, ipAddress, Port); } else { Console.WriteLine("The server at " + ServerIPOrHostName + " doesn't exit or cannot be found"); return null; diff --git a/MistoxServer/Server/MistoxServer.cs b/MistoxServer/Server/MistoxServer.cs index e7cd89e..d971b5b 100644 --- a/MistoxServer/Server/MistoxServer.cs +++ b/MistoxServer/Server/MistoxServer.cs @@ -13,9 +13,9 @@ namespace Mistox.Server { public float ConnectionRange { get; set; } = 40; // InGame distance for valid conections public int ConnectionTimeoutSeconds { get; set; } = 10; // If no reply in time kill the connection public bool Alive { get; set; } = true; // Is the server running - - ServerListener Listener { get; set;} - public ConcurrentDictionary Connections; + public ConcurrentDictionary Connections; // All the active connections. key is username + + private readonly ServerListener Listener; // The Connection Listener singleton public ServerEntity(IPEndPoint ListenerAddress) { Listener = new ServerListener(ListenerAddress, this); diff --git a/MistoxServer/Server/ServerConnection.cs b/MistoxServer/Server/ServerConnection.cs index 8dd8ae3..75fb230 100644 --- a/MistoxServer/Server/ServerConnection.cs +++ b/MistoxServer/Server/ServerConnection.cs @@ -11,23 +11,24 @@ namespace Mistox.Server { public string UserName { get; set; } = ""; // The Players UserName public bool IsAuthenticated { get; set; } = false; // Has the player authenticated public Vector3 Position { get; set; } // The ingame position of the player - public DateTime LastHeartbeat { get; set; } = DateTime.UtcNow; // Connection heartbeat for integrity public bool Connected { get; set; } = true; // Is the connection valid - ServerEntity _parent; - TcpClient _client; - IPEndPoint _remoteAddress; + private readonly ServerEntity _parent; + private readonly TcpClient _client; + private readonly IPEndPoint _remoteAddress; + private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1); + private readonly CancellationTokenSource _cts = new(); public ServerConnection( TcpClient tcpClient, ServerEntity parent, IPEndPoint remote ) { _parent = parent; - _client = tcpClient; _remoteAddress = remote; + _client = tcpClient; _client.NoDelay = true; + _client.ReceiveTimeout = _parent.ConnectionTimeoutSeconds * 1000; ServerInterface.ServerInstance.InvokeUserConnected( this ); // Start the receive thread - Thread receiveThread = new Thread(async () => await ReceiveThread() ); - receiveThread.Start(); + Task.Run(ReceiveThread); } async Task ReceiveThread() { @@ -36,33 +37,26 @@ namespace Mistox.Server { byte[] headerBuffer = new byte[DataPacket.HeaderLength]; byte[] payloadBuffer = new byte[65535]; // Upper limit for safety while( Connected && _parent.Alive ) { - - // Tick the last heartbeat or drop - DateTime now = DateTime.UtcNow; - if (LastHeartbeat.AddSeconds(_parent.ConnectionTimeoutSeconds) >= now) { - Connected = false; - Dispose(); - break; - } - LastHeartbeat = DateTime.UtcNow; // Read the header off - await ns.ReadExactlyAsync(headerBuffer, 0, 3); + await ns.ReadExactlyAsync(headerBuffer, 0, DataPacket.HeaderLength, _cts.Token); - // Parse the header - byte packetbyte = headerBuffer[0]; - ushort dataLength = (ushort)((headerBuffer[1] << 8) | headerBuffer[2]); + // Pack the header variables for faster memory passing + DataPacketHeader Header = new DataPacketHeader { + Type = headerBuffer[0], + DataLength = (ushort)((headerBuffer[1] << 8) | headerBuffer[2]) + }; // Make sure the data looks valid - if (dataLength > payloadBuffer.Length) { + if (Header.DataLength > payloadBuffer.Length) { throw new Exception("Incoming TCP payload is too large for the pre-allocated buffer."); } // Read off the data - await ns.ReadExactlyAsync(payloadBuffer, 0, dataLength); + await ns.ReadExactlyAsync(payloadBuffer, 0, Header.DataLength, _cts.Token); // Create the dataobject - dynamic dataobject = DataPacket.Deserialize( packetbyte, dataLength, payloadBuffer.AsMemory(DataPacket.HeaderLength, dataLength) ); + dynamic dataobject = DataPacket.Deserialize( Header, payloadBuffer.AsMemory(DataPacket.HeaderLength) ); // Return the object _parent.InvokeDataReceived(dataobject); @@ -71,20 +65,34 @@ namespace Mistox.Server { } catch (Exception e) { _parent.Alive = false; Dispose(); - Console.WriteLine("TCP Receive exception: " + e.Message); + Console.WriteLine($"[TCP] Connection lost ({_remoteAddress}): {e.Message}"); + } finally { + Dispose(); } } } public async Task Send( T packet ) { - byte[] byteData = DataPacket.Serialize( packet ); - await _client.GetStream().WriteAsync( byteData, 0, byteData.Length ); + byte[] byteData = DataPacket.Serialize(packet); + await _sendLock.WaitAsync(); + try { + await _client.GetStream().WriteAsync(byteData, 0, byteData.Length); + } finally { + _sendLock.Release(); + } } public void Dispose() { + if (!Connected) return; + Connected = false; - _parent.InvokeUserDisconnected( this ); - Connected = false; + _cts.Cancel(); + _client.Close(); + _sendLock.Dispose(); + _cts.Dispose(); + + _parent.Connections.TryRemove(UserName, out _); + _parent.InvokeUserDisconnected(this); _parent.Alive = false; // Remove this item from the Dictionary on the Server for garbage collection } diff --git a/MistoxServer/Server/ServerListener.cs b/MistoxServer/Server/ServerListener.cs index 3091ce2..a165c54 100644 --- a/MistoxServer/Server/ServerListener.cs +++ b/MistoxServer/Server/ServerListener.cs @@ -1,7 +1,6 @@ using System; using System.Net; using System.Net.Sockets; -using System.Threading; using System.Threading.Tasks; // Client Connections @@ -9,20 +8,22 @@ using System.Threading.Tasks; namespace Mistox.Server { public class ServerListener : IDisposable { - ServerEntity _parent; - IPEndPoint _bindAddress; - TcpListener _Listener; + private readonly ServerEntity _parent; + private readonly IPEndPoint _bindAddress; + private readonly TcpListener _Listener; public ServerListener(IPEndPoint listenerAddress, ServerEntity parent) { _bindAddress = listenerAddress; _parent = parent; - Thread ConnectionThread = new Thread(async () => await ListenerThread() ); - ConnectionThread.Start(); + + _Listener = new TcpListener( _bindAddress ); + _Listener.Start(); + + // Start the thread + Task.Run(ListenerThread); } async Task ListenerThread() { - _Listener = new TcpListener( _bindAddress ); - _Listener.Start(); while( _parent.Alive ) { TcpClient client = await _Listener.AcceptTcpClientAsync(); @@ -37,11 +38,12 @@ namespace Mistox.Server { Console.WriteLine( "New User Connected" ); } + Dispose(); } public void Dispose() { _Listener.Stop(); - _Listener = null; + _Listener.Dispose(); } } diff --git a/MistoxServer/mPacket/DataPacket.cs b/MistoxServer/mPacket/DataPacket.cs index 18f99d2..59baed0 100644 --- a/MistoxServer/mPacket/DataPacket.cs +++ b/MistoxServer/mPacket/DataPacket.cs @@ -1,69 +1,81 @@ using System; +using System.Buffers; using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using MessagePack; using Mistox.Attributes; namespace Mistox { + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DataPacketHeader { + public byte Type { get; set; } + public ushort DataLength { get; set; } + } + public class DataPacket { // The length of the packet header in bytes - public static readonly int HeaderLength = 3; + public static int HeaderLength => Unsafe.SizeOf(); - // 8b. 16b. value of datalength - // Data Packet [ Type | DataLength | Data ] - + public DataPacketHeader Header { get; set; } + public dynamic Data { get; set; } // Helper classes public static byte[] Serialize( T Object ) { - - // Get your custom packet attribute to find the ID + + // Get the attribute ID mPacket attribute = typeof(T).GetCustomAttribute(false); if (attribute == null) { throw new Exception($"The datastructure {typeof(T).Name} is not an mPacket"); } - // Serialize the payload directly to a temporary byte array. - byte[] payloadData = MessagePackSerializer.Serialize(Object); - ushort dataLength = (ushort)payloadData.Length; + // Use an expandable buffer writer from the system stack + var writer = new ArrayBufferWriter(256); - // Allocate an array for the final output (1 byte Type + 2 bytes Length + Payload) - byte[] finalPacket = new byte[HeaderLength + dataLength]; + // Advance the writer past the 3-byte header slot so MessagePack doesn't overwrite it + writer.Advance(HeaderLength); - // Write the 3-byte header directly into the final array - finalPacket[0] = attribute.ID; - finalPacket[1] = (byte)((dataLength >> 8) & 0xFF); // High byte of ushort - finalPacket[2] = (byte)(dataLength & 0xFF); // Low byte of ushort + // Serialize the payload DIRECTLY into the final buffer memory + MessagePackSerializer.Serialize(writer, Object); - // Copy the payload directly into the final array right after the header - // This replaces all your .Join() allocations with a single ultra-fast CPU copy - Buffer.BlockCopy(payloadData, 0, finalPacket, HeaderLength, dataLength); + // Build our header struct on the stack + DataPacketHeader header = new DataPacketHeader { + Type = attribute.ID, + DataLength = (ushort)(writer.WrittenCount - HeaderLength) + }; - return finalPacket; + // Map the struct directly onto the 3-byte reservation we left at the front + writer.WrittenMemory.Slice(0, HeaderLength); + MemoryMarshal.Write(writer.GetSpan(HeaderLength).Slice(0, HeaderLength), in header); + // 7. Extract the finished byte array. + // Note: In a production socket pipeline, you would pass writer.WrittenMemory directly + // to the socket instead of converting it to an array here! + return writer.WrittenSpan.ToArray(); } - public static object Deserialize( byte PacketByte, ushort DataLength, ReadOnlyMemory Data ) { + public static object Deserialize( DataPacketHeader Header, ReadOnlyMemory Data ) { // Get the type from the tag - if (!NetworkPacketDictionary.ServerToClientPackets.TryGetValue(PacketByte, out Type resolvedType)) { - throw new Exception($"Unknown packet type ID: {PacketByte}"); + if (!NetworkPacketDictionary.ServerToClientPackets.TryGetValue(Header.Type, out Type resolvedType)) { + throw new Exception($"Unknown packet type ID: {Header.Type}"); } - // Sanity check: Ensure the packet we received actually has the payload bytes it claims to have - if ( DataLength < Data.Length ) { + // Ensure the packet we received actually has the payload bytes it claims to have + if ( Header.DataLength < Data.Length ) { throw new Exception("Malformed packet: Payload size mismatch."); } // Make sure the data is exactly the size requested - ReadOnlyMemory payloadSlice = Data.Slice(0, DataLength); + ReadOnlyMemory payloadSlice = Data.Slice(0, Header.DataLength); // Create the low-level reader struct directly over the memory slice var reader = new MessagePackReader(payloadSlice); // Extract the object - dynamic obj = MessagePackSerializer.Deserialize( resolvedType, ref reader, MessagePackSerializerOptions.Standard ); - - return obj; + return MessagePackSerializer.Deserialize( resolvedType, ref reader, MessagePackSerializerOptions.Standard ); } diff --git a/MistoxServer/mPacket/ResevedPackets.cs b/MistoxServer/mPacket/ResevedPackets.cs index 34e6505..7ff7d5a 100644 --- a/MistoxServer/mPacket/ResevedPackets.cs +++ b/MistoxServer/mPacket/ResevedPackets.cs @@ -5,21 +5,28 @@ using Mistox.Attributes; namespace Mistox.ResevedPackets { [mPacket(0xFF)] - public class EntityPacket { - public ushort ObjectId { get; set; } - public byte[] InnerPayload { get; set; } - } - - [mPacket(0xFE)] public class UserConnectedPacket { public string UserName { get; set; } public IPAddress ClientIPAddress { get; set; } public int ClientPort { get; set; } } + [mPacket(0xFE)] + public class UserAuthPacket { + public string UserName { get; set; } = ""; + public string ConnectionKey { get; set; } = ""; + public int AuthSuccessful { get; set; } = 0; // 0 is standard, -1 is auth failure, 1 is auth success + } + [mPacket(0xFD)] + public class EntityPacket { + public ushort ObjectId { get; set; } + public byte[] InnerPayload { get; set; } + } + + [mPacket(0xFC)] public class UserDisconnectedPacket { - public UInt128 ConnectionKey { get; set; } + public string UserName { get; set; } } } \ No newline at end of file diff --git a/Todo.txt b/Todo.txt index 793fbe2..3bf1a06 100644 --- a/Todo.txt +++ b/Todo.txt @@ -1,10 +1,9 @@ -Make a username -> connectionID dictionary for quick lookups instaed of using foreach loops with breaks. - 1. Allow the server to setup a server name 2. Allow the client to setup a user name 3. Have the client connect to the server with a welcome packet 4. Have the server use the welcome packet to fillout the user information on the server 5. Respond to the client from the server with a server list of UDPEndpoints +6. Instead of looping through a list of avalible users have a local user dictionary that can be used to lookup clients IPEndPoints Test that the datapacket is formatted correctly. The packet size might be off from the actual packet size @@ -20,6 +19,22 @@ When user a joins a server The IP gets added to the servers master list The user needs to authenticate for username -When user leaves a server +when the server connection closes Notify all clients to drop the connections - Remove the user from the server table \ No newline at end of file + Remove the user from the server table + +Use the Server TCP connection to notify all clients when a user disconnects + + +Connection Flow: + 1. Connection to server is made working + Server object is created working + ServerConnection gets added to the Connections table working + Server fires user connected event working + + 2. User sends an Auth Packet to server working + Server responds with Auth sucess or fail and marks the users auth status in the server table. needs work + After sucessfull auth share the users IPEndPoint with close connections not started + + 3. If the TCP connection closes not started + Disconnect the user and tell all clients that the user is gone not started \ No newline at end of file