Один канал, full-duplex и фреймы
После рукопожатия обе стороны могут говорить одновременно по одному каналу — разбираемся, как.
Full-duplex — режим связи, при котором обе стороны передают данные одновременно и независимо, как при телефонном разговоре, а не по очереди.
Один канал на всё
В polling и long-polling под каждое сообщение открывался новый HTTP-запрос с кучей заголовков. WebSocket держит одно TCP-соединение всё время жизни. Открыли в начале — и гоняем по нему данные туда-сюда без повторных рукопожатий и без заголовков на каждое сообщение. Это резко снижает накладные расходы и задержку.
Half-duplex против full-duplex
| Режим | Аналогия | Кто где |
| Half-duplex | рация: «приём» | HTTP — клиент спросил, ждёт ответ |
| Full-duplex | телефон | WebSocket — оба говорят разом |
В WebSocket сервер может прислать уведомление в ту же секунду, когда браузер отправляет своё сообщение — они не мешают друг другу.
Фреймы вместо запросов
Данные передаются не запросами, а маленькими пакетами — фреймами. У каждого фрейма есть крошечный заголовок (несколько байт): тип (текст, бинарные данные, ping, pong, close), флаг «это последний фрагмент?» и длина. Никаких HTTP-заголовков. Поэтому одно короткое сообщение «привет» весит почти столько же, сколько сам текст — против сотен байт заголовков в HTTP.
Структура фрейма (упрощённо): +------+--------+------------------+ | тип | длина | полезные данные | | 1 б | 1-9 б | ...текст/байты | +------+--------+------------------+ Типы: text, binary, ping, pong, close
Как работает под капотом
Большое сообщение может разбиваться на несколько фреймов с флагом «продолжение». Фреймы от браузера к серверу дополнительно маскируются (XOR со случайным ключом) — это защита от атак на кеширующие прокси, которые могли бы принять данные за HTTP-запрос. От сервера к браузеру маскировки нет. Всё это скрыто внутри протокола: разработчик просто вызывает send() и получает onmessage, а сборкой и маскировкой занимается реализация WebSocket.
Частые ошибки
- Слать тысячи крошечных фреймов в секунду. Накладные расходы малы, но не нулевые; объединяйте мелкие события (батчинг), если их слишком много.
- Считать, что одно
send()= один сетевой пакет. Соответствие фрейма и TCP-пакета не гарантировано; на это нельзя опираться. - Слать гигантское сообщение целиком. Лучше дробить большие данные самостоятельно и показывать прогресс.
Итоги
- WebSocket держит один TCP-канал на всё время жизни соединения.
- Связь full-duplex: обе стороны передают данные одновременно.
- Данные идут лёгкими фреймами с крошечным заголовком — без HTTP-заголовков на каждое сообщение.
- Маскировка и фрагментация скрыты внутри протокола.