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
相关产品推荐
相关产品推荐

