)
1. Unity TCP通信基础搭建第一次在Unity里搞TCP通信时我对着Socket API文档发了半小时呆。后来发现其实就像打电话一样简单——服务器是座机客户端是手机IP地址就是电话号码。先来看看最基础的同步通信实现这个版本虽然性能一般但特别适合理解核心流程。服务器搭建就像开小卖部先准备个座机Socket告诉运营商你的店铺地址Bind打开来电显示功能Listen搬个小板凳等着接电话Accept用代码实现是这样的// 服务器初始化 Socket socketWatch new Socket( AddressFamily.InterNetwork, // 用IPv4地址 SocketType.Stream, // 流式传输 ProtocolType.Tcp); // TCP协议 IPAddress ip IPAddress.Parse(127.0.0.1); // 本地回环地址 IPEndPoint point new IPEndPoint(ip, 6000); // 端口号 socketWatch.Bind(point); // 绑定地址 socketWatch.Listen(10); // 开始监听 Debug.Log(小卖部开张啦); // 接电话专用线程 void Listen(object obj) { Socket socketWatch obj as Socket; while(true) { Socket socketSend socketWatch.Accept(); // 阻塞等待连接 Debug.Log($接到来自{socketSend.RemoteEndPoint}的电话); // 这里启动消息处理线程... } }客户端连接就像打电话Socket socketSend new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socketSend.Connect(new IPEndPoint( IPAddress.Parse(127.0.0.1), 6000)); // 拨打服务器地址 Debug.Log(电话接通啦);常见踩坑点端口被占用就像电话号码被占线换端口或重启VS防火墙拦截记得在Windows防火墙放行你的程序同步阻塞Accept和Receive会卡住主线程后面我们会解决2. 数据收发与线程管理实际测试发现直接用Unity主线程处理网络操作会导致游戏卡成PPT。这就好比让老板亲自接所有电话店铺肯定要倒闭。解决方案是用多线程——老板雇几个客服专员。消息接收线程示例void Received(object obj) { Socket socketSend obj as Socket; byte[] buffer new byte[1024]; // 接收缓冲区 while(true) { int len socketSend.Receive(buffer); // 阻塞接收 if(len 0) break; string msg Encoding.UTF8.GetString(buffer, 0, len); Debug.Log($收到消息{msg}); // 注意不能直接在这里更新UI // Unity主线程安全方案后面会讲 } }消息发送要注意的细节中文编码要用UTF8发送前检查连接状态处理网络异常void SendMessage(string msg) { if(socketSend null || !socketSend.Connected) return; try { byte[] data Encoding.UTF8.GetBytes(msg); socketSend.Send(data); } catch(Exception e) { Debug.LogError($发送失败{e.Message}); } }线程管理最佳实践用IsBackgroundtrue让线程随程序退出自动结束为每个连接创建独立收发线程使用try-catch包裹网络操作我在项目里封装了个线程管理器class ThreadManager { static ListThread threads new ListThread(); public static void StartThread(ThreadStart task) { Thread t new Thread(task) { IsBackground true }; threads.Add(t); t.Start(); } public static void CleanUp() { foreach(var t in threads) { if(t.IsAlive) t.Abort(); } } }3. 异步改造与主线程安全同步方案虽然简单但在Unity里会引发两个致命问题主线程卡顿接收数据时游戏冻结跨线程访问Unity API报错异步改造方案 用Async/Await替代原始线程就像把传统电话升级成智能呼叫中心// 异步接收消息 async Task ReceiveAsync(Socket socket) { byte[] buffer new byte[1024]; while(true) { try { int len await socket.ReceiveAsync( new ArraySegmentbyte(buffer), SocketFlags.None); string msg Encoding.UTF8.GetString(buffer, 0, len); MainThreadDispatcher.Execute(() { // 这里可以安全操作UI chatText.text $\n收到{msg}; }); } catch { break; } } }主线程调度器实现class MainThreadDispatcher : MonoBehaviour { static QueueAction actions new QueueAction(); void Update() { while(actions.Count 0) { actions.Dequeue()?.Invoke(); } } public static void Execute(Action action) { lock(actions) { actions.Enqueue(action); } } }性能对比测试方案类型内存占用CPU使用率主线程卡顿同步阻塞低波动大严重传统线程中等稳定无Async略高最稳定无实测发现Async方案在频繁小数据包场景下表现最佳这也是为什么现代网络库都采用这种模式。4. 粘包拆包处理实战TCP是流式协议就像水龙头接水——你永远不知道这次打开会接到多少水。我曾在项目里遇到三种典型情况粘包多次发送的数据被合并接收拆包单次发送的数据被拆分接收半包数据接收不完整解决方案设计定义消息头包含消息长度使用环形缓冲区暂存数据实现消息组装逻辑class MessageBuffer { byte[] buffer new byte[1024 * 10]; // 10KB环形缓冲区 int startIndex; // 有效数据起始位置 int dataCount; // 有效数据长度 public void AddData(byte[] newData, int length) { // 缓冲区扩容检查... Array.Copy(newData, 0, buffer, startIndex dataCount, length); dataCount length; } public bool TryGetMessage(out string message) { if(dataCount 4) { // 头部长度不足 message null; return false; } int msgLength BitConverter.ToInt32(buffer, startIndex); if(dataCount msgLength 4) { message Encoding.UTF8.GetString( buffer, startIndex 4, msgLength); // 更新缓冲区状态 startIndex msgLength 4; dataCount - msgLength 4; return true; } message null; return false; } }使用示例MessageBuffer msgBuffer new MessageBuffer(); // 接收数据时 msgBuffer.AddData(buffer, receivedLength); // 每帧尝试解析 while(msgBuffer.TryGetMessage(out string msg)) { Debug.Log($完整消息{msg}); }协议设计建议消息头建议包含消息IDint、长度int字符串统一用UTF8编码心跳包间隔建议3-5秒5. 完整项目优化实践经过三个版本的迭代我的TCP框架最终包含这些核心模块网络核心架构连接管理器自动重连、心跳检测消息路由器基于消息ID的分发线程安全队列生产者-消费者模式关键性能参数// Socket配置优化参数 socket.SendTimeout 5000; // 5秒发送超时 socket.ReceiveBufferSize 8192; // 8KB接收缓冲区 socket.NoDelay true; // 禁用Nagle算法日志监控系统class NetworkMonitor : MonoBehaviour { void OnGUI() { GUI.Label(new Rect(10,10,200,20), $连接数{ConnectionManager.Count}); GUI.Label(new Rect(10,40,200,20), $发送{TrafficCounter.SendKBps} KB/s); GUI.Label(new Rect(10,70,200,20), $接收{TrafficCounter.ReceiveKBps} KB/s); } }在MMO项目实测中这个框架可以稳定支持500玩家同时在线平均延迟控制在150ms以内。最让我自豪的是它的异常恢复能力——网络闪断后能自动重连并恢复游戏状态。