using UnityEngine; using System; 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 /// /// 统一的网络Socket类 - 自动适配TCP Socket和WebSocket /// TCP Socket: Windows, Mac, iOS, Android等平台 /// WebSocket: WebGL/微信小游戏等Web平台 /// 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]; 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 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; } } string ip; int port; Action onConnected = null; Queue sendQueue = new Queue(); 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 _onConnected) { Debug.unityLogger.logEnabled = true; try { ip = _ip; port = _port; onConnected = _onConnected; Debug.Log($"[ClientSocket][Connect] 尝试连接: ip={_ip}, port={_port}"); //目前测试到异步两个问题 // 1. BeginGetHostAddresses 不明情况下会很久才回调,导致触发超时 // 2. 超时的情况下多次尝试登录后,会触发多次OnGetHostAddresses,导致登录异常 //Dns.BeginGetHostAddresses(_ip, OnGetHostAddresses, null); IPAddress ipAddress; #if UNITY_IPHONE IPHostEntry ipAddresses = Dns.GetHostEntry(_ip); ipAddress = ipAddresses.AddressList[0]; #else IPAddress[] ipAddresses = Dns.GetHostAddresses(_ip); ipAddress = ipAddresses[0]; #endif Debug.Log($"[ClientSocket][Connect] 解析到ipAddress={ipAddress}, family={ipAddress.AddressFamily}"); if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) { Debug.Log("[ClientSocket][Connect] 当前使用的网络: IPV6"); m_Socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); } else { 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.LogError("[ClientSocket][Connect] IpEndPoint is null"); } m_Socket.BeginConnect(ipEndPoint, new AsyncCallback(ConnectCallBack), null); } catch (Exception e) { Debug.LogError($"[ClientSocket][Connect] 异常: {e.Message}"); } } private void OnGetHostAddresses(IAsyncResult _result) { var ipAddresses = Dns.EndGetHostAddresses(_result); if (ipAddresses[0].AddressFamily == AddressFamily.InterNetworkV6) { Debug.Log("当前使用的网络: IPV6"); m_Socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); } else { Debug.Log("当前使用的网络: IPV4"); m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); } var ipEndPoint = new IPEndPoint(ipAddresses[0], port); if (ipEndPoint == null) { Debug.Log("IpEndPoint is null"); } m_Socket.BeginConnect(ipEndPoint, new AsyncCallback(ConnectCallBack), null); } /// /// 链接成功时的回调 /// /// private void ConnectCallBack(IAsyncResult _result) { Debug.unityLogger.logEnabled = true; if (!_result.IsCompleted) { Debug.LogError("[ClientSocket][ConnectCallBack] 链接超时!"); CloseConnect(); if (onConnected != null) { Debug.LogError("[ClientSocket][ConnectCallBack] onConnected(false) 超时"); onConnected(false); onConnected = null; } } else { try { if (m_Socket != null && m_Socket.Connected) { Debug.Log("[ClientSocket][ConnectCallBack] 确认的链接实现"); OnConnectSuccess(); } else { Debug.LogError("[ClientSocket][ConnectCallBack] m_Socket为null或未连接"); if (m_Socket != null) { m_Socket.Disconnect(true); } } } catch (System.Exception 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; } } } onConnected = null; } /// /// 关闭链接 /// 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(); } } catch (System.Exception ex) { Debug.Log(ex); } try { if (m_Socket != null && m_Socket.Connected) { m_Socket.Shutdown(SocketShutdown.Both); m_Socket.Close(); } } catch (System.Exception ex) { Debug.Log(ex); } sendQueue.Clear(); m_Socket = null; } /// /// 链接成功 /// private void OnConnectSuccess() { if (m_packageThread != null) { m_packageThread.Abort(); m_packageThread = null; } m_LastPackageTime = DateTime.Now; m_packageThread = new Thread(new ThreadStart(ReceiveInfo)); // 启动线程接收信息 m_packageThread.IsBackground = true; m_packageThread.Start(); isStopTreading = false; Debug.unityLogger.logEnabled = true; Debug.Log("[ClientSocket][OnConnectSuccess] 连接成功,启动接收线程"); if (m_packageThread != null) { Debug.LogWarning("[ClientSocket][OnConnectSuccess] m_packageThread已存在,先Abort"); m_packageThread.Abort(); m_packageThread = null; } m_LastPackageTime = DateTime.Now; m_packageThread = new Thread(new ThreadStart(ReceiveInfo)); // 启动线程接收信息 m_packageThread.IsBackground = true; m_packageThread.Start(); isStopTreading = false; } /// /// 接收信息 /// private void ReceiveInfo() { while (!isStopTreading) { try { var shutdown = false; if (!m_Socket.Connected) { shutdown = true; } if (!shutdown) { var dataLength = m_Socket.Receive(bufferBytes); if (dataLength <= 0) { shutdown = true; } else { getBytesTotal += dataLength; var bytes = new byte[dataLength]; Array.Copy(bufferBytes, 0, bytes, 0, dataLength); ReadInfo(bytes); } } if (shutdown) { isStopTreading = true; m_Socket.Shutdown(SocketShutdown.Both); m_Socket.Close(); } } catch (Exception e) { Debug.Log(e); } } Debug.unityLogger.logEnabled = true; Debug.Log("[ClientSocket][ReceiveInfo] 接收线程启动"); while (!isStopTreading) { try { 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 { getBytesTotal += dataLength; var bytes = new byte[dataLength]; Array.Copy(bufferBytes, 0, bytes, 0, dataLength); ReadInfo(bytes); } } if (shutdown) { Debug.LogWarning("[ClientSocket][ReceiveInfo] shutdown=true,关闭Socket"); isStopTreading = true; m_Socket.Shutdown(SocketShutdown.Both); m_Socket.Close(); } } catch (Exception e) { Debug.LogError($"[ClientSocket][ReceiveInfo] 异常: {e}"); } } Debug.Log("[ClientSocket][ReceiveInfo] 接收线程退出"); } /// /// 解析数据包: FFCC+封包长度+封包(封包头+数据)结构体内容 /// /// private void ReadInfo(byte[] vBytes) { try { byte[] fixBytes = vBytes; // 如果存在留包,则并包 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); } 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; } vBodyLeng = BitConverter.ToInt32(fixBytes, vReadIndex + 2); 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) { // if (Launch.Instance.EnableNetLog) // { // Debug.LogFormat("收包:{0}", vNetpack.GetType().Name); // } m_LastPackageTime = DateTime.Now; GameNetSystem.Instance.PushPackage(vNetpack, this.socketType); isRegist = true; } } vReadIndex += 6 + vBodyLeng; // 未注册封包处理 if (!isRegist) { #if UNITY_EDITOR PackageRegedit.TransPack(socketType, cmd, vPackBytes); #endif } } } catch (Exception ex) { Debug.LogErrorFormat("收包异常:{0}", ex); } } // 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) { sendQueue.Enqueue(bytes); } else { m_Socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, new AsyncCallback(SendInfoCallBack), m_Socket); } } catch { Debug.LogError("网络数据包发送异常"); } } /// /// 发送完成的回调 /// /// private void SendInfoCallBack(IAsyncResult vAsyncSend) { try { if (sendQueue.Count > 0) { var bytes = sendQueue.Dequeue(); m_Socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, new AsyncCallback(SendInfoCallBack), m_Socket); } } catch (Exception ex) { Debug.Log(ex); } } public void DispatchMessageQueue() { // TCP不需要轮询 } #else // ==================== WebSocket 实现(WebGL平台)==================== public async void Connect(string _ip, int _port, Action _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 // ==================== 统一的公共接口(两个平台通用)==================== /// /// 发送协议包 /// 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 /// /// 发送原始字节数据 /// 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 }