Split network layer and session layer

This commit is contained in:
xeon 2024-02-09 12:44:42 +03:00
parent 9419cab81c
commit 35ebc910ed
28 changed files with 170 additions and 133 deletions

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class AchievementMessageHandler : MessageHandlerBase internal class AchievementMessageHandler : MessageHandlerBase
{ {
public AchievementMessageHandler(KcpSession session) : base(session) public AchievementMessageHandler(PlayerSession session) : base(session)
{ {
// AchievementMessageHandler. // AchievementMessageHandler.
} }

View file

@ -8,7 +8,7 @@ internal class AuthMessageHandler : MessageHandlerBase
{ {
private readonly ModelManager _modelManager; private readonly ModelManager _modelManager;
public AuthMessageHandler(KcpSession session, ModelManager modelManager) : base(session) public AuthMessageHandler(PlayerSession session, ModelManager modelManager) : base(session)
{ {
_modelManager = modelManager; _modelManager = modelManager;
} }

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class DailyActivityMessageHandler : MessageHandlerBase internal class DailyActivityMessageHandler : MessageHandlerBase
{ {
public DailyActivityMessageHandler(KcpSession session) : base(session) public DailyActivityMessageHandler(PlayerSession session) : base(session)
{ {
// DailyActivityMessageHandler. // DailyActivityMessageHandler.
} }

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class ExchangeRewardMessageHandler : MessageHandlerBase internal class ExchangeRewardMessageHandler : MessageHandlerBase
{ {
public ExchangeRewardMessageHandler(KcpSession session) : base(session) public ExchangeRewardMessageHandler(PlayerSession session) : base(session)
{ {
// ExchangeRewardMessageHandler. // ExchangeRewardMessageHandler.
} }

View file

@ -8,7 +8,7 @@ internal class FormationMessageHandler : MessageHandlerBase
{ {
private readonly ModelManager _modelManager; private readonly ModelManager _modelManager;
public FormationMessageHandler(KcpSession session, ModelManager modelManager) : base(session) public FormationMessageHandler(PlayerSession session, ModelManager modelManager) : base(session)
{ {
_modelManager = modelManager; _modelManager = modelManager;
} }

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class FriendMessageHandler : MessageHandlerBase internal class FriendMessageHandler : MessageHandlerBase
{ {
public FriendMessageHandler(KcpSession session) : base(session) public FriendMessageHandler(PlayerSession session) : base(session)
{ {
// FriendMessageHandler. // FriendMessageHandler.
} }

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class GachaMessageHandler : MessageHandlerBase internal class GachaMessageHandler : MessageHandlerBase
{ {
public GachaMessageHandler(KcpSession session) : base(session) public GachaMessageHandler(PlayerSession session) : base(session)
{ {
// GachaMessageHandler. // GachaMessageHandler.
} }

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class InfluenceReputationMessageHandler : MessageHandlerBase internal class InfluenceReputationMessageHandler : MessageHandlerBase
{ {
public InfluenceReputationMessageHandler(KcpSession session) : base(session) public InfluenceReputationMessageHandler(PlayerSession session) : base(session)
{ {
// InfluenceReputationMessageHandler. // InfluenceReputationMessageHandler.
} }

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class InventoryMessageHandler : MessageHandlerBase internal class InventoryMessageHandler : MessageHandlerBase
{ {
public InventoryMessageHandler(KcpSession session) : base(session) public InventoryMessageHandler(PlayerSession session) : base(session)
{ {
// InventoryMessageHandler. // InventoryMessageHandler.
} }

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class LordGymMessageHandler : MessageHandlerBase internal class LordGymMessageHandler : MessageHandlerBase
{ {
public LordGymMessageHandler(KcpSession session) : base(session) public LordGymMessageHandler(PlayerSession session) : base(session)
{ {
// LordGymMessageHandler. // LordGymMessageHandler.
} }

View file

@ -3,9 +3,9 @@
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal abstract class MessageHandlerBase internal abstract class MessageHandlerBase
{ {
protected KcpSession Session { get; } protected PlayerSession Session { get; }
public MessageHandlerBase(KcpSession session) public MessageHandlerBase(PlayerSession session)
{ {
Session = session; Session = session;
} }

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class RoguelikeMessageHandler : MessageHandlerBase internal class RoguelikeMessageHandler : MessageHandlerBase
{ {
public RoguelikeMessageHandler(KcpSession session) : base(session) public RoguelikeMessageHandler(PlayerSession session) : base(session)
{ {
// RoguelikeMessageHandler. // RoguelikeMessageHandler.
} }

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class RoleMessageHandler : MessageHandlerBase internal class RoleMessageHandler : MessageHandlerBase
{ {
public RoleMessageHandler(KcpSession session) : base(session) public RoleMessageHandler(PlayerSession session) : base(session)
{ {
// RoleMessageHandler. // RoleMessageHandler.
} }

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class ShopMessageHandler : MessageHandlerBase internal class ShopMessageHandler : MessageHandlerBase
{ {
public ShopMessageHandler(KcpSession session) : base(session) public ShopMessageHandler(PlayerSession session) : base(session)
{ {
// ShopMessageHandler. // ShopMessageHandler.
} }

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class TowerMessageHandler : MessageHandlerBase internal class TowerMessageHandler : MessageHandlerBase
{ {
public TowerMessageHandler(KcpSession session) : base(session) public TowerMessageHandler(PlayerSession session) : base(session)
{ {
// TowerMessageHandler. // TowerMessageHandler.
} }

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class TutorialMessageHandler : MessageHandlerBase internal class TutorialMessageHandler : MessageHandlerBase
{ {
public TutorialMessageHandler(KcpSession session) : base(session) public TutorialMessageHandler(PlayerSession session) : base(session)
{ {
// TutorialMessageHandler. // TutorialMessageHandler.
} }

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class WorldMapMessageHandler : MessageHandlerBase internal class WorldMapMessageHandler : MessageHandlerBase
{ {
public WorldMapMessageHandler(KcpSession session) : base(session) public WorldMapMessageHandler(PlayerSession session) : base(session)
{ {
// WorldMapMessageHandler. // WorldMapMessageHandler.
} }

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers; namespace GameServer.Handlers;
internal class WorldMessageHandler : MessageHandlerBase internal class WorldMessageHandler : MessageHandlerBase
{ {
public WorldMessageHandler(KcpSession session) : base(session) public WorldMessageHandler(PlayerSession session) : base(session)
{ {
// WorldMessageHandler. // WorldMessageHandler.
} }

View file

@ -0,0 +1,9 @@
using GameServer.Network.Packets;
namespace GameServer.Network;
internal interface IConnection : ISessionActionListener
{
bool Active { get; }
ValueTask<BaseMessage?> ReceiveMessageAsync();
}

View file

@ -0,0 +1,7 @@
using GameServer.Network.Packets;
namespace GameServer.Network;
internal interface ISessionActionListener
{
public Task OnServerMessageAvailable(BaseMessage message);
}

View file

@ -0,0 +1,62 @@
using GameServer.Extensions;
using GameServer.Handlers;
using GameServer.Network.Packets;
using System.Buffers;
using KcpSharp;
namespace GameServer.Network.Kcp;
internal class KcpConnection : IConnection
{
private readonly byte[] _recvBuffer;
private readonly KcpConversation _conv;
private uint _upStreamSeqNo;
private uint _downStreamSeqNo;
public KcpConnection(KcpConversation conv)
{
_conv = conv;
_recvBuffer = GC.AllocateUninitializedArray<byte>(8192);
}
public bool Active => !_conv.TransportClosed;
public async ValueTask<BaseMessage?> ReceiveMessageAsync()
{
KcpConversationReceiveResult result = await _conv.ReceiveAsync(_recvBuffer.AsMemory(), CancellationToken.None);
if (result.TransportClosed) return null;
ReadOnlyMemory<byte> buffer = _recvBuffer.AsMemory(0, result.BytesReceived);
BaseMessage message = MessageManager.DecodeMessage(buffer.Slice(BaseMessage.LengthFieldSize, buffer.Span.ReadInt24LittleEndian()));
if (message.SeqNo < _downStreamSeqNo) return null;
_downStreamSeqNo = message.SeqNo;
return message;
}
public Task OnServerMessageAvailable(BaseMessage message)
{
message.SeqNo = NextUpStreamSeqNo();
return SendAsyncImpl(message);
}
private async Task SendAsyncImpl(BaseMessage message)
{
int networkSize = message.NetworkSize;
using IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(networkSize);
Memory<byte> memory = memoryOwner.Memory;
memory.Span.WriteInt24LittleEndian(networkSize - BaseMessage.LengthFieldSize);
MessageManager.EncodeMessage(memory[BaseMessage.LengthFieldSize..], message);
if (_conv == null) throw new InvalidOperationException("Trying to send message when conv is null");
await _conv.SendAsync(memoryOwner.Memory[..networkSize]);
}
private uint NextUpStreamSeqNo()
{
return Interlocked.Increment(ref _upStreamSeqNo);
}
}

View file

@ -5,7 +5,7 @@ using System.Net.Sockets;
using KcpSharp; using KcpSharp;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace GameServer.Network; namespace GameServer.Network.Kcp;
internal class KcpGateway internal class KcpGateway
{ {
private const int KcpSynPacketSize = 1; private const int KcpSynPacketSize = 1;

View file

@ -1,104 +0,0 @@
using System.Buffers;
using GameServer.Extensions;
using GameServer.Handlers;
using GameServer.Network.Messages;
using GameServer.Network.Packets;
using GameServer.Network.Rpc;
using Google.Protobuf;
using KcpSharp;
using Microsoft.Extensions.Logging;
using Protocol;
namespace GameServer.Network;
internal class KcpSession
{
private readonly ILogger _logger;
private readonly MessageManager _messageManager;
private readonly byte[] _recvBuffer;
private KcpConversation? _conv;
private uint _upStreamSeqNo;
private uint _downStreamSeqNo;
public RpcManager Rpc { get; }
public KcpSession(ILogger<KcpSession> logger, MessageManager messageManager, RpcManager rpcManager)
{
_logger = logger;
_messageManager = messageManager;
Rpc = rpcManager;
_recvBuffer = GC.AllocateUninitializedArray<byte>(8192);
}
public async Task RunAsync()
{
while (_conv != null)
{
KcpConversationReceiveResult result = await _conv.ReceiveAsync(_recvBuffer.AsMemory(), CancellationToken.None);
if (result.TransportClosed) return;
ReadOnlyMemory<byte> buffer = _recvBuffer.AsMemory(0, result.BytesReceived);
await HandleMessageAsync(MessageManager.DecodeMessage(buffer.Slice(BaseMessage.LengthFieldSize, buffer.Span.ReadInt24LittleEndian())));
}
}
private async Task HandleMessageAsync(BaseMessage message)
{
if (_downStreamSeqNo >= message.SeqNo) return;
_downStreamSeqNo = message.SeqNo;
switch (message)
{
case RequestMessage request:
await Rpc.HandleRpcRequest(request);
break;
case PushMessage push:
if (!await _messageManager.ProcessMessage(push.MessageId, push.Payload))
_logger.LogWarning("Push message ({id}) was not handled", push.MessageId);
break;
}
}
public Task PushMessage<TProtoBuf>(MessageId id, TProtoBuf data) where TProtoBuf : IMessage<TProtoBuf>
{
return Send(new PushMessage
{
MessageId = id,
Payload = data.ToByteArray()
});
}
public Task Send(BaseMessage message)
{
message.SeqNo = NextUpStreamSeqNo();
return SendAsyncImpl(message);
}
public void SetConv(KcpConversation conv)
{
if (_conv != null) throw new InvalidOperationException("Conv was already set");
_conv = conv;
}
private uint NextUpStreamSeqNo()
{
return Interlocked.Increment(ref _upStreamSeqNo);
}
private async Task SendAsyncImpl(BaseMessage message)
{
int networkSize = message.NetworkSize;
using IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(networkSize);
Memory<byte> memory = memoryOwner.Memory;
memory.Span.WriteInt24LittleEndian(networkSize - BaseMessage.LengthFieldSize);
MessageManager.EncodeMessage(memory[BaseMessage.LengthFieldSize..], message);
if (_conv == null) throw new InvalidOperationException("Trying to send message when conv is null");
await _conv.SendAsync(memoryOwner.Memory[..networkSize]);
}
}

View file

@ -0,0 +1,53 @@
using GameServer.Handlers;
using GameServer.Network.Messages;
using GameServer.Network.Packets;
using GameServer.Network.Rpc;
using Google.Protobuf;
using Microsoft.Extensions.Logging;
using Protocol;
namespace GameServer.Network;
internal class PlayerSession
{
private readonly ILogger _logger;
private readonly MessageManager _messageManager;
public RpcManager Rpc { get; }
public ISessionActionListener? Listener { private get; set; }
public PlayerSession(ILogger<PlayerSession> logger, MessageManager messageManager, RpcManager rpcManager)
{
_logger = logger;
_messageManager = messageManager;
Rpc = rpcManager;
}
public async Task HandleMessageAsync(BaseMessage message)
{
switch (message)
{
case RequestMessage request:
await Rpc.HandleRpcRequest(request);
break;
case PushMessage push:
if (!await _messageManager.ProcessMessage(push.MessageId, push.Payload))
_logger.LogWarning("Push message ({id}) was not handled", push.MessageId);
break;
}
}
public Task PushMessage<TProtoBuf>(MessageId id, TProtoBuf data) where TProtoBuf : IMessage<TProtoBuf>
{
return Listener?.OnServerMessageAvailable(new PushMessage
{
MessageId = id,
Payload = data.ToByteArray()
}) ?? Task.CompletedTask;
}
public Task SendRpcRsp(ResponseMessage message)
{
return Listener?.OnServerMessageAvailable(message) ?? Task.CompletedTask;
}
}

View file

@ -5,14 +5,14 @@ namespace GameServer.Network.Rpc;
internal class RpcSessionEndPoint : IRpcEndPoint internal class RpcSessionEndPoint : IRpcEndPoint
{ {
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private KcpSession? _session; private PlayerSession? _session;
private KcpSession Session => _session ??= _serviceProvider.GetRequiredService<KcpSession>(); private PlayerSession Session => _session ??= _serviceProvider.GetRequiredService<PlayerSession>();
public RpcSessionEndPoint(IServiceProvider serviceProvider) public RpcSessionEndPoint(IServiceProvider serviceProvider)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
} }
public Task SendRpcResult(ResponseMessage message) => Session.Send(message); public Task SendRpcResult(ResponseMessage message) => Session.SendRpcRsp(message);
} }

View file

@ -1,4 +1,6 @@
using KcpSharp; using GameServer.Network.Kcp;
using GameServer.Network.Packets;
using KcpSharp;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -17,12 +19,19 @@ internal class SessionManager
public async Task RunSessionAsync(KcpConversation kcpConv) public async Task RunSessionAsync(KcpConversation kcpConv)
{ {
using IServiceScope scope = _scopeFactory.CreateScope(); using IServiceScope scope = _scopeFactory.CreateScope();
KcpSession session = scope.ServiceProvider.GetRequiredService<KcpSession>(); PlayerSession session = scope.ServiceProvider.GetRequiredService<PlayerSession>();
KcpConnection connection = new(kcpConv);
session.Listener = connection;
try try
{ {
session.SetConv(kcpConv); while (connection.Active)
await session.RunAsync(); {
BaseMessage? message = await connection.ReceiveMessageAsync();
if (message == null) break;
await session.HandleMessageAsync(message);
}
} }
catch (Exception exception) catch (Exception exception)
{ {

View file

@ -3,6 +3,7 @@ using GameServer.Handlers;
using GameServer.Handlers.Factory; using GameServer.Handlers.Factory;
using GameServer.Models; using GameServer.Models;
using GameServer.Network; using GameServer.Network;
using GameServer.Network.Kcp;
using GameServer.Network.Rpc; using GameServer.Network.Rpc;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@ -18,7 +19,7 @@ internal static class Program
builder.Logging.AddConsole(); builder.Logging.AddConsole();
builder.Services.AddHandlers() builder.Services.AddHandlers()
.AddSingleton<KcpGateway>().AddScoped<KcpSession>() .AddSingleton<KcpGateway>().AddScoped<PlayerSession>()
.AddScoped<MessageManager>().AddSingleton<MessageHandlerFactory>() .AddScoped<MessageManager>().AddSingleton<MessageHandlerFactory>()
.AddScoped<RpcManager>().AddScoped<IRpcEndPoint, RpcSessionEndPoint>() .AddScoped<RpcManager>().AddScoped<IRpcEndPoint, RpcSessionEndPoint>()
.AddSingleton<SessionManager>() .AddSingleton<SessionManager>()

View file

@ -1,5 +1,5 @@
using GameServer.Handlers.Factory; using GameServer.Handlers.Factory;
using GameServer.Network; using GameServer.Network.Kcp;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
namespace GameServer; namespace GameServer;