Tons of minor fixes

This commit is contained in:
derek.holloway committed 2026-08-03 10:01:31 -07:00
1 parent 873a6fa455
commit 37a6b5d59b
12 files changed
+216 -144

No files matched your search

+43 -16
View File
@@ -2,35 +2,51 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading.Tasks;
using Mistox.ResevedPackets; using Mistox.ResevedPackets;
namespace Mistox.Client { namespace Mistox.Client {
public class MistoxClient { 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 bool Alive { get; set; } = true; // Is the server running
public mTCPClient ServerConnection; public bool isAuthenticated { get; set; } = false; // Is the server authenticated
public UdpClient ClientSocket; public ConcurrentDictionary<IPEndPoint, mUDPClient> clientConnections; // UserName, Connection
public UDPReceiver UdpReceiver;
public ConcurrentDictionary<UInt128, mUDPClient> clientConnections; // IP Address, Client
public MistoxClient(IPEndPoint Server) { 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(string userName, string connectionKey, IPEndPoint Server) {
// Create and listen on UDP // Create and listen on UDP
ClientSocket = new UdpClient( Server.Port ); ClientSocket = new UdpClient( Server.Port );
UdpReceiver = new UDPReceiver( this ); UdpReceiver = new UDPReceiver( this );
UserName = userName;
ConnectionKey = connectionKey;
// Create and connect to server on TCP // Create and connect to server on TCP
Console.WriteLine( "The client is initilized and trying to connect to the server at ip : " + Server.Address ); Console.WriteLine( "The client is initilized and trying to connect to the server at ip : " + Server.Address );
try { try {
ServerConnection = new mTCPClient( this, Server ); ServerConnection = new mTCPClient( this, Server );
Console.WriteLine( "Connected to the server sucessfully" ); Console.WriteLine( "Connected to the server sucessfully" );
Alive = true;
} catch( Exception e ) { } catch( Exception e ) {
Console.WriteLine( "An error has occured with the connection to the server. Error { " ); Console.WriteLine( "An error has occured with the connection to the server. Error { " );
Console.WriteLine( e.ToString() ); Console.WriteLine( e.ToString() );
Console.WriteLine( "}" ); 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) { 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 UserName = packet.UserName
}; };
clientConnections[user.GetConnectionKey()] = user; clientConnections[remoteUser] = user;
InvokeOnUserConnected(user); InvokeOnUserConnected(user);
} }
public void UserDisconnected(UserDisconnectedPacket packet) { public void UserDisconnected(UserDisconnectedPacket packet) {
mUDPClient client; mUDPClient client = null;
bool found = clientConnections.TryGetValue(packet.ConnectionKey, out client); foreach(mUDPClient cur in clientConnections.Values) {
if (found) { if (cur.UserName.ToLower() == packet.UserName.ToLower()) {
client = cur;
break;
}
}
if (client != null) {
InvokeOnUserDisconnected( client ); InvokeOnUserDisconnected( client );
client.Alive = false; client.Alive = false;
client.Dispose(); client.Dispose();
clientConnections[packet.ConnectionKey] = null; clientConnections[client._remoteHost] = null;
} }
} }
///////////////////////////// /////////////////////////////
// Events // Events
///////////////////////////// /////////////////////////////
public event Action<dynamic> onDataReceived; public event Action<dynamic> onDataReceived;
public void InvokeOnReceived(dynamic DataPacket, mUDPClient remote = null){ public void InvokeOnReceived(dynamic DataPacket, mUDPClient remote = null){
@@ -81,6 +103,11 @@ namespace Mistox.Client {
public event Action onServerConnected; public event Action onServerConnected;
public void InvokeOnServerConnected(){ public void InvokeOnServerConnected(){
Alive = true; Alive = true;
Task.Run(async () => {
try {
await TryAuth();
} catch {}
});
onServerConnected?.Invoke(); onServerConnected?.Invoke();
} }
+19 -11
View File
@@ -9,16 +9,17 @@ using System.Threading.Tasks;
namespace Mistox.Client { namespace Mistox.Client {
public class mTCPClient : IDisposable { public class mTCPClient : IDisposable {
TcpClient _connection; private readonly TcpClient _connection;
MistoxClient _parent; private readonly MistoxClient _parent;
private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
public mTCPClient( MistoxClient parent, IPEndPoint ServerAddress ) { public mTCPClient( MistoxClient parent, IPEndPoint ServerAddress ) {
_connection = new TcpClient( AddressFamily.InterNetwork ); _connection = new TcpClient( AddressFamily.InterNetwork );
_connection.NoDelay = true; _connection.NoDelay = true;
_connection.Connect( ServerAddress ); _connection.Connect( ServerAddress );
_parent.InvokeOnServerConnected(); _parent.InvokeOnServerConnected();
Thread RThread = new Thread(async () => { await ReceiveThread(); }); // Start the receive thread
RThread.Start(); Task.Run(ReceiveThread);
} }
async Task ReceiveThread() { async Task ReceiveThread() {
@@ -32,19 +33,21 @@ namespace Mistox.Client {
await ns.ReadExactlyAsync(headerBuffer, 0, 3); await ns.ReadExactlyAsync(headerBuffer, 0, 3);
// Parse the header // Parse the header
byte packetbyte = headerBuffer[0]; DataPacketHeader Header = new DataPacketHeader {
ushort dataLength = (ushort)((headerBuffer[1] << 8) | headerBuffer[2]); Type = headerBuffer[0],
DataLength = (ushort)((headerBuffer[1] << 8) | headerBuffer[2])
};
// Make sure the data looks valid // 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."); throw new Exception("Incoming TCP payload is too large for the pre-allocated buffer.");
} }
// Read off the data // Read off the data
await ns.ReadExactlyAsync(payloadBuffer, 0, dataLength); await ns.ReadExactlyAsync(payloadBuffer, 0, Header.DataLength);
// Create the dataobject // 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 // Return the object
_parent.InvokeOnReceived(dataobject); _parent.InvokeOnReceived(dataobject);
@@ -60,13 +63,18 @@ namespace Mistox.Client {
public async Task Send<T>(T packet) { public async Task Send<T>(T packet) {
byte[] data = DataPacket.Serialize( 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() { public void Dispose() {
_parent.InvokeOnServerDisconnected(); _parent.InvokeOnServerDisconnected();
_connection.Close(); _connection.Close();
_connection = null; _connection.Dispose();
} }
} }
} }
+12 -18
View File
@@ -1,5 +1,6 @@
using System; using System;
using System.Net; using System.Net;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Mistox.Client { namespace Mistox.Client {
@@ -7,38 +8,31 @@ namespace Mistox.Client {
public string UserName { get; set; } = ""; // The remote players UserName public string UserName { get; set; } = ""; // The remote players UserName
public bool Alive { get; set; } = true; // Is the connection alive 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; private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
MistoxClient _parent; private readonly MistoxClient _parent;
public mUDPClient( MistoxClient parent, IPAddress RemoteIP, int Port ) { public mUDPClient( MistoxClient parent, IPEndPoint remoteHost ) {
_parent = parent; _parent = parent;
_remoteHost = new IPEndPoint( RemoteIP, Port ); _remoteHost = remoteHost;
_parent.Alive = true; _parent.Alive = true;
} }
public async Task Send<T>( T Data ) { public async Task Send<T>( T Data ) {
byte[] byteData = DataPacket.Serialize( Data ); byte[] byteData = DataPacket.Serialize( Data );
await _parent.ClientSocket.SendAsync( byteData, byteData.Length, _remoteHost ); await _sendLock.WaitAsync();
} try {
await _parent.ClientSocket.SendAsync( byteData, byteData.Length, _remoteHost );
public static UInt128 GetConnectionKey(IPAddress ipAddress) { } finally {
byte[] bytes = ipAddress.GetAddressBytes(); _sendLock.Release();
if (bytes.Length == 4) {
bytes = ipAddress.MapToIPv6().GetAddressBytes();
} }
return BitConverter.ToUInt128(bytes, 0);
}
public UInt128 GetConnectionKey() {
return GetConnectionKey( IPAddress.Address );
} }
public void Dispose() { public void Dispose() {
_parent.Alive = false; _parent.Alive = false;
_parent.ClientSocket.Close(); _parent.ClientSocket.Close();
_parent.ClientSocket = null; _parent.ClientSocket.Dispose();
} }
} }
} }
+13 -13
View File
@@ -1,17 +1,16 @@
using System; using System;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Mistox.Client { namespace Mistox.Client {
public class UDPReceiver : IDisposable { public class UDPReceiver : IDisposable {
MistoxClient _parent; private readonly MistoxClient _parent;
public UDPReceiver( MistoxClient parent) { public UDPReceiver( MistoxClient parent) {
_parent = parent; _parent = parent;
Thread Client = new Thread(async() => { await ReceiveThread(); }); // Start the receive thread
Client.Start(); Task.Run(ReceiveThread);
} }
async Task 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 // As UDP reads the whole next packet then drops. Im guaranteed to receive 1 packet at a time
UdpReceiveResult udpPacket = await _parent.ClientSocket.ReceiveAsync(); UdpReceiveResult udpPacket = await _parent.ClientSocket.ReceiveAsync();
// Compute the connectionKey
UInt128 ConnectionKey = mUDPClient.GetConnectionKey(udpPacket.RemoteEndPoint.Address);
// Try find the user // Try find the user
mUDPClient client = null; mUDPClient client = null;
bool clientExists = _parent.clientConnections.TryGetValue(ConnectionKey, out client); bool clientExists = _parent.clientConnections.TryGetValue(udpPacket.RemoteEndPoint, out client);
if (clientExists) { if (clientExists) {
// Create the data readers // Create the data readers
ReadOnlyMemory<byte> dataMemory = udpPacket.Buffer.AsMemory(); ReadOnlyMemory<byte> dataMemory = udpPacket.Buffer.AsMemory();
ReadOnlySpan<byte> dataPacket = dataMemory.Span; ReadOnlySpan<byte> dataPacket = dataMemory.Span;
// Split apart the packet // Create the packet header
byte dataType = dataPacket[0]; DataPacketHeader Header = new DataPacketHeader {
ushort DataLength = (ushort)((dataPacket[1] << 8) | dataPacket[2]); Type = dataPacket[0],
ReadOnlyMemory<byte> data = dataMemory.Slice(DataPacket.HeaderLength, DataLength); DataLength = (ushort)((dataPacket[1] << 8) | dataPacket[2])
};
// Slice out the data part of the packet
ReadOnlyMemory<byte> data = dataMemory.Slice(DataPacket.HeaderLength, Header.DataLength);
// Extract the object // Extract the object
dynamic buildObj = DataPacket.Deserialize( dataType, DataLength, data ); dynamic buildObj = DataPacket.Deserialize( Header, data );
// Return the object // Return the object
_parent.InvokeOnReceived( buildObj, client ); _parent.InvokeOnReceived( buildObj, client );
+7 -8
View File
@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Mistox.Client; using Mistox.Client;
@@ -15,7 +14,7 @@ namespace Mistox {
public event Action<mUDPClient> onUserDisconnected; public event Action<mUDPClient> onUserDisconnected;
public event Action onServerDisconnected; public event Action onServerDisconnected;
public ClientInterface( string IpOrHostName, int Port ) { public ClientInterface( string UserName, string ConnectionKey, string IpOrHostName, int Port ) {
// Get Server IP // Get Server IP
IPHostEntry host = Dns.GetHostEntry( IpOrHostName ); IPHostEntry host = Dns.GetHostEntry( IpOrHostName );
@@ -27,7 +26,7 @@ namespace Mistox {
} }
// Start and wire up the Mistox server client // Start and wire up the Mistox server client
serverInstance = new MistoxClient( ServerDenstination ); serverInstance = new MistoxClient( UserName, ConnectionKey, ServerDenstination );
serverInstance.onServerConnected += onServerConnected; serverInstance.onServerConnected += onServerConnected;
serverInstance.onUserConnected += (mUDPClient endpoint) => { onUserConnected?.Invoke(endpoint); }; serverInstance.onUserConnected += (mUDPClient endpoint) => { onUserConnected?.Invoke(endpoint); };
serverInstance.onDataReceived += (dynamic data) => { onDataReceived?.Invoke(data); }; serverInstance.onDataReceived += (dynamic data) => { onDataReceived?.Invoke(data); };
@@ -36,15 +35,15 @@ namespace Mistox {
} }
public async Task SendAllClients<Packet>(Packet data) { public async Task SendAllClients<Packet>(Packet data) {
foreach( KeyValuePair<UInt128, mUDPClient> cur in serverInstance.clientConnections ){ foreach( mUDPClient cur in serverInstance.clientConnections.Values ){
await cur.Value.Send( data ); await cur.Send( data );
} }
} }
public async Task SendClient<Packet>(string UserName, Packet data) { public async Task SendClient<Packet>(string UserName, Packet data) {
foreach( KeyValuePair<UInt128, mUDPClient> cur in serverInstance.clientConnections) { foreach( mUDPClient cur in serverInstance.clientConnections.Values ) {
if (cur.Value.UserName.ToLower() == UserName.ToLower()) { if (cur.UserName.ToLower() == UserName.ToLower()) {
await cur.Value.Send( data ); await cur.Send( data );
break; break;
} }
} }
+2 -2
View File
@@ -10,7 +10,7 @@ namespace Mistox {
return new ServerInterface(port, serverName); 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(); NetworkPacketDictionary.BuildNetworkPacketList();
int index = 0; int index = 0;
IPAddress[] remoteAddress = Dns.GetHostAddresses(ServerIPOrHostName); IPAddress[] remoteAddress = Dns.GetHostAddresses(ServerIPOrHostName);
@@ -21,7 +21,7 @@ namespace Mistox {
} }
string ipAddress = remoteAddress[index].ToString(); string ipAddress = remoteAddress[index].ToString();
if ( remoteAddress.Length > 0) { if ( remoteAddress.Length > 0) {
return new ClientInterface(ipAddress, Port); return new ClientInterface(UserName, ConnectionKey, ipAddress, Port);
} else { } else {
Console.WriteLine("The server at " + ServerIPOrHostName + " doesn't exit or cannot be found"); Console.WriteLine("The server at " + ServerIPOrHostName + " doesn't exit or cannot be found");
return null; return null;
+2 -2
View File
@@ -13,9 +13,9 @@ namespace Mistox.Server {
public float ConnectionRange { get; set; } = 40; // InGame distance for valid conections 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 int ConnectionTimeoutSeconds { get; set; } = 10; // If no reply in time kill the connection
public bool Alive { get; set; } = true; // Is the server running public bool Alive { get; set; } = true; // Is the server running
public ConcurrentDictionary<string, ServerConnection> Connections; // All the active connections. key is username
ServerListener Listener { get; set;} private readonly ServerListener Listener; // The Connection Listener singleton
public ConcurrentDictionary<string, ServerConnection> Connections;
public ServerEntity(IPEndPoint ListenerAddress) { public ServerEntity(IPEndPoint ListenerAddress) {
Listener = new ServerListener(ListenerAddress, this); Listener = new ServerListener(ListenerAddress, this);
+36 -28
View File
@@ -11,23 +11,24 @@ namespace Mistox.Server {
public string UserName { get; set; } = ""; // The Players UserName public string UserName { get; set; } = ""; // The Players UserName
public bool IsAuthenticated { get; set; } = false; // Has the player authenticated public bool IsAuthenticated { get; set; } = false; // Has the player authenticated
public Vector3 Position { get; set; } // The ingame position of the player 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 public bool Connected { get; set; } = true; // Is the connection valid
ServerEntity _parent; private readonly ServerEntity _parent;
TcpClient _client; private readonly TcpClient _client;
IPEndPoint _remoteAddress; 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 ) { public ServerConnection( TcpClient tcpClient, ServerEntity parent, IPEndPoint remote ) {
_parent = parent; _parent = parent;
_client = tcpClient;
_remoteAddress = remote; _remoteAddress = remote;
_client = tcpClient;
_client.NoDelay = true; _client.NoDelay = true;
_client.ReceiveTimeout = _parent.ConnectionTimeoutSeconds * 1000;
ServerInterface.ServerInstance.InvokeUserConnected( this ); ServerInterface.ServerInstance.InvokeUserConnected( this );
// Start the receive thread // Start the receive thread
Thread receiveThread = new Thread(async () => await ReceiveThread() ); Task.Run(ReceiveThread);
receiveThread.Start();
} }
async Task ReceiveThread() { async Task ReceiveThread() {
@@ -37,32 +38,25 @@ namespace Mistox.Server {
byte[] payloadBuffer = new byte[65535]; // Upper limit for safety byte[] payloadBuffer = new byte[65535]; // Upper limit for safety
while( Connected && _parent.Alive ) { 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 // Read the header off
await ns.ReadExactlyAsync(headerBuffer, 0, 3); await ns.ReadExactlyAsync(headerBuffer, 0, DataPacket.HeaderLength, _cts.Token);
// Parse the header // Pack the header variables for faster memory passing
byte packetbyte = headerBuffer[0]; DataPacketHeader Header = new DataPacketHeader {
ushort dataLength = (ushort)((headerBuffer[1] << 8) | headerBuffer[2]); Type = headerBuffer[0],
DataLength = (ushort)((headerBuffer[1] << 8) | headerBuffer[2])
};
// Make sure the data looks valid // 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."); throw new Exception("Incoming TCP payload is too large for the pre-allocated buffer.");
} }
// Read off the data // Read off the data
await ns.ReadExactlyAsync(payloadBuffer, 0, dataLength); await ns.ReadExactlyAsync(payloadBuffer, 0, Header.DataLength, _cts.Token);
// Create the dataobject // 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 // Return the object
_parent.InvokeDataReceived(dataobject); _parent.InvokeDataReceived(dataobject);
@@ -71,20 +65,34 @@ namespace Mistox.Server {
} catch (Exception e) { } catch (Exception e) {
_parent.Alive = false; _parent.Alive = false;
Dispose(); Dispose();
Console.WriteLine("TCP Receive exception: " + e.Message); Console.WriteLine($"[TCP] Connection lost ({_remoteAddress}): {e.Message}");
} finally {
Dispose();
} }
} }
} }
public async Task Send<T>( T packet ) { public async Task Send<T>( T packet ) {
byte[] byteData = DataPacket.Serialize( packet ); byte[] byteData = DataPacket.Serialize(packet);
await _client.GetStream().WriteAsync( byteData, 0, byteData.Length ); await _sendLock.WaitAsync();
try {
await _client.GetStream().WriteAsync(byteData, 0, byteData.Length);
} finally {
_sendLock.Release();
}
} }
public void Dispose() { public void Dispose() {
if (!Connected) return;
Connected = false; Connected = false;
_parent.InvokeUserDisconnected( this ); _cts.Cancel();
Connected = false; _client.Close();
_sendLock.Dispose();
_cts.Dispose();
_parent.Connections.TryRemove(UserName, out _);
_parent.InvokeUserDisconnected(this);
_parent.Alive = false; _parent.Alive = false;
// Remove this item from the Dictionary on the Server for garbage collection // Remove this item from the Dictionary on the Server for garbage collection
} }
+11 -9
View File
@@ -1,7 +1,6 @@
using System; using System;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
// Client Connections // Client Connections
@@ -9,20 +8,22 @@ using System.Threading.Tasks;
namespace Mistox.Server { namespace Mistox.Server {
public class ServerListener : IDisposable { public class ServerListener : IDisposable {
ServerEntity _parent; private readonly ServerEntity _parent;
IPEndPoint _bindAddress; private readonly IPEndPoint _bindAddress;
TcpListener _Listener; private readonly TcpListener _Listener;
public ServerListener(IPEndPoint listenerAddress, ServerEntity parent) { public ServerListener(IPEndPoint listenerAddress, ServerEntity parent) {
_bindAddress = listenerAddress; _bindAddress = listenerAddress;
_parent = parent; _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() { async Task ListenerThread() {
_Listener = new TcpListener( _bindAddress );
_Listener.Start();
while( _parent.Alive ) { while( _parent.Alive ) {
TcpClient client = await _Listener.AcceptTcpClientAsync(); TcpClient client = await _Listener.AcceptTcpClientAsync();
@@ -37,11 +38,12 @@ namespace Mistox.Server {
Console.WriteLine( "New User Connected" ); Console.WriteLine( "New User Connected" );
} }
Dispose();
} }
public void Dispose() { public void Dispose() {
_Listener.Stop(); _Listener.Stop();
_Listener = null; _Listener.Dispose();
} }
} }
+39 -27
View File
@@ -1,69 +1,81 @@
using System; using System;
using System.Buffers;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using MessagePack; using MessagePack;
using Mistox.Attributes; using Mistox.Attributes;
namespace Mistox { namespace Mistox {
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DataPacketHeader {
public byte Type { get; set; }
public ushort DataLength { get; set; }
}
public class DataPacket { public class DataPacket {
// The length of the packet header in bytes // The length of the packet header in bytes
public static readonly int HeaderLength = 3; public static int HeaderLength => Unsafe.SizeOf<DataPacketHeader>();
// 8b. 16b. value of datalength
// Data Packet [ Type | DataLength | Data ]
public DataPacketHeader Header { get; set; }
public dynamic Data { get; set; }
// Helper classes // Helper classes
public static byte[] Serialize<T>( T Object ) { public static byte[] Serialize<T>( T Object ) {
// Get your custom packet attribute to find the ID // Get the attribute ID
mPacket attribute = typeof(T).GetCustomAttribute<mPacket>(false); mPacket attribute = typeof(T).GetCustomAttribute<mPacket>(false);
if (attribute == null) { if (attribute == null) {
throw new Exception($"The datastructure {typeof(T).Name} is not an mPacket"); throw new Exception($"The datastructure {typeof(T).Name} is not an mPacket");
} }
// Serialize the payload directly to a temporary byte array. // Use an expandable buffer writer from the system stack
byte[] payloadData = MessagePackSerializer.Serialize(Object); var writer = new ArrayBufferWriter<byte>(256);
ushort dataLength = (ushort)payloadData.Length;
// Allocate an array for the final output (1 byte Type + 2 bytes Length + Payload) // Advance the writer past the 3-byte header slot so MessagePack doesn't overwrite it
byte[] finalPacket = new byte[HeaderLength + dataLength]; writer.Advance(HeaderLength);
// Write the 3-byte header directly into the final array // Serialize the payload DIRECTLY into the final buffer memory
finalPacket[0] = attribute.ID; MessagePackSerializer.Serialize(writer, Object);
finalPacket[1] = (byte)((dataLength >> 8) & 0xFF); // High byte of ushort
finalPacket[2] = (byte)(dataLength & 0xFF); // Low byte of ushort
// Copy the payload directly into the final array right after the header // Build our header struct on the stack
// This replaces all your .Join() allocations with a single ultra-fast CPU copy DataPacketHeader header = new DataPacketHeader {
Buffer.BlockCopy(payloadData, 0, finalPacket, HeaderLength, dataLength); 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<byte> Data ) { public static object Deserialize( DataPacketHeader Header, ReadOnlyMemory<byte> Data ) {
// Get the type from the tag // Get the type from the tag
if (!NetworkPacketDictionary.ServerToClientPackets.TryGetValue(PacketByte, out Type resolvedType)) { if (!NetworkPacketDictionary.ServerToClientPackets.TryGetValue(Header.Type, out Type resolvedType)) {
throw new Exception($"Unknown packet type ID: {PacketByte}"); 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 // Ensure the packet we received actually has the payload bytes it claims to have
if ( DataLength < Data.Length ) { if ( Header.DataLength < Data.Length ) {
throw new Exception("Malformed packet: Payload size mismatch."); throw new Exception("Malformed packet: Payload size mismatch.");
} }
// Make sure the data is exactly the size requested // Make sure the data is exactly the size requested
ReadOnlyMemory<byte> payloadSlice = Data.Slice(0, DataLength); ReadOnlyMemory<byte> payloadSlice = Data.Slice(0, Header.DataLength);
// Create the low-level reader struct directly over the memory slice // Create the low-level reader struct directly over the memory slice
var reader = new MessagePackReader(payloadSlice); var reader = new MessagePackReader(payloadSlice);
// Extract the object // Extract the object
dynamic obj = MessagePackSerializer.Deserialize( resolvedType, ref reader, MessagePackSerializerOptions.Standard ); return MessagePackSerializer.Deserialize( resolvedType, ref reader, MessagePackSerializerOptions.Standard );
return obj;
} }
+14 -7
View File
@@ -5,21 +5,28 @@ using Mistox.Attributes;
namespace Mistox.ResevedPackets { namespace Mistox.ResevedPackets {
[mPacket(0xFF)] [mPacket(0xFF)]
public class EntityPacket {
public ushort ObjectId { get; set; }
public byte[] InnerPayload { get; set; }
}
[mPacket(0xFE)]
public class UserConnectedPacket { public class UserConnectedPacket {
public string UserName { get; set; } public string UserName { get; set; }
public IPAddress ClientIPAddress { get; set; } public IPAddress ClientIPAddress { get; set; }
public int ClientPort { 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)] [mPacket(0xFD)]
public class EntityPacket {
public ushort ObjectId { get; set; }
public byte[] InnerPayload { get; set; }
}
[mPacket(0xFC)]
public class UserDisconnectedPacket { public class UserDisconnectedPacket {
public UInt128 ConnectionKey { get; set; } public string UserName { get; set; }
} }
} }
+18 -3
View File
@@ -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 1. Allow the server to setup a server name
2. Allow the client to setup a user name 2. Allow the client to setup a user name
3. Have the client connect to the server with a welcome packet 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 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 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 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 IP gets added to the servers master list
The user needs to authenticate for username The user needs to authenticate for username
When user leaves a server when the server connection closes
Notify all clients to drop the connections Notify all clients to drop the connections
Remove the user from the server table 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