.Net 6 SignalR 实际业务开发中遇到的问题及解决办法
一、SiganlR 使用的协议类型
- 1.websocket即时通讯协议
- 2.Server-Sent Events(SSE)服务器事件
- 3.longpolling 长轮询。
如果客户端开启协商,会按顺序选择可兼容的协议。(默认开启协议协商)
如果客户端禁用协商,只能websocket协议。
二、连接过程
- 客户端未禁用协商
- 1.客户端向服务器发http请求,询问服务器支持什么协议。
服务端返回支持的协议类型,并分配ConnectionId。(http 协商请求) - 2.客户端确认协议后发送 websocket 请求,建立连接。(客户端会按顺序选择可兼容的协议)
- 1.客户端向服务器发http请求,询问服务器支持什么协议。
- 客户端禁用协商
- 客户端直接发送 websocket 请求,建立连接。
//客户端建立signalr连接时,指定配置参数,禁用协商。 const options = { skipNegotiation: true,//是否跳过协商过程 transport: signalR.HttpTransportType.WebSockets,//直接指定使用websocket协议 }; connection = new signalR.HubConnectionBuilder() .withUrl("https://localhost:7002/Hubs/ChatRoomHub",options) .withAutomaticReconnect() //自动重连 .build();
三、身份认证
- websocket协议 无请求报文头headers,token只能放 QueryString 中,一般参数名定为 access_token。
- 在 Hub中引入身份认证服务,Hub OnConnectedAsync()中,即建立连接时,通过身份认证服务解析token是否合法并拿到用户信息。
四、分布式部署问题
问题1. 多节点环境,客户端协商过程,可能请求到不同服务器。
如果有AB两个服务器负载,在协商过程时
客户端第一次http请求A,A服务器记录了客户端连接信息上下文。
客户端第二次ws连接发送到B服务器,而B服务器没有客户端连接信息上下文,就出错了。
方式1. 粘性会话
网关服务器 或 负载均衡服务器 进行配置,把来自同一个客户端的请求都转发给同一台服务器。
- 优点:兼容性最好,客户端按兼容性依次选择协议
- 缺点:公网ip没办法平均分配,很难控制。
方式2. 禁用协商
客户端连接时设置禁用协商,连接时仅一次ws请求。
- 缺点:只能适用于兼容websocket的浏览器 只能使用websocket协议,无法使用 sse 和 longpolling
问题2. 多节点环境,消息不能跨服务器连接发送。
A服务器向全部连接发消息,只能发送到连接A服务器的客户端,不能发送给连接到B服务器的客户端。
- 解决办法:引入 Microsoft.AspNetCore.SignalR.StackExchangeRedis 包,注入分布式SignalR服务到DI中。
作用:在多个服务器实例之间共享消息和连接状态。确保跨服务器实例的连接同步和消息传递。services.AddSignalR(o => { o.EnableDetailedErrors = true;//详细异常信息 o.HandshakeTimeout = TimeSpan.FromSeconds(30);//握手超时时间 }).AddStackExchangeRedis("{redis连接字符串}", opt => { opt.Configuration.ChannelPrefix = "{RedisKey前缀}"; });//分布式 SignalR 消息
问题3. 在后台任务中使用Hub推送消息。
- 1.建立连接,从token中拿到用户信息。(分布式环境,多节点负载时,更新缓存需要加锁)
- 缓存: 用户id - [连接id集合]
- 缓存: 连接id - 用户id
- 2.断开连接,从缓存中移除 当前连接id。(分布式环境,多节点负载时,更新缓存需要加锁)
- 3.后台任务
- 注入分布式SignalR服务
- 注入Hub服务
- 业务中拿到要接收消息的 用户id集合
- 从缓存中找到对应的 全部连接id
- 调Hub向连接id推送SignalR消息