0Pricing
C# Academy · Урок

Группы, пользователи и управление подключениями

Управляйте группами, обращайтесь к отдельным пользователям или подключениям и обрабатывайте события жизненного цикла подключения и отключения.

«Группы, пользователи и управление подключениями» — бесплатный урок C# Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Зачем нужны группы и пользователи

В реальных приложениях редко требуется выполнять широковещательную отправку ALL клиентам. Группы SignalR позволяют выбирать подмножества соединений (например, комнату чата), а пользователи позволяют обращаться ко всем соединениям, принадлежащим определённому аутентифицированному пользователю.

Добавление соединений в группы

Вызовите 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");

Пользовательский поставщик идентификаторов

По умолчанию SignalR использует утверждение NameIdentifier как идентификатор пользователя. Реализуйте 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 (на нескольких серверах).

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);
    }
}

Сохранение членства в группе

Членство в группе не сохраняется — при перезапуске сервера или потере состояния шиной сообщений 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 для пользовательских утверждений с идентификатором пользователя
  • Членство в группах не сохраняется — повторно присоединяйтесь к ним после восстановления соединения
  • Отслеживайте количество подключений в ConcurrentDictionary для определения присутствия

Часто задаваемые вопросы

Урок «Группы, пользователи и управление подключениями» бесплатный?

Да — полный текст урока «Группы, пользователи и управление подключениями» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «Группы, пользователи и управление подключениями»?

Управляйте группами, обращайтесь к отдельным пользователям или подключениям и обрабатывайте события жизненного цикла подключения и отключения. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Группы, пользователи и управление подключениями»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Центры и подключения SignalR
  2. Группы, пользователи и управление подключениями
  3. Строго типизированные центры
  4. Масштабирование с помощью Redis Backplane
← Назад к C# Academy