116 lines
4.2 KiB
C#
116 lines
4.2 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|