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;
}
// ---- 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) --------------------------------------------------------------------------------------
private Packet HandleConnect(ConnectQueryPacket connectPacket)
{
return new ConnectReplyPacket
{
Unknown = "Online Worms Private Server",
Version = "114"
Unknown = _server.Name,
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>>();
private readonly Dictionary<Type, MethodInfo> _handlers;
private readonly TcpClient _tcpClient;
private readonly byte[] _receiveBuffer;
private readonly byte[] _sendDataBuffer;
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
/// <paramref name="tcpClient"/>.
/// </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)
{
_tcpClient = tcpClient;
Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} connected");
TcpClient = tcpClient;
_handlers = GetHandlers();
_receiveBuffer = new byte[_maxDataSize];
_receiveBuffer = new byte[_maxDataSize];
_receiveStream = new PacketStream(_tcpClient.GetStream());
_sendDataBuffer = new byte[_maxDataSize];
_receiveStream = new PacketStream(TcpClient.GetStream());
_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) ---------------------------------------------------------------------------------------
public void Dispose()
@ -77,7 +82,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
{
if (disposing)
{
_tcpClient.Dispose();
TcpClient.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) --------------------------------------------------------------------------------------
private Dictionary<Type, MethodInfo> GetHandlers()
@ -107,13 +116,13 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
private void HandlePacket(Packet inPacket)
{
OnPrePacketHandle(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}");
OnPrePacketSend(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>
/// Represents a packet with an ID specifying its contents. To allow the server to instantiate child packet classes,
@ -6,6 +12,93 @@
/// </summary>
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) -------------------------------------------------------------------------------------
internal abstract void Deserialize(PacketStream stream);

View File

@ -2,11 +2,19 @@
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)]
internal class PacketAttribute : Attribute
{
// ---- 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)
{
ID = id;
@ -14,6 +22,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
/// <summary>
/// Gets the ID of the packet the decorated class represents.
/// </summary>
public ushort ID { get; }
}
}

View File

@ -12,7 +12,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
{
// ---- 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 ------------------------------------------------------------------------------
@ -23,9 +23,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
{
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.");
_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)
{
#if DEBUG
return _packetTypeMap.TryGetValue(id, out Type type)
return _packetTypes.TryGetValue(id, out Type type)
? (Packet)Activator.CreateInstance(type, true)
: new RawQueryPacket(id);
#else
return (Packet)Activator.CreateInstance(_packetTypeMap[id], true);
return (Packet)Activator.CreateInstance(_packetTypes[id], true);
#endif
}
/// <summary>
/// Gets the ID for the class of the given <paramref name="packet"/>.
/// </summary>
/// <typeparam name="T">The type of the <see cref="Packet"/>.</typeparam>
/// <param name="packet">The packet, whose class ID will be returned.</param>
/// <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 (packet is RawQueryPacket rawPacket)
return rawPacket.ID;
else
#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()
{
// Strings are word prefixed and 0 termianted.
string value = _baseStream.ReadString(StringCoding.Int16CharCount);
_baseStream.Seek(1);
return value;
return _baseStream.ReadString(StringCoding.Int16CharCount);
}
internal void WriteString(string value)
{
// Strings are word prefixed and 0 termianted.
_baseStream.WriteString(value, StringCoding.Int16CharCount, _win949Encoding);
_baseStream.WriteByte(0);
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using Syroot.BinaryData;
namespace Syroot.Worms.OnlineWorms.Server.Net
{
@ -10,9 +11,11 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
{
// ---- 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) -------------------------------------------------------------------------------------
@ -21,7 +24,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
internal override void Serialize(PacketStream stream)
{
stream.WriteString(Unknown);
stream.WriteString(Version);
stream.WriteByte(0);
stream.WriteString(Unknown2);
stream.WriteUInt16(Version);
}
}
}

View File

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

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
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"/>
/// instances.
/// </summary>
public class Server
internal class Server
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
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) -------------------------------------------------------------------------------------
/// <summary>
@ -27,16 +34,20 @@ namespace Syroot.Worms.OnlineWorms.Server
{
TcpListener tcpListener = new TcpListener(IPAddress.Any, port);
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)
{
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);
// Dispatch the client into its listening thread and remove it when listening aborts.
Task.Run(client.Listen).ContinueWith(_ =>
{
Log.Write(LogCategory.Disconnect, $"{client.TcpClient.Client.RemoteEndPoint} disconnected");
_clients.Remove(client);
client.Dispose();
});

View File

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