WebSocket

Предпосылки: HTTP (запрос-ответ, заголовки, stateless), TCP (соединение, full-duplex).

Эволюция HTTP | Эталонные модели

Чат-приложение: Alice отправляет сообщение Bob. HTTP — это запрос-ответ: клиент спрашивает, сервер отвечает. Как сервер уведомит Bob о новом сообщении, если Bob ничего не запрашивал?

Polling и его пределы

Первый подход — polling: клиент Bob каждые 2 секунды отправляет запрос “есть новые сообщения?” Если сообщений нет (а нет их 95% времени) — это впустую потраченные ресурсы: HTTP-запрос, TCP-сегмент, заголовки, парсинг, обработка.

Long polling — улучшение: клиент отправляет запрос, и сервер не отвечает, пока не появится новое сообщение (или не истечёт таймаут). Это снижает количество пустых запросов, но каждое сообщение всё равно требует нового HTTP-запроса, а сервер держит открытые соединения для ожидающих клиентов.

Для чата с сотнями сообщений в минуту, биржевых котировок, real-time дашбордов, онлайн-игр нужен принципиально другой подход: постоянный двусторонний канал, по которому обе стороны могут отправлять данные в любой момент.

WebSocket: full-duplex канал после HTTP Upgrade

WebSocket начинается с HTTP-рукопожатия, потому что так проще пройти через существующую веб-инфраструктуру: порты 80/443, прокси, балансировщики, аутентификацию. Но после успешного Upgrade это уже не модель “запрос-ответ”, а постоянный двусторонний канал. И клиент, и сервер могут отправлять данные в любой момент.

Upgrade: переключение протокола

WebSocket-соединение начинается как обычный HTTP-запрос с заголовком Upgrade:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

Сервер, поддерживающий WebSocket, отвечает 101 Switching Protocols:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

С этого момента TCP-соединение больше не несёт HTTP-запросы и ответы — по нему идят WebSocket-фреймы. Sec-WebSocket-Key и Sec-WebSocket-Accept — механизм проверки, что обе стороны действительно договорились именно о WebSocket, а не просто увидели случайный заголовок Upgrade.

Фреймы

Данные в WebSocket передаются фреймами. Каждый фрейм имеет заголовок (2–14 байт) с типом данных (opcode), длиной и маской. Маска — XOR-ключ, которым клиент обязан маскировать данные при отправке серверу; это защита от атак на промежуточные прокси. Типы фреймов: text (UTF-8 текст), binary (произвольные байты), ping/pong (проверка связи), close (завершение соединения).

Фреймы минимальны по накладным расходам — 2 байта заголовка для коротких сообщений против десятков/сотен байт HTTP-заголовков при polling. Для чат-сообщения в 50 байт разница между WebSocket-фреймом (52 байта) и HTTP-запросом (~500 байт с заголовками) — порядок величины.

Heartbeat: обнаружение разрывов

TCP-соединение может незаметно разорваться — клиент потерял Wi-Fi, промежуточный прокси закрыл idle-соединение, NAT-таблица на роутере протухла. Без активного обмена данными ни одна сторона не узнает об обрыве.

WebSocket решает это через ping/pong: одна сторона (обычно сервер) периодически отправляет ping-фрейм, другая обязана ответить pong. Если pong не приходит — соединение считается разорванным.

Типичный интервал: 30–60 секунд. Слишком частый heartbeat создаёт лишний трафик; слишком редкий — долгое обнаружение разрыва.

Закрытие соединения

Корректное закрытие WebSocket — двусторонний процесс: одна сторона отправляет close-фрейм с кодом причины, другая отвечает своим close-фреймом, после чего TCP-соединение закрывается. Типичные коды: 1000 — нормальное закрытие, 1001 — сторона “уходит” (например, сервер выключается или пользователь закрывает вкладку), 1011 — сервер не смог корректно обработать запрос.

Код 1006 часто встречается в логах и API, но это важная тонкость: его нельзя отправлять в close-фрейме. Это локальная пометка “соединение оборвалось без нормального close handshake”.

Reconnect: жизнь с нестабильной сетью

WebSocket-соединение — stateful. При разрыве всё состояние теряется. Клиент должен переподключиться и, возможно, запросить пропущенные сообщения. Стандартный подход: reconnect с экспоненциальным backoff (1s, 2s, 4s, 8s…) и jitter (случайное смещение, чтобы все клиенты не переподключались одновременно после сбоя сервера).

Для надёжной доставки сообщений поверх WebSocket нужна прикладная логика: sequence numbers, подтверждения, хранение непрочитанных сообщений на сервере. WebSocket — транспорт, а не система доставки.

Когда использовать WebSocket

WebSocket оправдан, когда сервер должен инициировать отправку данных клиенту: чат, уведомления, real-time дашборды, совместное редактирование, биржевые котировки, онлайн-игры.

Для однократных запросов, CRUD API, загрузки файлов HTTP остаётся правильным выбором. WebSocket добавляет сложность: управление соединениями, reconnect, масштабирование (sticky sessions — привязка клиента к конкретному серверу — или синхронизация через общий брокер сообщений).

Альтернатива для случаев, когда нужен только server-to-client поток — Server-Sent Events (SSE): однонаправленный канал от сервера к клиенту поверх обычного HTTP. Проще WebSocket, но не поддерживает двустороннюю связь.

Sources


Эволюция HTTP | Эталонные модели