Глава 22 / 51
Том 3 · Разработка протоколов

WebSocket для продвинутых: широковещание, лимиты и медленные клиенты

Эхо лишь доказывает проходимость трубы; вопросы реального бизнеса: как рассылать, как отсекать огромные сообщения, не утопят ли медленные клиенты всех и кто приберёт зомби-соединения.

Обзор

Позиционирование: эксплуатационные способности, отрастающие от реестра гл. 21. Четвёрка «широковещание + лимиты + противодавление + уборка простоев» — стандартное шасси чатов и push-сервисов.

22.1 Введение

У масштабных проблем WS-сервисов общий корень: состояние одного соединения влияет на все — клиент с гигантскими сообщениями ест память, клиент, не читающий данные, копит буферы отправки, зомби-соединение занимает реестр. Механизмы лимитов и уборки даёт хост (ws_message_limit, обработка простоев); стратегия широковещания и гигиена реестра — ответственность приложения — глава ровно про это.

22.2 Паттерны широковещания

Рассылка обходом реестра (поверх инфраструктуры гл. 21)
static void BroadcastText(xstrview tMsg)
{
    xmapiter it;
    xrtMutexLock(&G_WsLock);
    /* обходим реестр: каждому соединению — своя копия */
    for ( it = First(G_WsConns); !AtEnd(G_WsConns, it); Next(G_WsConns, &it) ) {
        xwsstream* pWs = KeyOf(it);
        (void)xrtWsStreamText(pWs, tMsg);  /* копирующий: буфер можно переиспользовать сразу */
    }
    xrtMutexUnlock(&G_WsLock);
}

Что только что произошло — внутри три инженерных решения:

  • Обход под блокировкой: чтобы открытие/закрытие соединений не меняло структуру таблицы посреди обхода (гранулярность блокировки режется под операции вашего реестра)
  • Копирующая отправка: после рассылки буфер сообщения переиспользуется немедленно — не нужно ждать асинхронных отправок всех соединений
  • Терпимость к отказам: неудача отправки одному соединению не рвёт рассылку — отказавшее соединение уходит из таблицы само через терминальный Close

Широковещание по каналам (группы чата) — это замена реестра на двухуровневую структуру канал → множество соединений; паттерн полностью изоморфен.

22.3 Лимиты и подпротоколы

МеханизмКонфигурацияПоведение
Предел сообщенийws_message_limit (Custom уровня server)накопленное превышение → close 1009 (Message Too Big); проверка переполнения закрывает обходы
Обязательный подпротоколws_protocolнесовпадение при рукопожатии → 400; законность конфигурации проверяется при старте
OOM—отказ аллокации на сервере → close 1011
Облегчение аккумулятора—аккумуляторы сообщений >256 КБ освобождаются по концу сообщения, не живут постоянно

Смысл 1009 — «клиент, прекрати слать огромные сообщения»: значение предела = разумный потолок одного сообщения вашего бизнеса (чату 64 КБ, котировкам 1 МБ) — не гадайте дефолтом.

22.4 Медленные клиенты и противодавление

  • Отправка WS-слоя — асинхронный буфер: медленно читающий клиент копит данные на отправочной стороне; линии обороны памяти — предел сообщений и ваш контроль частоты рассылки
  • Политику определяет приложение: соединения с более чем N неотправленными сообщениями деградируют (пропуск некритичных пушей) или закрываются — в реестре живёт «флаг деградации»
  • Уборка простоев: по истечении idle_timeout драйвер закрывает через межпоточно-безопасный Abort (не Close — скан простоев может случиться на любом worker, терминальный потокобезопасный путь только Abort)
  • Реестр подстраивается: удаление — в терминальном колбэке WsClose; любая причина смерти (обрыв клиента, лимит, простой, ваше решение) идёт одним путём очистки
Слоистая память: противодавление кадрового слоя — у xrt (буферы и повторные входы); слоя политики — у вас (кого деградировать, кого выгнать); временную линию обороны держит idle_timeout. Три слоя, три работы — не сваливайте всё в бизнес-код.

22.5 Контракт

  • Широковещание: обход под блокировкой, копирующая отправка, терпимость к отказам; очистка единым путём через терминал WsClose.
  • Лимиты: 1009/1011 шлёт хост; значения пределов задаются бизнесом и документируются.
  • Утилизация: закрытие простоев через Abort (межпоточно безопасно); реестр верит только терминалу Close.

Частые ошибки

Обход реестра вне блокировки
Симптом
Под конкурентностью случайные падения обхода, пропуски или дубли рассылки.
Причина
WsOpen/WsClose правят таблицу на других worker одновременно — обход обязан завершаться под блокировкой реестра.
Семантика колбэка Close вызвана не из WS-потока
Симптом
Собственный поток очистки трогает объекты соединений напрямую — падение.
Причина
Межпоточные операции с соединением обязаны идти по потокобезопасным путям вроде Abort; логика очистки живёт в колбэке WsClose, а не в самовольном ковырянии соединений.

Упражнения

БазовыйШироковещание чата

Реализуйте многолюдный чат: сообщение любого клиента уходит всем остальным (без него самого).

Приёмка: 3 конкурентных клиента видят друг друга; отключение одного не рвёт рассылку.
ПродвинутыйТри состояния лимита

Задайте ws_message_limit=1 МБ; отправьте 0,9 МБ, 1 МБ и 1,1 МБ и зафиксируйте поведение; затем ws_protocol="chat.v1" и проверка отказа рукопожатия.

Приёмка: три состояния сходятся с таблицей 22.3; клиент с чужим подпротоколом получает 400.
ВызовДеградация медленного клиента

Соберите клиента, не читающего данные; реализуйте «3 подряд неудачи рассылки → деградация на 30 секунд».

Приёмка: в деградации медленный клиент не получает некритичных пушей; возобновление чтения снимает деградацию автоматически.

Итоги

ТезисВывод
Широковещаниеобход под блокировкой + копирующая отправка + терпимость; каналы — двухуровневый реестр
Лимитыws_message_limit → 1009; OOM → 1011; значения от бизнеса
Подпротоколws_protocol обязателен, 400 при рукопожатии
Слои противодавлениякадровый — xrt / политический — приложение / временной — idle_timeout
УтилизацияAbort межпоточно безопасен; очистка единообразно в терминале WsClose
API, затронутые в главе
Что дальше по xrt