feat: improve packet handling

This commit is contained in:
Aaron Yarborough 2025-02-19 18:50:46 +00:00
parent 6d80785dfd
commit 3f53dff0bd
11 changed files with 102 additions and 149 deletions

View file

@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NetCoreServer" Version="8.0.7" />
</ItemGroup>
</Project>

View file

@ -1,51 +0,0 @@
using System.Net.Sockets;
using NetCoreServer;
namespace GServer.NCSServer;
public class GameSession : TcpSession
{
public GameSession(TcpServer server) : base(server)
{
}
protected override void OnConnected()
{
Console.WriteLine($"Chat TCP session with Id {Id} connected!");
}
protected override void OnConnecting()
{
base.OnConnecting();
}
protected override void OnDisconnecting()
{
base.OnDisconnecting();
}
protected override void OnDisconnected()
{
Console.WriteLine($"Chat TCP session with Id {Id} disconnected!");
}
protected override void OnReceived(byte[] buffer, long offset, long size)
{
base.OnReceived(buffer, offset, size);
}
protected override void OnSent(long sent, long pending)
{
base.OnSent(sent, pending);
}
protected override void OnEmpty()
{
base.OnEmpty();
}
protected override void OnError(SocketError error)
{
base.OnError(error);
}
}

View file

@ -1,4 +0,0 @@
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
app.Run();

View file

@ -1,38 +0,0 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:58673",
"sslPort": 44396
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5051",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7192;http://localhost:5051",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View file

@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View file

@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View file

@ -0,0 +1,21 @@
namespace GServer.Server.Business.Services;
public interface IAuthService
{
/// <summary>
/// Checks whether a given email and password combination match.
/// </summary>
/// <param name="email"></param>
/// <param name="password"></param>
/// <returns></returns>
bool IsPasswordCorrect(string email, string password);
}
public class AuthService : IAuthService
{
public bool IsPasswordCorrect(string username, string password)
{
// TODO: Check DB
return username == "aaronyarbz" && password == "password123";
}
}

View file

@ -4,6 +4,11 @@
<ProjectReference Include="..\GServer.Common\GServer.Common.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.2" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>

View file

@ -1,4 +1,7 @@
using System.Net;
using GServer.Server.Business.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace GServer.Server;
@ -7,17 +10,44 @@ internal sealed class Program
{
private const int ListenPort = 11000;
private static void Main(string[] args)
private static async Task Main(string[] args)
{
CancellationTokenSource cancellationTokenSource = new();
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
Thread serverWorker = new(delegate()
// Register services
_ = builder.Services.AddScoped<IAuthService, AuthService>();
_ = builder.Services.AddScoped<ITcpMessageHandler, TcpMessageHandler>();
_ = builder.Services.AddTransient<ITcpGameServer>((services) =>
{
TcpGameServer server = new(new IPEndPoint(IPAddress.Any, ListenPort), new GameServerOptions());
return new TcpGameServer(
new IPEndPoint(IPAddress.Any, ListenPort),
services.GetRequiredService<ITcpMessageHandler>()
);
});
// Start service
using IHost host = builder.Build();
ApplicationLifetime(host.Services);
await host.RunAsync();
}
private static void ApplicationLifetime(IServiceProvider hostProvider)
{
using IServiceScope serviceScope = hostProvider.CreateScope();
Thread serverWorker = new(() =>
{
ITcpGameServer server = serviceScope.ServiceProvider.GetRequiredService<ITcpGameServer>();
server.Start();
while (true)
{
// Sleep to not consume too much CPU while waiting
Thread.Sleep(1000);
}
});
serverWorker.Start();
serverWorker.Join(); // Wait for the thread to complete before disposing the scope
}
}

View file

@ -1,15 +1,21 @@
using System.Net;
using System.Net.Sockets;
using GServer.Common.Networking.Core;
using GServer.Common.Networking.Messages;
namespace GServer.Server;
public class TcpGameServer(IPEndPoint endPoint, GameServerOptions options) : IDisposable
public interface ITcpGameServer
{
void Dispose();
void Start();
}
public class TcpGameServer(
IPEndPoint endPoint,
ITcpMessageHandler messageHandler
) : IDisposable, ITcpGameServer
{
private readonly GameServerOptions _options = options;
private readonly TcpListener _tcpListener = new(endPoint);
private readonly IMessageHandler _messageHandler = new TcpMessageHandler();
/// <summary>
/// Bind the server to the given endpoint.
@ -26,41 +32,46 @@ public class TcpGameServer(IPEndPoint endPoint, GameServerOptions options) : IDi
{
Console.WriteLine("Waiting for a connection...");
_tcpListener.AcceptTcpClient();
TcpClient client = _tcpListener.AcceptTcpClient();
Console.WriteLine("Client accepted!");
Thread worker = new Thread(new ParameterizedThreadStart(HandleClient!));
worker.Start();
Thread worker = new(new ParameterizedThreadStart(HandleClient!)); // TODO: use thread pools instead
worker.Start(client);
}
finally
catch (Exception ex)
{
_tcpListener.Stop();
Console.WriteLine($"An error occured while processing a tcp connection: {ex.Message}");
}
}
}
private void HandleClient(object clientObj)
private async void HandleClient(object clientObj)
{
TcpClient tcpClient = (TcpClient)clientObj;
if (clientObj is not TcpClient tcpClient)
{
return;
}
try
{
using NetworkStream stream = tcpClient.GetStream();
byte[] data = new byte[tcpClient.ReceiveBufferSize];
while (stream.Read(data, 0, data.Length) != 0)
using (tcpClient)
using (NetworkStream stream = tcpClient.GetStream())
{
// Use the in-memory buffer to process the message
_messageHandler.HandleMessageAsync(stream.Socket, new MessageMemoryStream(data)).Wait();
byte[] data = new byte[tcpClient.ReceiveBufferSize];
while (stream.Read(data, 0, data.Length) != 0)
{
// Use the in-memory buffer to process the message
await messageHandler.HandleMessageAsync(stream.Socket, new MessageMemoryStream(data));
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.WriteLine($"Error handling client: {ex.Message}");
}
}
private async void Stop()
private void Stop()
{
Console.WriteLine($"Stopping ${nameof(TcpGameServer)} listener...");
_tcpListener.Stop();

View file

@ -4,10 +4,18 @@ using GServer.Common.Networking.Enums;
using GServer.Common.Networking.Messages;
using GServer.Common.Networking.Messages.Client;
using GServer.Common.Networking.Messages.Server;
using GServer.Server.Business.Services;
namespace GServer.Server;
public class TcpMessageHandler : IMessageHandler
public interface ITcpMessageHandler
{
Task HandleMessageAsync(Socket clientSocket, MessageMemoryStream messageStream);
}
public class TcpMessageHandler(
IAuthService authService
) : IMessageHandler, ITcpMessageHandler
{
public async Task HandleMessageAsync(Socket clientSocket, MessageMemoryStream messageStream)
{
@ -20,7 +28,8 @@ public class TcpMessageHandler : IMessageHandler
case ServerPacketIn.Auth:
AuthMessage msg = new(messageStream);
AuthResponseMessage resp = msg is { Username: "aaronyarbz", Password: "password123" }
bool isPasswordCorrect = authService.IsPasswordCorrect(msg.Username, msg.Password);
AuthResponseMessage resp = isPasswordCorrect
? new(true, Guid.NewGuid().ToString(), failureReason: null)
: new(false, null, AuthResponseFailure.IncorrectLoginOrPassword);