WebSocket
Протокол WebSocket («веб-сокет»), описанный в спецификации RFC 6455, обеспечивает возможность обмена данными между браузером и сервером через постоянное соединение. Данные передаются по нему в обоих направлениях в виде «пакетов», без разрыва соединения и дополнительных HTTP-запросов.
WebSocket особенно хорош для сервисов, которые нуждаются в постоянном обмене данными, например онлайн игры, торговые площадки, работающие в реальном времени, и т.д.
Простой пример
Чтобы открыть веб-сокет-соединение, нам нужно создать объект new WebSocket , указав в url-адресе специальный протокол ws :
let socket = new WebSocket("ws://javascript.info");
Также существует протокол wss:// , использующий шифрование. Это как HTTPS для веб-сокетов.
Всегда предпочитайте wss://
Протокол wss:// не только использует шифрование, но и обладает повышенной надёжностью.
Это потому, что данные ws:// не зашифрованы, видны для любого посредника. Старые прокси-серверы не знают о WebSocket, они могут увидеть «странные» заголовки и закрыть соединение.
С другой стороны, wss:// – это WebSocket поверх TLS (так же, как HTTPS – это HTTP поверх TLS), безопасный транспортный уровень шифрует данные от отправителя и расшифровывает на стороне получателя. Пакеты данных передаются в зашифрованном виде через прокси, которые не могут видеть, что внутри, и всегда пропускают их.
Как только объект WebSocket создан, мы должны слушать его события. Их всего 4:
…А если мы хотим отправить что-нибудь, то вызов socket.send(data) сделает это.
let socket = new WebSocket("wss://javascript.info/article/websocket/demo/hello"); socket.onopen = function(e) < alert("[open] Соединение установлено"); alert("Отправляем данные на сервер"); socket.send("Меня зовут Джон"); >; socket.onmessage = function(event) < alert(`[message] Данные получены с сервера: $`); >; socket.onclose = function(event) < if (event.wasClean) < alert(`[close] Соединение закрыто чисто, код=$причина=$`); > else < // например, сервер убил процесс или сеть недоступна // обычно в этом случае event.code 1006 alert('[close] Соединение прервано'); >>; socket.onerror = function(error) < alert(`[error]`); >;
Для демонстрации есть небольшой пример сервера server.js, написанного на Node.js, для запуска примера выше. Он отвечает «Привет с сервера, Джон», после ожидает 5 секунд и закрывает соединение.
Так вы увидите события open → message → close .
В общем-то, всё, мы уже можем общаться по протоколу WebSocket. Просто, не так ли?
Теперь давайте поговорим более подробно.
Открытие веб-сокета
Когда new WebSocket(url) создан, он тут же сам начинает устанавливать соединение.
Браузер, при помощи специальных заголовков, спрашивает сервер: «Ты поддерживаешь Websocket?» и если сервер отвечает «да», они начинают работать по протоколу WebSocket, который уже не является HTTP.
Вот пример заголовков для запроса, который делает new WebSocket(«wss://javascript.info/chat») .
GET /chat Host: javascript.info Origin: https://javascript.info Connection: Upgrade Upgrade: websocket Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q== Sec-WebSocket-Version: 13
- Origin – источник текущей страницы (например https://javascript.info ). Объект WebSocket по своей природе не завязан на текущий источник. Нет никаких специальных заголовков или других ограничений. Старые сервера всё равно не могут работать с WebSocket, поэтому проблем с совместимостью нет. Но заголовок Origin важен, так как он позволяет серверу решать, использовать ли WebSocket с этим сайтом.
- Connection: Upgrade – сигнализирует, что клиент хотел бы изменить протокол.
- Upgrade: websocket – запрошен протокол «websocket».
- Sec-WebSocket-Key – случайный ключ, созданный браузером для обеспечения безопасности.
- Sec-WebSocket-Version – версия протокола WebSocket, текущая версия 13.
Запрос WebSocket нельзя эмулировать
Мы не можем использовать XMLHttpRequest или fetch для создания такого HTTP-запроса, потому что JavaScript не позволяет устанавливать такие заголовки.
Если сервер согласен переключиться на WebSocket, то он должен отправить в ответ код 101:
101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=
Здесь Sec-WebSocket-Accept – это Sec-WebSocket-Key , перекодированный с помощью специального алгоритма. Браузер использует его, чтобы убедиться, что ответ соответствует запросу.
После этого данные передаются по протоколу WebSocket, и вскоре мы увидим его структуру («фреймы»). И это вовсе не HTTP.
Расширения и подпротоколы
Могут быть дополнительные заголовки Sec-WebSocket-Extensions и Sec-WebSocket-Protocol , описывающие расширения и подпротоколы.
- Sec-WebSocket-Extensions: deflate-frame означает, что браузер поддерживает сжатие данных. Расширение – это что-то, связанное с передачей данных, расширяющее сам протокол WebSocket. Заголовок Sec-WebSocket-Extensions отправляется браузером автоматически со списком всевозможных расширений, которые он поддерживает.
- Sec-WebSocket-Protocol: soap, wamp означает, что мы будем передавать не только произвольные данные, но и данные в протоколах SOAP или WAMP (The WebSocket Application Messaging Protocol» – «протокол обмена сообщениями WebSocket приложений»). То есть этот заголовок описывает не передачу, а формат данных, который мы собираемся использовать. Официальные подпротоколы WebSocket регистрируются в каталоге IANA. Этот необязательный заголовок ставим мы сами, передавая массив подпротоколов вторым параметром new WebSocket , вот так:
let socket = new WebSocket("wss://javascript.info/chat", ["soap", "wamp"]);
Сервер должен ответить перечнем протоколов и расширений, которые он может использовать.
GET /chat Host: javascript.info Upgrade: websocket Connection: Upgrade Origin: https://javascript.info Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q== Sec-WebSocket-Version: 13 Sec-WebSocket-Extensions: deflate-frame Sec-WebSocket-Protocol: soap, wamp
101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g= Sec-WebSocket-Extensions: deflate-frame Sec-WebSocket-Protocol: soap
Здесь сервер отвечает, что поддерживает расширение – deflate-frame и может использовать только протокол SOAP из всего списка запрошенных подпротоколов.
Передача данных
Поток данных в WebSocket состоит из «фреймов», фрагментов данных, которые могут быть отправлены любой стороной, и которые могут быть следующих видов:
- «текстовые фреймы» – содержат текстовые данные, которые стороны отправляют друг другу.
- «бинарные фреймы» – содержат бинарные данные, которые стороны отправляют друг другу.
- «пинг-понг фреймы» используется для проверки соединения; отправляется с сервера, браузер реагирует на них автоматически.
- также есть «фрейм закрытия соединения» и некоторые другие служебные фреймы.
В браузере мы напрямую работаем только с текстовыми и бинарными фреймами.
Метод WebSocket .send() может отправлять и текстовые, и бинарные данные.
Вызов socket.send(body) принимает body в виде строки или любом бинарном формате включая Blob , ArrayBuffer и другие. Дополнительных настроек не требуется, просто отправляем в любом формате.
При получении данных, текст всегда поступает в виде строки. А для бинарных данных мы можем выбрать один из двух форматов: Blob или ArrayBuffer .
Это задаётся свойством socket.binaryType , по умолчанию оно равно «blob» , так что бинарные данные поступают в виде Blob -объектов.
socket.binaryType = "arraybuffer"; socket.onmessage = (event) => < // event.data является строкой (если текст) или arraybuffer (если двоичные данные) >;
Ограничение скорости
Представим, что наше приложение генерирует много данных для отправки. Но у пользователя медленное соединение, возможно, он в интернете с мобильного телефона и не из города.
Мы можем вызывать socket.send(data) снова и снова. Но данные будут буферизованы (сохранены) в памяти и отправлены лишь с той скоростью, которую позволяет сеть.
Свойство socket.bufferedAmount хранит количество байт буферизованных данных на текущий момент, ожидающих отправки по сети.
Мы можем изучить его, чтобы увидеть, действительно ли сокет доступен для передачи.
// каждые 100мс проверить сокет и отправить больше данных, // только если все текущие отосланы setInterval(() => < if (socket.bufferedAmount == 0) < socket.send(moreData()); >>, 100);
Закрытие подключения
Обычно, когда сторона хочет закрыть соединение (браузер и сервер имеют равные права), они отправляют «фрейм закрытия соединения» с кодом закрытия и указывают причину в виде текста.
Метод для этого:
socket.close([code], [reason]);
- code – специальный WebSocket-код закрытия (не обязателен).
- reason – строка с описанием причины закрытия (не обязательна).
Затем противоположная сторона в обработчике события close получит и код code и причину reason , например:
// закрывающая сторона: socket.close(1000, "работа закончена"); // другая сторона: socket.onclose = event => < // event.code === 1000 // event.reason === "работа закончена" // event.wasClean === true (закрыто чисто) >;
code – это не любое число, а специальный код закрытия WebSocket.
Наиболее распространённые значения:
- 1000 – по умолчанию, нормальное закрытие,
- 1006 – невозможно установить такой код вручную, указывает, что соединение было потеряно (нет фрейма закрытия).
Есть и другие коды:
- 1001 – сторона отключилась, например сервер выключен или пользователь покинул страницу,
- 1009 – сообщение слишком большое для обработки,
- 1011 – непредвиденная ошибка на сервере,
- …и так далее.
Полный список находится в RFC6455, §7.4.1.
Коды WebSocket чем-то похожи на коды HTTP, но они разные. В частности, любые коды меньше 1000 зарезервированы. Если мы попытаемся установить такой код, то получим ошибку.
// в случае, если соединение сброшено socket.onclose = event => < // event.code === 1006 // event.reason === "" // event.wasClean === false (нет закрывающего кадра) >;
Состояние соединения
Чтобы получить состояние соединения, существует дополнительное свойство socket.readyState со значениями:
- 0 – «CONNECTING»: соединение ещё не установлено,
- 1 – «OPEN»: обмен данными,
- 2 – «CLOSING»: соединение закрывается,
- 3 – «CLOSED»: соединение закрыто.
Пример чата
Давайте рассмотрим пример чата с использованием WebSocket API и модуля WebSocket сервера Node.js https://github.com/websockets/ws. Основное внимание мы, конечно, уделим клиентской части, но и серверная весьма проста.
HTML: нам нужна форма для отправки данных и для отображения сообщений:
От JavaScript мы хотим 3 вещи:
- Открыть соединение.
- При отправке формы пользователем – вызвать socket.send(message) для сообщения.
- При получении входящего сообщения – добавить его в div#messages .
let socket = new WebSocket("wss://javascript.info/article/websocket/chat/ws"); // отправка сообщения из формы document.forms.publish.onsubmit = function() < let outgoingMessage = this.message.value; socket.send(outgoingMessage); return false; >; // получение сообщения - отобразить данные в div#messages socket.onmessage = function(event)
Серверный код выходит за рамки этой главы. Здесь мы будем использовать Node.js, но вы не обязаны это делать. Другие платформы также поддерживают средства для работы с WebSocket.
Серверный алгоритм действий будет таким:
- Создать clients = new Set() – набор сокетов.
- Для каждого принятого веб-сокета – добавить его в набор clients.add(socket) и поставить ему обработчик события message для приёма сообщений.
- Когда сообщение получено: перебрать клиентов clients и отправить его всем.
- Когда подключение закрыто: clients.delete(socket) .
const ws = new require('ws'); const wss = new ws.Server(); const clients = new Set(); http.createServer((req, res) => < // в реальном проекте здесь может также быть код для обработки отличных от websoсket-запросов // здесь мы работаем с каждым запросом как с веб-сокетом wss.handleUpgrade(req, req.socket, Buffer.alloc(0), onSocketConnect); >); function onSocketConnect(ws) < clients.add(ws); ws.on('message', function(message) < message = message.slice(0, 50); // максимальный размер сообщения 50 for(let client of clients) < client.send(message); >>); ws.on('close', function() < clients.delete(ws); >); >
Вот рабочий пример:
Вы также можете скачать его (верхняя правая кнопка в ифрейме) и запустить локально. Только не забудьте установить Node.js и выполнить команду npm install ws до запуска.
Итого
WebSocket – это современный способ иметь постоянное соединение между браузером и сервером.
- Нет ограничений, связанных с кросс-доменными запросами.
- Имеют хорошую поддержку браузерами.
- Могут отправлять/получать как строки, так и бинарные данные.
- socket.send(data) ,
- socket.close([code], [reason]) .
WebSocket сам по себе не содержит такие функции, как переподключение при обрыве соединения, аутентификацию пользователей и другие механизмы высокого уровня. Для этого есть клиентские и серверные библиотеки, а также можно реализовать это вручную.
Иногда, чтобы добавить WebSocket к уже существующему проекту, WebSocket-сервер запускают параллельно с основным сервером. Они совместно используют одну базу данных. Запросы к WebSocket отправляются на wss://ws.site.com – поддомен, который ведёт к WebSocket-серверу, в то время как https://site.com ведёт на основной HTTP-сервер.
Конечно, возможны и другие пути интеграции.
Как использовать Websocket на примере простого Express API?
Websocket — это протокол связи поверх TCP-соединения, предназначенный для обмена сообщениями между браузером и веб-сервером в режиме реального времени.
Для установления соединения WebSocket клиент и сервер используют протокол, похожий на HTTP. Клиент формирует особый HTTP-запрос, на который сервер отвечает определенным образом.
Несмотря на «похожесть» новых запросов и ответов на запросы и ответы протокола HTTP, они таковыми не являются. Например, в запросе есть тело, но в заголовках поле «Content-Length» отсутствует (что нарушает соглашения HTTP). Подробнее об этом можно прочитать в Википедии.
Одним из главных преимуществ технологии — это ее простота. На клиенте и сервере есть всего 4 события для обработки:
Почему Websocket?
Кроме ws существуют еще два способа непрерывной передачи данных: Server-Sent Events (SSE) и Long Polling.
Приведем сравнения механизмов непрерывной связи сервера и клиента, а также сделаем выводы, почему стоит (или не стоит) использовать вебсокет.
| Websocket | sse | long pooling | |
|---|---|---|---|
| протокол | websocket (ws, или wss) | HTTP(S) | HTTP(S) |
| скорость | высокая | низкая | низкая |
| направленность потоков данных | двунаправленная | однонаправленная | двунаправленная |
| дополнительно | передача бинарных данных, отсутствует поддержка некоторых старых браузеров |
автоматическое переподключение при обрыве соединения |
Одним из главных преимуществ технологии ws — это скорость передачи данных. SSE и LP используют протокол HTTP(S) и работают примерно так:
- Делаем запрос на изменения;
- Если изменения на сервере появились, то сервер их отправляет;
- Когда клиент получает изменения, клиент делает новый запрос.
- Вебсокет не требует от клиента постоянно запрашивать изменения и именно поэтому он быстрее.
- Вебсокет позволяет передавать бинарные данные, что не позволяет протокол HTTP(S).
- Вебсокет не нужно использовать, если проект требует совместимость со старыми версиями браузеров. Читать о совместимости с браузерами
Пример работы простейшего api.
const http = require("http"); const express = require( "express"); const WebSocket = require( "ws"); const app = express(); const server = http.createServer(app); const webSocketServer = new WebSocket.Server(< server >); webSocketServer.on('connection', ws => < ws.on('message', m => < webSocketServer.clients.forEach(client =>client.send(m)); >); ws.on("error", e => ws.send(e)); ws.send('Hi there, I am a WebSocket server'); >); server.listen(8999, () => console.log("Server started"))
Что здесь происходит?
Чтобы создать сервер поддерживающий ws, мы создаем обычный http сервер, а потом привязываем к нему при создании websocket сервер.
Функция “on” помогает управлять событиями websocket. Самым примечательным является событие message, так что рассмотрим его подробнее.
Здесь функция получает параметр m — сообщение, то есть то, что отправил пользователь. Таким образом мы можем отправить с клиента строку и обработать ее на сервере. В данном случае сервер просто пересылает это сообщение всем, кто подключен к серверу websocket. Массив clients объекта webSocketServer содержит все подключения к серверу. Объект ws в то же время хранит данные только об одном подключении.
Не стоит использовать такой подход в реальном приложении. Если описать api таким образом, то сервер не может отличить один запрос от другого. О том, как можно построить api на основе websocket будет написано далее.
Взаимодействие с сервером на клиенте будет выглядеть так:
export const wsConnection = new WebSocket("ws://localhost:8999"); wsConnection.onopen = function() < alert("Соединение установлено."); >; wsConnection.onclose = function(event) < if (event.wasClean) < alert('Соединение закрыто чисто'); >else < alert('Обрыв соединения'); // например, "убит" процесс сервера >alert('Код: ' + event.code + ' причина: ' + event.reason); >; wsConnection.onerror = function(error) < alert("Ошибка " + error.message); >; export const wsSend = function(data) < // readyState - true, если есть подключение if(!wsConnection.readyState)< setTimeout(function ()< wsSend(data); >,100); > else < wsConnection.send(data); >>;
API на основе Websocket
В отличие от REST API, где запросы распределены по разным url, Websocket API имеет только один url. Для того, чтобы построить полноценное API на основе вебсокетов, необходимо научить систему отличать один запрос от другого. Это можно реализовать следующим образом:
1) С клиента мы будем передавать запросы в виде строки-json, которую распарсим на сервере:
const sendMessage = (message) => conn.send(JSON.stringify(< event: "chat-message", payload: < userName, message >>));
2) На сервере мы распарсим строку и выделем в ней поле event — тип запроса. Пропишем для каждого типа соответствующий ответ:
const dispatchEvent = (message, ws) => < const json = JSON.parse(message); switch (json.event) < case "chat-message": webSocketServer.clients.forEach(client =>client.send(message)); default: ws.send((new Error("Wrong query")).message); > >
Таким образом мы можем отправлять разные запросы на сервер и обрабатывать ответ в зависимости от запроса.
Заключение
Если вам была дана задача сделать API и вы узнали, что поддержка старых браузеров заказчика не интересует, то API на основе WebSocket — отличный выбор. Для вашего удобства мы подготовили код клиентской и серверной части по ссылке.
WebSocket: разбираем как работает

HTTP — Протокол для передачи гипертесктовых данных (Hyper Text Transfer Protocol), который используется повсеместно. HTTP используется в клиент-серверной архитектуре, там всю работу можно показать с помощью одной диаграммы:

Особенности HTTP
- HTTP не поддерживает соединение, после того, как отдает ответ на запрос.
- HTTP обязует клиентов заранее оговаривать действие, которое клиент хочет
сделать в заголовке (HTTP Headers) — GET, POST, PUT, DELETE - Мы отправляем заголовок что хотим сделать каждый раз, как обраемся к серверу
Существует большое количество сайтов, с помощью которых вы можете посмотреть как работает HTTP, давайте возьмем забавный сайт с REST API по мультивселенной Рик и Морти — https://rickandmortyapi.com/documentation.
Вы можете отправить запрос с помощью Postman или обычного cURL, я буду использовать второй Давайте возьмем информацию о персонаже (Рике) и посмотрим что нам пришлёт сервер, для этого используем данную ссылку: https://rickandmortyapi.com/api/character/1
# Отправляем запрос с помощью cURL и парсим пришедший ответ с помощью JQ curl https://rickandmortyapi.com/api/character/1 | jq
После данной команды мы получим следующий ответ. Как мы видим мы просто отправили запрос на получение информации с сервера (GET), сервер отдал нам информацию и после этого мы разрываем соединение. После того как мы получим ответ мы ничего не знаем о сервере

Вторая ступень: AJAX
AJAX — асинхронные запросы с помощью JavaScript (Asynchonous JavaScript and XML). AJAX преследует все те же цели, что и HTTP, только делает это уже асинхронно. Если ранее нужно было для каждого запроса прописывать свой URL и перезагружать страницу, то теперь можно просто использовать AJAX и он сам будет отправлять нужные URL серверу и получать данные.

Особенности AJAX
- Все ещё обычный запрос, который не поддерживает соединение, после того, как отдает ответ на запрос.
- Все ещё заранее оговариваем действие, которое клиент хочет сделать в заголовке (HTTP Headers) — GET, POST, PUT, DELETE
- Мы отправляем заголовок что хотим сделать каждый раз, как обраемся к серверу
- Теперь мы делаем это асинхронно благодаря JavaScript
Самым простым примером AJAX является следующая реализация:
// Реализация на NodeJS ^17.5 // Будет работать в браузерах ~если вы не на Internet Explorer 8~ /** * Метод для того чтобы показать вывод AJAX-запроса * @param url - ссылка, которую будем подтягивать * @returns */ function showAJAXResponse(url) < // Выполняем запрос на сервер fetch(url) .then(res =>res.json()) // Формируем JSON .then(data => console.log(data)); // Выводим > // Отправляем AJAX запрос showAJAXResponse('https://rickandmortyapi.com/api/character/1');
Вот что мы получим в итоге:

Мы можем выполнить множество таких запросов (серьзено, хоть 1000, если сервер позволит), как мы можем увидить мы ничего не перезагружаем (нам даже перезагружать нечего, мы делаем все на бэк-энде Node.js)
Как мы можем увидеть, мы все ещё не держим связь с сервером. Мы отправили запрос, получили ответ и все Что дальше происходит с сервером нам неизвестно.
Третья ступень: WS или WebSocket
WebSocket — протокол для общения между клиентом и сервером, предоставляющий двухсторонне общение сверх протокола TCP.
Мы подключаем WS один раз, а затем сервер может отдавать нам ответы тогда, когда посчитает нужным:

Как это работает?
Первое что мы делаем — отправляем обычный TCP-запрос на сервер, мы говорим, что хотим подключиться к серверу и ждём от него ответа. Такой процесс называется “рукопожатие” (Handshake), он используется повсеместно, например когда вы подключаетесь к роутеру ваш телефон отправляем запрос роутеру с ключами, роутер отвечает ОК и вы успешно подключаетесь.
Затем происходит обмен данными: допустим один из множества клиентов отправил HTTP-запрос серверу и нужно отдать ответ не только одному клиенту, а целой сети! Сервер в таком случае отдаст обычный ответ отправителю запроса, а всем другим пришлёт пакеты по WebSocket-соединению с полезными данными.
Разберем более подробно на примере. Вы — клиент, отправляете запрос серверу на подключение. Запрос и ответ будут выглядеть примерно так:
// Отправляем запрос серверу по ссылке example.com/connect-to-ws // Вот что примерно мы пришлём: GET /connect-to-ws HTTP/1.1 Host: example.com:8000 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 // А вот что нам на такой запрос ответит сервер при успешном рукопожатии: HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Как мы видим сервер ответил не кодом 200 (успешное завершение запроса), а 101 — переключение протоколов. Это происходит потому, что мы отправили HTTP запрос, а хотим получить не только HTTP-ответ, а ещё и другие ответы по WS, сервер как бы предупреждает клиент, что будет присылать ответы множество раз
А как сервер узнает, что мы до сих пор подключены?
Ответ на данный вопрос достаточно легкий — сервер и клиент играют в пинг-понг
Сервер периодически присылает ответ по WS с просьбой о действии — послать запрос на сервер. Если клиент отвечает до истечения тайм-аута — он подключен, если нет, то происходит разрыв соединения до следующего рукопожатия
Полезно знать, что все ответы от сервера по WS игнорируются клиентом и сервером, до того как случится рукопожатие
Почему соединение называется двухсторонним (дуплексным), а ответы мы получаем только от сервера?
На самом деле мы не только получаем ответы от сервера, а ещё и можем в двухстороннем порядке отправлять через WS запросы!
Чтобы лучше понять, давайте рассмотрим лёгенький и полностью задокументированный код на JavaScript:
// Создаем WS-объект, с помощьюю него будем рулить потоками // (отправка, принятие запросов) const myWS = new WebSocket(url, protocols); // До того как сервер и клиент совершат рукопожатие // статус у WS-клиента будет CONNECTING console.log(myWS.readyState); // CONNECTING // После того как рукопожатие (Handshake) пройдет успешно // readyState будет OPEN console.log(myWS.readyState); // OPEN
После того как мы открыли соединение по WS мы сразу же можем отправить сообщение серверу:
/* Не забываем, что мы можем отправить сообщение серверу только если соединение открыто Поместим все общение с сервером внутрь ивента onopen, именно он срабатывает, когда соединение открыто */ myWS.onopen = function (event) < // Отправляем сообщение по WS myWS.send('Привет, сервер!'); >
Сервер получит данный запрос и возможно захочет ответить, но мы не сможем прочитать ответ! Почему? Да просто потому что у нас нет слушателя на событие получения сообщения от сервера Сделаем же его:
// Вешаем слушатель на принятие сообщений myWS.onmessage = (event) => < // Делаем с данными все что захотим, но я их просто выведу console.log(event.data); >
Закрытие соединения
Для закрытия соединения мы должны отправить запрос серверу, а он по истечению таймаута тоже должен отправить ответ на подтверждение закрытия. В JavaScript это делается одним методом
// Закрываем соединение myWS.close(); // Ну и естественно слушаем событие onclose, чтобы выполнить какие-то действия myWS.onclose = (event) => < // . >;
Особенности WS
- Поддерживает двухсторонее соединение в реальном времени
- Отправляет заголовок только один раз
Дебаггинг WS
Отлаживать WS-соединение совсем несложно Рассмотрим пример отладки WS на Google Chrome, перейдем на данный сайт: https://websocketstest.com/
Откроем DevTools, выберем вкладку Networks и перейдем в таб WS:

Как мы видим ответ от сервера действительно 101 Switching Protocols, однако как нам увидеть данные, которые приходят по WS, вкладки Reponse же нет
Вкладки Response нет, зато появилась новая — Messages. Открываем её и видим там примерно следующее:

Красной стрелкой вниз показаны пакеты, которые пришли нам (пусть вас не вводит в заблуждение красная стрелка, это не упавшие, а пришедшие пакеты), отправленные пакеты в свою очередь будут показаны зелёной стрелкой, которая стремится вверх⬆
Вот и все Если вам было интересно читать статью и вы хотите больше такого контента, то можете перейти в телеграм-канал и подписаться, там много интересного материала✨ Был рад поделиться информацией, увидимся ещё не раз
- Веб-разработка
- Тестирование веб-сервисов
Что такое веб-сокеты и как они вообще работают

Коммуникация в режиме реального времени и практически мгновенная передача данных являются обязательными стандартами современного интернета. Чтобы удовлетворить эти стандарты, в 2011 году появился протокол связи WebSocket, который позволяет сайтам отправлять и получать данные без задержки. С помощью веб-сокетов можно создавать многопользовательские игры, мессенджеры, а также сервисы для совместной работы.
Что такое веб-сокеты
WebSocket — это технология, которая позволяет клиенту установить двухстороннюю ( «дуплексную» ) связь с сервером. Сразу поясним: клиент — это приложение на компьютере или смартфоне пользователя, а сервер — это удаленный компьютер, на котором хранится веб-сайт и связанные с ним данные.
Ключевое слово в этом определении — двусторонний: с помощью веб-сокетов клиент и сервер могут инициировать связь друг с другом, а также могут отправлять сообщения одновременно. Почему это так важно? Чтобы в полной мере оценить возможности WebSocket, сделаем шаг назад и рассмотрим несколько самых распространенных способов, с помощью которых компьютеры могут получать данные с сервера.
Node.js-разработчик — с нуля до трудоустройства за 10 месяцев
- Постоянная поддержка от наставника и учебного центра
- Помощь с трудоустройством
- Готовое портфолио к концу обучения
- Практика с первого урока
Вы получите именно те инструменты и навыки, которые позволят вам найти работу
Как технически устроен обмен данными в интернете
Ответ на запрос
В традиционном HTTP, который сегодня использует большинство сайтов, веб-сервер предназначен для приема запросов от клиентов, а также для ответа на них. При этом коммуникация может быть инициирована только в одном направлении: от клиента к серверу. Код сервера определяет, какой тип запросов он должен ожидать и как реагировать на каждый из них.
Этот вид коммуникации можно сравнить с работой кухни в ресторане:
- Вы (клиент) размещаете заказ (HTTP-запрос), который официант несет на кухню (сервер)
- Кухня принимает заказ и проверяет, может ли его сделать (сервер обрабатывает запрос)
- Если на кухне знают, как приготовить блюдо, то сделают заказ (сервер получает данные из базы данных)
- Если на кухне не распознают заказ или не могут его обслужить, то отправляют официанта обратно с плохими новостями (если сервер не может ответить на запрос, он отправляет обратно код ошибки — например, 404)
- Официант возвращается к клиенту в любом случае (вы получаете HTTP-ответ с соответствующим кодом, например, 200 OK или 403 Forbidden).
Здесь важно отметить, что кухня понятия не имеет, от кого исходит заказ. Технический способ сказать это так: «HTTP не имеет состояния», — он рассматривает каждый новый запрос как полностью независимый.
У нас есть способы обойти это правило — например, можно отправлять файлы cookie, которые помогают серверу идентифицировать клиента. При этом сами HTTP-сообщения все равно будут читаться и выполняться независимо друг от друга.
В этом подходе кроется достаточно серьезная проблема: кухня не может сама прислать официанта к клиенту. Она может только выдать официанту блюдо или сказать, что еды больше нет — и только в том случае, когда клиент отправил официанта на кухню. То есть кухня ничего не знает о клиенте: она получает информацию только о поступающих заказах. Говоря языком сервера, единственный способ для клиентов получать обновленную информацию с сервера — отправлять запросы.
Представьте себе чат-приложение, в котором вы разговариваете с другом. Вы отправляете сообщение на сервер в виде запроса с текстом в качестве полезной нагрузки. Сервер получает ваш запрос и сохраняет сообщение. Но у него нет возможности связаться с компьютером вашего друга. Компьютер вашего друга также должен отправить запрос на проверку наличия новых сообщений — только тогда сервер может отправить ваше сообщение другому пользователю.
В таком виде оба клиента должны постоянно проверять сервер на наличие обновлений, внося неловкие задержки между каждым сообщением.
При этом в современном обществе любой пользователь рассчитывает, что сервер мгновенно покажет собеседнику, что он получил сообщение. HTTP-запрос и ответ отлично работают, когда пользователю нужно загрузить статическую страницу. Но этого становится недостаточно, когда нужна прямая коммуникация в режиме реального времени.
Короткий опрос
Одним из самых простых решений этой проблемы является метод «Короткий опрос». Просто заставьте клиента повторно пинговать сервер, скажем, каждые 500 мс. Таким образом, пользователь получит новые данные каждые 500 мс. Однако у этого подхода есть несколько очевидных недостатков:
- Задержка данных как минимум на 500 мс
- Пользователь потребляет ресурсы сервера при большом количестве запросов
- Большинство запросов возвращаются пустыми, если данные не часто обновляются.
Долгий опрос
Еще одним обходным путем задержки получения данных является метод «Долгий опрос». При его использовании сервер получает запрос от клиента, но не отвечает на него, пока не получит новые данные из другого запроса. Долгий опрос более эффективен, чем многократная проверка связи с сервером, поскольку он избавляет от хлопот, связанных с анализом заголовков запросов, запросом новых данных и отправкой часто пустых ответов.
Однако теперь сервер должен отслеживать несколько запросов и порядок их получения. Кроме того, время ожидания запросов может истекать, поэтому пользователю необходимо периодически отправлять новые запросы.
События, отправленные сервером: (SSE)/EventSource
Другой метод отправки сообщений — Server-Sent Events API , который позволяет серверу отправлять обновления клиенту, используя интерфейс JavaScript EventSource. Этот интерфейс создает постоянное однонаправленное соединение с сервером через HTTP и использует специальный заголовок текста/потока событий. В итоге все запросы обрабатываются кодом как события JavaScript, поэтому практически нет задержки между запросом и ответом.
Это почти то, что мы ищем — теперь мы можем получать обновления с сервера. Поскольку события Server-Sent (SSE) — однонаправленные, они отлично подходят для приложений, в которых вам не нужно отправлять на сервер какие-либо данные. К таким сервисам можно приравнять ленту новостей в Twitter или информационную панель с котировками акций в режиме реального времени.
Однако SSE не поддерживается старыми браузерами, а большинство существующих браузеров ограничивают количество одновременных подключений SSE. Но и этого еще недостаточно для создания современного мессенджера. Получать обновления в реальном времени — хорошо, но мы хотели бы иметь возможность их отправлять — и тоже в режиме реального времени.
Подробнее про веб-сокеты
Итак, нам нужен способ для моментальной отправки информации на сервер и такого же быстрого получения обновлений с сервера. Это возвращает нас к двусторонней («дуплексной») связи, с которой хорошо справляются именно веб-сокеты.
Поддерживаемый почти всеми современными браузерами API WebSocket позволяет открывать именно такое двустороннее соединение с сервером. Кроме того, сервер может отслеживать каждого клиента и отправлять сообщения подмножеству клиентов. То есть с помощью веб-сокетов мы можем пригласить всех друзей в наш чат и отправлять сообщения всем или некоторым из них, а не только одному человеку, как при использовании других протоколов связи.
Веб-сокеты под капотом
Итак, как именно работает это волшебство? Не пугайтесь настройки — современные библиотеки WebSocket, такие как socket.io, настраиваются почти автоматически. Более глубоко разобраться в настройке можно здесь , а мы приведем краткую выжимку по устройству веб-сокетов.
В тексте мы уже несколько раз упоминали HTTP — протокол и набор правил взаимодействия компьютеров в сети. Он состоит из запросов и ответов, каждый из которых содержит строку запроса («GET /assets/icon.png»), заголовки и необязательное тело сообщения, используемое, например, в POST-запросах для отправки некоторых данных на сервер.
WebSocket — это еще один протокол для отправки и получения сообщений. Как и HTTP, веб-сокеты отправляют сообщения через соединение TCP (протокол управления передачей данных). Это стандарт, который обеспечивает надежную и предсказуемую доставку данных, отправляемых в пакетах.
Читайте также: DevTools: как открыть инструменты разработчика в браузере и почему они делают работу эффективнее
Итак, HTTP и WebSocket используют один и тот же механизм доставки на уровне пакетов, но протоколы структурирования сообщений у них различаются:
- Чтобы установить соединение веб-сокетов с сервером, клиент сначала отправляет HTTP-запрос «рукопожатия» с заголовком нашего обновления, указывая, что клиент хочет установить соединение WebSocket.
- Запрос отправляется на ws: или wss:: URI (аналог http или https).
- Если сервер устанавливает соединение WebSocket, и это соединение разрешено, например, если запрос исходит от клиента, прошедшего проверку подлинности или внесенного в белый список, то сервер отправляет ответ об успешном рукопожатии. На это указывает HTTP-код 101 Switching Protocols.
После обновления соединения протокол переключается с HTTP на WebSocket. И хотя все пакеты по-прежнему отправляются через TCP, связь теперь соответствует формату сообщений WebSocket. Это происходит, потому что TCP является дуплексным протоколом, где клиент и сервер могут отправлять сообщения одновременно. Все данные могут быть фрагментированы, поэтому через этот формат можно отправить даже очень большое сообщение — например, изображение. В этом случае веб-сокеты разбивают его на фреймы. Каждый фрейм содержит небольшой заголовок, который указывает длину и тип полезной нагрузки, а также информацию, является ли этот кадр последним.
Сервер может открывать соединения WebSocket с несколькими клиентами — даже несколько соединений с одним и тем же клиентом. Затем он может отправить сообщение одному, нескольким или всем этим клиентам. На практике это означает, что к нашему чату могут подключиться несколько человек, и мы можем отправлять сообщения некоторым из них одновременно.
Наконец, когда соединение можно закрыть, либо клиент, либо сервер могут отправить сообщение «закрыть».
Как все это работает
В нашем примере для внешнего интерфейса мы будем использовать JavaScript, с помощью которого установим соединение с сервером с поддержкой WebSockets. После этого интерфейс будет воспринимать сообщения как события JavaScript — так же, как он обрабатывает все генерируемые пользователем события, такие как клики и нажатия клавиш.
В ванильном JavaScript мы делаем это, создавая новый объект WebSocket и добавляя обработчик событий для действий open, message и close. Мы также используем метод send для отправки данных на сервер:
const socket = new WebSocket('ws://localhost:8080'); socket.addEventListener('open', (event) => socket.send('Hello Server!'); >); socket.addEventListener('message', (event) => console.log('Message from server ', event.data); >); socket.addEventListener('close', (event) => console.log('The connection has been closed'); >);
На сервере нам нужно аналогично обработать запросы веб-сокетов. Например, в Node.js мы можем использовать популярный пакет ws для открытия соединения и обработки сообщений:
const WebSocket = require('ws'); const ws = new WebSocket.Server( port: 8080 >); ws.on('connection', (wsConnection) => wsConnection.on('message', (message) => console.log(`server received: $message>`); >); wsConnection.send('got your message!'); >);
Хотя в этом примере мы отправляем просто строки, самым распространенным вариантом использования WebSocket является отправка строковых данных в формате JSON, либо в двоичном формате. Это позволяет структурировать сообщения в удобном для пользователя виде.
В качестве еще одного примера можно привести Socket.io, популярный интерфейсный фреймворк для создания и управления соединениями веб-сокетов. В нем есть фантастическое пошаговое руководство по созданию приложения для чата Node/JavaScript. Эта библиотека автоматически переключается между WebSocket и методом «Долгий опрос», а также упрощает рассылку сообщений группам подключенных пользователей.
Как устанавливать соединение веб-сокетов
Процесс начинается с рукопожатия WebSocket — он включает в себя использование ws или wss, о котором мы писали чуть выше. Чтобы быстрее понять их, можно считать ws или wss эквивалентными HTTP и безопасному HTTP (HTTPS), соответственно.
Ожидается, что при использовании этой схемы серверы и клиенты будут следовать стандартному протоколу подключения WebSocket. Установка соединения веб-сокетов начинается с обновления HTTP-запроса, который содержит пару заголовков, таких как Connection: Upgrade, Upgrade: WebSocket, Sec-WebSocket-Key .
Запрос
Заголовок Upgrade в коде запроса ниже означает рукопожатие WebSocket, в то время как Sec-WebSocket-Key содержит случайное значение с использованием кодировки Base64. Это значение произвольно генерируется во время каждого рукопожатия WebSocket. Кроме того, заголовок ключа также является частью этого запроса.
Перечисленные выше заголовки образуют GET-запрос:
b6gjhT32u488lpuRwKaOWs==
Ответ
При получении ответа заголовок Sec-WebSocket-Accept часть значения, представленного в заголовке запроса Sec-WebSocket-Key . Это связано со спецификацией протокола — такой подход используется для подтверждения, что сервер поддерживает веб-сокет и позволяет обойти ошибки, в случае, когда веб-сокеты не поддерживаются:
rG8wsswmHTJ85lJgAE3M5RTmcCE=
Где используют веб-сокеты
Хотя вы можете вручную написать серверный код с использованием веб-сокетов, они уже очень удобно встроены во многие популярные платформы. Помимо Socket.io, существует множество других реализаций веб-сокетов в разных языках программирования:
- ActionCable в Ruby on Rails
- Channels в Django для Python
- Gorilla на Go
- Meteor — полноценный JavaScript-фреймворк, основанный на WebSocket вместо HTTP
- Apollo — сервер GraphQL, который помогает получать данные в режиме реального времени с помощью веб-сокетов.
Итак, какие типы проектов чаще всего используют веб-сокеты?
- Мессенджеры: концептуально самая простая реализация веб-сокетов. Пользователи отправляют сообщения на сервер, который мгновенно отправляет эти сообщения получателю. Также сервер может хранить группы подключений в каналах — так можно отправлять сообщения нескольким людям одновременно в общие чаты или просматривать сообщения от нескольких людей, например, в канале Slack.
- Многопользовательские игры: общий шаблон для многопользовательских игр заключается в том, что сервер хранит игровое состояние пользователей. Игроки выполняют какие-то действия, которые сразу отправляются на сервер. Он обновляет состояние игры и быстро отправляет его обратно пользователям. С HTTP каждый игрок должен был бы регулярно запрашивать состояние игры. С WebSocket каждое действие мгновенно передается всем игрокам.
- Приложения для совместной работы: Веб-сокеты позволяют сразу нескольким пользователям работать в одном документе и мгновенно обновлять его для всех.
- Инструменты разработчика: инструменты непрерывной интеграции, такие как CircleCI, используют веб-сокеты для мгновенного уведомления о завершении сборки.
- Карты: обновляются моментально при даже небольшом изменении GPS-координат пользователя.
Итоги
Протокол WebSocket — прекрасный инструмент для создания сервисов, которые могут обновляться в реальном времени. Но, как и все инструменты, это не панацея.
Использование WebSocket может быть излишним для простых приложений. Для обычной ленты новостей, ленты метрик или любого приложения, в котором вам нужно обновить контент, но не получать информацию взамен, веб-сокеты будут просто не нужны. Особенно учитывая, что настраивать запросы и HTTP-вызовы намного проще, чем веб-сокеты.
Профессия «Node.js-разработчик»
- Освойте платформу Node.JS и начните использовать JavaScript для работы с бэкендом
- Научитесь использовать современные инструменты для создания бэкенд-приложений, включая Express и Fastify
- Получите возможность стать Fullstack-разработчиком и повысить свою ценность на рынке труда