Разработка
Даниил Шило

WebSocket: смотрим как работает за кулисами

Сегодня поговорим о том как работает WS, напишем простенький клиент на JS, обсудим как дебажить данный протокол ну и просто обсудим несколько интересных фактов. Погнали😈

Первая ступень развития: HTTP

Для начала стоит начать с того что такое HTTP?

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), сервер отдал нам информацию и после этого мы разрываем соединение. После того как мы получим ответ мы ничего не знаем о сервере👀

Нам пришли данные в формате JSON как ответ от сервера

Вторая ступень: 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 {string} url - ссылка, которую будем подтягивать * @returns {void} */ 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. Открываем её и видим там примерно следующее:

Красной стрелкой вниз показаны пакеты, которые пришли нам (пусть вас не вводит в заблуждение красная стрелка, это не упавшие, а пришедшие пакеты), отправленные пакеты в свою очередь будут показаны зелёной стрелкой, которая стремится вверх⬆

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

0
10 комментариев
Написать комментарий...
Нехороший кубок

мне понравилось наличие смайликов в посте

Ответить
Развернуть ветку
Роман Анисимов

Разве вебсокет - этот не просто открытый канал без каких-либо пинг-понгов?

Ответить
Развернуть ветку
Даниил Шило
Автор

Ситуация: у вас обрубился интернет, а вы общались с кем-то в чате. Как серверу узнать, что вам не нужно присылать WS-пакеты?😊

WS - действительно открытый канал, но как серверу и клиенту узнать, что он внезапно не закрылся? Именно для этого пинг-понг и нужен

Легенькую реализацию можно подсмотреть здесь😉

https://github.com/websockets/ws#how-to-detect-and-close-broken-connections

Ответить
Развернуть ветку
Please be patient ihave autism

Я бы уточнил, что "пинг-понг" способ узнать валидно ли данное подключение, но сам по себе протокол это не реализует (то есть не нативная фича "из коробки")

Ответить
Развернуть ветку
Ияза Гара

Вообще недурно отслеживать событие дисконнекта, и через тайм-аут попробовать приконнектиться заново.
Заодно - забрать get запросом данные за время когда был обрыв связи.

Ответить
Развернуть ветку
Richard Daniel

Разберите сниффинг запросов на вебсокете winline в образовательных целях))

Ответить
Развернуть ветку
Александр

Опишу эволюцию веба чуть другими словами, вдруг кому полезно будет.

Во времена супер медленного (по текущим меркам) интернета — страницы грузились целиком.

AJAX — пришёл с развитием JavaScript, лет 15 назад(а может уже и больше), когда появилась идея и возможность обновлять не всю страницу, а только её часть, для экономии трафика. Это все те же запросы по HTTP, через JavaScript. Браузер стучал на сервер, получал какие-то данные и подставлял в нужное место страницы. Формат данных был определен достаточный: текст, XML, JSON. Как видим спустя десяток лет — JSON прижился лучше.

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

Ответить
Развернуть ветку
Boris Lebedev

Вам на хабр или медиум, вы сайтом ошиблись

Ответить
Развернуть ветку
Rodya The Fighter

Заходишь на дтв, чтобы отвлечься от кодинга, а тут опять эти коды :(

Я мож че не знаю, почему на дтв стали появляться статьи про кодинг, если есть хабр?

ПС: пардон, ошибся приложением, ушел на дтв (:

Ответить
Развернуть ветку
Даниил Шило
Автор

Тред разработки же) Вот и говорим о разработке😅

Ответить
Развернуть ветку
Читать все 10 комментариев
null