feat: wip: implement TCPGameServer

This commit is contained in:
Aaron Yarborough 2024-05-22 18:21:27 +01:00
parent 35755eb7ec
commit c640988354
11 changed files with 130 additions and 121 deletions

View file

@ -13,9 +13,9 @@ internal class Program
{ {
IPEndPoint serverEP = new(IPAddress.Any, SERVER_PORT); IPEndPoint serverEP = new(IPAddress.Any, SERVER_PORT);
UdpClient udpClient = new(); TcpClient tcpClient = new();
udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
udpClient.Connect(serverEP); tcpClient.Connect(serverEP);
Console.WriteLine("Username..."); Console.WriteLine("Username...");
string username = Console.ReadLine()!; string username = Console.ReadLine()!;
@ -24,31 +24,35 @@ internal class Program
string password = Console.ReadLine()!; string password = Console.ReadLine()!;
AuthMessage authMessage = new(username, password); AuthMessage authMessage = new(username, password);
udpClient.Send(authMessage.Serialize()); _ = tcpClient.Client.Send(authMessage.Serialize());
try try
{ {
while (true) while (true)
{ {
byte[] bytes = udpClient.Receive(ref serverEP); byte[] bytes = new byte[tcpClient.Client.ReceiveBufferSize];
_ = tcpClient.Client.Receive(bytes);
MessageNetworkStream stream = new(bytes); MessageMemoryStream stream = new(bytes);
ClientPacketIn packetIn = (ClientPacketIn)stream.ReadByte(); ClientPacketIn packetIn = (ClientPacketIn)stream.ReadByte();
switch (packetIn) switch (packetIn)
{ {
case ClientPacketIn.AUTH_RESPONSE: case ClientPacketIn.AUTH_RESPONSE:
var authResultMessage = new AuthResponseMessage(stream); AuthResponseMessage authResultMessage = new(stream);
Console.WriteLine("Success = " + authResultMessage.IsSuccessful); Console.WriteLine("Success = " + authResultMessage.IsSuccessful);
Console.WriteLine("SessionToken = " + authResultMessage.SessionToken ?? "null"); Console.WriteLine("SessionToken = " + authResultMessage.SessionToken ?? "null");
Console.WriteLine("FailureReason = " + authResultMessage.FailureReason ?? "null"); Console.WriteLine("FailureReason = " + authResultMessage.FailureReason ?? "null");
break; break;
case ClientPacketIn.LIST_SERVERS_RESPONSE:
break;
case ClientPacketIn.UNKNOWN:
break;
default: default:
Console.WriteLine($"Received unsupported packet."); Console.WriteLine($"Received unsupported packet.");
break; break;
@ -61,7 +65,7 @@ internal class Program
} }
finally finally
{ {
udpClient.Close(); tcpClient.Close();
} }

View file

@ -4,6 +4,10 @@ namespace GServer.Common;
public class MessageMemoryStream : MemoryStream public class MessageMemoryStream : MemoryStream
{ {
public MessageMemoryStream()
{
}
public MessageMemoryStream(byte[] buffer) : base(buffer) public MessageMemoryStream(byte[] buffer) : base(buffer)
{ {
} }

View file

@ -30,7 +30,7 @@ public class AuthResponseMessage : BaseMessage, IMessage<AuthResponseMessage>
FailureReason = failureReason; FailureReason = failureReason;
} }
public AuthResponseMessage(MessageNetworkStream stream) : base((byte)ClientPacketIn.AUTH_RESPONSE) public AuthResponseMessage(MessageMemoryStream stream) : base((byte)ClientPacketIn.AUTH_RESPONSE)
{ {
IsSuccessful = stream.ReadBoolean(); IsSuccessful = stream.ReadBoolean();
@ -47,7 +47,7 @@ public class AuthResponseMessage : BaseMessage, IMessage<AuthResponseMessage>
public byte[] Serialize() public byte[] Serialize()
{ {
using MessageNetworkStream stream = new(); using MessageMemoryStream stream = new();
stream.WriteByte(PacketId); stream.WriteByte(PacketId);
stream.WriteBoolean(IsSuccessful); stream.WriteBoolean(IsSuccessful);

View file

@ -1,24 +0,0 @@
using GServer.Common.Game.Entities;
using GServer.Common.Networking.Enums;
namespace GServer.Common.Networking.Messages.Client;
public class ListServersResponseMessage : BaseMessage, IMessage<ListServersResponseMessage>
{
public IEnumerable<ServerListing> ServerListings { get; set; }
public ListServersResponseMessage(IEnumerable<ServerListing> serverListings) : base((byte)ClientPacketIn.LIST_SERVERS_RESPONSE)
{
ServerListings = serverListings;
}
public ListServersResponseMessage(MessageNetworkStream stream) : base((byte)ClientPacketIn.LIST_SERVERS_RESPONSE)
{
throw new NotImplementedException();
}
public byte[] Serialize()
{
throw new NotImplementedException();
}
}

View file

@ -0,0 +1,8 @@
using System.Net.Sockets;
namespace GServer.Common.Networking.Messages;
public interface IMessageHandler
{
Task HandleMessageAsync(Socket clientSocket, MessageMemoryStream messageStream);
}

View file

@ -14,7 +14,7 @@ public class AuthMessage : BaseMessage, IMessage<AuthMessage>
Password = password; Password = password;
} }
public AuthMessage(MessageNetworkStream stream) : base((byte)ServerPacketIn.AUTH) public AuthMessage(MessageMemoryStream stream) : base((byte)ServerPacketIn.AUTH)
{ {
byte usernameLen = (byte)stream.ReadByte(); byte usernameLen = (byte)stream.ReadByte();
string username = stream.ReadUTF8String(usernameLen); string username = stream.ReadUTF8String(usernameLen);

View file

@ -1,20 +1,15 @@
using System.Net; using System.Net;
using GServer.Server; using GServer.Server;
internal class Program internal sealed class Program
{ {
private const int LISTEN_PORT = 11000; private const int LISTEN_PORT = 11000;
private static void Main(string[] args) private static void Main(string[] args)
{ {
UDPGameServer udpGameServer = new(new IPEndPoint(IPAddress.Any, LISTEN_PORT)); TCPGameServer server = new(new IPEndPoint(IPAddress.Any, LISTEN_PORT), new GameServerOptions());
CancellationTokenSource cancellationTokenSource = new();
udpGameServer.Start(); server.Start(cancellationTokenSource.Token).Wait();
while (true)
{
_ = udpGameServer.ProcessAsync();
}
} }
} }

View file

@ -1,25 +1,24 @@
using GServer.Common.Game.Entities; using GServer.Common.Game.Entities;
namespace GServer.Server.Services namespace GServer.Server.Services;
{
public class ServerListService
{
public ServerListService()
{
}
public IEnumerable<ServerListing> List() public class ServerListService
{ {
return [ public ServerListService()
new ServerListing {
{ }
Name = "Smallville",
Description = "A tiny development server!", public IEnumerable<ServerListing> List()
IPAddress = "localhost", {
Port = 11001, return [
Playercount = 1, new ServerListing
} {
]; Name = "Smallville",
} Description = "A tiny development server!",
IPAddress = "localhost",
Port = 11001,
Playercount = 1,
}
];
} }
} }

View file

@ -1,9 +1,7 @@
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using GServer.Common; using GServer.Common;
using GServer.Common.Networking.Enums; using GServer.Common.Networking.Messages;
using GServer.Common.Networking.Messages.Client;
using GServer.Common.Networking.Messages.Server;
namespace GServer.Server; namespace GServer.Server;
@ -12,6 +10,7 @@ public class TCPGameServer : IDisposable
private readonly TcpListener _tcpListener; private readonly TcpListener _tcpListener;
private readonly IPEndPoint _endPoint; private readonly IPEndPoint _endPoint;
private readonly GameServerOptions _options; private readonly GameServerOptions _options;
private readonly IMessageHandler _messageHandler;
public TCPGameServer(IPEndPoint endPoint, GameServerOptions options) public TCPGameServer(IPEndPoint endPoint, GameServerOptions options)
{ {
@ -19,32 +18,42 @@ public class TCPGameServer : IDisposable
_options = options; _options = options;
_tcpListener = new TcpListener(endPoint); _tcpListener = new TcpListener(endPoint);
_messageHandler = new TCPMessageHandler();
} }
/// <summary> /// <summary>
/// Bind the server to the given endpoint. /// Bind the server to the given endpoint.
/// </summary> /// </summary>
public async void Start() public async Task Start(CancellationToken cancellationToken)
{ {
Console.WriteLine("Starting TCPGameServer listener..."); Console.WriteLine("Starting TCPGameServer listener...");
_tcpListener.Start(); _tcpListener.Start();
_ = cancellationToken.Register(_tcpListener.Stop);
Console.WriteLine($"TCPGameServer listening on {_endPoint}"); Console.WriteLine($"TCPGameServer listening on {_endPoint}");
try while (!cancellationToken.IsCancellationRequested)
{ {
while (true) try
{ {
await ProcessAsync(await _tcpListener.AcceptTcpClientAsync()); using TcpClient tcpClient = await _tcpListener.AcceptTcpClientAsync(cancellationToken);
await ProcessAsync(tcpClient);
}
catch (SocketException) when (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("TcpListener stopped listening because cancellation was requested.");
}
catch
{
throw;
}
finally
{
_tcpListener.Stop();
} }
} }
catch
{
throw;
}
finally
{
_tcpListener.Stop();
}
} }
/// <summary> /// <summary>
@ -57,7 +66,7 @@ public class TCPGameServer : IDisposable
{ {
using NetworkStream stream = tcpClient.GetStream(); using NetworkStream stream = tcpClient.GetStream();
byte[] data = new byte[_options.PacketLength]; byte[] data = new byte[tcpClient.ReceiveBufferSize];
int bytesRead = 0; int bytesRead = 0;
int chunkSize = 1; int chunkSize = 1;
@ -70,7 +79,7 @@ public class TCPGameServer : IDisposable
} }
// Use the in-memory buffer to process the message // Use the in-memory buffer to process the message
await HandleMessageAsync(stream.Socket, new MessageMemoryStream(data)); await _messageHandler.HandleMessageAsync(stream.Socket, new MessageMemoryStream(data));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -78,39 +87,8 @@ public class TCPGameServer : IDisposable
} }
} }
private async Task HandleMessageAsync(Socket clientSocket, MessageMemoryStream messageStream)
{
ServerPacketIn serverPacketIn = (ServerPacketIn)messageStream.ReadByte();
Console.WriteLine($"Handling message {serverPacketIn} from {client}...");
switch (serverPacketIn)
{
case ServerPacketIn.AUTH:
AuthMessage msg = new(messageStream);
AuthResponseMessage resp = msg.Username == "aaronyarbz" && msg.Password == "password123"
? new(true, Guid.NewGuid().ToString(), null)
: new(false, null, AuthResponseFailure.IncorrectLoginOrPassword);
byte[] buffer = resp.Serialize();
_ = await TcpClient.Client.SendAsync(buffer,
break;
case ServerPacketIn.LIST_SERVERS:
throw new NotImplementedException();
default:
Console.WriteLine($"Received unsupported packet.");
break;
}
}
public void Dispose() public void Dispose()
{ {
TcpClient.Close();
TcpClient.Dispose();
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
} }

View file

@ -0,0 +1,44 @@
using System.Net.Sockets;
using GServer.Common;
using GServer.Common.Networking.Enums;
using GServer.Common.Networking.Messages;
using GServer.Common.Networking.Messages.Client;
using GServer.Common.Networking.Messages.Server;
namespace GServer.Server;
public class TCPMessageHandler : IMessageHandler
{
public TCPMessageHandler()
{
}
public async Task HandleMessageAsync(Socket clientSocket, MessageMemoryStream messageStream)
{
ServerPacketIn serverPacketIn = (ServerPacketIn)messageStream.ReadByte();
Console.WriteLine($"Handling message {serverPacketIn} from {clientSocket.RemoteEndPoint}...");
switch (serverPacketIn)
{
case ServerPacketIn.AUTH:
AuthMessage msg = new(messageStream);
AuthResponseMessage resp = msg.Username == "aaronyarbz" && msg.Password == "password123"
? new(true, Guid.NewGuid().ToString(), null)
: new(false, null, AuthResponseFailure.IncorrectLoginOrPassword);
byte[] buffer = resp.Serialize();
_ = await clientSocket.SendAsync(buffer);
break;
case ServerPacketIn.LIST_SERVERS:
throw new NotImplementedException();
default:
Console.WriteLine($"Received unsupported packet.");
break;
}
}
}

View file

@ -41,7 +41,7 @@ public class UDPGameServer : IDisposable
{ {
UdpReceiveResult res = await UdpClient.ReceiveAsync(); UdpReceiveResult res = await UdpClient.ReceiveAsync();
byte[] bytes = res.Buffer; byte[] bytes = res.Buffer;
MessageNetworkStream stream = new(bytes); MessageMemoryStream stream = new(bytes);
await HandleMessageAsync(stream, res.RemoteEndPoint); await HandleMessageAsync(stream, res.RemoteEndPoint);
} }
catch (Exception ex) catch (Exception ex)
@ -50,18 +50,19 @@ public class UDPGameServer : IDisposable
} }
} }
private async Task HandleMessageAsync(MessageNetworkStream stream, IPEndPoint remoteEndPoint) private async Task HandleMessageAsync(MessageMemoryStream stream, IPEndPoint remoteEndPoint)
{ {
byte serverPacketInByte = (byte)stream.ReadByte(); byte serverPacketInByte = (byte)stream.ReadByte();
ServerPacketIn serverPacketIn = (ServerPacketIn)serverPacketInByte; ServerPacketIn serverPacketIn = (ServerPacketIn)serverPacketInByte;
Console.WriteLine($"Handling UDP message {serverPacketInByte} from {remoteEndPoint}..."); Console.WriteLine($"Handling UDP message {serverPacketInByte} from {remoteEndPoint}...");
switch (serverPacketIn) throw serverPacketIn switch
{ {
default: ServerPacketIn.AUTH => new NotImplementedException(),
throw new NotImplementedException($"Received unsupported packet {serverPacketInByte}"); ServerPacketIn.LIST_SERVERS => new NotImplementedException(),
} _ => new NotImplementedException($"Received unsupported packet {serverPacketInByte}"),
};
} }
public void Dispose() public void Dispose()