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

TCP Socket客户端/服务器双向通信阻塞问题排查求助

问题排查与解决方案

核心问题原因

你的双向通信阻塞完全是因为**ConverteToBytes方法的读取逻辑错误**:

  • TCP是无消息边界的字节流协议,原方法依赖「流关闭时ReadAsync返回0」作为读取结束的标志,但双向通信场景中,双方发送完数据后都不会立即关闭流(还要等待对方的响应),导致ReadAsync一直阻塞,等待不存在的后续数据。
  • 比如服务器端:客户端发完请求后保持流打开以等待响应,服务器的ConverteToBytes会卡在ReadAsync步骤,永远读不到流关闭的信号,自然无法执行后续的响应代码。

解决方案:定义消息边界

要解决这个问题,必须手动定义消息的结束规则——最常用的方式是先发送消息的字节长度,再发送消息内容,让读取方明确知道需要读取多少字节就停止。

1. 重写序列化/反序列化辅助方法

替换原ConverteToBytes,新增包含消息边界处理的工具类:

public static class SerializeHelper
{
    // 发送消息:先传4字节的消息长度,再传序列化数据
    public static async Task SendMessageAsync(NetworkStream stream, object obj)
    {
        var serializedData = MessagePackSerializer.Serialize(obj);
        // 将长度转为4字节的int数组
        var lengthBytes = BitConverter.GetBytes(serializedData.Length);
        await stream.WriteAsync(lengthBytes);
        await stream.WriteAsync(serializedData);
    }

    // 读取消息:先读取长度,再读取对应长度的数据
    public static async Task<T> ReceiveMessageAsync<T>(NetworkStream stream)
    {
        // 读取4字节的消息长度
        var lengthBuffer = new byte[4];
        int readLength = await stream.ReadAsync(lengthBuffer, 0, 4);
        if (readLength != 4)
            throw new InvalidOperationException("Failed to read message length");
        
        int dataLength = BitConverter.ToInt32(lengthBuffer, 0);
        var dataBuffer = new byte[dataLength];
        int totalRead = 0;

        // 循环读取直到获取完整的消息数据
        while (totalRead < dataLength)
        {
            int currentRead = await stream.ReadAsync(dataBuffer, totalRead, dataLength - totalRead);
            if (currentRead == 0)
                throw new InvalidOperationException("Stream closed before receiving full message");
            totalRead += currentRead;
        }

        return MessagePackSerializer.Deserialize<T>(dataBuffer);
    }
}

2. 修改服务器代码

使用新的辅助方法处理消息收发:

var tcpListener = new TcpListener(IPAddress.Any, 8888);
try
{
    tcpListener.Start();
    Console.WriteLine("Server is running, waiting for incoming connections");
    while (true)
    {
        using var tcpClient = await tcpListener.AcceptTcpClientAsync();
        await using var stream = tcpClient.GetStream();
        
        // 读取客户端发送的Person消息
        var incomePerson = await SerializeHelper.ReceiveMessageAsync<Person>(stream);
        Console.WriteLine($"Income message from {incomePerson.Name}: '{incomePerson.Message}'");

        // 发送响应消息
        var responseMessage = $"Thank you, {incomePerson.Name} for the message";
        await SerializeHelper.SendMessageAsync(stream, responseMessage);
    }
}
finally
{
    tcpListener.Stop();
}

3. 修改客户端代码

同样替换为新的消息处理逻辑:

using TcpClient tcpClient = new TcpClient();
await tcpClient.ConnectAsync("127.1.199.250", 8888);

Console.WriteLine("Hello, client!");

try
{
    var stream = tcpClient.GetStream();
    Console.WriteLine("Enter your name");
    var name = Console.ReadLine();
    Console.WriteLine("Enter message");
    var message = Console.ReadLine();
    Person tom = new Person(name, message);
    
    // 发送Person消息
    await SerializeHelper.SendMessageAsync(stream, tom);
    Console.WriteLine("Message was sent to the server");

    // 读取服务器响应
    var incomeResponceMessage = await SerializeHelper.ReceiveMessageAsync<string>(stream);
    Console.WriteLine($"Server's message: {incomeResponceMessage}");
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}

为什么原代码会阻塞?

原ConverteToBytes的do-while循环逻辑是「只要流能读且读取到数据,就一直读」,但双向通信时:

  • 客户端发完请求后,为了接收响应不会关闭流,服务器的ReadAsync会一直阻塞等待更多数据,无法执行后续的响应代码。
  • 就算服务器能发送响应,客户端的ConverteToBytes也会因为服务器没关闭流而持续阻塞,永远无法处理响应。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 16:46:17