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

ASP.NET SignalR无法断开空闲客户端问题排查求助

ASP.NET SignalR 空闲客户端未断开问题排查与解决

问题原因

  • 客户端自动心跳响应:SignalR官方客户端(JS、.NET等)会自动处理服务器发送的KeepAlive帧,主动回复ACK信号。此时服务器会判定连接处于活跃状态,不会触发ClientTimeoutInterval——这个配置的作用是**服务器在连续一段时间内收不到任何客户端消息(包括心跳ACK)**时才断开连接,并非检测用户的业务操作空闲。
  • 代理/负载均衡干扰:如果部署环境存在Nginx、IIS ARR等反向代理,这些服务可能会自行维护长连接,忽略SignalR的超时配置。代理层的超时设置如果远大于你的配置,会导致连接被代理持续保持。
  • 配置未正确生效:若中间件注册顺序错误,或者在Hub级别单独配置了超时参数,会覆盖全局的ClientTimeoutInterval和KeepAliveInterval设置。

实现空闲客户端终止的方案

1. 业务级空闲检测(推荐)

由于SignalR的内置超时是基于连接层面的心跳,而非用户操作空闲,需要自行维护每个连接的最后活跃时间,定时清理超时连接:

public class YourHub : Hub
{
    // 存储连接ID与最后活跃时间
    private static readonly Dictionary<string, DateTime> _lastActive = new Dictionary<string, DateTime>();
    private static readonly Timer _idleCleanupTimer;
    // 空闲阈值,可根据需求调整
    private static readonly TimeSpan _idleThreshold = TimeSpan.FromSeconds(10);

    static YourHub()
    {
        // 每10秒执行一次清理
        _idleCleanupTimer = new Timer(CleanupIdleConnections, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
    }

    public override Task OnConnectedAsync()
    {
        lock (_lastActive)
        {
            _lastActive[Context.ConnectionId] = DateTime.UtcNow;
        }
        return base.OnConnectedAsync();
    }

    public override Task OnDisconnectedAsync(Exception? exception)
    {
        lock (_lastActive)
        {
            _lastActive.Remove(Context.ConnectionId);
        }
        return base.OnDisconnectedAsync(exception);
    }

    // 客户端有业务操作时调用此方法更新活跃时间
    public Task UpdateActiveTime()
    {
        lock (_lastActive)
        {
            _lastActive[Context.ConnectionId] = DateTime.UtcNow;
        }
        return Task.CompletedTask;
    }

    private static void CleanupIdleConnections(object? state)
    {
        var now = DateTime.UtcNow;
        List<string> idleConnections;

        lock (_lastActive)
        {
            idleConnections = _lastActive
                .Where(kv => now - kv.Value > _idleThreshold)
                .Select(kv => kv.Key)
                .ToList();
        }

        var hubContext = GlobalHost.ConnectionManager.GetHubContext<YourHub>();
        foreach (var connectionId in idleConnections)
        {
            // 先通知客户端即将断开
            _ = hubContext.Clients.Client(connectionId).SendAsync("IdleTimeout");
            // 主动断开连接
            hubContext.Connection.Abort(connectionId);
            lock (_lastActive)
            {
                _lastActive.Remove(connectionId);
            }
        }
    }
}

2. 调整代理层配置

  • Nginx:添加或修改以下配置,确保超时时间与SignalR配置匹配:
    location /yourhubpath {
        proxy_pass http://yourbackend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 10s; # 与ClientTimeoutInterval一致
        proxy_send_timeout 10s;
    }
    
  • IIS:设置应用程序池的“空闲超时”为对应值,同时在站点配置中调整WebSocket的超时时间。

3. 验证配置生效

在Hub的OnConnectedAsync方法中打印当前配置,确认全局设置已生效:

public override Task OnConnectedAsync()
{
    var options = Context.Features.Get<HubOptions>();
    Console.WriteLine($"ClientTimeoutInterval: {options?.ClientTimeoutInterval}");
    Console.WriteLine($"KeepAliveInterval: {options?.KeepAliveInterval}");
    return base.OnConnectedAsync();
}

内容的提问来源于stack exchange,提问作者Victornor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 21:30:24