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

Blazor Server中SignalR Hub如何追踪用户关闭浏览器的操作?

解决Blazor Server中SignalR Hub无法捕获浏览器关闭导致的断开连接问题

当用户直接关闭浏览器时,客户端可能来不及向SignalR Hub发送断开请求,导致OnDisconnectedAsync无法及时触发。以下是几种可行的解决方法,覆盖不同场景:

1. 客户端监听页面卸载事件主动通知Hub

利用浏览器的beforeunload事件,在页面卸载前主动调用Hub方法告知服务器,确保用户列表及时更新。

步骤1:在Blazor组件中添加交互逻辑

在MainLayout.razor(或其他全局组件)中注入Hub上下文和JS运行时,注册页面卸载监听:

@inject IHubContext<MessagesHub> HubContext
@inject IJSRuntime JSRuntime

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JSRuntime.InvokeVoidAsync("setupBeforeUnload", DotNetObjectReference.Create(this));
        }
        await base.OnAfterRenderAsync(firstRender);
    }

    [JSInvokable]
    public async Task NotifyServerDisconnect()
    {
        try
        {
            await HubContext.Clients.Caller.SendAsync("UserInitiatedDisconnect");
            // 给服务器预留处理时间
            await Task.Delay(100);
        }
        catch { } // 忽略网络异常导致的发送失败
    }
}

步骤2:添加JS监听代码

在wwwroot/js/site.js中添加事件监听逻辑:

function setupBeforeUnload(dotNetRef) {
    window.addEventListener('beforeunload', async () => {
        try {
            await dotNetRef.invokeMethodAsync('NotifyServerDisconnect');
        } catch (e) {
            console.log('通知服务器断开失败:', e);
        }
    });
}

步骤3:在Hub中添加处理方法

修改MessagesHub,新增处理主动断开的方法:

public async Task UserInitiatedDisconnect()
{
    _messagesHubUsers.RemoveAll(x => x.ConnectionId == Context.ConnectionId);
    await Clients.All.SendAsync("UsersListChanged");
}

2. 配置SignalR心跳与超时机制

通过调整SignalR的内置心跳参数,让服务器更快检测到无响应的客户端,自动触发OnDisconnectedAsync。

服务器端配置(Program.cs)

builder.Services.AddSignalR(options =>
{
    // 客户端30秒未发送心跳则判定断开
    options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
    // 服务器每10秒发送一次心跳包
    options.KeepAliveInterval = TimeSpan.FromSeconds(10);
});

客户端配置(Hub连接初始化)

在初始化HubConnection时设置客户端超时:

var hubConnection = new HubConnectionBuilder()
    .WithUrl($"{NavigationManager.BaseUri}messagesHub")
    .WithAutomaticReconnect() // 可选:添加自动重连逻辑
    .Build();

// 客户端超时设置为35秒(略长于服务器超时)
hubConnection.ServerTimeout = TimeSpan.FromSeconds(35);
hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(10);

await hubConnection.StartAsync();

配置后,当客户端关闭浏览器,服务器会在超时后自动触发OnDisconnectedAsync,完成用户列表更新。

3. 服务器端定时清理无效连接

作为兜底方案,添加定时任务定期检查并清理用户列表中的无效连接。

实现定时清理服务

创建UserListCleanupService继承IHostedService:

public class UserListCleanupService : IHostedService, IDisposable
{
    private readonly IHubContext<MessagesHub> _hubContext;
    private Timer _cleanupTimer;

    public UserListCleanupService(IHubContext<MessagesHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        // 每1分钟执行一次清理
        _cleanupTimer = new Timer(CleanupStaleUsers, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
        return Task.CompletedTask;
    }

    private async void CleanupStaleUsers(object state)
    {
        var hub = (HubContext<MessagesHub>)_hubContext;
        // 获取所有活跃连接ID(根据SignalR版本调整实现)
        var activeConnectionIds = await hub.Clients.All.SendAsync<IEnumerable<string>>("GetActiveConnections");
        
        // 筛选并移除无效用户
        var staleUsers = MessagesHub._messagesHubUsers
            .Where(u => !activeConnectionIds.Contains(u.ConnectionId))
            .ToList();

        if (staleUsers.Any())
        {
            MessagesHub._messagesHubUsers.RemoveAll(u => staleUsers.Contains(u));
            await _hubContext.Clients.All.SendAsync("UsersListChanged");
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _cleanupTimer?.Change(Timeout.Infinite, 0);
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _cleanupTimer?.Dispose();
    }
}

注册服务(Program.cs)

builder.Services.AddHostedService<UserListCleanupService>();

注意:获取活跃连接ID的方式需根据SignalR版本调整,若版本不支持直接获取,可在Hub的OnConnectedAsync中记录活跃连接,OnDisconnectedAsync中移除。

组合方案推荐

优先使用方案1+方案2的组合:主动通知处理正常浏览器关闭场景,心跳超时处理网络中断、客户端崩溃等异常情况;方案3作为兜底,覆盖极端场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 11:30:20