You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Server Socket停止接受新连接问题排查与解决

分析TCP捕获器运行数天后停止接受连接的问题

我仔细看了你的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:40:46