Группы, пользователи и управление подключениями
Управляйте группами, обращайтесь к отдельным пользователям или подключениям и обрабатывайте события жизненного цикла подключения и отключения.
«Группы, пользователи и управление подключениями» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- Центры и подключения SignalR
- Группы, пользователи и управление подключениями
- Строго типизированные центры
- Масштабирование с помощью Redis Backplane