Add common server logging.

This commit is contained in:
Ray Koopa 2018-12-25 22:12:37 +01:00
parent bb7d633f21
commit 30c1454522
11 changed files with 212 additions and 39 deletions

View File

@ -26,14 +26,27 @@ namespace Syroot.Worms.OnlineWorms.Server
_server = server; _server = server;
} }
// ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
protected override void OnPrePacketHandle(Packet packet)
{
_server.Log.Write(LogCategory.Client, $"{TcpClient.Client.RemoteEndPoint} >> {packet}");
}
protected override void OnPrePacketSend(Packet packet)
{
_server.Log.Write(LogCategory.Server, $"{TcpClient.Client.RemoteEndPoint} << {packet}");
}
// ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- // ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private Packet HandleConnect(ConnectQueryPacket connectPacket) private Packet HandleConnect(ConnectQueryPacket connectPacket)
{ {
return new ConnectReplyPacket return new ConnectReplyPacket
{ {
Unknown = "Online Worms Private Server", Unknown = _server.Name,
Version = "114" Unknown2 = _server.RegionName,
Version = _server.Version
}; };
} }

View File

@ -0,0 +1,40 @@
using System;
namespace Syroot.Worms.OnlineWorms.Server
{
/// <summary>
/// Represents simplistic textual logging.
/// </summary>
internal class Log
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private readonly object _lock = new object();
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal void Write(LogCategory category, string text) => Write((ConsoleColor)category, text);
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private void Write(ConsoleColor color, string text)
{
lock (_lock)
{
ConsoleColor prevColor = Console.ForegroundColor;
Console.ForegroundColor = color;
Console.WriteLine(text);
Console.ForegroundColor = prevColor;
}
}
}
internal enum LogCategory
{
Info = ConsoleColor.White,
Connect = ConsoleColor.Cyan,
Disconnect = ConsoleColor.Magenta,
Client = ConsoleColor.DarkCyan,
Server = ConsoleColor.DarkMagenta
}
}

View File

@ -23,7 +23,6 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
= new Dictionary<Type, Dictionary<Type, MethodInfo>>(); = new Dictionary<Type, Dictionary<Type, MethodInfo>>();
private readonly Dictionary<Type, MethodInfo> _handlers; private readonly Dictionary<Type, MethodInfo> _handlers;
private readonly TcpClient _tcpClient;
private readonly byte[] _receiveBuffer; private readonly byte[] _receiveBuffer;
private readonly byte[] _sendDataBuffer; private readonly byte[] _sendDataBuffer;
private readonly PacketStream _receiveStream; private readonly PacketStream _receiveStream;
@ -36,19 +35,25 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
/// Initializes a new instance of the <see cref="GameConnection"/> class, handling the given /// Initializes a new instance of the <see cref="GameConnection"/> class, handling the given
/// <paramref name="tcpClient"/>. /// <paramref name="tcpClient"/>.
/// </summary> /// </summary>
/// <param name="tcpClient">The <see cref="TcpClient"/> to communicate with.</param> /// <param name="tcpClient">The <see cref="System.Net.Sockets.TcpClient"/> to communicate with.</param>
internal GameConnection(TcpClient tcpClient) internal GameConnection(TcpClient tcpClient)
{ {
_tcpClient = tcpClient; TcpClient = tcpClient;
Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} connected");
_handlers = GetHandlers(); _handlers = GetHandlers();
_receiveBuffer = new byte[_maxDataSize]; _receiveBuffer = new byte[_maxDataSize];
_receiveBuffer = new byte[_maxDataSize]; _sendDataBuffer = new byte[_maxDataSize];
_receiveStream = new PacketStream(_tcpClient.GetStream()); _receiveStream = new PacketStream(TcpClient.GetStream());
_sendStream = new PacketStream(new MemoryStream(_sendDataBuffer, 0, _maxDataSize, true)); _sendStream = new PacketStream(new MemoryStream(_sendDataBuffer, 0, _maxDataSize, true));
} }
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the <see cref="TcpClient"/> with which the connection communicates.
/// </summary>
internal TcpClient TcpClient { get; }
// ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- // ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public void Dispose() public void Dispose()
@ -77,7 +82,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
{ {
if (disposing) if (disposing)
{ {
_tcpClient.Dispose(); TcpClient.Dispose();
_sendStream.Dispose(); _sendStream.Dispose();
} }
@ -85,6 +90,10 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
} }
} }
protected virtual void OnPrePacketHandle(Packet packet) { }
protected virtual void OnPrePacketSend(Packet packet) { }
// ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- // ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private Dictionary<Type, MethodInfo> GetHandlers() private Dictionary<Type, MethodInfo> GetHandlers()
@ -107,13 +116,13 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
private void HandlePacket(Packet inPacket) private void HandlePacket(Packet inPacket)
{ {
OnPrePacketHandle(inPacket);
Type packetType = inPacket.GetType(); Type packetType = inPacket.GetType();
Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} >> {packetType.Name}");
// Invoke the handler and send back any packet resulting from it. // Invoke the handler and send back any packet resulting from it.
if (_handlers[packetType].Invoke(this, new[] { inPacket }) is Packet outPacket) if (_handlers[packetType].Invoke(this, new[] { inPacket }) is Packet outPacket)
{ {
Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} << {outPacket.GetType().Name}"); OnPrePacketSend(outPacket);
SendPacket(outPacket); SendPacket(outPacket);
} }
} }

View File

@ -1,4 +1,10 @@
namespace Syroot.Worms.OnlineWorms.Server.Net using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Syroot.Worms.OnlineWorms.Server.Net
{ {
/// <summary> /// <summary>
/// Represents a packet with an ID specifying its contents. To allow the server to instantiate child packet classes, /// Represents a packet with an ID specifying its contents. To allow the server to instantiate child packet classes,
@ -6,6 +12,93 @@
/// </summary> /// </summary>
internal abstract class Packet internal abstract class Packet
{ {
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public override string ToString()
{
#if DEBUG
string dump(object obj, int indentLevel)
{
StringBuilder sb = new StringBuilder();
string indent = new string(' ', indentLevel * 4);
switch (obj)
{
case null:
sb.Append(indent);
sb.Append("null");
break;
case String stringValue:
sb.Append(indent);
sb.Append('"');
sb.Append(stringValue);
sb.Append('"');
break;
case Byte[] byteArrayValue:
sb.Append(indent);
sb.Append(String.Join(" ", byteArrayValue.Select(x => x.ToString("X2"))));
break;
case IEnumerable iEnumerableValue:
sb.Append(indent);
sb.Append("[ ");
foreach (object element in iEnumerableValue)
sb.Append(dump(element, indentLevel)).Append(" ");
sb.Append("]");
break;
case Byte byteValue:
sb.Append(indent);
sb.Append("0x" + byteValue.ToString("X2"));
break;
case Int16 int16Value:
sb.Append(indent);
sb.Append("0x" + int16Value.ToString("X4"));
break;
case Int32 int32Value:
sb.Append(indent);
sb.Append("0x" + int32Value.ToString("X8"));
break;
case Int64 int64Value:
sb.Append(indent);
sb.Append("0x" + int64Value.ToString("X16"));
break;
case UInt16 uint16Value:
sb.Append(indent);
sb.Append("0x" + uint16Value.ToString("X4"));
break;
case UInt32 uint32Value:
sb.Append(indent);
sb.Append("0x" + uint32Value.ToString("X8"));
break;
case UInt64 uint64Value:
sb.Append(indent);
sb.Append("0x" + uint64Value.ToString("X16"));
break;
case Enum enumValue:
sb.Append(indent);
sb.Append(enumValue.ToString());
break;
default:
foreach (PropertyInfo property in obj.GetType().GetProperties(
BindingFlags.Instance | BindingFlags.NonPublic))
{
// Ignore indexers.
if (property.GetIndexParameters().Length > 0)
continue;
sb.AppendLine();
sb.Append((indent + property.Name).PadRight(20));
sb.Append(" ");
sb.Append(dump(property.GetValue(obj), indentLevel + 1));
}
sb.AppendLine();
break;
}
return sb.ToString().TrimEnd();
}
return String.Concat(GetType().Name, dump(this, 1));
#else
return GetType().Name;
#endif
}
// ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- // ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal abstract void Deserialize(PacketStream stream); internal abstract void Deserialize(PacketStream stream);

View File

@ -2,11 +2,19 @@
namespace Syroot.Worms.OnlineWorms.Server.Net namespace Syroot.Worms.OnlineWorms.Server.Net
{ {
/// <summary>
/// Decorates a <see cref="Packet"/> child class with the ID of the packet it represents.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
internal class PacketAttribute : Attribute internal class PacketAttribute : Attribute
{ {
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="PacketAttribute"/> class, decorating a class with the ID of the
/// packet it represents.
/// </summary>
/// <param name="id">The ID of the packet which the decorated class represents.</param>
public PacketAttribute(ushort id) public PacketAttribute(ushort id)
{ {
ID = id; ID = id;
@ -14,6 +22,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
// ---- PROPERTIES --------------------------------------------------------------------------------------------- // ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets the ID of the packet the decorated class represents.
/// </summary>
public ushort ID { get; } public ushort ID { get; }
} }
} }

View File

@ -12,7 +12,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
{ {
// ---- FIELDS ------------------------------------------------------------------------------------------------- // ---- FIELDS -------------------------------------------------------------------------------------------------
private static readonly Dictionary<ushort, Type> _packetTypeMap = new Dictionary<ushort, Type>(); private static readonly Dictionary<ushort, Type> _packetTypes = new Dictionary<ushort, Type>();
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
@ -23,9 +23,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
{ {
foreach (PacketAttribute packetAttribute in type.GetCustomAttributes<PacketAttribute>()) foreach (PacketAttribute packetAttribute in type.GetCustomAttributes<PacketAttribute>())
{ {
if (_packetTypeMap.ContainsKey(packetAttribute.ID)) if (_packetTypes.ContainsKey(packetAttribute.ID))
throw new InvalidOperationException($"Packet {packetAttribute.ID} mapped to multiple classes."); throw new InvalidOperationException($"Packet {packetAttribute.ID} mapped to multiple classes.");
_packetTypeMap.Add(packetAttribute.ID, type); _packetTypes.Add(packetAttribute.ID, type);
} }
} }
} }
@ -41,27 +41,27 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
internal static Packet Create(ushort id) internal static Packet Create(ushort id)
{ {
#if DEBUG #if DEBUG
return _packetTypeMap.TryGetValue(id, out Type type) return _packetTypes.TryGetValue(id, out Type type)
? (Packet)Activator.CreateInstance(type, true) ? (Packet)Activator.CreateInstance(type, true)
: new RawQueryPacket(id); : new RawQueryPacket(id);
#else #else
return (Packet)Activator.CreateInstance(_packetTypeMap[id], true); return (Packet)Activator.CreateInstance(_packetTypes[id], true);
#endif #endif
} }
/// <summary> /// <summary>
/// Gets the ID for the class of the given <paramref name="packet"/>. /// Gets the ID for the class of the given <paramref name="packet"/>.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the <see cref="Packet"/>.</typeparam>
/// <param name="packet">The packet, whose class ID will be returned.</param> /// <param name="packet">The packet, whose class ID will be returned.</param>
/// <returns>The ID of the <see cref="Packet"/> class.</returns> /// <returns>The ID of the <see cref="Packet"/> class.</returns>
internal static ushort GetID<T>(T packet) where T : Packet internal static ushort GetID(Packet packet)
{ {
#if DEBUG #if DEBUG
if (packet is RawQueryPacket rawPacket) if (packet is RawQueryPacket rawPacket)
return rawPacket.ID; return rawPacket.ID;
else
#endif #endif
return _packetTypeMap.Where(x => x.Value == packet.GetType()).First().Key; return _packetTypes.Where(x => x.Value == packet.GetType()).First().Key;
} }
} }
} }

View File

@ -90,17 +90,12 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
internal string ReadString() internal string ReadString()
{ {
// Strings are word prefixed and 0 termianted. return _baseStream.ReadString(StringCoding.Int16CharCount);
string value = _baseStream.ReadString(StringCoding.Int16CharCount);
_baseStream.Seek(1);
return value;
} }
internal void WriteString(string value) internal void WriteString(string value)
{ {
// Strings are word prefixed and 0 termianted.
_baseStream.WriteString(value, StringCoding.Int16CharCount, _win949Encoding); _baseStream.WriteString(value, StringCoding.Int16CharCount, _win949Encoding);
_baseStream.WriteByte(0);
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using Syroot.BinaryData;
namespace Syroot.Worms.OnlineWorms.Server.Net namespace Syroot.Worms.OnlineWorms.Server.Net
{ {
@ -10,9 +11,11 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
{ {
// ---- PROPERTIES --------------------------------------------------------------------------------------------- // ---- PROPERTIES ---------------------------------------------------------------------------------------------
public string Unknown { get; set; } internal string Unknown { get; set; }
public string Version { get; set; } internal string Unknown2 { get; set; }
internal ushort Version { get; set; }
// ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- // ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
@ -21,7 +24,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
internal override void Serialize(PacketStream stream) internal override void Serialize(PacketStream stream)
{ {
stream.WriteString(Unknown); stream.WriteString(Unknown);
stream.WriteString(Version); stream.WriteByte(0);
stream.WriteString(Unknown2);
stream.WriteUInt16(Version);
} }
} }
} }

View File

@ -1,5 +1,4 @@
#if DEBUG #if DEBUG
namespace Syroot.Worms.OnlineWorms.Server.Net namespace Syroot.Worms.OnlineWorms.Server.Net
{ {
/// <summary> /// <summary>

View File

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -10,12 +9,20 @@ namespace Syroot.Worms.OnlineWorms.Server
/// Represents a server listening for incoming client connections and dispatching them into <see cref="Client"/> /// Represents a server listening for incoming client connections and dispatching them into <see cref="Client"/>
/// instances. /// instances.
/// </summary> /// </summary>
public class Server internal class Server
{ {
// ---- FIELDS ------------------------------------------------------------------------------------------------- // ---- FIELDS -------------------------------------------------------------------------------------------------
private readonly List<Client> _clients = new List<Client>(); private readonly List<Client> _clients = new List<Client>();
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
internal string Name => "Online Worms Private Server";
internal string RegionName => "Global";
internal ushort Version => 114;
internal Log Log { get; } = new Log();
// ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- // ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <summary> /// <summary>
@ -27,16 +34,20 @@ namespace Syroot.Worms.OnlineWorms.Server
{ {
TcpListener tcpListener = new TcpListener(IPAddress.Any, port); TcpListener tcpListener = new TcpListener(IPAddress.Any, port);
tcpListener.Start(); tcpListener.Start();
Console.WriteLine($"Listening on port {port}..."); Log.Write(LogCategory.Server, $"Listening on port {port}...");
// Continually accept clients and dispatch them to their listening thread.
while (true) while (true)
{ {
Client client = new Client(tcpListener.AcceptTcpClient(), this); // Continually accept clients.
TcpClient tcpClient = tcpListener.AcceptTcpClient();
Log.Write(LogCategory.Connect, $"{tcpClient.Client.RemoteEndPoint} connected");
Client client = new Client(tcpClient, this);
_clients.Add(client); _clients.Add(client);
// Dispatch the client into its listening thread and remove it when listening aborts.
Task.Run(client.Listen).ContinueWith(_ => Task.Run(client.Listen).ContinueWith(_ =>
{ {
Log.Write(LogCategory.Disconnect, $"{client.TcpClient.Client.RemoteEndPoint} disconnected");
_clients.Remove(client); _clients.Remove(client);
client.Dispose(); client.Dispose();
}); });

View File

@ -4,9 +4,6 @@
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Syroot.BinaryData" Version="5.0.0" /> <PackageReference Include="Syroot.BinaryData" Version="5.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.0" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.0" />