Split network layer and session layer
This commit is contained in:
parent
9419cab81c
commit
35ebc910ed
28 changed files with 170 additions and 133 deletions
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
9
GameServer/Network/IConnection.cs
Normal file
9
GameServer/Network/IConnection.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using GameServer.Network.Packets;
|
||||
|
||||
namespace GameServer.Network;
|
||||
internal interface IConnection : ISessionActionListener
|
||||
{
|
||||
bool Active { get; }
|
||||
ValueTask<BaseMessage?> ReceiveMessageAsync();
|
||||
|
||||
}
|
7
GameServer/Network/ISessionActionListener.cs
Normal file
7
GameServer/Network/ISessionActionListener.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
using GameServer.Network.Packets;
|
||||
|
||||
namespace GameServer.Network;
|
||||
internal interface ISessionActionListener
|
||||
{
|
||||
public Task OnServerMessageAvailable(BaseMessage message);
|
||||
}
|
62
GameServer/Network/Kcp/KcpConnection.cs
Normal file
62
GameServer/Network/Kcp/KcpConnection.cs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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]);
|
||||
}
|
||||
}
|
53
GameServer/Network/PlayerSession.cs
Normal file
53
GameServer/Network/PlayerSession.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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>()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using GameServer.Handlers.Factory;
|
||||
using GameServer.Network;
|
||||
using GameServer.Network.Kcp;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace GameServer;
|
||||
|
|
Loading…
Reference in a new issue