using System.Net.Sockets; using Google.Protobuf; using RPG.Network.Proto; using RPG.Services.Core.Network; using RPG.Services.Core.Session; using RPG.Services.Gateserver.Network; namespace RPG.Services.Gateserver.Session; internal class NetworkSession : RPGSession { private const int ReadTimeoutMs = 30000; private const int ReceiveBufferSize = 16384; private readonly byte[] _recvBuffer; public Socket? Socket { private get; set; } public PlayerGetTokenCsReq? GetTokenCsReq { get; private set; } public NetworkSession(ulong sessionId, ServiceBox serviceBox) : base(sessionId, serviceBox) { _recvBuffer = GC.AllocateUninitializedArray(ReceiveBufferSize); } public async Task RunAsync() { if (Socket == null) throw new InvalidOperationException("RunAsync called but socket was not set!"); int recvBufferIdx = 0; Memory recvBufferMem = _recvBuffer.AsMemory(); while (true) { int readAmount = await ReadWithTimeoutAsync(Socket, recvBufferMem[recvBufferIdx..], ReadTimeoutMs); if (readAmount == 0) break; recvBufferIdx += readAmount; do { NetPacket.DeserializationResult result = NetPacket.TryDeserialize(recvBufferMem[..recvBufferIdx], out NetPacket? packet, out int bytesRead); if (result == NetPacket.DeserializationResult.BufferExceeded) break; if (result == NetPacket.DeserializationResult.Corrupted) throw new Exception(); HandleSessionPacketAsync(packet!); Buffer.BlockCopy(_recvBuffer, bytesRead, _recvBuffer, 0, recvBufferIdx -= bytesRead); } while (recvBufferIdx >= NetPacket.Overhead); } } public async Task SendAsync(ushort cmdType, TBody body) where TBody : IMessage { await SendAsync(new(cmdType, ReadOnlyMemory.Empty, body.ToByteArray())); } public async Task SendAsync(NetPacket packet) { if (Socket == null) return; byte[] buffer = GC.AllocateUninitializedArray(packet.Size); packet.Serialize(buffer); await Socket!.SendAsync(buffer); } private void HandleSessionPacketAsync(NetPacket packet) { switch ((CmdType)packet.CmdType) { case CmdType.CmdPlayerGetTokenCsReq: HandlePlayerGetTokenCsReq(PlayerGetTokenCsReq.Parser.ParseFrom(packet.Body.Span)); break; case CmdType.CmdPlayerKeepAliveNotify: break; default: ForwardToGameserver(packet); break; } } private void HandlePlayerGetTokenCsReq(PlayerGetTokenCsReq req) { GetTokenCsReq = req; PlayerUid = uint.Parse(req.AccountUid); BindService(RPGServiceType.Gameserver); } private void ForwardToGameserver(NetPacket packet) { SendToService(RPGServiceType.Gameserver, ServiceCommandType.ForwardGameMessage, new CmdForwardGameMessage { SessionId = SessionId, CmdType = packet.CmdType, Payload = ByteString.CopyFrom(packet.Body.Span) }); } private static async ValueTask ReadWithTimeoutAsync(Socket socket, Memory buffer, int timeoutMs) { CancellationTokenSource cts = new(TimeSpan.FromMilliseconds(timeoutMs)); return await socket.ReceiveAsync(buffer, cts.Token); } public override void Dispose() { Socket?.Close(); } }