应避免使用TcpClient/TcpListener构建高并发服务端,因其阻塞Accept、不支持Socket选项配置、无数据预判接口、易句柄泄漏;推荐用Socket.AcceptAsync配合IOCP,并严格按协议解析字节流、显式Shutdown再Close。

别用 TcpClient 和 TcpListener 写高并发服务端——它们是单连接封装,底层仍是 Socket,但屏蔽了关键控制点,一上生产就卡死或丢连接。
为什么 TcpClient.AcceptTcpClient() 不能用于多客户端长连接服务
它本质是阻塞式同步 Accept,每次调用只返回一个 TcpClient,你得手动开新线程/任务去处理,而 TcpClient 自身不支持复用、不暴露底层 Socket 的 SetSocketOption 或 IOControl 调用。实际运行中会出现:
- 客户端连上来后,主线程卡在
AcceptTcpClient(),下一个连接必须等前一个处理完才被接受(除非你主动循环+多线程) - 无法设置
SO_KEEPALIVE、SO_LINGER等关键 socket 选项 -
NetworkStream.Read()返回字节数不可靠,但TcpClient不提供Available或Poll()接口来预判是否有数据可读 - 异常时句柄泄漏风险高:
TcpClient.Close()不等于底层Socket.Dispose(),尤其在未读完流时关闭会丢包
Socket.BeginAccept() 和 Socket.AcceptAsync() 怎么选
两者都解决“非阻塞接收连接”问题,但行为差异直接影响稳定性:
-
BeginAccept()是 .NET Framework 2.0 风格异步,需配对EndAccept(),回调中若抛异常容易吞掉错误,且无法取消挂起的 Accept 操作 -
Socket.AcceptAsync()是 IOCP 模型原生支持,性能更好,支持取消(通过SocketAsyncEventArgs.SetBuffer()+Cancel()),推荐用于 .NET Framework 4.5+ 或 .NET Core/.NET 5+ - 注意:
AcceptAsync()必须复用SocketAsyncEventArgs实例,不能每次 new —— 否则 GC 压力大,且可能触发InvalidOperationException: “The I/O operation has been aborted because of either a thread exit or an application request.”
示例关键片段:
var args = new SocketAsyncEventArgs();
args.Completed += OnAcceptCompleted;
if (!listener.AcceptAsync(args)) {
OnAcceptCompleted(listener, args);
}
// 在 OnAcceptCompleted 中:检查 args.SocketError == SocketError.Success,然后 args.AcceptSocket.SetSocketOption(...)
收发数据时,Socket.Receive() 和 NetworkStream.Read() 的边界陷阱
TCP 是字节流,没有消息头尾。这两个 API 都不保证一次调用读完“一条完整业务消息”,但错误假设会导致粘包/半包:
-
NetworkStream.Read():返回值仅表示本次从内核缓冲区拷贝了多少字节,可能 1 字节、也可能 1024 字节,和你的业务协议完全无关 -
Socket.Receive():同理,且默认无超时;若对方只发一半就断开,你可能永远卡在Receive()上(除非设ReceiveTimeout) - 正确做法:必须按协议解析。比如定长包,就循环读直到凑够长度;TLV 包,先读 4 字节长度头,再读对应 body;文本行协议,就得自己扫描
\n或\r\n - 千万别写
stream.Read(buffer, 0, buffer.Length)就直接Encoding.UTF8.GetString()—— buffer 剩余部分是脏数据,解码会出错
资源释放不等于调用 Close(),必须显式 Shutdown()
Socket.Close() 只是释放托管资源,不保证 TCP 四次挥手完成;而 Dispose() 在某些 .NET 版本里甚至不调用 Shutdown()。后果是:
- 对方收到 RST 而非 FIN,认为连接异常中断
- TIME_WAIT 状态堆积,端口耗尽(尤其客户端高频短连)
- 服务端
socket.Available > 0但Receive()返回 0(对端已关闭),此时不Shutdown(SocketShutdown.Both)就直接Close(),下次 Accept 可能失败
标准清理顺序应为:
clientSocket.Shutdown(SocketShutdown.Both); // 发送 FIN clientSocket.Receive(new byte[1], 0, 1, SocketFlags.Peek); // 确保对方 FIN 已到(可选) clientSocket.Close(); // 或 clientSocket.Dispose();
真正难的是:shutdown 和 close 的时机必须和业务逻辑强绑定,比如消息收发完毕、心跳超时、认证失败——这些状态不归 Socket 管,得你自己维护。











