跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

C#中Socket网络编程_C#网络套接字编程教程【核心】

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()
返回的是本次从内核缓冲区实际拷贝的字节数,可能远小于你申请的缓冲区长度——尤其在高延迟或小包场景下。 C函数速查手册(CHM版) C函数速查手册(CHM版) 下载 不要这样写:
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

相关文章