signalr 是自动协商传输层的抽象,需通过 ihubcontext 推送消息;hub 实例不可手动创建,clients 属性仅在生命周期内有效;路由必须在 useendpoints 内注册,url 前后端严格一致;group/user 推送失败不报错,需确保名称精确匹配;negotiate 响应结构和 cors 配置是连接关键。

SignalR 不是 WebSocket 封装库,也不是让你手动选协议的工具——它是自动协商传输层的抽象,你不需要、也不应该去“实现 WebSocket 推送”,而是用 IHubContext<t></t> 做服务端主动推送。
Hub 类不能 new,Clients.All.SendAsync 不能在控制器里直接调
常见错误是写 var hub = new ChatHub(),然后调 hub.Clients.All.SendAsync(...),这会立刻抛出 InvalidOperationException: Hub has not been initialized。因为 Clients 属性由 SignalR 运行时注入,仅在 Hub 实例生命周期内(即客户端方法执行中)有效。
- Hub 实例按连接粒度创建,每次调用都是新对象,不能缓存、不能静态持有
- 控制器、后台服务、定时任务中发消息,必须通过构造函数注入
IHubContext<chathub></chathub> - 注册方式:在
Program.cs中调用builder.Services.AddSignalR(),再注入IHubContext<chathub></chathub> - 别用 Singleton 生命周期注册
IHubContext<t></t>——它内部有连接池和缓存,多线程下可能错乱
MapHub 路由位置错,negotiate 请求直接 404
浏览器控制台看到 Failed to start the connection: Error: Failed to complete negotiation with the server,90% 是路由没挂对位置,而不是 Hub 代码有问题。
- 错误写法:
app.MapHub<chathub>("/chathub"); app.UseEndpoints(...);</chathub>→MapHub在UseEndpoints外,negotiate 请求根本进不到 SignalR 管道 - 正确写法:必须在
UseRouting()之后、UseEndpoints()内部:app.UseEndpoints(endpoints => { endpoints.MapHub<chathub>("/chathub"); });</chathub> - 前端连接 URL 必须与
MapHub参数严格一致:new HubConnectionBuilder().withUrl("/chathub"),不能多斜杠、不能少斜杠、不能加前缀如/api/chathub - Linux 部署时路径区分大小写,
/ChatHub和/chathub是两个不同端点
Clients.Group / Clients.User 推送静默失败,连日志都没有
SignalR 对 Group 名和 User ID 完全不校验,拼错、空格、大小写不一致,消息就直接丢弃,不会报错也不会警告。
-
Clients.Group("admin")中的"admin"是纯字符串,不是从数据库查出来的名称,也不是角色名,就是你传进去的那个字面量 - 加群逻辑别硬编码:
await Groups.AddToGroupAsync(Context.ConnectionId, "admin")→ 改成从 token 或 query string 提取:Context.GetHttpContext().Request.Query["group"] - User ID 应该来自认证上下文:
Context.UserIdentifier或Context.User?.Identity?.Name,不要用Context.ConnectionId当用户标识 - 断连后 SignalR 不自动移出 Group,必须在
OnDisconnectedAsync里手动调Groups.RemoveFromGroupAsync
客户端连接不上?先盯住 negotiate 响应体
连接失败的第一排查点不是 Hub 代码,而是 negotiate 请求返回的内容是否合法。
- 如果用了 JWT 认证,negotiate 响应 JSON 必须含
accessToken字段,否则 JS 客户端初始化失败 - CORS 配置必须允许凭证(
AllowCredentials()),且不能只写*:要明确指定前端域名,如https://myapp.com - IIS 或 Azure App Service 需手动启用 WebSocket 支持,否则降级为长轮询,延迟飙升且 negotiate 可能超时
- 前端
connection.invoke("SendMessage", ...)的方法名,必须和 Hub 里public async Task SendMessage(...)的名字完全一致(大小写、拼写、无重载混淆)
最易被忽略的是 Group 清理和 negotiate 响应结构——前者导致消息越积越多发不出,后者让整个连接卡在握手阶段,但错误日志里几乎不体现。











