Введение: зачем понадобился новый протокол

Если вы уже знакомы с HTTP, вы привыкли к определённой логике работы веба. Браузер обращается к серверу, сервер отвечает, соединение закрывается. Так устроена загрузка страниц, отправка форм, получение JSON из REST API. Модель проста, предсказуема и хорошо масштабируется — именно поэтому она стала основой интернета.

Однако не все задачи укладываются в схему «спросил — получил — забыл». Представьте видеозвонок: было бы странно каждые две секунды заново набирать номер, произносить «алло, вы меня слышите?» и ждать ответа, прежде чем сказать следующую фразу. Разговор требует постоянного канала, по которому обе стороны могут говорить в любой момент. Именно такую задачу решает WebSocket — протокол постоянного двустороннего обмена данными между клиентом и сервером.

WebSocket не заменяет HTTP. Скорее, он дополняет его там, где классического запроса-ответа недостаточно. Понимание этой границы — ключ к правильному выбору технологии в проекте.

Аналогия: почтовые отправления и телефонный разговор

Чтобы почувствовать разницу, полезно провести простую аналогию. HTTP похож на обмен письмами. Вы отправляете конверт с вопросом, ждёте ответного письма, затем, если нужно продолжить диалог, отправляете новое письмо. Каждое письмо — самостоятельное действие с собственным «конвертом»: заголовками, адресом, телом сообщения. Сервер не может сам прислать вам письмо, пока вы не инициируете новый обмен.

WebSocket ближе к телефонному звонку. Сначала вы «дозваниваетесь» — это этап установления соединения. Когда линия открыта, обе стороны могут говорить когда угодно, не повторяя процедуру набора номера. Сервер может передать информацию клиенту в тот же момент, когда она появилась, без ожидания очередного запроса.

Сравнение HTTP и WebSocket: обмен письмами против постоянного телефонного разговора
HTTP работает как обмен письмами — каждый запрос отдельный. WebSocket — как телефонный разговор: одна линия, обе стороны говорят в любой момент.

Эта аналогия не идеальна технически, но помогает запомнить главное: WebSocket — это не «ещё один HTTP-запрос», а другой режим работы поверх того же сетевого соединения.

Определение и место WebSocket в сетевой модели

WebSocket — сетевой протокол прикладного уровня, стандартизованный в документе RFC 6455. Он работает поверх TCP и обеспечивает полнодупlexную связь: данные могут идти одновременно в обе стороны по одному открытому каналу.

Слово «полный дуплекс» означает, что клиент и сервер равноправны как отправители. В HTTP инициатором почти всегда выступает клиент. В WebSocket сервер получает право первым отправить данные — например, уведомление о новом сообщении в чате или обновление котировки на биржевом терминале.

Для веб-разработчика WebSocket — это одновременно протокол на уровне сети и программный интерфейс в браузере. В JavaScript доступен встроенный объект WebSocket. На сервере используются библиотеки и фреймворки: websockets или встроенная поддержка в FastAPI для Python, пакет ws для Node.js, Spring WebSocket для Java. Адрес WebSocket-сервера записывают со схемой ws:// для незашифрованного соединения или wss:// для соединения через TLS — по аналогии с различием между http:// и https://.

Проблема опроса и её «костыли»

Прежде чем WebSocket стал массовым, разработчики решали задачи реального времени поверх обычного HTTP. Самый прямолинейный подход — polling, периодический опрос. Клиент раз в несколько секунд спрашивает сервер: «Появилось что-нибудь новое?» Большую часть времени ответ отрицательный, но запрос всё равно уходит. При тысячах пользователей сервер обрабатывает тысячи почти пустых обращений в минуту.

Long polling улучшает ситуацию. Клиент отправляет запрос и не закрывает его сразу — сервер держит соединение открытым, пока не появятся данные или не истечёт таймаут. Когда ответ наконец приходит, клиент почти сразу отправляет следующий «висящий» запрос. Задержка уменьшается, но архитектурно это по-прежнему серия HTTP-запросов, а не единый канал.

Polling и long polling против постоянного WebSocket-соединения
При polling клиент постоянно спрашивает сервер. WebSocket устраняет лишние запросы: сервер сам отправляет данные, как только они появляются.

WebSocket устраняет саму причину постоянных переподключений. После одного рукопожатия соединение остаётся активным, и обмен идёт лёгкими фреймами без повторения HTTP-заголовков на каждое сообщение.

Рукопожатие: как HTTP превращается в WebSocket

WebSocket не открывается на отдельном порту в обход веб-инфраструктуры. Установление соединения начинается как обычный HTTP-запрос. Клиент отправляет GET с особыми заголовками: Upgrade: websocket, Connection: Upgrade и ключ Sec-WebSocket-Key — случайная строка, подтверждающая, что клиент действительно хочет перейти на WebSocket.

Сервер, поддерживающий WebSocket, отвечает статусом 101 Switching Protocols. В ответе он возвращает заголовок Sec-WebSocket-Accept, значение которого вычисляется из ключа клиента по фиксированному алгоритму, описанному в стандарте. Если вычисление совпало, обе стороны считают, что «переключение протокола» произошло успешно.

WebSocket handshake: HTTP-запрос, ответ 101 Switching Protocols и обмен фреймами
Рукопожатие WebSocket: HTTP-запрос с Upgrade, ответ 101, затем обмен фреймами по тому же TCP-соединению.

После этого TCP-соединение физически остаётся тем же, но прикладной протокол меняется. Дальнейший обмен идёт не в формате HTTP-сообщений, а в виде WebSocket-фреймов. Именно поэтому WebSocket хорошо проходит через прокси и балансировщики на стандартных портах 80 и 443 — для сетевой инфраструктуры начало выглядит как обычный веб-трафик.

Фреймы и сообщения: что происходит «под капотом»

После успешного рукопожатия данные передаются фреймами — небольшими блоками с коротким служебным заголовком. Фрейм может содержать текстовые данные, бинарные данные или служебную информацию. Текстовые фреймы удобны для JSON-сообщений: чаты, уведомления, игровые команды в текстовом виде. Бинарные фреймы используют, когда важна компактность или скорость — передача координат в игре, аудиофрагментов, сериализованных структур в protobuf.

Протокол предусматривает служебные кадры ping и pong для проверки, что соединение ещё живо. Кадр close сигнализирует о корректном завершении. С точки зрения прикладного кода вы обычно работаете с целыми сообщениями: в JavaScript метод send() принимает строку или бинарные типы, а обработчик onmessage получает уже собранные данные в event.data. WebSocket намеренно не навязывает формат содержимого — поверх него можно построить собственный протокол с полями type и payload.

WebSocket в браузере: жизненный цикл соединения

Создание соединения в клиентском JavaScript начинается с вызова конструктора: const socket = new WebSocket('wss://example.com/ws');. С этого момента объект проходит через несколько состояний, которые отражает свойство readyState. Сначала соединение находится в состоянии CONNECTING — идёт рукопожатие. После успешного ответа 101 состояние становится OPEN, и канал готов к обмену. При закрытии объект проходит через CLOSING и завершается в CLOSED.

Обработчик onopen вызывается, когда соединение установлено. Здесь логично отправить первое служебное сообщение — например, запрос на присоединение к комнате чата или передачу токена аутентификации. Обработчик onmessage срабатывает при каждом входящем сообщении. Обработчик onerror сообщает об ошибке на уровне соединения. Обработчик onclose вызывается при закрытии канала и предоставляет код и текстовую причину. Отправка данных выполняется методом send(). Важно помнить: вызов send() до перехода в состояние OPEN приведёт к ошибке.

const socket = new WebSocket('wss://api.example.com/ws');

socket.onopen = () => {
  socket.send(JSON.stringify({ action: 'join', room: 'general' }));
};

socket.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Получено:', data);
};

socket.onclose = (event) => {
  console.log('Соединение закрыто', event.code, event.reason);
};

Серверная сторона: состояние и рассылка

На сервере WebSocket принципиально отличается от обработки обычных HTTP-маршрутов. REST-обработчик получает запрос, формирует ответ и забывает о клиенте. WebSocket-обработчик принимает соединение и держит его открытым — сервер должен помнить, какие клиенты сейчас подключены. Типичная архитектура чата хранит множество активных сокетов. Когда один пользователь отправляет сообщение, сервер сразу рассылает данные всем подключённым клиентам нужной «комнаты».

Масштабирование WebSocket сложнее, чем масштабирование stateless REST. Если приложение работает на нескольких серверах, клиент, подключённый к серверу A, не получит сообщение, которое пришло на сервер B, если между инстансами нет общего слоя обмена. На практике используют Redis pub/sub, RabbitMQ, Kafka или аналогичные брокеры: сервер, получив сообщение, публикует его в канал, а все инстансы подписаны и пересылают его своим локальным клиентам.

WebSocket и REST: разделение ответственности

REST over HTTP остаётся лучшим выбором для операций, которые имеют чёткое начало и конец: создать заказ, получить профиль пользователя, загрузить историю сообщений при входе в чат. Каждый такой запрос самодостаточен, сервер не обязан хранить контекст клиента между обращениями.

WebSocket берёт на себя всё, что требует непрерывного потока и инициативы со стороны сервера. Новое сообщение в чате, изменение цены акции, ход соперника в шахматах — эти события должны доходить до клиента без его явного запроса. В зрелых приложениях оба подхода сосуществуют. Пользователь авторизуется через REST, получает JWT, загружает историю переписки через GET-запрос, а затем открывает WebSocket-соединение на wss://... и передаёт токен — в query-параметре или в первом сообщении после connect.

REST и WebSocket в одном приложении: авторизация через HTTP, live-сообщения через wss
REST отвечает за разовые операции — авторизацию, историю, создание ресурсов. WebSocket — за живой поток событий.

Server-Sent Events: когда достаточно одного направления

Не каждая задача требует полного дуплекса. Если клиент только слушает поток с сервера — лента новостей, прогресс длительной операции, мониторинг метрик — часто хватает Server-Sent Events. SSE работает поверх обычного HTTP: сервер держит ответ открытым и периодически отправляет строки формата data: .... Браузер поддерживает SSE через объект EventSource и умеет автоматически переподключаться. WebSocket выбирают, когда клиент тоже должен часто и быстро отправлять данные: чат, совместное редактирование, онлайн-игра.

Socket.IO и чистый WebSocket

Начинающие разработчики нередко отождествляют Socket.IO и WebSocket. Это разные вещи. WebSocket — стандарт, встроенный в браузеры. Socket.IO — библиотека более высокого уровня для real-time приложений. Она может использовать WebSocket как транспорт, но при необходимости откатывается на long polling. Socket.IO добавляет именованные события, комнаты, подтверждения доставки, автоматическое переподключение. Для понимания фундаментальных принципов полезно работать с чистым WebSocket: вы увидите handshake в DevTools, поймёте состояния соединения и научитесь самостоятельно проектировать формат сообщений.

Где WebSocket применяется на практике

Мессенджеры — наиболее наглядный пример. Сообщение, отправленное одним участником, должно мгновенно появиться у остальных. Онлайн-игры требуют синхронизации состояния с минимальной задержкой. Торговые терминалы передают котировки в реальном времени. Совместное редактирование документов строится на постоянном канале, по которому передаются операции изменения текста. Системы уведомлений сообщают пользователю о событиях без перезагрузки страницы. Админ-панели мониторинга получают поток логов и метрик в live-режиме.

WebSocket не подходит для загрузки больших файлов — для этого лучше HTTP с поддержкой range-запросов. Он не заменяет публичное CRUD API. Если соединение нужно на несколько секунд раз в час, накладные расходы на handshake и удержание сокета не оправданы.

Безопасность

В production используют только wss://. Шифрование TLS защищает данные от перехвата так же, как HTTPS защищает обычный веб-трафик. Протокол WebSocket не содержит встроенной аутентификации — передачу JWT или идентификатора сессии нужно спроектировать явно. На сервере при handshake проверяют заголовок Origin, чтобы сторонний сайт не мог инициировать WebSocket-соединение к вашему API от имени пользователя. Атака Cross-Site WebSocket Hijacking по механизму напоминает CSRF. Защита — проверка Origin, использование токенов вместо одних только cookies, настройка SameSite для cookies. Каждое входящее сообщение нужно валидировать так же тщательно, как тело HTTP-запроса.

Надёжность: обрывы и переподключение

TCP-соединения рвутся по множеству причин: пользователь переключился с Wi-Fi на мобильную сеть, NAT-роутер закрыл неактивный канал, сервер перезапустился. Браузерный WebSocket не переподключается автоматически бесконечно — это задача прикладного кода. Типичная стратегия переподключения использует exponential backoff: первая попытка через секунду, следующая через две, затем через четыре, с верхним пределом и ограничением числа попыток. Ping/pong на уровне протокола или прикладные heartbeat-сообщения помогают обнаружить «мёртвое» соединение, когда промежуточный прокси закрыл канал, не уведомив клиента явно.

Учебный пример: простой эхо-чат

Клиент подключается к wss://localhost:8000/ws. Сервер после handshake добавляет сокет в общее множество подключений. Когда приходит текстовое сообщение, сервер парсит JSON и рассылает его всем остальным участникам. Историю сообщений при входе разумно загрузить один раз через REST — GET /api/messages?room=general. Всё новое после подключения идёт только через WebSocket. На клиенте обработчик onmessage добавляет полученный текст в DOM. Вторая вкладка браузера, подключённая к тому же серверу, получит broadcast без каких-либо дополнительных запросов.

Как начать практиковаться

Поднимите минимальный сервер с одной WebSocket-точкой — в FastAPI это несколько строк, в Node.js с пакетом ws — аналогично. Откройте простую HTML-страницу с new WebSocket(...), отправьте строку и убедитесь, что она возвращается или появляется во второй вкладке. Откройте вкладку Network → WS в инструментах разработчика браузера — вы увидите handshake со статусом 101, затем обмен фреймами. Добавьте переподключение при обрыве. Затем введите простую схему сообщений — каждый JSON содержит поле type и поле payload. Так строят прикладные протоколы поверх WebSocket в production-системах.

Запомните главное

WebSocket — протокол постоянного двустороннего канала между клиентом и сервером. Он начинается как HTTP-запрос с заголовком Upgrade и после ответа 101 Switching Protocols переходит в режим обмена лёгкими фреймами. Сервер может отправлять данные клиенту без предварительного запроса — это ключевое отличие от классической модели HTTP.

WebSocket не заменяет REST. Авторизация, загрузка ресурсов и разовые операции остаются на HTTP. WebSocket берёт на себя живой поток событий: чаты, уведомления, игры, мониторинг. Умение читать оба формата и понимать, когда какой применять, — необходимый навык разработчика, работающего с современными веб-приложениями.