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
相关产品推荐
相关产品推荐

