WebSocket для продвинутых: широковещание, лимиты и медленные клиенты
Эхо лишь доказывает проходимость трубы; вопросы реального бизнеса: как рассылать, как отсекать огромные сообщения, не утопят ли медленные клиенты всех и кто приберёт зомби-соединения.
Обзор
22.1 Введение
У масштабных проблем WS-сервисов общий корень: состояние одного соединения влияет на все — клиент с гигантскими сообщениями ест память, клиент, не читающий данные, копит буферы отправки, зомби-соединение занимает реестр. Механизмы лимитов и уборки даёт хост (ws_message_limit, обработка простоев); стратегия широковещания и гигиена реестра — ответственность приложения — глава ровно про это.
22.2 Паттерны широковещания
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; любая причина смерти (обрыв клиента, лимит, простой, ваше решение) идёт одним путём очистки
22.5 Контракт
- Широковещание: обход под блокировкой, копирующая отправка, терпимость к отказам; очистка единым путём через терминал WsClose.
- Лимиты: 1009/1011 шлёт хост; значения пределов задаются бизнесом и документируются.
- Утилизация: закрытие простоев через Abort (межпоточно безопасно); реестр верит только терминалу Close.
Частые ошибки
- Симптом
- Под конкурентностью случайные падения обхода, пропуски или дубли рассылки.
- Причина
- WsOpen/WsClose правят таблицу на других worker одновременно — обход обязан завершаться под блокировкой реестра.
- Симптом
- Собственный поток очистки трогает объекты соединений напрямую — падение.
- Причина
- Межпоточные операции с соединением обязаны идти по потокобезопасным путям вроде Abort; логика очистки живёт в колбэке WsClose, а не в самовольном ковырянии соединений.
Упражнения
Реализуйте многолюдный чат: сообщение любого клиента уходит всем остальным (без него самого).
Задайте ws_message_limit=1 МБ; отправьте 0,9 МБ, 1 МБ и 1,1 МБ и зафиксируйте поведение; затем ws_protocol="chat.v1" и проверка отказа рукопожатия.
Соберите клиента, не читающего данные; реализуйте «3 подряд неудачи рассылки → деградация на 30 секунд».
Итоги
| Тезис | Вывод |
|---|---|
| Широковещание | обход под блокировкой + копирующая отправка + терпимость; каналы — двухуровневый реестр |
| Лимиты | ws_message_limit → 1009; OOM → 1011; значения от бизнеса |
| Подпротокол | ws_protocol обязателен, 400 при рукопожатии |
| Слои противодавления | кадровый — xrt / политический — приложение / временной — idle_timeout |
| Утилизация | Abort межпоточно безопасен; очистка единообразно в терминале WsClose |
- справочник xrt · websocket — коды close и семантика буферов кадрового слоя
- справочник xrt · sync — выбор блокировок для реестра