用 TcpListener 启动基础服务端需指定 IPAddress.Any 和可用端口,调用 Start() 前检查端口占用;接收数据须循环 Read() 并校验返回值,按协议分包;多客户端需独立 Task 处理并正确释放资源。

怎么用 TcpListener 启动一个基础服务端
直接 new TcpListener 并调用 Start() 就能监听,但必须指定有效的 IP 和端口。常见错误是传 IPAddress.Any 却忘了端口被占用,或者用 127.0.0.1 导致外部连不上。
-
TcpListener构造时推荐用IPAddress.Any+ 端口号,避免绑定到特定网卡导致连接失败 - 启动前务必检查端口是否空闲,否则
Start()会抛SocketException(错误码 10048) - 不要在主线程里直接
AcceptTcpClient()阻塞等待,否则整个程序卡死——得用BeginAcceptTcpClient或AcceptTcpClientAsync() - .NET 6+ 更推荐用
TcpListener.StartAsync()配合await foreach处理客户端,代码更清晰
接收数据时为什么总读不满、丢包或卡住
根本原因在于 TCP 是流式协议,NetworkStream.Read() 不保证一次读完所有已到达的数据,也不保证单次调用返回多少字节。新手常误以为 Read() 会等满缓冲区才返回。
- 永远检查
Read()的返回值:0 表示对端关闭连接;小于缓冲区长度不等于“没数据了” - 不能依赖单次
Read()获取完整消息——得自己实现分包逻辑,比如按换行符、固定头长度或自定义协议字段 - 别用
StreamReader.ReadLine()直接读,它内部会缓冲并阻塞等待换行符,如果客户端不发 \n 就一直卡着 - 设置
client.Client.ReceiveTimeout防止异常网络下无限等待
如何安全地处理多个客户端连接
每个 TcpClient 必须独立处理,但共享的资源(如日志、全局计数器)要加锁,而 NetworkStream 本身不是线程安全的——不能多个线程同时读写同一个流。
- 为每个客户端分配独立线程或
Task,用async/await避免线程耗尽(尤其高并发时) - 客户端断开后,必须显式调用
client.Close()或stream.Dispose(),否则 socket 句柄泄漏,Windows 下很快达到上限 - 别在连接处理逻辑里 throw 未捕获异常,会导致 Task 中断且连接资源不释放——用 try/catch 包住整个处理循环
- 考虑用
ConcurrentDictionary存活连接列表,避免遍历时被修改引发InvalidOperationException
TcpListener 在 .NET Core/.NET 5+ 里要注意什么
旧版基于 System.Net.Sockets 的写法依然可用,但默认行为有变化:比如 SO_REUSEADDR 在 Linux 上默认开启,Windows 需手动设;另外 Kestrel 已经接管了大部分 HTTP 场景,纯 TCP 服务端容易被当成“非标方案”忽略。
- .NET 5+ 中
TcpListener默认启用ExclusiveAddressUse = false,多个进程可 bind 同一端口(需权限),但实际业务中建议保持true避免冲突 - Docker 容器内监听
0.0.0.0:port才能被宿主机访问,只写127.0.0.1会无法连通 - Linux 上注意
/proc/sys/net/core/somaxconn值,默认 128,高并发时可能触发ConnectionRefused - 别把
TcpListener当成 Web 服务器用——没有 TLS、连接复用、心跳保活等机制,这些都得自己补
真正难的不是启动监听,而是怎么在连接频繁上下线、网络延迟抖动、客户端不按协议发数据的情况下,让服务端既不崩溃也不漏消息。协议设计和超时控制,比写第一行 new TcpListener 重要得多。









