TcpClient和TcpListener是C#中安全易用的TCP通信入口,需处理消息边界、异步接受连接、资源释放及底层Socket选项配置。

TcpClient 和 TcpListener 是 C# 中最直接、最安全的 TCP 通信入口,不用手动处理 Socket 底层细节,适合绝大多数局域网或可控环境下的双向通信需求。别一上来就写裸 Socket,除非你明确需要控制连接超时、重用地址、非阻塞 I/O 或兼容旧协议(比如 Modbus TCP 的 MBAP 头拼接)。
用 TcpClient 写客户端,别漏掉 NetworkStream 的读写边界
常见错误是直接 stream.Read(buffer, 0, buffer.Length) 后就认为读完了整条消息——但 TCP 是字节流,没有消息边界。服务器发来 "OK\r\n",你可能只读到 "O" 就返回,下一次才读到 "K\r\n"。
- 必须自己定义协议:比如每条消息以
\r\n结尾,或开头带 4 字节长度字段 -
stream.Read()返回的是**本次实际读到的字节数**,不是“等够了才返回”,要循环读直到满足预期长度或遇到分隔符 - 发送时也别直接
stream.Write(Encoding.UTF8.GetBytes(msg))就完事——如果对方没按长度读,就会粘包或截断 - 示例片段(按行读):
string line = null; using (var reader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen: true)) { line = await reader.ReadLineAsync(); }
用 TcpListener 写服务端,AcceptTcpClient() 是阻塞点,得异步化
同步调用 listener.AcceptTcpClient() 会让主线程卡住,UI 冻结、无法响应按钮点击。WinForm 或 WPF 下必须用 BeginAcceptTcpClient / EndAcceptTcpClient,或者更推荐的 AcceptTcpClientAsync()(.NET 4.5+)。
-
AcceptTcpClientAsync()返回Task<tcpclient></tcpclient>,配合await不会阻塞 UI 线程 - 每个新连接都应单独开一个
Task处理收发,避免一个客户端卡死拖垮全部 - 别在
Form.Load里直接Start()监听——窗体还没完全初始化,容易触发跨线程异常 - 监听前务必校验端口范围:
1024–65535,低于 1024 需管理员权限,且多数被系统占用
TcpClient 和 Socket 混用时,Client.Client 是唯一桥梁
有些场景需要底层控制(比如设置 SocketOptionName.KeepAlive、SocketOptionName.Linger),但又不想放弃 TcpClient 的易用性。这时得通过 TcpClient.Client 属性拿到内部 Socket 实例。
-
TcpClient构造后、Connect()前才能访问Client属性,否则抛InvalidOperationException - 设置 KeepAlive 示例:
tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, new LingerOption(true, 5));
- 一旦调用了
Connect(),再改Client的选项可能无效,尤其是已建立连接的状态 - 注意:
TcpClient自身不暴露超时配置,所有超时(连接/读/写)都得靠Socket层或上层封装
真正难的不是连上,而是断开后资源没清干净:NetworkStream 关了,TcpClient 忘关,Socket 还在 TIME_WAIT 状态占着端口;或是多线程并发读写同一个 stream 导致 IOException。这些不会立刻报错,但压测一跑就崩。











