Рассылки
Рассылка (broadcast) — это сообщение, инициированное сервером, с именем действия и необязательными данными.
Клиенты получают его в виде { "id": "@", "action": "…", "data": … }.
Публикуйте рассылку из обработчика через методы Broadcast*Async класса HandlerBase
или из любого сервиса, внедрив IBroadcaster:
public sealed class OrderNotifier(IBroadcaster broadcaster) {
public Task OrderShippedAsync(Order order, CancellationToken ct) =>
broadcaster.BroadcastToGroupAsync(
$"account:{order.AccountId}",
"order:shipped",
new { order.Id, order.TrackingNumber },
ct);
}
Перегрузки без данных не отправляют поле data. Перегрузки с данными тоже опускают
data, если значение равно null.
Получатели
Получателей определяет вызываемый метод:
| Метод | Получатели |
|---|---|
BroadcastAsync(action, …) | Все подключения |
BroadcastToConnectionAsync(connectionId, action, …) (только IBroadcaster) | Одно подключение |
BroadcastToSessionAsync(sessionId, action, …) | Все подключения одной сессии, например все вкладки пользователя |
BroadcastToGroupAsync(group, action, …) | Подключения, в сессии которых указана эта группа |
BroadcastToSelfAsync(action, …) (только в обработчиках) | Вызывающее подключение |
У каждого метода есть перегрузка без данных и обобщённая перегрузка с данными, и каждый
принимает необязательный CancellationToken. У каждой рассылки ровно одна цель; чтобы
охватить несколько групп, отправьте рассылку в каждую группу.
Семантика доставки
- Выбор получателей использует один снимок индексов групп и сессий.
- Сериализация выполняется один раз на рассылку; все получатели используют одни и те же байты.
- Локальная доставка пишет получателям параллельно, ограниченное число за раз.
Сокет, который не принимает запись в течение
BroadcastSendTimeout(10 секунд), обрывается. - Порядок рассылок, опубликованных одновременно, не гарантируется. Если порядок важен, добавляйте в данные версию или порядковый номер.
- Отмена: с in-memory backplane токен публикующей стороны передаётся и в локальную доставку. Его отмена во время доставки рассылки пропускает оставшихся получателей и отменяет выполняющиеся записи, что обрывает эти сокеты. Redis backplane проверяет токен только перед публикацией.
- Завершение: с in-memory backplane метод рассылки завершается после локальной доставки; с Redis — когда Redis принимает сообщение.
- Без повторной доставки: отключённый клиент пропускает рассылку. Считайте рассылки уведомлениями об изменениях и заставляйте клиентов перезагружать состояние после переподключения.
Подключения и группы
Внедрите singleton ConnectionStorage, чтобы просматривать локальные подключения:
public sealed class PresenceService(ConnectionStorage connections) {
public int OnlineInAccount(Guid accountId) =>
connections.GetByGroup($"account:{accountId}").Count;
}
| Член | Назначение |
|---|---|
GetAll() | Все подключения на этом экземпляре |
GetByConnection(id) | Подключение с этим идентификатором или пустая коллекция |
GetBySession(id) | Подключения одной сессии |
GetByGroup(name) | Участники одной группы |
Add(connection) | Добавляет или заменяет подключение по идентификатору и заново считывает группы его сессии в индексы |
Remove(connection) | Удаляет именно это подключение; возвращает false, если его нет или оно было заменено |
DarkWS сам добавляет и удаляет подключения. Запросы возвращают снимки.
Снимок групп делается при добавлении подключения и после каждого auth: или logout.
Если ваша сессия предоставляет группы, которые меняются независимо, снова вызовите
Add(connection), чтобы обновить индексы. Add не проверяет, открыто ли ещё
подключение: повторное добавление уже удалённого подключения регистрирует его снова.
Эти запросы видят только текущий экземпляр. С Redis рассылки всё равно доходят до подключений на всех экземплярах.
Backplane
Broadcaster передаёт каждое сообщение в IDarkWsBackplane, который доставляет его
на каждый экземпляр сервера. Стандартный in-memory backplane обслуживает один экземпляр.
Для нескольких экземпляров используйте Redis backplane. Код обработчиков не
меняется при смене backplane.
Собственный backplane реализует PublishAsync(DarkWsBroadcast), SubscribeAsync(listener)
и UnsubscribeAsync(). DarkWS подписывается при запуске хоста и отписывается при его
остановке. Зарегистрируйте backplane как singleton IDarkWsBackplane через
services.Replace(...) или через AddSingleton до вызова AddDarkWs().