Unity项目中C#异步TCP客户端无法接收服务器响应求助
问题分析与解决方案
从你的代码来看,核心问题出在TCP流的处理逻辑以及对异步接收回调的终止条件判断上,下面一步步拆解问题并给出修复方案:
1. 根本问题:错误的接收终止条件
你在ReceiveCallback里只在bytesRead == 0(也就是服务器主动关闭TCP连接)时,才会处理StringBuilder里的内容并触发RECEIVING 2日志。但正常游戏交互中,服务器发送数据后不会立刻断开连接,而是保持连接等待后续请求,所以这个else分支永远不会被触发——这就是你只看到RECEIVING日志,却看不到RECEIVING 2的核心原因。
TCP是面向流的协议,它不会自动划分数据包边界,你不能等连接断开才处理数据,必须自己实现数据包的解析逻辑(比如用长度前缀、特殊分隔符标记完整数据包的结束)。
2. 代码中的其他潜在问题
- 错误使用静态
TCPClient:在ReceiveCallback里你用TCPClient.EndReceive(ar),正确做法应该用state.workSocket.EndReceive(ar),虽然当前是单客户端,但这样更健壮,避免后续扩展时出现冲突。 receiveDone的逻辑误用:StartTCPClient里的receiveDone.WaitOne()会阻塞线程直到连接断开,导致Socket线程被卡死,后续接收逻辑无法正常运行。- 缺少数据包边界处理:TCP会把多段发送的数据合并,或者把一段数据拆分成多次接收,你需要一套协议来识别完整数据包,否则会出现数据拼接错误。
修复后的核心代码示例
第一步:修改ReceiveCallback,实现完整数据包解析(以换行符为分隔符为例)
private void ReceiveCallback(IAsyncResult ar) { try { Debug.Log("RECEIVING"); StateObject state = (StateObject)ar.AsyncState; Socket clientSocket = state.workSocket; int bytesRead = clientSocket.EndReceive(ar); if (bytesRead > 0) { // 将接收到的字节转成字符串追加到缓冲区 state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, bytesRead)); // 检查是否有完整的数据包(用换行符\n作为分隔标记) string content = state.sb.ToString(); int newlineIndex = content.IndexOf('\n'); // 循环处理所有已到达的完整数据包 while (newlineIndex != -1) { // 提取完整数据包 string completeData = content.Substring(0, newlineIndex); // 触发业务逻辑回调 Debug.Log("RECEIVING 2: " + completeData); UnityThread.executeInUpdate(() => { OnIncomingTCPData(completeData); }); // 清理已处理的内容,保留剩余未完成的数据 state.sb.Clear(); content = content.Substring(newlineIndex + 1); newlineIndex = content.IndexOf('\n'); } // 将未处理的剩余内容放回缓冲区 if (!string.IsNullOrEmpty(content)) { state.sb.Append(content); } // 继续监听下一次数据接收 clientSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state); } else { // 服务器主动关闭连接,执行清理逻辑 Debug.Log("Server closed the connection"); clientSocket.Shutdown(SocketShutdown.Both); clientSocket.Close(); } } catch (Exception e) { Debug.Log(e.ToString()); } }
第二步:修正StartTCPClient,移除阻塞线程的receiveDone.WaitOne()
private void StartTCPClient() { try { TCPClient = new Socket(serverTCPEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); TCPClient.BeginConnect(serverTCPEndPoint, new AsyncCallback(ConnectCallback), TCPClient); connectDone.WaitOne(); // 启动持续异步接收,无需等待receiveDone(它只在连接断开时触发) TCPReceive(); Debug.Log("TCP client started, waiting for server data..."); } catch (Exception e) { Debug.Log(e.ToString()); } }
第三步:确保StateObject定义正确(如果还未定义)
public class StateObject { // 缓冲区大小可根据业务需求调整 public const int BufferSize = 1024; public Socket workSocket = null; public byte[] buffer = new byte[BufferSize]; public StringBuilder sb = new StringBuilder(); }
额外优化建议
- 使用长度前缀协议替代分隔符:分隔符可能与数据内容冲突,更可靠的方式是每个数据包开头先发送4字节的int(表示后续数据长度),客户端先读取长度,再读取对应字节数的内容组成完整数据包。
- 添加重连逻辑:如果连接意外断开,客户端应自动尝试重新连接,避免游戏功能失效。
- 替换静态变量:
TCPClient、response等静态变量在多场景或多客户端场景下会引发问题,建议改用实例变量。
内容的提问来源于stack exchange,提问作者Venelin
相关产品推荐
相关产品推荐

