C# Socket绑定出现Address already in use异常的问题求助
我基于.NET 6.0.301开发Socket通信功能,测试时采用本机自收发数据的方式,此前该功能正常运行数周,可正常建立连接、收发数据及关闭连接。但重启路由器后,监听Socket绑定IPAddress.Any(0.0.0.0)端点时抛出SocketException(98),提示“Address already in use”,源代码未做任何修改。
监听Socket用于接收任意IP的连接,使用的是固定未变更的闲置随机端口,相关简化代码如下:
// 基类构造函数中: IpEndPoint = new IPEndPoint(IPAddress.Any, Port); // 固定未变更的闲置随机端口 // ... // 派生类构造函数中调用 private async Task ReceiveDataAsync() { using Socket listener = new(IpEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); try { listener.Bind(IpEndPoint); // 此处抛出异常!!! listener.Listen(100); } catch(Exception e) { Console.WriteLine(e); throw; } while(true) { using Socket handler = await listener.AcceptAsync(); // 接收数据... } }
该方法仅被调用一次,首次执行即抛出异常,调用时间远早于连接尝试时间。另外,接收数据后未调用Shutdown、DisconnectAsync或Close关闭连接,但之前均是客户端发送完数据后执行这些操作。
异常信息:
System.Net.Sockets.SocketException (98): Address already in use at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, String callerName) at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress) at System.Net.Sockets.Socket.Bind(EndPoint localEP) at ....ReceiveDataAsync() ...
补充:IDE(JetBrains Rider)打开项目但未运行时,执行netstat -ntpl | grep 0.0.0.0输出:
tcp 0 0 0.0.0.0:31415 0.0.0.0:* LISTEN 10064/dotnet tcp 0 0 127.0.0.1:42103 0.0.0.0:* LISTEN 9560/Rider.Backend tcp 0 0 127.0.0.1:33747 0.0.0.0:* LISTEN 9560/Rider.Backend tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
杀死占用端口的进程后,重启应用又会生成新进程占用该端口。
清理残留的.NET进程
从netstat输出可见,未运行项目时已有dotnet进程占用目标端口,说明之前的应用进程未完全退出。可执行以下命令彻底清理:pkill dotnet操作后重启应用,观察端口占用是否恢复正常。
添加ReusePort Socket选项
仅设置ReuseAddress在Linux环境下可能不足以解决端口占用问题,需额外添加ReusePort选项,允许多个进程绑定同一端口(若业务需要):listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); // 添加ReusePort选项 listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReusePort, true);注意:
ReusePort在Windows系统下仅支持Windows 10/Server 2016及以上版本。确保Socket连接完全释放
虽然接收端的handler使用了using语句自动释放资源,但建议在接收完数据后显式调用Shutdown,确保Socket资源彻底释放:using Socket handler = await listener.AcceptAsync(); try { // 接收数据逻辑... handler.Shutdown(SocketShutdown.Both); } finally { handler.Close(); }排查端口抢占问题
重启路由器后可能有系统服务或其他应用抢占了目标端口,可更换一个端口测试,或用更精准的命令确认占用情况:ss -tulpn | grep 目标端口号检查是否有非dotnet进程占用端口。
清理Rider残留进程
Rider有时会残留调试进程,即使停止运行也不会退出。可在Rider中点击Run -> Stop All,或直接杀死Rider.Backend相关进程后,重新启动项目。
内容的提问来源于stack exchange,提问作者User

