using Cysharp.Threading.Tasks;
|
using NativeWebSocket;
|
using System;
|
using System.Collections.Generic;
|
using System.Text;
|
using UnityEngine;
|
|
/// <summary>
|
/// WebSocket 网络服务 - 用于小游戏平台
|
/// </summary>
|
public class WebSocketNetworkService : INetworkService
|
{
|
private WebSocket webSocket;
|
private NetworkState _state = NetworkState.Disconnected;
|
private string currentUrl;
|
|
// 统计信息(与 ClientSocket 对齐)
|
private long getBytesTotal = 0; // 接收的数据总量
|
private long sendBytesTotal = 0; // 发送的数据总量
|
private DateTime m_LastPackageTime = DateTime.MinValue;
|
|
/// <summary>
|
/// 最后收包时间(实现接口)
|
/// </summary>
|
public DateTime LastPackageTime { get { return m_LastPackageTime; } }
|
|
// 发送队列(与 ClientSocket 对齐)
|
private Queue<byte[]> sendQueue = new Queue<byte[]>();
|
private bool isSending = false;
|
|
// 编解码器(与 ClientSocket 对齐)
|
private GameNetEncode encoder = new GameNetEncode();
|
|
// 服务器类型(与 ClientSocket 对齐)
|
private ServerType socketType = ServerType.Main;
|
|
/// <summary>
|
/// 构造函数(与 ClientSocket 对齐)
|
/// </summary>
|
public WebSocketNetworkService(ServerType type)
|
{
|
this.socketType = type;
|
}
|
|
public NetworkState State
|
{
|
get => _state;
|
private set
|
{
|
if (_state != value)
|
{
|
_state = value;
|
OnStateChanged?.Invoke(value);
|
}
|
}
|
}
|
|
public bool IsConnected => State == NetworkState.Connected;
|
|
public event Action<NetworkState> OnStateChanged;
|
|
/// <summary>
|
/// 连接到 WebSocket 服务器
|
/// </summary>
|
public async UniTask<bool> ConnectAsync(string url)
|
{
|
if (IsConnected)
|
{
|
Debug.LogWarning($"[WebSocket] 已经连接到 {currentUrl}");
|
return true;
|
}
|
|
try
|
{
|
State = NetworkState.Connecting;
|
currentUrl = url;
|
|
Debug.Log($"[WebSocket] 开始连接: {url}");
|
|
// 创建 WebSocket 连接(不传 headers,使用默认行为)
|
webSocket = new WebSocket(url);
|
|
// 注册事件监听
|
webSocket.OnOpen += OnWebSocketOpen;
|
webSocket.OnMessage += OnWebSocketMessage;
|
webSocket.OnError += OnWebSocketError;
|
webSocket.OnClose += OnWebSocketClose;
|
|
// 开始连接
|
Debug.Log($"[WebSocket] 调用 Connect()...");
|
|
// 启动连接任务(不等待它完成,因为它会一直运行到连接关闭)
|
var connectTask = webSocket.Connect();
|
|
// 等待 OnOpen 触发(最多5秒)
|
var timeout = DateTime.Now.AddSeconds(5);
|
while (State != NetworkState.Connected && DateTime.Now < timeout)
|
{
|
await UniTask.Yield();
|
}
|
|
Debug.Log($"[WebSocket] 等待结束,当前状态: {State}, WebSocket.State: {webSocket.State}");
|
|
if (IsConnected)
|
{
|
Debug.Log("[WebSocket] ✓ 连接成功");
|
return true;
|
}
|
else
|
{
|
Debug.LogError($"[WebSocket] ✗ 连接超时或失败,State={State}, WebSocket.State={webSocket?.State}");
|
return false;
|
}
|
}
|
catch (Exception ex)
|
{
|
Debug.LogError($"[WebSocket] 连接异常: {ex.Message}\n{ex.StackTrace}");
|
return false;
|
}
|
}
|
|
/// <summary>
|
/// 断开连接(异步)
|
/// </summary>
|
public async UniTask DisconnectAsync()
|
{
|
Debug.LogWarning($"[WebSocket] DisconnectAsync 被调用! State={State}");
|
Debug.LogWarning($"[WebSocket] 调用堆栈:\n{System.Environment.StackTrace}");
|
|
if (webSocket != null)
|
{
|
// 清空发送队列
|
lock (sendQueue)
|
{
|
sendQueue.Clear();
|
isSending = false;
|
}
|
|
await webSocket.Close();
|
webSocket = null;
|
}
|
State = NetworkState.Disconnected;
|
}
|
|
/// <summary>
|
/// 断开连接(同步,适配 ClientSocket.CloseConnect)
|
/// </summary>
|
public void Disconnect()
|
{
|
Debug.LogWarning($"[WebSocket] Disconnect 被调用! State={State}");
|
Debug.LogWarning($"[WebSocket] 调用堆栈:\n{System.Environment.StackTrace}");
|
DisconnectAsync().Forget();
|
}
|
|
/// <summary>
|
/// 发送协议包(与 ClientSocket.SendInfo 完全对齐)
|
/// </summary>
|
public void SendInfo(GameNetPackBasic protocol)
|
{
|
if (!IsConnected)
|
{
|
Debug.LogError("[WebSocket] 未连接,无法发送协议包");
|
return;
|
}
|
|
if (protocol == null)
|
{
|
Debug.LogError("[WebSocket] 要发的信息对象为空");
|
return;
|
}
|
|
try
|
{
|
// 1. 准备协议数据
|
if (protocol.combineBytes == null)
|
{
|
protocol.WriteToBytes();
|
}
|
|
// 2. 组合数据(与 ClientSocket.SendInfo 一致)
|
protocol.CombineDatas(encoder);
|
|
#if UNITY_EDITOR
|
// 3. 记录包信息到 NetPackageWindow(与 ClientSocket 对齐)
|
NetPkgCtl.RecordPackage(socketType, protocol.vInfoCont, NetPackagetType.Client,
|
protocol.ToString(), FieldPrint.PrintFields(protocol), FieldPrint.PrintFieldsExpand(protocol, true));
|
#endif
|
|
byte[] data = protocol.dataBytes;
|
|
// 4. 加密
|
byte[] encryptedData = encoder.BaseXorAdd(data);
|
|
// 5. 添加包头(6字节:0xFF 0xCC + 4字节长度小端序,与 ClientSocket 一致)
|
int bodyLength = encryptedData.Length;
|
byte[] sendData = new byte[bodyLength + 6];
|
sendData[0] = 0xFF; // 与 ClientSocket 一致
|
sendData[1] = 0xCC; // 与 ClientSocket 一致
|
// 使用 BitConverter 小端序(与 ClientSocket 一致)
|
byte[] lengthBytes = BitConverter.GetBytes(bodyLength);
|
Array.Copy(lengthBytes, 0, sendData, 2, 4);
|
Array.Copy(encryptedData, 0, sendData, 6, bodyLength);
|
|
// 6. 更新统计(与 ClientSocket 对齐)
|
sendBytesTotal += sendData.Length;
|
|
// 7. 发送(异步)
|
SendAsync(sendData).Forget();
|
}
|
catch (Exception ex)
|
{
|
Debug.LogError($"[WebSocket] 发送协议包异常: {ex.Message}\n{ex.StackTrace}");
|
}
|
}
|
|
/// <summary>
|
/// 发送二进制数据(同步重载,适配 ClientSocket.SendInfo)
|
/// </summary>
|
public void SendInfo(byte[] data)
|
{
|
SendAsync(data).Forget();
|
}
|
|
/// <summary>
|
/// 发送二进制数据(带队列机制,与 ClientSocket 对齐)
|
/// </summary>
|
public async UniTask SendAsync(byte[] data)
|
{
|
if (!IsConnected)
|
{
|
Debug.LogError("[WebSocket] 未连接,无法发送数据");
|
return;
|
}
|
|
if (data == null || data.Length == 0)
|
{
|
Debug.LogError("[WebSocket] 发送数据为空");
|
return;
|
}
|
|
// 检查消息大小(最大 64KB)
|
if (data.Length > 65536)
|
{
|
Debug.LogError($"[WebSocket] 消息过大: {data.Length} bytes(最大 64KB)");
|
return;
|
}
|
|
// 队列机制:如果正在发送,加入队列
|
lock (sendQueue)
|
{
|
if (isSending)
|
{
|
sendQueue.Enqueue(data);
|
Debug.Log($"[WebSocket] 数据加入发送队列,队列长度: {sendQueue.Count}");
|
return;
|
}
|
else
|
{
|
isSending = true;
|
}
|
}
|
|
// 发送数据
|
await SendBytesInternal(data);
|
}
|
|
/// <summary>
|
/// 内部发送方法(处理队列)
|
/// </summary>
|
private async UniTask SendBytesInternal(byte[] data)
|
{
|
try
|
{
|
await webSocket.Send(data);
|
sendBytesTotal += data.Length;
|
Debug.Log($"[WebSocket] 发送成功: {data.Length} bytes,总发送: {sendBytesTotal} bytes");
|
|
// 处理队列中的下一个消息
|
byte[] nextData = null;
|
lock (sendQueue)
|
{
|
if (sendQueue.Count > 0)
|
{
|
nextData = sendQueue.Dequeue();
|
}
|
else
|
{
|
isSending = false;
|
}
|
}
|
|
if (nextData != null)
|
{
|
await SendBytesInternal(nextData);
|
}
|
}
|
catch (Exception ex)
|
{
|
Debug.LogError($"[WebSocket] 发送失败: {ex.Message}");
|
|
lock (sendQueue)
|
{
|
isSending = false;
|
}
|
}
|
}
|
|
/// <summary>
|
/// WebSocket 连接成功回调
|
/// </summary>
|
private void OnWebSocketOpen()
|
{
|
Debug.Log($"[WebSocket] ✓✓✓ OnOpen 事件触发 - 连接成功: {currentUrl}");
|
Debug.Log($"[WebSocket] WebSocket.State: {webSocket?.State}");
|
State = NetworkState.Connected;
|
Debug.Log($"[WebSocket] 当前状态已更新为: {State}");
|
}
|
|
/// <summary>
|
/// WebSocket 消息接收回调(与 ClientSocket.ReadInfo 对齐)
|
/// </summary>
|
private void OnWebSocketMessage(byte[] data)
|
{
|
// 完整的包处理流程(与 ClientSocket.ReadInfo 对齐)
|
try
|
{
|
if (data == null || data.Length < 6)
|
{
|
Debug.LogError($"[WebSocket] 收到无效数据包: {data?.Length ?? 0} bytes");
|
return;
|
}
|
|
// 更新统计信息
|
getBytesTotal += data.Length;
|
Debug.Log($"[WebSocket] 收到原始消息: {data.Length} bytes");
|
|
// 检查包头(0xFF 0xCC,与 ClientSocket 一致)
|
if (data[0] != 0xFF || data[1] != 0xCC)
|
{
|
Debug.LogError($"[WebSocket] 包头错误: 0x{data[0]:X2} 0x{data[1]:X2},期望 0xFF 0xCC");
|
return;
|
}
|
|
// 读取包体长度(小端序,与 ClientSocket 一致)
|
int bodyLength = BitConverter.ToInt32(data, 2);
|
|
if (data.Length < bodyLength + 6)
|
{
|
Debug.LogError($"[WebSocket] 包长度不匹配: 声明 {bodyLength + 6}, 实际 {data.Length}");
|
return;
|
}
|
|
// 提取加密的包体(跳过6字节包头)
|
byte[] encryptedBody = new byte[bodyLength];
|
Array.Copy(data, 6, encryptedBody, 0, bodyLength);
|
|
// 解密包体(与 ClientSocket.ReadInfo 一致)
|
byte[] decryptedData = encoder.BaseXorSub(encryptedBody);
|
|
// 解析 CMD(前2字节)
|
byte[] cmdBytes = new byte[2];
|
Array.Copy(decryptedData, 0, cmdBytes, 0, 2);
|
var cmd = (ushort)((ushort)(cmdBytes[0] << 8) + cmdBytes[1]);
|
|
Debug.Log($"[WebSocket] 解密成功,CMD=0x{cmd:X4}");
|
|
bool isRegist = false;
|
|
// 转换协议包(与 ClientSocket.ReadInfo 一致)
|
if (PackageRegedit.Contain(cmd))
|
{
|
GameNetPackBasic pack = PackageRegedit.TransPack(socketType, cmd, decryptedData);
|
if (pack != null)
|
{
|
Debug.Log($"[WebSocket] 收到协议: {pack.GetType().Name}");
|
m_LastPackageTime = DateTime.Now;
|
|
// 直接推送到 GameNetSystem(与 ClientSocket 一致)
|
GameNetSystem.Instance.PushPackage(pack, socketType);
|
isRegist = true;
|
}
|
}
|
|
// 未注册封包处理(与 ClientSocket 一致)
|
if (!isRegist)
|
{
|
#if UNITY_EDITOR
|
PackageRegedit.TransPack(socketType, cmd, decryptedData);
|
#endif
|
}
|
}
|
catch (Exception ex)
|
{
|
Debug.LogError($"[WebSocket] 消息处理异常: {ex.Message}\n{ex.StackTrace}");
|
}
|
}
|
|
/// <summary>
|
/// WebSocket 错误回调
|
/// </summary>
|
private void OnWebSocketError(string error)
|
{
|
Debug.LogError($"[WebSocket] ✗ OnError 事件触发: {error}");
|
Debug.LogError($"[WebSocket] 当前状态: {State}, WebSocket.State: {webSocket?.State}");
|
}
|
|
/// <summary>
|
/// WebSocket 关闭回调(不自动重连,与 ClientSocket 一致)
|
/// </summary>
|
private void OnWebSocketClose(WebSocketCloseCode code)
|
{
|
Debug.LogError($"[WebSocket] ⊗ OnClose 事件触发!");
|
Debug.LogError($"[WebSocket] 关闭代码: {code} ({(int)code})");
|
Debug.LogError($"[WebSocket] 之前状态: {State}\n");
|
|
// 判断是谁关闭的
|
if (code == WebSocketCloseCode.Normal)
|
{
|
Debug.LogWarning("[WebSocket] → 正常关闭(客户端主动)");
|
}
|
else if (code == WebSocketCloseCode.Abnormal)
|
{
|
Debug.LogError("[WebSocket] → 异常关闭(连接丢失或服务器断开)");
|
}
|
else
|
{
|
Debug.LogError($"[WebSocket] → 其他原因关闭: {code}");
|
}
|
|
State = NetworkState.Disconnected;
|
|
// 注意:不自动重连,与 ClientSocket 一致,由 GameNetSystem 控制重连逻辑
|
Debug.Log("[WebSocket] 连接已断开,等待 GameNetSystem 处理");
|
}
|
|
/// <summary>
|
/// 获取接收统计信息
|
/// </summary>
|
public long GetBytesTotal() => getBytesTotal;
|
|
/// <summary>
|
/// 获取发送统计信息
|
/// </summary>
|
public long GetSendBytesTotal() => sendBytesTotal;
|
|
/// <summary>
|
/// 获取发送队列长度
|
/// </summary>
|
public int GetSendQueueCount()
|
{
|
lock (sendQueue)
|
{
|
return sendQueue.Count;
|
}
|
}
|
|
/// <summary>
|
/// 每帧处理 WebSocket 消息(必须在 MonoBehaviour 的 Update 中调用)
|
/// </summary>
|
public void Update()
|
{
|
#if !UNITY_WEBGL || UNITY_EDITOR
|
webSocket?.DispatchMessageQueue();
|
#endif
|
}
|
}
|