Зачем нужен канал, который остаётся открытым
Классический HTTP устроен так, что инициатива всегда у клиента: сервер отвечает, но сам первым не пишет. Для чата или биржевого табло это неудобно: страница вынуждена каждую секунду спрашивать, нет ли новостей. Такая схема называется опросом, и большая часть запросов в ней возвращает пустой ответ, зря нагружая и сеть, и сервер.
WebSocket решает задачу иначе. Один раз соединение устанавливается, а потом остаётся открытым, и обе стороны могут отправлять сообщения в любой момент, не задавая формальных вопросов. Стандарт описан в 2011 году, а в браузерах доступен через встроенный объект с тем же названием.
Рукопожатие: как HTTP превращается в другой протокол
Соединение начинается с обычного HTTP-запроса, в котором клиент присылает заголовки Upgrade: websocket и Connection: Upgrade, а также случайный ключ. Сервер, согласный на переход, отвечает кодом 101 Switching Protocols и возвращает производное от ключа значение, подтверждая, что понимает протокол. После этого TCP-соединение продолжает жить уже не как HTTP, а как поток сообщений WebSocket.
Такой старт выбран сознательно: запрос проходит через те же порты, 80 и 443, что и обычный веб-трафик, поэтому подключение чаще всего беспрепятственно проходит через файрволы и промежуточные узлы. Адреса в этом протоколе начинаются со схемы ws или wss, где вторая буква s указывает на защиту TLS, как и в HTTPS.
Если перед приложением стоит шлюз или балансировщик, ему нужно явно разрешить переход протокола: пересылать заголовки Upgrade и Connection и увеличить тайм-ауты чтения. Типичная ошибка при развёртывании — приложение локально работает, а за таким шлюзом соединение сразу закрывается или падает в ошибку рукопожатия, потому что эти заголовки были потеряны по дороге.
Кадры, ping и закрытие
Данные передаются кадрами. У кадра есть тип: текстовый в кодировке UTF-8, двоичный, а также служебные, ping, pong и close. Ping и pong используются, чтобы убедиться в живости соединения и не дать промежуточным устройствам закрыть неактивный канал. Когда одна из сторон хочет завершить работу, она отправляет close с кодом причины, и вторая сторона подтверждает закрытие.
Сообщения от браузера к серверу маскируются случайным ключом. Это не шифрование, а защита от специфических атак на кеширующие промежуточные узлы, и скрытием содержимого маска не является. Шифрует только TLS, потому для реальных данных следует использовать именно wss.
Где встречается на практике и чем не является
Типичные применения: веб-чаты и мессенджеры, совместное редактирование документов, онлайн-игры в браузере, котировки и уведомления в реальном времени, панели мониторинга. Многие клиентские библиотеки оборачивают WebSocket и при недоступности переходят на резервные способы вроде долгого опроса.
Не стоит путать его с WebRTC. WebSocket всегда идёт между клиентом и сервером по TCP, а WebRTC соединяет браузеры напрямую и рассчитан на медиа, о чём написано на отдельной странице. Ещё одно частое допущение — будто протокол «быстрее» HTTP. Он экономит на повторных рукопожатиях и заголовках, но скорость канала не увеличивает: выигрыш именно в отсутствии лишних запросов.
Заблуждения и как посмотреть самому
Соединение не живёт вечно. Балансировщики, шлюзы и мобильные сети закрывают простаивающие каналы, поэтому приложение должно уметь переподключаться и периодически посылать ping. Также нужно самостоятельно проверять заголовок Origin на сервере: протокол не подчиняется политике одного источника так, как обычные запросы, и без проверки страница постороннего сайта может подключиться от имени вашего браузера.
Посмотреть работу просто. В инструментах разработчика откройте вкладку «Сеть», отфильтруйте по типу WS, выберите соединение и перейдите на вкладку «Сообщения»: там видны кадры, которыми обмениваются страница и сервер, вместе с направлением и размером.
На стороне сервера у долгоживущих соединений есть ещё одно следствие: каждое из них занимает память и дескриптор, поэтому при десятках тысяч одновременных подключений важна конфигурация балансировщика и лимитов операционной системы. Для небольших сайтов это редко становится проблемой, а вот для крупных чатов заметно.