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

TCP客户端接收数据后停滞,需服务器断开才输出问题排查

问题根源与解决方案

你的问题核心在于TCP是基于流的协议,没有内置的消息边界——你的代码里的内层Read循环会一直阻塞,直到服务器主动关闭流(也就是断开连接),因为NetworkStream.Read()方法在没有数据可读但连接仍处于打开状态时,会一直等待而不是返回0。这就是为什么你必须断开服务器连接,才能执行到后面的打印语句。

原代码的核心问题

  1. 错误依赖Available属性:_socketConnection.Available只能告诉你当前缓冲区中有多少字节可读,但不能作为"当前消息已发送完毕"的标志——服务器可能分多次发送同一个消息的片段,Available会在中间出现0的情况,但这并不意味着消息结束。
  2. 内层Read循环无终止条件:没有约定消息边界的情况下,Read()会一直等待新数据,直到连接关闭,所以你的代码会卡在这个循环之后的打印语句前,直到服务器断开。

维持长连接的正确方案:约定消息边界

要在长连接下正常接收多段数据,你需要和服务器约定消息边界规则,常见的实现方式有三种:

  • 固定长度消息:每次发送固定字节数的数据,客户端读取对应长度
  • 前缀长度:发送数据前先发送数据的长度(比如用4字节的整数表示),客户端先读长度再读取对应字节数
  • 特殊分隔符:用特定字符串/字节标记消息结束(比如\r\n,但要确保数据本身不包含该分隔符)

这里推荐使用前缀长度的方式,兼容性和可靠性都比较好,下面是修改后的客户端代码:

private Thread _tcpThread;
private TcpClient _socketConnection;
private const int BUFFER_SIZE = 1024;

public void Connect()
{
    try
    {
        _tcpThread = new Thread(ReciveDataClient);
        _tcpThread.IsBackground = true;
        _tcpThread.Start();
    }
    catch (Exception e)
    {
        print(e.Message);
    }
}

private void ReciveDataClient()
{
    try
    {
        _socketConnection = new TcpClient("xxx.xxx.xxx.xxx", 54321);
        print("TCP -> Connection Success!");
        
        // 长连接下复用同一个NetworkStream,不要每次循环重新获取
        using var netstream = _socketConnection.GetStream();
        var bytes = new byte[BUFFER_SIZE];

        while (true)
        {
            // 第一步:读取数据长度(4字节int,需和服务器端的字节序一致)
            byte[] lengthBuffer = new byte[4];
            int lengthBytesRead = 0;
            while (lengthBytesRead < 4)
            {
                int read = netstream.Read(lengthBuffer, lengthBytesRead, 4 - lengthBytesRead);
                if (read == 0)
                {
                    // Read返回0表示连接已关闭
                    print("TCP -> connection was terminated by the server");
                    return;
                }
                lengthBytesRead += read;
            }
            // 转换为数据长度(如果服务器用大端序,需要反转字节数组)
            int dataLength = BitConverter.ToInt32(lengthBuffer, 0);

            // 第二步:读取对应长度的数据内容
            int totalRecBytes = 0;
            StringBuilder fileBuilder = new StringBuilder();
            while (totalRecBytes < dataLength)
            {
                // 计算本次需要读取的字节数,避免超过剩余长度
                int bytesToRead = Math.Min(BUFFER_SIZE, dataLength - totalRecBytes);
                int recBytes = netstream.Read(bytes, 0, bytesToRead);
                
                if (recBytes == 0)
                {
                    print("TCP -> connection closed before receiving full data");
                    return;
                }

                var serverMessage = Encoding.ASCII.GetString(bytes, 0, recBytes);
                fileBuilder.Append(serverMessage);
                totalRecBytes += recBytes;
            }

            // 数据接收完成,执行后续逻辑
            string file = fileBuilder.ToString();
            print($"TCP -> Data received:\n{file}\n\n{totalRecBytes} Bytes");
        }
    }
    catch (Exception e)
    {
        print(e.Message);
        return;
    }
}

关键优化点说明

  1. 复用NetworkStream:长连接下不需要每次循环都重新获取流,直接复用同一个流即可,用using确保连接关闭时流能被正确释放。
  2. 使用StringBuilder:避免频繁拼接字符串产生大量临时对象,提升性能。
  3. 处理部分读取:Read()可能不会一次读取所有请求的字节,所以需要循环读取直到获取到约定长度的全部数据。
  4. 正确判断连接状态:通过Read()返回0来判断连接是否关闭,而不是依赖_socketConnection.Connected(该属性仅反映最后一次操作的状态,可能存在延迟)。

服务器端配合

服务器端需要对应修改发送逻辑:先将数据长度转换为4字节的二进制,再发送数据内容。例如:

// 服务器端示例代码
string data = "要发送的内容";
byte[] dataBytes = Encoding.ASCII.GetBytes(data);
byte[] lengthBytes = BitConverter.GetBytes(dataBytes.Length);

// 先发送长度,再发送数据
netstream.Write(lengthBytes, 0, lengthBytes.Length);
netstream.Write(dataBytes, 0, dataBytes.Length);

这样就能在维持TCP长连接的前提下,正确接收每一段数据,无需断开连接。

内容的提问来源于stack exchange,提问作者derHugo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:09:31