signalr 是自动协商协议的抽象层,hub 由框架托管生命周期,服务端推送必须通过 ihubcontext,路由需在 useendpoints 内注册,认证需返回 accesstoken,group/user 标识需准确,连接失败多因 negotiate 环节问题。

SignalR 不是“选 WebSocket 还是 SSE”的手动配置项,而是自动协商传输协议的通信抽象层;你不需要、也不应该手动创建 WebSocket 实例来对接 SignalR。
Hub 类不能被 new 出来,必须由框架托管生命周期
常见错误是写 var hub = new ChatHub() 然后试图调用 Clients.All.SendAsync —— 这会直接抛出 InvalidOperationException: Hub has not been initialized。Hub 实例由 SignalR 运行时按连接粒度创建和销毁,仅在 OnConnectedAsync、OnDisconnectedAsync 或客户端方法执行期间有效。
- 服务端主动推送消息,唯一合法入口是
IHubContext<chathub></chathub> - 必须在
Program.cs中注册:builder.Services.AddSignalR() - 注入时只能用 Scoped 或 Transient 生命周期;Singleton 会导致线程安全问题(例如共享
Clients缓存) - 控制器或后台服务中推送,必须通过构造函数注入
IHubContext<chathub></chathub>,别存字段、别静态缓存
MapHub 路由必须放在 UseRouting() 之后、UseEndpoints() 之内
顺序错会导致 404,且 negotiate 请求根本收不到响应。典型错误写法:
app.UseEndpoints(endpoints => { ... });
app.MapHub<chathub>("/chathub"); // ❌ 错位:在 UseEndpoints 之外</chathub>
正确写法(.NET 6+):
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<chathub>("/chathub"); // ✅ 必须在 UseEndpoints 内部
});</chathub>
- 如果用了 JWT 认证,
negotiate响应体里必须含accessToken字段,否则 JS 客户端连接失败 - 客户端 URL 必须与
MapHub路径严格一致,比如/chathub就不能写成/hub/chathub - 部署到 IIS 时,需确认 Windows Server 已启用 WebSocket 协议支持(否则降级为长轮询,延迟飙升)
Clients.Group 和 Clients.User 的字符串值不是随意拼的
Group 名和 User ID 都是纯字符串标识,SignalR 不做校验,拼错就静默丢消息——连日志都不会报。
- 加群组别写死字符串:
await Groups.AddToGroupAsync(Context.ConnectionId, "admin"),推荐从 token 或 query string 提取,比如Context.GetHttpContext().Request.Query["group"] - 用户标识建议用
ClaimsPrincipal.Identity.Name或Context.UserIdentifier,避免自己解析 JWT -
Clients.Client(connectionId)发送前务必检查 connectionId 是否仍在线——断连后 ID 失效,但调用不报错,消息直接消失 - Group 成员清理必须手动:在
OnDisconnectedAsync里调Groups.RemoveFromGroupAsync,否则离线用户持续收消息
客户端连接失败,90% 是 negotiate 环节卡住
浏览器控制台看到 Failed to start the connection: Error: Failed to complete negotiation with the server,基本不是 Hub 代码问题,而是握手链路不通。
- 先 curl 测试 negotiate 接口:
curl -v https://yourdomain.com/chathub/negotiate,看是否返回 200 + JSON - 确认 CORS 配置允许带凭证(
credentials: true):不能只写AllowAnyOrigin(),得明确指定域名 - 如果用了反向代理(Nginx / Cloudflare),确保转发了
Upgrade和Connection头,否则 WebSocket 握手失败 - JS 初始化必须
await connection.start(),漏掉 await 会导致后续invoke或on绑定无效
最易被忽略的一点:SignalR 的“实时”是基于连接状态的,不是基于时间戳或轮询。一旦连接中断,所有未确认的消息都会丢失——它不保证投递,也不重放历史。需要可靠消息语义时,得自己在业务层加持久化和 ACK 机制。











