diff --git a/src/Syroot.OnlineWorms.Server/Client.cs b/src/Syroot.OnlineWorms.Server/Client.cs index 8eef7d7..7164de3 100644 --- a/src/Syroot.OnlineWorms.Server/Client.cs +++ b/src/Syroot.OnlineWorms.Server/Client.cs @@ -6,7 +6,7 @@ namespace Syroot.Worms.OnlineWorms.Server /// /// Represents a connection with an Online Worms client which replies to received packets appropriately. /// - internal class Client : PacketHandler + internal class Client : GameConnection { // ---- FIELDS ------------------------------------------------------------------------------------------------- diff --git a/src/Syroot.OnlineWorms.Server/Core/MathFuncs.cs b/src/Syroot.OnlineWorms.Server/Core/MathFuncs.cs deleted file mode 100644 index 72ac934..0000000 --- a/src/Syroot.OnlineWorms.Server/Core/MathFuncs.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Syroot.Worms.OnlineWorms.Server.Core -{ - /// - /// Represents common mathematical operations. - /// - internal static class MathFuncs - { - // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- - - /// - /// Returns the nearest power of two bigger than the given . - /// - /// The value to get the nearest, bigger power of two for. - /// The nearest, bigger power of two. - internal static int NextPowerOfTwo(int value) - { - value--; - value |= value >> 1; - value |= value >> 2; - value |= value >> 4; - value |= value >> 8; - value |= value >> 16; - return ++value; - } - } -} diff --git a/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs b/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs index 414841a..ef96c4f 100644 --- a/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs +++ b/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs @@ -1,128 +1,161 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net.Sockets; -using System.Text; +using System.Reflection; using Syroot.BinaryData; namespace Syroot.Worms.OnlineWorms.Server.Net { /// - /// Represents a stream of instances send between a game client and server. + /// Represents a class capable of dispatching received instances to a corresponding method. /// - internal class GameConnection + internal class GameConnection : IDisposable { - // A complete packet consists of the following: - // - ushort id - // - ushort dataSize - // - byte[dataSize] data - // ---- CONSTANTS ---------------------------------------------------------------------------------------------- private const int _maxDataSize = 2048; // ---- FIELDS ------------------------------------------------------------------------------------------------- - private readonly Stream _tcpStream; - private readonly byte[] _receiveBuffer = new byte[_maxDataSize]; - private readonly byte[] _sendDataBuffer = new byte[_maxDataSize]; - private readonly BinaryStream _writeStream; + private static readonly Dictionary> _connectionClassesCache + = new Dictionary>(); + + private readonly Dictionary _handlers; + private readonly TcpClient _tcpClient; + private readonly byte[] _receiveBuffer; + private readonly byte[] _sendDataBuffer; + private readonly PacketStream _receiveStream; + private readonly PacketStream _sendStream; + private bool _disposed; // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ /// - /// Initializes a new instance of the class, receiving and sending packets from and - /// to the given . + /// Initializes a new instance of the class, handling the given + /// . /// /// The to communicate with. internal GameConnection(TcpClient tcpClient) { - _tcpStream = tcpClient.GetStream(); - _writeStream = new BinaryStream(new MemoryStream(_sendDataBuffer, 0, _maxDataSize, true), - encoding: Encoding.GetEncoding(949), stringCoding: StringCoding.Int16CharCount); + _tcpClient = tcpClient; + Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} connected"); + + _handlers = GetHandlers(); + _receiveBuffer = new byte[_maxDataSize]; + _receiveBuffer = new byte[_maxDataSize]; + _receiveStream = new PacketStream(_tcpClient.GetStream()); + _sendStream = new PacketStream(new MemoryStream(_sendDataBuffer, 0, _maxDataSize, true)); } // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing). + Dispose(true); + } + + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + /// - /// Returns a read from the stream. If the packet could not be read, - /// is returned. + /// Starts handling packets incoming from this connection and dispatches them to corresponding methods. /// - /// The read instance or if the packet could not be read. - /// - internal Packet ReceivePacket() + internal void Listen() + { + Packet packet; + while ((packet = ReceivePacket()) != null) + HandlePacket(packet); + } + + // ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------ + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _tcpClient.Dispose(); + _sendStream.Dispose(); + } + + _disposed = true; + } + } + + // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- + + private Dictionary GetHandlers() + { + Type classType = GetType(); + if (!_connectionClassesCache.TryGetValue(classType, out Dictionary handlerMethods)) + { + handlerMethods = new Dictionary(); + // Find all packet handling methods which are methods accepting and returning a specific packet. + foreach (MethodInfo method in classType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)) + { + Type inPacket = method.GetParameters().FirstOrDefault()?.ParameterType; + if (typeof(Packet).IsAssignableFrom(inPacket) && typeof(Packet).IsAssignableFrom(method.ReturnType)) + handlerMethods.Add(inPacket, method); + } + _connectionClassesCache.Add(classType, handlerMethods); + } + return handlerMethods; + } + + private void HandlePacket(Packet inPacket) + { + Type packetType = inPacket.GetType(); + Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} >> {packetType.Name}"); + + // Invoke the handler and send back any packet resulting from it. + if (_handlers[packetType].Invoke(this, new[] { inPacket }) is Packet outPacket) + { + Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} << {outPacket.GetType().Name}"); + SendPacket(outPacket); + } + } + + private Packet ReceivePacket() { // Receive the raw packet data. ushort id; ushort dataSize; try { - id = _tcpStream.ReadUInt16(); - dataSize = _tcpStream.ReadUInt16(); - ReadComplete(_receiveBuffer, 0, dataSize); + id = _receiveStream.ReadUInt16(); + dataSize = _receiveStream.ReadUInt16(); + _receiveStream.ReadAll(_receiveBuffer, 0, dataSize); } catch (IOException) { return null; } // The underlying socket closed. catch (ObjectDisposedException) { return null; } // The underlying stream closed. // Deserialize and return the packet. Packet packet = PacketFactory.Create(id); - using (BinaryStream readStream = new BinaryStream(new MemoryStream(_receiveBuffer, 0, dataSize, false), - encoding: Encoding.GetEncoding(949), stringCoding: StringCoding.Int16CharCount)) - { + using (PacketStream readStream = new PacketStream(new MemoryStream(_receiveBuffer, 0, dataSize, false))) packet.Deserialize(readStream); - } return packet; } - /// - /// Sends the given and returns if the packet was sent - /// successfully. - /// - /// The to send. - /// when the packet was sent. - internal bool SendPacket(Packet packet) + private bool SendPacket(Packet packet) { // Serialize the raw packet data. - _writeStream.Position = 0; - packet.Serialize(_writeStream); - ushort dataSize = (ushort)_writeStream.Position; + _sendStream.Position = 0; + packet.Serialize(_sendStream); + ushort dataSize = (ushort)_sendStream.Position; // Send the data and return success. try { - _tcpStream.WriteUInt16(PacketFactory.GetID(packet)); - _tcpStream.WriteUInt16(dataSize); - _tcpStream.Write(_sendDataBuffer, 0, dataSize); + _receiveStream.WriteUInt16(PacketFactory.GetID(packet)); + _receiveStream.WriteUInt16(dataSize); + _receiveStream.Write(_sendDataBuffer, 0, dataSize); return true; } catch (IOException) { return false; } // A network error appeared, and communication should end. catch (ObjectDisposedException) { return false; } // The underlying stream was most apparently closed. } - - // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- - - /// - /// Reads number of bytes to the given , starting at the - /// specified , and returns the number of bytes read. Since this method ensures that - /// the requested number of bytes is always read, it always returns , and otherwise - /// throws an if the required bytes could not be read. - /// - /// The byte array to write the read data to. - /// The offset into at which to start writing data. - /// The number of bytes to read into . - /// The cannot store the requested number of - /// bytes. - /// The requested number of bytes could not be read. - private void ReadComplete(byte[] buffer, int offset, int count) - { - int totalRead = 0; - while (totalRead < count) - { - // Read returns 0 only when the underlying socket is closed, otherwise it blocks. - int read = _tcpStream.Read(buffer, offset + totalRead, count - totalRead); - if (read == 0) - throw new IOException("The underlying stream has closed, 0 bytes were retrieved."); - totalRead += read; - } - } } } diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/Packet.cs b/src/Syroot.OnlineWorms.Server/Net/Packet.cs similarity index 66% rename from src/Syroot.OnlineWorms.Server/Net/Packets/Packet.cs rename to src/Syroot.OnlineWorms.Server/Net/Packet.cs index f9cef3a..ad648d1 100644 --- a/src/Syroot.OnlineWorms.Server/Net/Packets/Packet.cs +++ b/src/Syroot.OnlineWorms.Server/Net/Packet.cs @@ -1,6 +1,4 @@ -using Syroot.BinaryData; - -namespace Syroot.Worms.OnlineWorms.Server.Net +namespace Syroot.Worms.OnlineWorms.Server.Net { /// /// Represents a packet with an ID specifying its contents. To allow the server to instantiate child packet classes, @@ -10,8 +8,8 @@ namespace Syroot.Worms.OnlineWorms.Server.Net { // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- - internal abstract void Deserialize(BinaryStream stream); + internal abstract void Deserialize(PacketStream stream); - internal abstract void Serialize(BinaryStream stream); + internal abstract void Serialize(PacketStream stream); } } diff --git a/src/Syroot.OnlineWorms.Server/Net/PacketHandler.cs b/src/Syroot.OnlineWorms.Server/Net/PacketHandler.cs deleted file mode 100644 index c85c75f..0000000 --- a/src/Syroot.OnlineWorms.Server/Net/PacketHandler.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Sockets; -using System.Reflection; - -namespace Syroot.Worms.OnlineWorms.Server.Net -{ - /// - /// Represents a class capable of dispatching received instances to a corresponding method. - /// - internal class PacketHandler : IDisposable - { - // ---- FIELDS ------------------------------------------------------------------------------------------------- - - private static readonly Dictionary> _packetHandlers - = new Dictionary>(); - - private readonly TcpClient _tcpClient; - private readonly Dictionary _handlerMethods; - private readonly GameConnection _connection; - private bool _disposed; - - // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ - - internal PacketHandler(TcpClient tcpClient) - { - Console.WriteLine($"{tcpClient.Client.RemoteEndPoint} connected"); - _tcpClient = tcpClient; - _handlerMethods = GetHandlerMethods(); - _connection = new GameConnection(_tcpClient); - } - - // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- - - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing). - Dispose(true); - } - - // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- - - /// - /// Starts handling packets incoming from this client and calls corresponding methods. - /// - internal void Accept() - { - Packet packet; - while ((packet = _connection.ReceivePacket()) != null) - HandlePacket(packet); - } - - // ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------ - - protected virtual void Dispose(bool disposing) - { - if (!_disposed) - { - if (disposing) - _tcpClient.Dispose(); - - _disposed = true; - } - } - - // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- - - private Dictionary GetHandlerMethods() - { - Type classType = GetType(); - if (!_packetHandlers.TryGetValue(classType, out Dictionary handlerMethods)) - { - handlerMethods = new Dictionary(); - // Find all packet handling methods which are methods accepting and returning a specific packet. - foreach (MethodInfo method in classType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)) - { - Type inPacket = method.GetParameters().FirstOrDefault()?.ParameterType; - if (typeof(Packet).IsAssignableFrom(inPacket) && typeof(Packet).IsAssignableFrom(method.ReturnType)) - handlerMethods.Add(inPacket, method); - } - _packetHandlers.Add(classType, handlerMethods); - } - return handlerMethods; - } - - private void HandlePacket(Packet inPacket) - { - Type packetType = inPacket.GetType(); - Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} >> {packetType.Name}"); - - // Invoke the handler and send back any packet resulting from it. - if (_handlerMethods[packetType].Invoke(this, new[] { inPacket }) is Packet outPacket) - { - Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} << {outPacket.GetType().Name}"); - _connection.SendPacket(outPacket); - } - } - } -} diff --git a/src/Syroot.OnlineWorms.Server/Net/PacketStream.cs b/src/Syroot.OnlineWorms.Server/Net/PacketStream.cs new file mode 100644 index 0000000..fd7e0d7 --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Net/PacketStream.cs @@ -0,0 +1,106 @@ +using System.IO; +using System.Text; +using Syroot.BinaryData; + +namespace Syroot.Worms.OnlineWorms.Server.Net +{ + /// + /// Represents a stream formatting data for being sent or received from instances. + /// + internal class PacketStream : Stream + { + // ---- FIELDS ------------------------------------------------------------------------------------------------- + + private static readonly Encoding _win949Encoding; + + private readonly Stream _baseStream; + private bool _disposed; + + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + static PacketStream() + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + _win949Encoding = Encoding.GetEncoding(949); + } + + internal PacketStream(Stream baseStream) + { + _baseStream = baseStream; + } + + // ---- PROPERTIES --------------------------------------------------------------------------------------------- + + public override bool CanRead => _baseStream.CanRead; + public override bool CanSeek => _baseStream.CanSeek; + public override bool CanWrite => _baseStream.CanWrite; + public override long Length => _baseStream.Length; + public override long Position + { + get => _baseStream.Position; + set => _baseStream.Position = value; + } + + // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- + + public override void Flush() => _baseStream.Flush(); + public override int Read(byte[] buffer, int offset, int count) => _baseStream.Read(buffer, offset, count); + public override long Seek(long offset, SeekOrigin origin) => _baseStream.Seek(offset, origin); + public override void SetLength(long value) => _baseStream.SetLength(value); + public override void Write(byte[] buffer, int offset, int count) => _baseStream.Write(buffer, offset, count); + + // ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------ + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + _baseStream.Dispose(); + + _disposed = true; + base.Dispose(disposing); + } + + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + /// + /// Reads number of bytes to the given , starting at the + /// specified , and returns the number of bytes read. Since this method ensures that + /// the requested number of bytes is always read, it always returns , and otherwise + /// throws an if the required bytes could not be read. + /// + /// The byte array to write the read data to. + /// The offset into at which to start writing data. + /// The number of bytes to read into . + /// The requested number of bytes could not be read. + internal void ReadAll(byte[] buffer, int offset, int count) + { + int totalRead = 0; + while (totalRead < count) + { + // Read returns 0 only when the underlying socket is closed, otherwise it blocks. + int read = _baseStream.Read(buffer, offset + totalRead, count - totalRead); + if (read == 0) + throw new IOException("The underlying stream has closed, 0 bytes were retrieved."); + totalRead += read; + } + } + + internal string ReadString() + { + // Strings are word prefixed and 0 termianted. + string value = _baseStream.ReadString(StringCoding.Int16CharCount); + _baseStream.Seek(1); + return value; + } + + internal void WriteString(string value) + { + // Strings are word prefixed and 0 termianted. + _baseStream.WriteString(value, StringCoding.Int16CharCount, _win949Encoding); + _baseStream.WriteByte(0); + } + } +} diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectQueryPacket.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectQueryPacket.cs index 71edf94..65bbd14 100644 --- a/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectQueryPacket.cs +++ b/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectQueryPacket.cs @@ -1,5 +1,4 @@ using System; -using Syroot.BinaryData; namespace Syroot.Worms.OnlineWorms.Server.Net { @@ -11,8 +10,8 @@ namespace Syroot.Worms.OnlineWorms.Server.Net { // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- - internal override void Deserialize(BinaryStream stream) { } + internal override void Deserialize(PacketStream stream) { } - internal override void Serialize(BinaryStream stream) => throw new NotImplementedException(); + internal override void Serialize(PacketStream stream) => throw new NotImplementedException(); } } diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs index 9224c21..066d6a4 100644 --- a/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs +++ b/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs @@ -1,5 +1,4 @@ using System; -using Syroot.BinaryData; namespace Syroot.Worms.OnlineWorms.Server.Net { @@ -17,9 +16,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- - internal override void Deserialize(BinaryStream stream) => throw new NotImplementedException(); + internal override void Deserialize(PacketStream stream) => throw new NotImplementedException(); - internal override void Serialize(BinaryStream stream) + internal override void Serialize(PacketStream stream) { stream.WriteString(Unknown); stream.WriteString(Version); diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/LoginReplyPacket.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/LoginReplyPacket.cs index 02f33a8..84c4a52 100644 --- a/src/Syroot.OnlineWorms.Server/Net/Packets/LoginReplyPacket.cs +++ b/src/Syroot.OnlineWorms.Server/Net/Packets/LoginReplyPacket.cs @@ -15,9 +15,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- - internal override void Deserialize(BinaryStream stream) => throw new NotImplementedException(); + internal override void Deserialize(PacketStream stream) => throw new NotImplementedException(); - internal override void Serialize(BinaryStream stream) + internal override void Serialize(PacketStream stream) { stream.WriteBoolean(LoginResult == LoginResult.Success); stream.WriteEnum(LoginResult); diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs index 676e386..f3081bf 100644 --- a/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs +++ b/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs @@ -1,5 +1,4 @@ #if DEBUG -using Syroot.BinaryData; namespace Syroot.Worms.OnlineWorms.Server.Net { @@ -23,13 +22,13 @@ namespace Syroot.Worms.OnlineWorms.Server.Net // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- - internal override void Deserialize(BinaryStream stream) + internal override void Deserialize(PacketStream stream) { Data = new byte[stream.Length]; stream.Read(Data, 0, Data.Length); } - internal override void Serialize(BinaryStream stream) + internal override void Serialize(PacketStream stream) { stream.Write(Data, 0, Data.Length); } diff --git a/src/Syroot.OnlineWorms.Server/Program.cs b/src/Syroot.OnlineWorms.Server/Program.cs index f1e1c73..768ef7f 100644 --- a/src/Syroot.OnlineWorms.Server/Program.cs +++ b/src/Syroot.OnlineWorms.Server/Program.cs @@ -1,5 +1,4 @@ using System; -using System.Text; namespace Syroot.Worms.OnlineWorms.Server { @@ -8,13 +7,6 @@ namespace Syroot.Worms.OnlineWorms.Server /// internal class Program { - // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ - - static Program() - { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - } - // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- private static void Main(string[] args) diff --git a/src/Syroot.OnlineWorms.Server/Server.cs b/src/Syroot.OnlineWorms.Server/Server.cs index c97351d..5e70c17 100644 --- a/src/Syroot.OnlineWorms.Server/Server.cs +++ b/src/Syroot.OnlineWorms.Server/Server.cs @@ -29,12 +29,13 @@ namespace Syroot.Worms.OnlineWorms.Server tcpListener.Start(); Console.WriteLine($"Listening on port {port}..."); + // Continually accept clients and dispatch them to their listening thread. while (true) { Client client = new Client(tcpListener.AcceptTcpClient(), this); _clients.Add(client); - Task.Run(client.Accept).ContinueWith(_ => + Task.Run(client.Listen).ContinueWith(_ => { _clients.Remove(client); client.Dispose();