using System; using System.Collections.Generic; using UnityEngine; using XWorld.Framework; using XWorld.Framework.Protocol; namespace XGame.MiniGame { // 客户端到服务端的帧通道。每帧轮询底层 socket,解码出的帧分发: // Game 帧 -> OnGameMessage(NetMessage)(交给 IGameClient.OnNetMessage) // Framework 帧 -> OnFrameworkFrame(Frame)(宿主处理 MatchFound/RoomEnd 等) public sealed class MiniGameNetChannel { private readonly XWebSocket _sock; private readonly List _recvBuffer = new List(4096); public Action OnGameMessage; public Action OnFrameworkFrame; public MiniGameNetChannel(XWebSocket sock) { _sock = sock; } public bool IsConnected => _sock != null && _sock.IsConected(); // 业务输入:IGameClient 调 ctx.Send(NetMessage) -> 这里包成 Game 帧发出 public void SendGame(NetMessage msg) { byte[] frame = FrameCodec.Encode(new Frame(Channel.Game, msg.Opcode, msg.Payload)); SendRaw(frame); } public void SendFramework(FrameworkOpcode op, byte[] payload) { byte[] frame = FrameCodec.Encode(new Frame(Channel.Framework, (ushort)op, payload)); SendRaw(frame); } private void SendRaw(byte[] frame) { string err = null; _sock.send(frame, null, ref err); if (!string.IsNullOrEmpty(err)) Debug.LogError("[MiniGameNetChannel] send err: " + err); } // 每帧由宿主调用:把底层收到的字节解出并分发。 // XWebSocket 会把多条 WS 消息追加到同一个 buffer;Frame 自带 payloadLen, // 所以这里按 FrameCodec 的线网格式从累计字节流中拆出 0..N 帧。 public void Pump() { if (_sock == null) return; string err = null; while (_sock.isDataRecv()) { byte[] data = _sock.recv(ref err); if (!string.IsNullOrEmpty(err)) { Debug.LogError("[MiniGameNetChannel] recv err: " + err); break; } if (data == null || data.Length == 0) break; _recvBuffer.AddRange(data); } while (TryPopFrame(out var frameBytes)) { Frame f; try { f = FrameCodec.Decode(frameBytes); } catch (FormatException) { Debug.LogWarning("[MiniGameNetChannel] 畸形帧,丢弃"); continue; } if (f.Channel == Channel.Game) OnGameMessage?.Invoke(new NetMessage(f.Opcode, f.Payload)); else OnFrameworkFrame?.Invoke(f); } } private bool TryPopFrame(out byte[] frameBytes) { frameBytes = null; if (_recvBuffer.Count < 4) return false; // channel + opcode + at least 1-byte payload len int pos = 3; uint payloadLen = 0; int shift = 0; while (true) { if (pos >= _recvBuffer.Count) return false; if (shift > 28) { Debug.LogWarning("[MiniGameNetChannel] payloadLen varuint too long, clear buffer"); _recvBuffer.Clear(); return false; } byte b = _recvBuffer[pos++]; if (shift == 28 && (b & 0x70) != 0) { Debug.LogWarning("[MiniGameNetChannel] payloadLen overflow, clear buffer"); _recvBuffer.Clear(); return false; } payloadLen |= (uint)(b & 0x7F) << shift; if ((b & 0x80) == 0) break; shift += 7; } if (payloadLen > 1024 * 1024) { Debug.LogWarning("[MiniGameNetChannel] frame too large, clear buffer"); _recvBuffer.Clear(); return false; } int totalLen = pos + (int)payloadLen; if (_recvBuffer.Count < totalLen) return false; frameBytes = _recvBuffer.GetRange(0, totalLen).ToArray(); _recvBuffer.RemoveRange(0, totalLen); return true; } } }