C# Server Socket停止接受新连接问题排查与解决
我仔细看了你的TCP捕获器代码,结合Windows服务长时间运行的场景,梳理了几个可能导致运行2-4天后停止接受新连接的原因,以及对应的修复方案:
1. 单例SocketAsyncEventArgs复用的阻塞风险
你代码里复用了同一个SocketAsyncEventArgs实例处理所有Accept操作,这里藏着个坑:如果ConnectionCatched事件触发的上层处理逻辑耗时很长,或者抛出未捕获的异常,这个唯一的异步事件对象会被卡住,没法继续发起新的Accept请求,直接导致后续连接无法被捕获。
修复方案:
- 不要只用一个
SocketAsyncEventArgs,而是维护一个对象池,每次发起Accept时从池里取可用实例,处理完再放回。就算某个实例出问题,其他实例还能正常工作。 - 退一步说,也可以在
OnAcceptSocket回调里,直接创建新的SocketAsyncEventArgs来发起下一次Accept,彻底避免复用带来的阻塞问题。
2. 未捕获异常中断Accept循环
在AcceptSocket的循环里,不管是同步还是异步分支,调用ConnectionCatched时如果上层代码抛出未捕获的异常,会直接打断整个Accept循环,之后就再也不会发起新的Accept了。而且Windows服务后台运行,这种异常大概率不会被你注意到,排查起来特别麻烦。
修复方案:
- 在所有调用
ConnectionCatched的地方加上try-catch,把异常记录到日志里:
// 异步回调里的处理 try { ConnectionCatched(asyncArg.AcceptSocket); } catch (Exception ex) { OnLog($"处理捕获的Socket时出错: {ex.Message}\n{ex.StackTrace}"); } // 同步分支里的处理也要加 if (!isAsync) { OnLog($"AcceptAsync finished Sync."); if (_acceptAsyncArg.SocketError == SocketError.Success) { try { ConnectionCatched(_acceptAsyncArg.AcceptSocket); } catch (Exception ex) { OnLog($"同步处理Socket时出错: {ex.Message}\n{ex.StackTrace}"); } } }
3. Socket资源泄漏导致句柄耗尽
如果上层代码拿到ConnectionCatched传递的Socket后,没有正确关闭或释放,长时间运行会把系统的Socket句柄耗光,新连接根本没法被Accept。另外你的Finish方法也有问题:监听Socket不需要调用Disconnect,而且如果_serverSocket已经是null,调用这些方法会直接抛异常。
修复方案:
- 上层代码一定要在使用完Socket后调用
Close()或Dispose(),最好用using语句包裹Socket的使用:
// 上层处理示例 void OnConnectionCatched(Socket socket) { using (socket) { // 处理Socket逻辑 } }
- 优化
Finish方法的资源清理逻辑,加上null检查和事件解绑:
public void Finish() { OnLog($"Stopping"); IsStarted = false; // 先标记停止,防止回调里继续发起Accept if (_serverSocket != null) { try { _serverSocket.Close(); _serverSocket.Dispose(); } catch (Exception ex) { OnLog($"关闭服务Socket时出错: {ex.Message}"); } finally { _serverSocket = null; } } // 清理异步事件对象 _acceptAsyncArg.Completed -= OnAcceptSocket; _acceptAsyncArg.Dispose(); }
4. 监听Socket的Backlog队列被占满
你用SocketOptionName.MaxConnections作为Listen的backlog参数,但这个值并不是实际的最大连接数,而且如果上层处理连接的速度跟不上Accept的速度,backlog队列会被堆满,新连接会被直接拒绝。另外长时间运行后,大量半连接(SYN_RCVD状态)也会耗尽backlog。
修复方案:
- 不要用
SocketOptionName.MaxConnections,设置一个合理的backlog值(比如100,根据你的业务量调整):
_serverSocket.Listen(100);
- 用线程池处理每个连接的逻辑,避免阻塞Accept流程,确保上层处理速度能跟上连接到来的速度。
5. 主线程阻塞导致Accept流程停滞
如果Start方法是在Windows服务的主线程里执行的,直接调用AcceptSocket会把主线程卡住,不仅影响服务的其他功能,一旦主线程出问题,Accept循环也会跟着停掉。
修复方案:
- 在
Start里把AcceptSocket的执行放到单独的Task里,避免阻塞主线程:
public void Start() { OnLog($"Starting"); IsStarted = true; _serverSocket?.Close(); _serverSocket = new Socket(SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); _serverSocket.Bind(new IPEndPoint(IPAddress.Any, Port)); _serverSocket.Listen(100); // 用Task启动Accept循环,不阻塞主线程 Task.Run(() => AcceptSocket()); }
额外排查小技巧
- 加更详细的日志:记录当前连接数、进程的Socket句柄数(用
Process.GetCurrentProcess().HandleCount获取),问题出现时就能快速判断是不是句柄耗尽。 - 用Windows的
netstat -ano命令或者Process Explorer工具,查看服务进程的Socket状态,如果有大量TIME_WAIT或CLOSE_WAIT状态的连接,那基本就是资源泄漏了。
内容的提问来源于stack exchange,提问作者Qui-Gon173

