ASP.NET Core 5.0 SignalR重连消息未送达,同组用户重连有可用示例吗?
ASP.NET Core 5.0 SignalR 重连保留通信组实现方案
SignalR 每次客户端建立连接都会生成唯一的 ConnectionId,默认断开后连接销毁,重连生成的新 ConnectionId 不会自动加入旧连接所属的组,这就是重连后消息收不到的核心原因。实现重连保留组的逻辑,核心是将用户身份标识和所属组的关联关系持久化存储,不受单次连接生命周期影响。
1. 自定义跨连接用户标识逻辑
首先要给每个客户端分配唯一的、跨连接不变的用户标识,比如用用户ID、设备ID这类业务标识,不要依赖默认的ConnectionId。可以实现IUserIdProvider替换默认的用户标识获取逻辑:
public class CustomUserIdProvider : IUserIdProvider { public string GetUserId(HubConnectionContext connection) { // 从请求头、Query参数或者Claims中取跨连接不变的用户标识 // 示例:从Query参数取clientId,客户端连接时带上固定的clientId return connection.GetHttpContext()?.Request.Query["clientId"].ToString(); } }
在Startup.cs的ConfigureServices方法中注册替换:
services.AddSingleton<IUserIdProvider, CustomUserIdProvider>(); services.AddSignalR();
2. 持久化用户-组映射关系
这里用线程安全的内存存储做示例,生产环境可以用Redis、数据库替换,适配单实例/多实例部署场景:
public static class GroupMappingStore { // 键:用户标识,值:该用户所属的组列表 private static readonly ConcurrentDictionary<string, List<string>> _userGroups = new(); public static void AddUserToGroup(string userId, string groupName) { _userGroups.AddOrUpdate(userId, _ => new List<string> { groupName }, (_, groups) => { if (!groups.Contains(groupName)) groups.Add(groupName); return groups; }); } public static List<string> GetUserGroups(string userId) { return _userGroups.TryGetValue(userId, out var groups) ? groups : new List<string>(); } public static void RemoveUserFromGroup(string userId, string groupName) { if (_userGroups.TryGetValue(userId, out var groups)) { groups.Remove(groupName); if (groups.Count == 0) _userGroups.TryRemove(userId, out _); } } }
3. 重写Hub的连接生命周期方法
在客户端连接成功时,自动将新连接加入该用户所有所属的组:
public class ChatHub : Hub { public override async Task OnConnectedAsync() { var userId = Context.UserIdentifier; if (!string.IsNullOrEmpty(userId)) { var groups = GroupMappingStore.GetUserGroups(userId); foreach (var group in groups) { await Groups.AddToGroupAsync(Context.ConnectionId, group); } } await base.OnConnectedAsync(); } // 封装加组方法,加组同时更新存储的映射关系 public async Task AddToGroup(string groupName) { var userId = Context.UserIdentifier; if (string.IsNullOrEmpty(userId)) return; GroupMappingStore.AddUserToGroup(userId, groupName); await Groups.AddToGroupAsync(Context.ConnectionId, groupName); } // 封装退组方法 public async Task RemoveFromGroup(string groupName) { var userId = Context.UserIdentifier; if (string.IsNullOrEmpty(userId)) return; GroupMappingStore.RemoveUserFromGroup(userId, groupName); await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); } }
4. 客户端重连配置
客户端开启自动重连,连接时带上固定的clientId参数,JS客户端示例:
const clientId = '当前用户的唯一固定标识,比如登录后的用户ID'; const connection = new signalR.HubConnectionBuilder() .withUrl(`/chathub?clientId=${clientId}`) .withAutomaticReconnect() // 开启自动重连 .build();
配置完成后,客户端断开重连时会用同一个clientId发起连接,服务端拿到用户标识后会自动把新的ConnectionId加入该用户之前加入过的所有组,组消息就可以正常投递到重连后的客户端。
注意事项
- 生产环境不要用内存存储映射关系,多实例部署时用分布式缓存比如Redis同步数据,避免实例间映射不一致
- 如果用户同一账号多端登录,可以把映射关系调整为用户标识下对应多个连接,根据业务需求调整加组逻辑
- 可以定期清理长时间不活跃的用户映射数据,避免存储占用过高
内容的提问来源于stack exchange,提问作者rahul tiwari
相关产品推荐
相关产品推荐

