0Pricing
C# Academy · 课时

组、用户与连接管理

管理组,针对特定用户或连接,并处理连接和断开连接的生命周期事件。

组、用户与连接管理 是 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. SignalR 中心与连接
  2. 组、用户与连接管理
  3. 强类型中心
  4. 使用 Redis 后端扩展
← 返回 C# Academy