C#中StreamReader搭配NetworkStream导致程序阻塞的问题排查
TCP聊天程序StreamReader/StreamWriter使用问题修复
原代码核心问题
- 服务器端阻塞问题:
reader.ReadToEnd()会一直等待到流完全关闭才返回结果,而TCP聊天是长连接场景,连接不会主动关闭,导致服务器永远无法读取到客户端发送的消息。 - 消息边界缺失:客户端使用
writer.Write(message)发送消息时没有添加换行符,即使服务器改用ReadLine(),也因为没有换行标记而无法识别完整消息。 - 资源管理隐患:创建
StreamReader和StreamWriter时未指定leaveOpen: true,默认会在释放读写器时关闭底层的NetworkStream,导致TCP连接意外断开。
修正后的服务器代码
using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.IO; using System.Threading.Tasks; public static class Server { public static List<TcpClients> clients = new List<TcpClients>(); public static void Main(string[] args) { TcpListener listener = new TcpListener(IPAddress.Any, 7777); listener.Start(); Console.WriteLine("服务器已启动,等待客户端连接..."); while (true) { var tcpClient = listener.AcceptTcpClient(); Console.WriteLine("新客户端连接。"); var client = new TcpClients(tcpClient); clients.Add(client); Task.Run(() => client.HandleTcpClient()); } } } public class TcpClients : IDisposable { public TcpClient tcpClient; public NetworkStream networkStream; public StreamReader reader; public StreamWriter writer; private bool disposed = false; public TcpClients(TcpClient client) { tcpClient = client; networkStream = tcpClient.GetStream(); // 指定leaveOpen: true,避免读写器释放时关闭NetworkStream reader = new StreamReader(networkStream, leaveOpen: true); writer = new StreamWriter(networkStream, leaveOpen: true); // 自动刷新StreamWriter,避免消息滞留 writer.AutoFlush = true; } public async void HandleTcpClient() { try { string message; // 使用ReadLineAsync异步读取,直到客户端断开 while ((message = await reader.ReadLineAsync()) != null) { Console.WriteLine($"收到消息:{message}"); SendMessage(message); } } catch (IOException) { Console.WriteLine("客户端断开连接"); } finally { Dispose(); Server.clients.Remove(this); } } public void SendMessage(string message) { foreach (TcpClients client in Server.clients) { if (client == this) continue; // 发送消息时添加换行符,让客户端的ReadLine能识别 client.writer.WriteLine(message); } } public void Dispose() { if (!disposed) { reader?.Dispose(); writer?.Dispose(); networkStream?.Dispose(); tcpClient?.Close(); disposed = true; } } }
修正后的客户端代码
using System; using System.Net; using System.Net.Sockets; using System.IO; using System.Threading.Tasks; public class Client { public static TcpClient tcpClient = new TcpClient(); public static NetworkStream ns; public static StreamReader reader; public static StreamWriter writer; static async Task Main(string[] args) { try { tcpClient.Connect(IPAddress.Loopback, 7777); ns = tcpClient.GetStream(); reader = new StreamReader(ns, leaveOpen: true); writer = new StreamWriter(ns, leaveOpen: true) { AutoFlush = true }; // 启动异步任务接收服务器消息 _ = Task.Run(ReceiveMessages); Console.WriteLine("已连接到服务器,输入消息发送:"); while (true) { var message = Console.ReadLine(); if (string.IsNullOrEmpty(message)) continue; // 发送消息时添加换行符 writer.WriteLine(message); } } catch (SocketException) { Console.WriteLine("无法连接到服务器"); } catch (IOException) { Console.WriteLine("与服务器断开连接"); } finally { reader?.Dispose(); writer?.Dispose(); ns?.Dispose(); tcpClient?.Close(); } } static async Task ReceiveMessages() { string message; while ((message = await reader.ReadLineAsync()) != null) { Console.WriteLine($"收到其他客户端消息:{message}"); } } }
关键修改说明
- 替换ReadToEnd为ReadLineAsync:异步读取直到换行符,既不会阻塞线程,又能正确识别每条消息的边界。
- 添加换行符标记:客户端发送和服务器转发消息时使用
WriteLine,自动添加换行符,让ReadLine能正确分割消息。 - 资源管理优化:指定
leaveOpen: true保护底层流,实现IDisposable接口确保资源正确释放,添加异常处理捕获连接断开场景。 - 异步处理:使用异步方法避免主线程阻塞,提升程序响应性。
内容的提问来源于stack exchange,提问作者J. Holtan
相关产品推荐
相关产品推荐

