Socket.Bind()报“地址已在使用中”主因是端口被占或绑定地址错误;应设ReuseAddress、用IPAddress.Any、避特权端口;Accept()后须调Receive()循环读取;多客户端需线程隔离与异常处理;优先选TcpListener。
为什么 Socket.Bind() 报“地址已在使用中”
绝大多数新手卡在这一步,不是代码写错,而是端口被占或绑定地址不对。windows 下 netstat -ano | findstr :<code>端口号 能快速定位谁在用;更常见的是绑了 127.0.0.1 却想让局域网设备连——得改用 ipaddress.any(即 0.0.0.0)。
实操建议:
-
Bind()前必须先调用Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true),否则重启服务时容易因 TIME_WAIT 状态报错 - 监听 IPv4 就用
AddressFamily.InterNetwork,别混用InterNetworkV6,否则Bind()直接抛SocketException - 端口号避开 1–1023(需管理员权限),开发阶段优先选 8080、5000、9001 这类常见非特权端口
Accept() 后收不到数据?检查 Receive() 的缓冲区和循环逻辑
Socket.Accept() 只是建立连接,不读数据;真正收消息得靠客户端发完后,服务端主动调 Receive()。很多人写了 Accept() 就以为“连上了”,结果一运行就卡住——其实是阻塞在没调 Receive() 上。
实操建议:
- 别用单次
Receive()期待读完全部内容:TCP 是流式协议,一次调用可能只收到部分字节,必须循环读直到返回值 ≤ 0 - 缓冲区大小建议设为 1024 或 8192,太小导致频繁系统调用,太大浪费内存且无意义
- 如果客户端发的是文本,记得用
Encoding.UTF8.GetString(buffer, 0, bytesReceived)解码,别直接当字符串用字节数组
怎么安全地处理多个客户端连接(不用 async/await 的最简方案)
用 Thread + BeginAccept() 是 .NET Framework 旧项目常见做法,但线程数失控容易拖垮服务。真要手动管理,必须限制并发连接数,并给每个客户端分配独立线程+异常捕获。
实操建议:
- 启动监听前设置最大连接数:用
socket.Listen(100),第二个参数是等待队列长度,不是总连接数 - 每个
Socket实例必须单独封装处理逻辑,不能共用同一个缓冲区变量,否则多线程下数据会错乱 - 在线程里务必包
try/catch,捕获SocketException和ObjectDisposedException,避免一个客户端断连导致整个线程退出 - 别忘了在连接关闭后调用
clientSocket.Shutdown(SocketShutdown.Both)再Close(),否则连接可能滞留在 CLOSE_WAIT 状态
Socket 和 TcpListener 该选哪个
纯 TCP 服务端,TcpListener 是更轻量、更语义明确的封装,底层仍用 Socket,但省去地址解析、协议族选择等细节。除非你需要自定义 IP 多播、原始套接字或精细控制 IO 模型,否则直接用 TcpListener 更稳。
实操建议:
-
TcpListener.Start()后用AcceptTcpClient()获取TcpClient,再通过GetStream()拿到NetworkStream,比裸Socket少写十几行错误检查 -
TcpClient.Client属性可以回退到原始Socket,需要时再深入也不晚 - .NET Core 3.0+ 推荐用
System.Net.Sockets.SocketAsyncEventArgs配合池化,但那是高并发场景的事,小项目先别碰
最常被忽略的一点:没有心跳机制的服务端,会在客户端异常断网后长时间保留半开连接。加个简单的 SendTimeout 和定期 Poll() 检测可读性,比等系统超时快得多。











