yyl
2026-05-11 51b0f6ed9f4e1d3bb6f8144470b46908c7699a96
Main/Core/NetworkPackage/Socket/ClientSocket.cs
@@ -1,47 +1,88 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
#if !UNITY_WEBGL
using System.Net;
using System.Net.Sockets;
using System.Threading;
#else
using NativeWebSocket;
using Cysharp.Threading.Tasks;
#endif
/// <summary>
/// 统一的网络Socket类 - 自动适配TCP Socket和WebSocket
/// TCP Socket: Windows, Mac, iOS, Android等平台
/// WebSocket: WebGL/微信小游戏等Web平台
/// </summary>
public class ClientSocket
{
    GameNetEncode encoder = new GameNetEncode();
#if !UNITY_WEBGL
    // TCP Socket 实现(非WebGL平台)
    Socket m_Socket;
    public Socket socket { get { return m_Socket; } }
    private Thread m_packageThread;
    private byte[] bufferBytes = new byte[4096];                       // 4K,单包字节数组缓存
    private byte[] fragmentBytes;                                               //留包后的内容
    private long getBytesTotal = 0;                                            //发送的数据总量
    private long sendBytesTotal = 0;                                         //发送的数据总量
    private byte[] bufferBytes = new byte[4096];
    private byte[] fragmentBytes; // TCP分包缓存
    bool isStopTreading = false;
#else
    // WebSocket 实现(WebGL平台)
    WebSocket webSocket;
    public WebSocket socket { get { return webSocket; } }
    private byte[] fragmentBytes; // TCP-to-WS网关按TCP缓冲区拆包,需要跨消息重组
#endif
    public bool connected { get { return m_Socket == null ? false : m_Socket.Connected; } }
    public Action OnDisconnected;
    private long getBytesTotal = 0;
    private long sendBytesTotal = 0;
    public bool connected
    {
        get
        {
#if !UNITY_WEBGL
            return m_Socket == null ? false : m_Socket.Connected;
#else
            return webSocket != null && webSocket.State == WebSocketState.Open;
#endif
        }
    }
    ServerType socketType = ServerType.Main;
    DateTime m_LastPackageTime;
    public DateTime lastPackageTime { get { return m_LastPackageTime; } }
    bool isStopTreading = false;
    string ip;
    int port;
    Action<bool> onConnected = null;
    Queue<byte[]> sendQueue = new Queue<byte[]>();
    static byte[] vCmdBytes = new byte[2];
#if UNITY_WEBGL
    string webSocketUrl;
    bool webSocketOpened;
#endif
    public ClientSocket(ServerType type)
    {
        this.socketType = type;
    }
#if !UNITY_WEBGL
    // ==================== TCP Socket 实现 ====================
    public void Connect(string _ip, int _port, Action<bool> _onConnected)
    {
        Debug.unityLogger.logEnabled = true;
        try
        {
            ip = _ip;
            port = _port;
            onConnected = _onConnected;
            Debug.Log($"[ClientSocket][Connect] 尝试连接: ip={_ip}, port={_port}");
            //目前测试到异步两个问题
            // 1. BeginGetHostAddresses 不明情况下会很久才回调,导致触发超时
            // 2. 超时的情况下多次尝试登录后,会触发多次OnGetHostAddresses,导致登录异常
@@ -57,29 +98,29 @@
            ipAddress = ipAddresses[0];
#endif
            Debug.Log($"[ClientSocket][Connect] 解析到ipAddress={ipAddress}, family={ipAddress.AddressFamily}");
            if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
            {
                Debug.Log("当前使用的网络: IPV6");
                Debug.Log("[ClientSocket][Connect] 当前使用的网络: IPV6");
                m_Socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
            }
            else
            {
                Debug.Log("当前使用的网络: IPV4");
                Debug.Log("[ClientSocket][Connect] 当前使用的网络: IPV4");
                m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            }
            var ipEndPoint = new IPEndPoint(ipAddress, port);
            if (ipEndPoint == null)
            {
                Debug.Log("IpEndPoint is null");
                Debug.LogError("[ClientSocket][Connect] IpEndPoint is null");
            }
            m_Socket.BeginConnect(ipEndPoint, new AsyncCallback(ConnectCallBack), null);
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
            Debug.LogError($"[ClientSocket][Connect] 异常: {e.Message}");
        }
@@ -115,12 +156,14 @@
    /// <param name="_result"></param>
    private void ConnectCallBack(IAsyncResult _result)
    {
        Debug.unityLogger.logEnabled = true;
        if (!_result.IsCompleted)
        {
            Debug.Log("链接超时!");
            Debug.LogError("[ClientSocket][ConnectCallBack] 链接超时!");
            CloseConnect();
            if (onConnected != null)
            {
                Debug.LogError("[ClientSocket][ConnectCallBack] onConnected(false) 超时");
                onConnected(false);
                onConnected = null;
            }
@@ -131,11 +174,12 @@
            {
                if (m_Socket != null && m_Socket.Connected)
                {
                    Debug.Log("确认的链接实现");
                    Debug.Log("[ClientSocket][ConnectCallBack] 确认的链接实现");
                    OnConnectSuccess();
                }
                else
                {
                    Debug.LogError("[ClientSocket][ConnectCallBack] m_Socket为null或未连接");
                    if (m_Socket != null)
                    {
                        m_Socket.Disconnect(true);
@@ -144,12 +188,13 @@
            }
            catch (System.Exception ex)
            {
                Debug.Log(ex);
                Debug.LogError($"[ClientSocket][ConnectCallBack] 异常: {ex}");
            }
            finally
            {
                if (onConnected != null)
                {
                    Debug.Log($"[ClientSocket][ConnectCallBack] onConnected({m_Socket != null && m_Socket.Connected})");
                    onConnected(m_Socket != null && m_Socket.Connected);
                    onConnected = null;
                }
@@ -166,17 +211,21 @@
    public void CloseConnect()
    {
        Debug.Log("==== CloseConnect");
        Debug.unityLogger.logEnabled = true;
        Debug.Log("[ClientSocket][CloseConnect] ==== CloseConnect");
        try
        {
            isStopTreading = true;
            if (m_packageThread != null)
            {
                m_packageThread.Abort();
                m_packageThread = null;
            }
        }
        catch (System.Exception ex)
        {
            Debug.Log(ex);
            m_packageThread = null;
        }
        try
@@ -203,15 +252,19 @@
    {
        if (m_packageThread != null)
        {
            Debug.LogWarning("[ClientSocket][OnConnectSuccess] m_packageThread已存在,先Abort");
            m_packageThread.Abort();
            m_packageThread = null;
        }
        fragmentBytes = null; // 清除上次连接残留的碎片缓存
        m_LastPackageTime = DateTime.Now;
        isStopTreading = false;
        m_packageThread = new Thread(new ThreadStart(ReceiveInfo)); // 启动线程接收信息
        m_packageThread.IsBackground = true;
        m_packageThread.Start();
        isStopTreading = false;
        Debug.unityLogger.logEnabled = true;
        Debug.Log("[ClientSocket][OnConnectSuccess] 连接成功,启动接收线程");
    }
    /// <summary>
@@ -219,6 +272,8 @@
    /// </summary>
    private void ReceiveInfo()
    {
        Debug.unityLogger.logEnabled = true;
        Debug.Log("[ClientSocket][ReceiveInfo] 接收线程启动");
        while (!isStopTreading)
        {
            try
@@ -226,14 +281,17 @@
                var shutdown = false;
                if (!m_Socket.Connected)
                {
                    Debug.LogWarning("[ClientSocket][ReceiveInfo] m_Socket 已断开");
                    shutdown = true;
                }
                if (!shutdown)
                {
                    var dataLength = m_Socket.Receive(bufferBytes);
                    Debug.Log($"[ClientSocket][ReceiveInfo] 收到数据长度: {dataLength}");
                    if (dataLength <= 0)
                    {
                        Debug.LogWarning("[ClientSocket][ReceiveInfo] dataLength <= 0,准备断开");
                        shutdown = true;
                    }
                    else
@@ -247,6 +305,7 @@
                if (shutdown)
                {
                    Debug.LogWarning("[ClientSocket][ReceiveInfo] shutdown=true,关闭Socket");
                    isStopTreading = true;
                    m_Socket.Shutdown(SocketShutdown.Both);
                    m_Socket.Close();
@@ -254,13 +313,13 @@
            }
            catch (Exception e)
            {
                Debug.Log(e);
                Debug.LogError($"[ClientSocket][ReceiveInfo] 异常: {e}");
            }
        }
        Debug.Log("[ClientSocket][ReceiveInfo] 接收线程退出");
    }
    static byte[] vCmdBytes = new byte[2];
    /// <summary>
    /// 解析数据包: FFCC+封包长度+封包(封包头+数据)结构体内容
    /// </summary>
@@ -274,6 +333,7 @@
            if (fragmentBytes != null && fragmentBytes.Length > 0)
            {
                Array.Resize(ref fixBytes, vBytes.Length + fragmentBytes.Length);
                Debug.Log($"[ClientSocket][ReadInfo] 存在fragmentBytes, 长度: {fragmentBytes.Length}");
                Array.Copy(fragmentBytes, 0, fixBytes, 0, fragmentBytes.Length);
                Array.Copy(vBytes, 0, fixBytes, fragmentBytes.Length, vBytes.Length);
            }
@@ -295,9 +355,21 @@
                    Array.Copy(fixBytes, vReadIndex, fragmentBytes, 0, vLeavingLeng);
                    break;
                }
                // 打印包头原始字节,便于排查 OverflowException
                byte h0 = fixBytes[vReadIndex], h1 = fixBytes[vReadIndex + 1],
                     h2 = fixBytes[vReadIndex + 2], h3 = fixBytes[vReadIndex + 3],
                     h4 = fixBytes[vReadIndex + 4], h5 = fixBytes[vReadIndex + 5];
                vBodyLeng = BitConverter.ToInt32(fixBytes, vReadIndex + 2);
                Debug.Log($"[ClientSocket][ReadInfo] vReadIndex={vReadIndex} vTotalLeng={vTotalLeng} vLeavingLeng={vLeavingLeng} header=[{h0:X2} {h1:X2} {h2:X2} {h3:X2} {h4:X2} {h5:X2}] vBodyLeng={vBodyLeng}");
                if (vBodyLeng < 0)
                {
                    Debug.LogError($"[ClientSocket][ReadInfo] vBodyLeng异常({vBodyLeng}),丢弃剩余数据!fragmentBytes总长={fixBytes.Length} vReadIndex={vReadIndex}");
                    fragmentBytes = null;
                    break;
                }
                if (vBodyLeng > vLeavingLeng - 6)// 未完整的包则留包
                {
                    Debug.Log($"[ClientSocket][ReadInfo] 包不完整,留包: vBodyLeng={vBodyLeng} vLeavingLeng={vLeavingLeng}");
                    fragmentBytes = new byte[vLeavingLeng];
                    Array.Copy(fixBytes, vReadIndex, fragmentBytes, 0, vLeavingLeng);
                    break;
@@ -317,10 +389,10 @@
                    vNetpack = PackageRegedit.TransPack(socketType, cmd, vPackBytes);
                    if (vNetpack != null)
                    {
                        if (Launch.Instance.EnableNetLog)
                        {
                            Debug.LogFormat("收包:{0}", vNetpack.GetType().Name);
                        }
                        // if (Launch.Instance.EnableNetLog)
                        // {
                        //     Debug.LogFormat("收包:{0}", vNetpack.GetType().Name);
                        // }
                        m_LastPackageTime = DateTime.Now;
                        GameNetSystem.Instance.PushPackage(vNetpack, this.socketType);
                        isRegist = true;
@@ -346,71 +418,14 @@
    }
    /// <summary>
    /// 发送信息
    /// </summary>
    public void SendInfo(GameNetPackBasic protocol)
    {
        if (!connected)
        {
            return;
        }
        if (protocol == null)
        {
            Debug.LogError("要发的信息对象为空");
            return;
        }
        if (Launch.Instance.EnableNetLog)
        {
            Debug.LogFormat("发包:{0}", protocol.GetType().Name);
        }
        if (protocol.combineBytes == null)
        {
            protocol.WriteToBytes();
        }
        protocol.CombineDatas(encoder);
#if UNITY_EDITOR
        NetPkgCtl.RecordPackage(socketType, protocol.vInfoCont, NetPackagetType.Client, protocol.ToString(), FieldPrint.PrintFields(protocol), FieldPrint.PrintFieldsExpand(protocol, true));
#endif
        sendBytesTotal += protocol.combineBytes.Length;
        SendBytes(protocol.combineBytes);
    }
    /// <summary>
    /// 发送信息
    /// </summary>
    /// <param name="vBytes"></param>
    public void SendInfo(byte[] vBytes)
    {
        if (!connected)
        {
            Debug.LogError("尚未与该后端链接!无法发送信息");
            return;
        }
        if (vBytes == null || vBytes.Length < 2)
        {
            Debug.LogError("要发的信息数据为空或数据不足");
            return;
        }
        vBytes = encoder.BaseXorAdd(vBytes);
        byte[] vFrameHead = new byte[] { 255, 204 };
        byte[] vMsgBodyLength = BitConverter.GetBytes(vBytes.Length);
        byte[] vTotal = new byte[vBytes.Length + 6];
        Array.Copy(vFrameHead, 0, vTotal, 0, vFrameHead.Length);
        Array.Copy(vMsgBodyLength, 0, vTotal, 2, vMsgBodyLength.Length);
        Array.Copy(vBytes, 0, vTotal, 6, vBytes.Length);
        SendBytes(vTotal);
    }
    Queue<byte[]> sendQueue = new Queue<byte[]>();
    // TCP Socket 的 SendBytes(私有方法,由统一的 SendInfo 调用)
    private void SendBytes(byte[] bytes)
    {
        // 调试日志:输出发送的字节数据
        // string hexString = "[TCP] SendBytes Length=" + bytes.Length + " Data=" + BitConverter.ToString(bytes, 0, Math.Min(bytes.Length, 32)).Replace("-", " ");
        // if (bytes.Length > 32) hexString += "...";
        // Debug.Log(hexString);
        try
        {
            if (sendQueue.Count > 0)
@@ -448,4 +463,318 @@
        }
    }
    public void DispatchMessageQueue()
    {
        // TCP不需要轮询
    }
#else
    // ==================== WebSocket 实现(WebGL平台)====================
    public async void Connect(string _ip, int _port, Action<bool> _onConnected)
    {
        Debug.unityLogger.logEnabled = true;
        ip = _ip;
        port = _port;
        onConnected = _onConnected;
        var scheme = Application.absoluteURL.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ? "wss" : "ws";
        webSocketUrl = $"{scheme}://{_ip}:{_port}";
        webSocketOpened = false;
        Debug.Log($"[ClientSocket-WebSocket] 开始连接: {webSocketUrl}, pageUrl={Application.absoluteURL}");
        try
        {
            webSocket = new WebSocket(webSocketUrl);
            // 注册WebSocket回调
            webSocket.OnOpen += OnWebSocketOpen;
            webSocket.OnMessage += OnWebSocketMessage;
            webSocket.OnError += OnWebSocketError;
            webSocket.OnClose += OnWebSocketClose;
            await webSocket.Connect();
        }
        catch (Exception ex)
        {
            Debug.LogError($"[ClientSocket-WebSocket] 连接异常: {ex.Message}");
            if (onConnected != null)
            {
                onConnected(false);
                onConnected = null;
            }
        }
    }
    private void OnWebSocketOpen()
    {
        webSocketOpened = true;
        Debug.Log($"[ClientSocket-WebSocket] 连接成功: {webSocketUrl}");
        m_LastPackageTime = DateTime.Now;
        if (onConnected != null)
        {
            onConnected(true);
            onConnected = null;
        }
    }
    private void OnWebSocketMessage(byte[] data)
    {
        try
        {
            getBytesTotal += data.Length;
            byte[] fixBytes = data;
            // TCP-to-WS网关按TCP缓冲区大小拆分,需跨消息重组(与TCP ReadInfo逻辑一致)
            if (fragmentBytes != null && fragmentBytes.Length > 0)
            {
                fixBytes = new byte[fragmentBytes.Length + data.Length];
                Array.Copy(fragmentBytes, 0, fixBytes, 0, fragmentBytes.Length);
                Array.Copy(data, 0, fixBytes, fragmentBytes.Length, data.Length);
            }
            fragmentBytes = null;
            int vReadIndex = 0;
            byte[] vPackBytes;
            int vLeavingLeng = 0;
            int vBodyLeng = 0;
            int vTotalLeng = fixBytes.Length;
            GameNetPackBasic vNetpack;
            while (vReadIndex < vTotalLeng)
            {
                vLeavingLeng = vTotalLeng - vReadIndex;
                if (vLeavingLeng < 6)
                {
                    fragmentBytes = new byte[vLeavingLeng];
                    Array.Copy(fixBytes, vReadIndex, fragmentBytes, 0, vLeavingLeng);
                    break;
                }
                // 校验FFCC包头,防止数据错位
                if (fixBytes[vReadIndex] != 0xFF || fixBytes[vReadIndex + 1] != 0xCC)
                {
                    Debug.LogError($"[ClientSocket-WebSocket] FFCC包头异常: {fixBytes[vReadIndex]:X2} {fixBytes[vReadIndex + 1]:X2}, 丢弃剩余 {vLeavingLeng} 字节");
                    fragmentBytes = null;
                    break;
                }
                vBodyLeng = BitConverter.ToInt32(fixBytes, vReadIndex + 2);
                if (vBodyLeng <= 0)
                {
                    Debug.LogError($"[ClientSocket-WebSocket] 包体长度非法: {vBodyLeng}, 丢弃");
                    fragmentBytes = null;
                    break;
                }
                if (vBodyLeng > vLeavingLeng - 6)
                {
                    fragmentBytes = new byte[vLeavingLeng];
                    Array.Copy(fixBytes, vReadIndex, fragmentBytes, 0, vLeavingLeng);
                    break;
                }
                vPackBytes = new byte[vBodyLeng];
                Array.Copy(fixBytes, vReadIndex + 6, vPackBytes, 0, vBodyLeng);
                vPackBytes = encoder.BaseXorSub(vPackBytes);
                Array.Copy(vPackBytes, 0, vCmdBytes, 0, 2);
                var cmd = (ushort)((ushort)(vCmdBytes[0] << 8) + vCmdBytes[1]);
                bool isRegist = false;
                if (PackageRegedit.Contain(cmd))
                {
                    vNetpack = PackageRegedit.TransPack(socketType, cmd, vPackBytes);
                    if (vNetpack != null)
                    {
                        m_LastPackageTime = DateTime.Now;
                        GameNetSystem.Instance.PushPackage(vNetpack, socketType);
                        isRegist = true;
                    }
                }
                vReadIndex += 6 + vBodyLeng;
                if (!isRegist)
                {
#if UNITY_EDITOR
                    PackageRegedit.TransPack(socketType, cmd, vPackBytes);
#endif
                }
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"[ClientSocket-WebSocket] 收包异常:{ex}");
        }
    }
    private void OnWebSocketError(string error)
    {
        Debug.LogError($"[ClientSocket-WebSocket] 错误: {error}");
        if (!webSocketOpened && onConnected != null)
        {
            onConnected(false);
            onConnected = null;
        }
    }
    private void OnWebSocketClose(WebSocketCloseCode code)
    {
        Debug.LogError($"[ClientSocket-WebSocket] 连接关闭: code={code}, url={webSocketUrl}, opened={webSocketOpened}");
        if (!webSocketOpened && onConnected != null)
        {
            onConnected(false);
            onConnected = null;
        }
        OnDisconnected?.Invoke();
    }
    public async void CloseConnect()
    {
        Debug.Log("[ClientSocket-WebSocket] ==== CloseConnect\n" + System.Environment.StackTrace);
        fragmentBytes = null;
        if (webSocket != null)
        {
            sendQueue.Clear();
            await webSocket.Close();
            webSocket = null;
        }
    }
    private bool isSending = false;
    private async void SendBytes(byte[] bytes)
    {
        // 调试日志:输出发送的字节数据
        // string hexString = "[WebSocket] SendBytes Length=" + bytes.Length + " Data=" + BitConverter.ToString(bytes, 0, Math.Min(bytes.Length, 32)).Replace("-", " ");
        // if (bytes.Length > 32) hexString += "...";
        // Debug.Log(hexString);
        if (webSocket == null || webSocket.State != WebSocketState.Open)
        {
            Debug.LogError("[ClientSocket-WebSocket] 未连接,无法发送");
            return;
        }
        // 队列机制
        lock (sendQueue)
        {
            if (isSending)
            {
                sendQueue.Enqueue(bytes);
                return;
            }
            isSending = true;
        }
        try
        {
            await webSocket.Send(bytes);
            // 处理队列中的下一个
            lock (sendQueue)
            {
                if (sendQueue.Count > 0)
                {
                    byte[] nextBytes = sendQueue.Dequeue();
                    isSending = false;
                    SendBytes(nextBytes); // 递归发送
                }
                else
                {
                    isSending = false;
                }
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"[ClientSocket-WebSocket] 发送异常: {ex.Message}");
            lock (sendQueue)
            {
                isSending = false;
            }
        }
    }
    // WebGL需要在Update中轮询消息
    public void DispatchMessageQueue()
    {
#if UNITY_EDITOR
        webSocket?.DispatchMessageQueue();
#endif
    }
#endif
    // ==================== 统一的公共接口(两个平台通用)====================
    /// <summary>
    /// 发送协议包
    /// </summary>
    public void SendInfo(GameNetPackBasic protocol)
    {
        if (!connected)
        {
            return;
        }
        if (protocol == null)
        {
            Debug.LogError("要发的信息对象为空");
            return;
        }
        if (protocol.combineBytes == null)
        {
            protocol.WriteToBytes();
        }
        protocol.CombineDatas(encoder);
#if UNITY_EDITOR
        NetPkgCtl.RecordPackage(socketType, protocol.vInfoCont, NetPackagetType.Client,
            protocol.ToString(), FieldPrint.PrintFields(protocol), FieldPrint.PrintFieldsExpand(protocol, true));
#endif
        // 调试日志:查看 combineBytes 的内容
        string hexString = "[SendInfo] Protocol=" + protocol.ToString() + " combineBytes.Length=" + protocol.combineBytes.Length +
            " Data=" + BitConverter.ToString(protocol.combineBytes, 0, Math.Min(protocol.combineBytes.Length, 32)).Replace("-", " ");
        if (protocol.combineBytes.Length > 32) hexString += "...";
        Debug.Log(hexString);
        sendBytesTotal += protocol.combineBytes.Length;
        SendBytes(protocol.combineBytes);
    }
#if UNITY_EDITOR
    /// <summary>
    /// 发送原始字节数据
    /// </summary>
    public void SendInfo(byte[] vBytes)
    {
        if (!connected)
        {
            Debug.LogError("尚未与该后端链接!无法发送信息");
            return;
        }
        if (vBytes == null || vBytes.Length < 2)
        {
            Debug.LogError("要发的信息数据为空或数据不足");
            return;
        }
        // 加密并组装包头
        vBytes = encoder.BaseXorAdd(vBytes);
        byte[] vFrameHead = new byte[] { 255, 204 };
        byte[] vMsgBodyLength = BitConverter.GetBytes(vBytes.Length);
        byte[] vTotal = new byte[vBytes.Length + 6];
        Array.Copy(vFrameHead, 0, vTotal, 0, vFrameHead.Length);
        Array.Copy(vMsgBodyLength, 0, vTotal, 2, vMsgBodyLength.Length);
        Array.Copy(vBytes, 0, vTotal, 6, vBytes.Length);
        SendBytes(vTotal);
    }
#endif
}