.NET Core 3.1 SignalR Hub运行20分钟后连接中断重连无数据
问题场景
环境为.NET Core 3.1,基于Kestrel将SignalR Hub作为Windows服务运行,客户端为v5.0.9版本的JavaScript SignalR客户端。可稳定复现运行20分钟后连接无理由中断,2秒后自动重连,但重连后的新连接无法发送新数据,未修改任何默认超时配置。
相关代码
服务端Startup.cs配置
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddCors(); services.AddSignalR(hubOptions => { hubOptions.EnableDetailedErrors = true; hubOptions.MaximumReceiveMessageSize = 1048576; //1MB }).AddJsonProtocol(options => { options.PayloadSerializerOptions = ServiceTools.GetSerializerOptions(); options.PayloadSerializerOptions.PropertyNameCaseInsensitive = true; }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseCors(builder => builder .AllowAnyHeader() .AllowAnyMethod() .SetIsOriginAllowed(_ => true) .AllowCredentials() ); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapHub<VisuNotificationHub>("/signalr"); }); } }
客户端JS连接配置
pfw2.connection = new signalR.HubConnectionBuilder() .withUrl(signalrUrl, ['webSockets', 'serverSentEvents', 'longPolling']) .configureLogging(signalR.LogLevel.Debug) .withAutomaticReconnect({ nextRetryDelayInMilliseconds: retryContext => { if (retryContext.elapsedMilliseconds < 10000) { //<10秒 return 1000; //每秒重试 } else if (retryContext.elapsedMilliseconds < 60000) { //<60秒/1分钟 return 10000; //每10秒重试 } else if (retryContext.elapsedMilliseconds < 1800000) { //<30分钟 return 300000; //每5分钟重试 } else { //>= 30分钟 return 900000; //每15分钟重试 } } }) .build(); pfw2.connection.serverTimeoutInMilliseconds = 60000; //重连中回调 pfw2.connection.onreconnecting(error => { var reconnectErrorData = [{ Message: `<p style="background:red;color:white;font-size:24px">@ResOperatorDialog.ErrorPleaseRefresh</p>` }]; console.assert(connection.state === signalR.HubConnectionState.Reconnecting); console.log("Hub发生错误!", error); UpdateTicker(reconnectErrorData); }); //重连成功回调 pfw2.connection.onreconnected((connectionId) => { onHubConnectionDone(); console.log(connectionId); }); //连接关闭回调 pfw2.connection.onclose((e) => { var connectioNClosedErrorData = [{Message: `<p style="background:red;color:white">连接已关闭,请刷新页面重试。</p>`}]; UpdateTicker(connectioNClosedErrorData); console.log(e); }); //启动连接 pfw2.connection.start() .catch((err) => { return console.error(err.toString()); }) .then(() => { console.log("连接成功,连接ID=",pfw2.connection.connectionId); onHubConnectionDone(); });
解决方案
按优先级依次排查修改:
1. 对齐超时配置,避免无意义断连
20分钟断连大概率是Kestrel默认的空闲连接超时、SignalR默认的客户端超时与客户端配置不匹配导致,修改两处配置:
- 服务端SignalR配置补充超时、心跳设置,和客户端60秒超时匹配:
services.AddSignalR(hubOptions => { hubOptions.EnableDetailedErrors = true; hubOptions.MaximumReceiveMessageSize = 1048576; //1MB // 新增配置 hubOptions.ClientTimeoutInterval = TimeSpan.FromSeconds(120); // 设置为心跳间隔的2倍即可 hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(30); // 服务端每30秒发送心跳包 });
- 服务端Kestrel配置延长空闲连接超时,避免操作系统/Kestrel主动切断长期空闲的连接,在Program.cs的WebHost配置中添加:
webBuilder.ConfigureKestrel(options => { options.Limits.IdleTimeout = TimeSpan.FromHours(2); // 可按需调整时长 });
2. 修复重连后无法发数据的问题
- 检查
onHubConnectionDone方法逻辑:确认所有客户端监听服务端事件的注册代码、身份验证逻辑都在该方法内实现,不要只写在首次start()的成功回调里,重连后需要重新完成所有注册、鉴权操作才能正常收发数据。 - 检查自定义连接映射/分组逻辑:如果你的服务端自己维护了ConnectionId和用户/业务的映射关系,重连后生成的新ConnectionId需要重新加入映射表、重新加入对应的分组,否则服务端推送消息还是会发送到已失效的旧连接,看起来就是新连接无法收发数据。
- 强制指定WebSocket传输:修改客户端
withUrl参数,暂时关闭长轮询、ServerSentEvents,排除传输协议切换导致的异常:
.withUrl(signalrUrl, signalR.HttpTransportType.WebSockets)
3. 排查版本兼容问题
服务端为.NET Core 3.1,客户端为SignalR 5.0版本,跨大版本可能存在序列化兼容问题,确认ServiceTools.GetSerializerOptions()返回的序列化配置和客户端默认序列化规则完全对齐,比如字段名大小写、空值处理、日期格式等。
内容的提问来源于stack exchange,提问作者DeckardCain2020
相关产品推荐
相关产品推荐

