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

Azure SignalR服务端超时配置与连接状态管理问题咨询

针对你的Azure SignalR问题,分点解答如下:

1. 关于OnDisconnectedAsync的触发时机

是的,当客户端在指定时间内未响应时,OnDisconnectedAsync会被触发,但需要正确配置超时参数。你当前遇到离线后仍能收到Ping的问题,是因为默认超时设置不符合你的预期。

要让服务器在30秒内未收到客户端响应时判定连接丢失,需在ConfigureServices中配置SignalR的超时选项:

services.AddSignalR(options =>
{
    // 客户端超过30秒未响应则视为断开
    options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
    // 服务器发送Ping的间隔(建议设为ClientTimeout的1/3到1/2,确保及时检测断开)
    options.KeepAliveInterval = TimeSpan.FromSeconds(10);
}).AddAzureSignalR(Configuration.GetSection("Values")["AzureSignalRConnectionString"]);

当客户端离线超过ClientTimeoutInterval,服务器会标记连接为断开并触发OnDisconnectedAsync。Azure SignalR服务端也会自动清理长时间无响应的连接,上述配置能进一步加快断开逻辑的触发。

2. 页面刷新导致连接堆积的解决方案

页面刷新时旧连接未触发OnDisconnectedAsync,是因为客户端直接关闭页面,没有主动向服务器发送断开请求,导致服务器误认为连接仍存活。解决办法有两个:

  • 客户端主动断开:在页面的beforeunload事件中调用SignalR连接的stop()方法,确保服务器收到断开通知:
    window.addEventListener('beforeunload', () => {
        if (connection) {
            connection.stop();
        }
    });
    
  • 缩短超时时间:结合上面的ClientTimeoutInterval配置,让服务器更快回收僵尸连接,避免连接数达到免费层上限。

3. 更好的Hub状态维护方式

你当前使用的static Dictionary存在几个问题:线程不安全(多连接并发操作会引发异常)、多服务器部署时状态无法共享、服务器重启后状态丢失。推荐以下优化方案:

方案一:单实例部署用ConcurrentDictionary

替换static Dictionary为线程安全的ConcurrentDictionary,避免并发冲突:

// 替换原来的静态字典,确保线程安全
private static readonly ConcurrentDictionary<string, UserInMall> ConnectedUsers = new ConcurrentDictionary<string, UserInMall>();

public override async Task OnConnectedAsync()
{
    try
    {
        var userId = Context.User.Claims.FirstOrDefault(c => c.Type == "id").Value;
        var mallCodename = Context.User.Claims.FirstOrDefault(c => c.Type == "mcn").Value;
        
        // 用AddOrUpdate处理并发场景:若用户已存在则更新ConnectionId(支持同一用户多设备登录)
        ConnectedUsers.AddOrUpdate(userId, 
            _ => new UserInMall { ConnectionId = Context.ConnectionId, MallCodename = mallCodename },
            (_, existing) => { existing.ConnectionId = Context.ConnectionId; return existing; });
        
        var offers = await _offerService.GetUserOffersById(userId);
        await Clients.Client(Context.ConnectionId).SendAsync("updateOffers", offers);
        await base.OnConnectedAsync();
    }
    catch (Exception e)
    {
        throw new HubException(e.Message);
    }
}

public override Task OnDisconnectedAsync(Exception exception)
{
    var userId = Context.User.Claims.FirstOrDefault(c => c.Type == "id")?.Value;
    if (!string.IsNullOrEmpty(userId))
    {
        ConnectedUsers.TryRemove(userId, out _);
    }
    return base.OnDisconnectedAsync(exception);
}
方案二:多实例/分布式部署用缓存(如Azure Redis)

如果你的API需要横向扩展多实例,静态字典无法跨实例共享状态,此时需要用分布式缓存存储连接映射。例如使用Azure Redis Cache:

  1. 安装StackExchange.Redis NuGet包
  2. 注入IDistributedCache或Redis客户端
  3. 将ConnectedUsers的操作替换为Redis的键值对操作,同时设置与ClientTimeoutInterval一致的过期时间,定期清理过期条目。

另外,SignalR本身支持通过UserIdentifier直接向指定用户发送消息,你可以在Hub配置中指定用户ID提供者,简化消息发送:

services.AddSignalR()
    .AddAzureSignalR(Configuration.GetSection("Values")["AzureSignalRConnectionString"])
    .AddHubOptions<MallHub>(options =>
    {
        // 设置用户ID为你的领域userId
        options.UserIdProvider = context => context.User.Claims.FirstOrDefault(c => c.Type == "id")?.Value;
    });

之后可以直接用Clients.User(userId).SendAsync("updateOffers", offers)发送消息,无需手动查找ConnectionId(如果不需要额外的MallCodename信息的话)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:09:15