using System; using System.Net; using System.Net.Sockets; using System.Numerics; using System.Threading; using System.Threading.Tasks; namespace Mistox.Server { public class ServerConnection : IDisposable { 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 bool Connected { get; set; } = true; // Is the connection valid private readonly MistoxServer _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, MistoxServer parent, IPEndPoint remote ) { _parent = parent; _remoteAddress = remote; _client = tcpClient; _client.NoDelay = true; _client.ReceiveTimeout = _parent.ConnectionTimeoutSeconds * 1000; _parent.InvokeUserConnected( this ); // Start the receive thread Task.Run(ReceiveThread); } async Task ReceiveThread() { using( NetworkStream ns = _client.GetStream() ) { try { byte[] headerBuffer = new byte[DataPacket.HeaderLength]; byte[] payloadBuffer = new byte[65535]; // Upper limit for safety while( Connected && _parent.Alive ) { // Read the header off await ns.ReadExactlyAsync(headerBuffer, 0, DataPacket.HeaderLength, _cts.Token); // Pack the header variables for faster memory passing DataPacketHeader Header = new DataPacketHeader { Type = (ushort)((headerBuffer[0] << 8) | headerBuffer[1]), DataLength = (ushort)((headerBuffer[2] << 8) | headerBuffer[3]) }; // Make sure the data looks valid 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, Header.DataLength, _cts.Token); // Create the dataobject dynamic dataobject = DataPacket.Deserialize( Header, payloadBuffer.AsMemory(DataPacket.HeaderLength) ); // Return the object _parent.InvokeDataReceived(dataobject); } } catch (Exception e) { _parent.Alive = false; Dispose(); Console.WriteLine($"[TCP] Connection lost ({_remoteAddress}): {e.Message}"); } finally { Dispose(); } } } public async Task Send( T packet ) { 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; _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 } } }