TcpListener 和 TcpClient 封装了底层 Socket 细节,避免资源泄漏、连接僵死等问题;ConnectAsync 支持超时控制;Read() 需循环读取或按协议解析消息边界。

TcpListener 和 TcpClient 足够应付绝大多数 TCP 场景,别一上来就碰原生 Socket 类——它暴露太多细节,容易漏关连接、卡死线程、读不到完整消息。
为什么用 TcpListener 而不是裸 Socket 写服务器
裸 Socket 要自己 Bind、Listen、Accept、管理 NetworkStream 生命周期,稍不注意就会:SocketException: An existing connection was forcibly closed by the remote host 或句柄泄漏。而 TcpListener 把这些封装成一步 AcceptTcpClient(),返回的 TcpClient 自带 GetStream() 和 Close(),资源释放路径清晰。
常见错误现象:
- 用裸
Socket.Accept()后没调用clientSocket.Shutdown(SocketShutdown.Both)就直接Close(),客户端收不到 FIN 包,连接僵死 - 监听时传了
IPAddress.Loopback却想让局域网设备连,结果连不上 - 没设
SO_REUSEADDR(对应Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true)),改代码重启服务时报“地址已在使用中”
TcpClient.Connect() 怎么加超时控制
TcpClient 的 Connect() 是同步阻塞的,网络不通时会卡住几十秒。不能靠 Thread.Sleep + Abort,那是过时且危险的做法。
正确做法是用异步 + CancellationToken:
TcpClient client = new TcpClient();
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
await client.ConnectAsync("127.0.0.1", 8080, cts.Token);
}
catch (OperationCanceledException)
{
// 超时
client.Dispose();
}注意:ConnectAsync 是 .NET Framework 4.5+ / .NET Core 1.0+ 才支持;如果还在用老框架,只能新建线程 + ManualResetEvent + BeginConnect 组合实现。
读数据时为什么 stream.Read() 总是只读到一半
TCP 是字节流,没有天然消息边界。stream.Read() 返回的是本次从内核缓冲区实际拷贝的字节数,可能远小于你申请的缓冲区长度——尤其在高延迟或小包场景下。
不要这样写:
byte[] buf = new byte[1024]; int len = stream.Read(buf, 0, buf.Length); // ❌ len 可能是 3、127、512…… string msg = Encoding.UTF8.GetString(buf); // ❌ 解码整个 buf,后面全是 \0
应该:
- 用协议约定长度头(比如前 4 字节是 int32 消息体长度),先读够头再读正文
- 或用分隔符(如
\r\n),配合StreamReader.ReadLine()(但需确保编码一致) - 或循环读取直到满足预期长度:
while (totalRead
忽略这点,发个 "hello" 可能只收到 "he",然后后续所有通信全乱序。
NetworkStream 关闭顺序为什么必须是 client → stream → listener
顺序错会导致资源泄漏或异常:
- 先
stream.Close()再client.Close():可能抛ObjectDisposedException,因为TcpClient内部也持有该流 - 先
listener.Stop()再处理完已连接的client:新连接被拒,但已有连接仍在跑,client关不干净 - 用
using包裹NetworkStream是安全的,但TcpClient必须显式Close()或Dispose(),否则底层 socket 句柄不会立即释放
最稳妥模式:
using (TcpClient client = listener.AcceptTcpClient())
using (NetworkStream stream = client.GetStream())
{
// 处理读写
}
// client 和 stream 都已释放真正的坑不在怎么写,而在忘了——哪怕只漏一个 client.Close(),跑几天后 Windows 上就可能报 System.Net.Sockets.SocketException: Only one usage of each socket address is normally permitted。



















