Pull to refresh

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

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

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

Cтоит начать с того что такое 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 как ответ от сервера
Нам пришли данные в формате 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. Открываем её и видим там примерно следующее:

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

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

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.