Initial commit: Client Doc docs Server Tools
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
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<byte> _recvBuffer = new List<byte>(4096);
|
||||
public Action<NetMessage> OnGameMessage;
|
||||
public Action<Frame> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user