SignalR客户端异常断开时OnDisconnectedAsync未触发问题排查
我实现了一个重写OnDisconnectedAsync方法的SignalR Hub,发现客户端调用close()优雅关闭连接时,该方法能正常触发,但客户端异常断开时却毫无反应。
比如:打开客户端标签页后切换到其他标签页再关闭,不会触发断开事件;VPN断开公司网络时也一样。简言之,所有非正常断开场景下,服务器始终认为连接处于打开状态。
我已经尝试缩短KeepAliveInterval和ClientTimeoutInterval参数,哪怕等5分钟,OnDisconnectedAsync还是没触发。
我的Hub配置或代码哪里有问题?
我的配置代码
services.AddSignalR(opts => { opts.MaximumParallelInvocationsPerClient = 10; opts.KeepAliveInterval = TimeSpan.FromSeconds(5); opts.ClientTimeoutInterval = TimeSpan.FromSeconds(5); });
我的Hub代码
public override async Task OnDisconnectedAsync(Exception? exception) { if (exception != null) { Log.Error(exception.Message); } Log.Information("${Context.UserIdentifier} Disconnected"); // 其他业务逻辑 ... await base.OnDisconnectedAsync(exception); }
排查方向与解决方案
客户端主动处理断开事件
浏览器关闭标签页时会触发beforeunload事件,客户端应主动调用connection.stop(),不要完全依赖服务器超时判断。示例JS代码:window.addEventListener('beforeunload', () => { connection.stop(); });修正超时参数逻辑
ClientTimeoutInterval必须大于KeepAliveInterval(建议设为2-3倍),当前两者都设为5秒会导致超时逻辑冲突。调整配置:opts.KeepAliveInterval = TimeSpan.FromSeconds(5); opts.ClientTimeoutInterval = TimeSpan.FromSeconds(15); // 至少为心跳间隔的2倍原理:服务器每隔
KeepAliveInterval发送心跳包,若ClientTimeoutInterval内未收到客户端响应,才会判定连接断开。参数相等会导致逻辑紊乱。检查反向代理配置
若服务部署在Nginx、IIS ARR等反向代理后,需确保代理支持WebSocket长连接并设置合理超时:
以Nginx为例:proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 30s; // 需大于服务器的ClientTimeoutInterval代理层超时若短于服务器端,会直接切断连接,服务器无法感知断开事件。
验证日志输出有效性
确认Context.UserIdentifier是否有值(未认证用户可能为null),可临时修改日志排查:Log.Information("Connection {ConnectionId} Disconnected", Context.ConnectionId);同时检查日志级别配置,确保Information级别的日志能正常输出。
确认Hub生命周期配置
SignalR Hub默认生命周期为Transient(每个请求创建实例),不要自定义为单例或其他错误生命周期,否则会导致OnDisconnectedAsync无法被正确调用。
内容的提问来源于stack exchange,提问作者Mark Barton

