组、用户与连接管理
管理组,针对特定用户或连接,并处理连接和断开连接的生命周期事件。
组、用户与连接管理 是 CoddyKit 上的免费 C# Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。
为什么需要群组和用户?
在实际应用中,您很少希望向 ALL 客户端广播。SignalR 的群组可以定位部分连接(例如聊天房间),而用户可以向属于某个经过身份验证的用户的所有连接发送消息。
将连接添加到群组
使用连接 ID 和群组名称调用 Groups.AddToGroupAsync()。一个连接可以同时属于多个群组。
public class ChatHub : Hub
{
public async Task JoinRoom(string roomName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
await Clients.Group(roomName)
.SendAsync("UserJoined", Context.User!.Identity!.Name);
}
public async Task LeaveRoom(string roomName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName);
await Clients.Group(roomName)
.SendAsync("UserLeft", Context.User!.Identity!.Name);
}
}向群组发送消息
使用 Clients.Group(name) 向群组中的所有连接发送消息。其他定位选项包括 GroupExcept 和 Groups(一次定位多个群组)。
public async Task SendToRoom(string roomName, string message)
{
var sender = Context.User!.Identity!.Name;
// Everyone in the room
await Clients.Group(roomName)
.SendAsync("ReceiveMessage", sender, message);
// Everyone in the room except the sender
await Clients.GroupExcept(roomName, Context.ConnectionId)
.SendAsync("ReceiveMessage", sender, message);
}基于用户的定位
当用户打开多个浏览器标签页或设备时,就会拥有多个连接。Clients.User(userId) 会同时向该用户的 ALL 个连接发送消息。
// Send to all connections of user with ID "user-123"
await Clients.User("user-123")
.SendAsync("Notification", "Your order has shipped!");
// SignalR uses the NameIdentifier claim by default.
// Context.UserIdentifier returns the current user's ID.
var myId = Context.UserIdentifier;
await Clients.User(myId!).SendAsync("Ack", "Server received your message");自定义用户 ID 提供程序
默认情况下,SignalR 使用 NameIdentifier 声明作为用户 ID。实现 IUserIdProvider,即可使用其他声明或自定义逻辑。
public class EmailUserIdProvider : IUserIdProvider
{
public string? GetUserId(HubConnectionContext connection)
{
return connection.User?.FindFirst(ClaimTypes.Email)?.Value;
}
}
// Register:
builder.Services.AddSingleton<IUserIdProvider, EmailUserIdProvider>();跟踪连接状态
SignalR 不会在断开连接后持久化连接状态。请在单服务器环境中使用线程安全的字典,或在多服务器环境中使用 Redis,自行维护用户 ID 到 connection ID 的映射。
public class ConnectionTracker
{
private readonly ConcurrentDictionary<string, HashSet<string>> _userConnections
= new();
public void AddConnection(string userId, string connectionId)
=> _userConnections.GetOrAdd(userId, _ => new()).Add(connectionId);
public void RemoveConnection(string userId, string connectionId)
{
if (_userConnections.TryGetValue(userId, out var conns))
{
conns.Remove(connectionId);
if (conns.Count == 0) _userConnections.TryRemove(userId, out _);
}
}
public IEnumerable<string> GetConnections(string userId)
=> _userConnections.TryGetValue(userId, out var c) ? c : Enumerable.Empty<string>();
}在 OnConnected/OnDisconnected 中自动管理群组
一种常见模式是:在 OnConnectedAsync 中自动将经过身份验证的用户添加到其个人群组,这样无需自定义跟踪即可通过群组名称定位该用户。
public override async Task OnConnectedAsync()
{
// Add to a personal group named by user ID
var userId = Context.UserIdentifier;
if (userId is not null)
await Groups.AddToGroupAsync(Context.ConnectionId, userId);
await base.OnConnectedAsync();
}
// Now send to a user by their personal group:
await _hub.Clients.Group(userId)
.SendAsync("Update", payload);在后台服务中结合群组发送消息
使用 IHubContext,您可以从后台服务定位群组和用户,这非常适合处理由系统事件触发的 async 通知。
public class OrderShippedHandler : INotificationHandler<OrderShipped>
{
private readonly IHubContext<OrderHub> _hub;
public OrderShippedHandler(IHubContext<OrderHub> hub) => _hub = hub;
public async Task Handle(OrderShipped notification, CancellationToken ct)
{
// Push to the customer's personal group
await _hub.Clients.Group(notification.CustomerId.ToString())
.SendAsync("OrderShipped",
new { notification.OrderId, notification.TrackingNumber },
ct);
}
}Group 成员关系持久化
组成员关系不会持久化——如果服务器重启,或 Redis 回程总线丢失状态,组就会变为空。请使用客户端 onreconnected 事件,在重新连接时重新加入组。
// JavaScript client: re-join groups after reconnect
connection.onreconnected(async connectionId => {
console.log(`Reconnected as ${connectionId}`);
// Re-join previously joined rooms
for (const room of joinedRooms) {
await connection.invoke("JoinRoom", room);
}
});在线状态:在线/离线检测
通过在 OnConnected/OnDisconnected 中统计每个用户的连接数,并向相关组广播在线状态事件,来跟踪在线状态。
public override async Task OnConnectedAsync()
{
var userId = Context.UserIdentifier!;
var count = _tracker.AddConnection(userId, Context.ConnectionId);
if (count == 1) // first connection for this user
await Clients.Others.SendAsync("UserOnline", userId);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? ex)
{
var userId = Context.UserIdentifier!;
var count = _tracker.RemoveConnection(userId, Context.ConnectionId);
if (count == 0) // no more connections
await Clients.Others.SendAsync("UserOffline", userId);
await base.OnDisconnectedAsync(ex);
}快速检查
当一个用户打开三个浏览器标签页时,Clients.User(userId) 会执行什么操作?
回顾:组、用户与连接管理
要点:
Groups.AddToGroupAsync/RemoveFromGroupAsync:管理组成员关系Clients.Group(name):向部分连接发送消息Clients.User(userId):向某个用户的所有连接发送消息- 实现
IUserIdProvider以使用自定义用户 ID 声明 - 组不会持久化——重新连接时重新加入
- 在 ConcurrentDictionary 中跟踪连接数,以检测在线状态
常见问题解答
「组、用户与连接管理」课时是免费的吗?
是的 — 「组、用户与连接管理」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。
「组、用户与连接管理」这节课中我会学到什么?
管理组,针对特定用户或连接,并处理连接和断开连接的生命周期事件。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「组、用户与连接管理」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- SignalR 中心与连接
- 组、用户与连接管理
- 强类型中心
- 使用 Redis 后端扩展