Основы WebSocket: двусторонняя связь в шести колбэках
Рукопожатие, маскирование, фрагментация, управляющие кадры и сжатие RFC 6455 целиком завершаются в XServer и слое xrt — ваш скрипт это шесть функций в конце конвейера.
Обзор
21.1 Введение
Список боли рукописного WebSocket: валидация Upgrade-рукопожатия (SHA-1+Base64 от Sec-WebSocket-Key), разбор заголовков кадров (бит маски, расширенные длины), пересборка фрагментов, вплетение управляющих кадров, автомат состояний close, согласование permessage-deflate. Разделение труда в XServer: хост делает рукопожатие и проверку (провал — 400), xrtWsStreamAttach — кадровый слой; колбэк получает «сообщение целиком», отправка — один вызов функции.
21.2 Рукопожатие и колбэки
- Рукопожатие маршрутизируется по vhost (Host/SNI как у HTTP); неудача маршрутизации — 404
- Host без экспортированных WsText/WsBinary → 503 (сервис есть, бизнеса нет)
- Pong отвечает автоматически на уровне xrt — колбэки WsPing/WsPong только наблюдают (статистика пульса)
- Жёсткая граница линии в рукопожатии: защита от медленных атак «заполнить ReadLimit и ждать вечно»
21.3 Семейство отправки и эхо
static void WsOpen(XS_HostInfo* pHost, xwsstream* pWs)
{
(void)xrtWsStreamText(pWs, xrtStrView("connected"));
}
static void WsText(XS_HostInfo* pHost, xwsstream* pWs, xstrview tText)
{
(void)xrtWsStreamText(pWs, tText); /* echo */
}
static void WsBinary(XS_HostInfo* pHost, xwsstream* pWs, xbytesview tData)
{
(void)xrtWsStreamBinary(pWs, tData);
}
Что только что произошло: во главе семейства отправки xrtWsStreamText / Binary (копирующая отправка); есть варианты ...Ref (заимствующая — буфер нельзя трогать до возврата) и ...Take (передача владения, ноль копий для больших сообщений) — выбирайте по происхождению сообщения.
21.4 Контекст соединения
WS-бизнесу почти всегда нужно «состояние на каждое соединение» (личность пользователя, список подписок). Рекомендуемый подход — реестр соединений (собственный map: указатель соединения → контекст), вставка в WsOpen, удаление в WsClose:
static xmutex G_WsLock;
static xmap* G_WsConns; /* xwsstream* → ConnCtx* (межколбэковое заимствование — ваша забота) */
typedef struct ConnCtx {
char sUser[64];
uint64 iLastSeen;
} ConnCtx;
static void WsOpen(XS_HostInfo* pHost, xwsstream* pWs)
{
ConnCtx* pCtx = xrtCalloc(1, sizeof(ConnCtx));
/* вставка (под блокировкой) ... */
}
static void WsClose(XS_HostInfo* pHost, xwsstream* pWs,
uint16 iCode, xstrview tReason)
{
/* удаление и освобождение (под блокировкой); WsClose гарантированно терминальный */
}
Обратите внимание: жизненным циклом соединения владеет хост, контекста — вы; паттерн реестра держит их в точной синхронизации. Эта таблица же — инфраструктура широковещания из гл. 22.
21.5 Контракт
- Колбэки: шесть исполняются на worker-потоках конкурентно; представления сообщений (tText/tData) действительны в колбэке.
- Терминальность: WsClose — терминальный колбэк каждого соединения; чистка реестра здесь надёжнее всего.
- Отправка: Text/Binary копирует, Ref заимствует, Take передаёт — вариант выбирается по владению буфером.
Частые ошибки
- Симптом
- Последующие чтения дают перепутанные данные.
- Причина
- tText/tData — заимствованные представления колбэка; перед закладкой в таблицу копируйте (реестр хранит ваш ConnCtx, не представления).
- Симптом
- Случайная порча содержимого сообщений.
- Причина
- xrtWsStreamTextRef заимствует ваш буфер — возврат функции ≠ завершение отправки (при противодавлении данные ещё в буфере). Нужно переиспользовать немедленно — берите копирующий или Take-вариант.
Упражнения
Запустив эхо, отправьте любым WS-клиентом текст / бинарные данные / большое сообщение (1 МБ).
Договоритесь, что первое сообщение — "login:имя"; реестр фиксирует личность, дальнейшее эхо с префиксом "[имя] ".
Через WsPing ведите статистику RTT; соединения без единого сообщения 60 секунд закрывайте сами.
Итоги
| Тезис | Вывод |
|---|---|
| Задача хоста | валидация рукопожатия (400/404/503) + кадровый слой Attach |
| Шесть колбэков | Open/Text/Binary/Ping/Pong/Close; Pong отвечает сам |
| Семейство отправки | Text/Binary (копия), Ref (заимствование), Take (передача) |
| Контекст | свой реестр: вставка в Open, удаление в Close (терминальная гарантия) |
- справочник xrt · websocket_stream — потоковая отправка и семантика счётчика ссылок
- справочник xrt · websocket_upgrade — детали рукопожатия на уровне библиотеки