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;
internal class AchievementMessageHandler : MessageHandlerBase
{
public AchievementMessageHandler(KcpSession session) : base(session)
public AchievementMessageHandler(PlayerSession session) : base(session)
{
// AchievementMessageHandler.
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ using Protocol;
namespace GameServer.Handlers;
internal class WorldMessageHandler : MessageHandlerBase
{
public WorldMessageHandler(KcpSession session) : base(session)
public WorldMessageHandler(PlayerSession session) : base(session)
{
// 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 Microsoft.Extensions.Logging;
namespace GameServer.Network;
namespace GameServer.Network.Kcp;
internal class KcpGateway
{
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
{
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)
{
_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.Logging;
@ -17,12 +19,19 @@ internal class SessionManager
public async Task RunSessionAsync(KcpConversation kcpConv)
{
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
{
session.SetConv(kcpConv);
await session.RunAsync();
while (connection.Active)
{
BaseMessage? message = await connection.ReceiveMessageAsync();
if (message == null) break;
await session.HandleMessageAsync(message);
}
}
catch (Exception exception)
{

View file

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

View file

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