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