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
}