You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:06:09