WebSocket
- это протокол, который позволяет устанавливать постоянное двустороннее соединение между клиентом и сервером. Данные передаются по нему в обоих направлениях в виде «пакетов», без разрыва соединения и дополнительных HTTP-запросов.
Чтобы открыть веб-сокет-соединение, нам нужно создать объект new WebSocket
, указав в url-адресе специальный протокол ws
:
let socket = new WebSocket("ws://javascript.info")
Протокол wss://
не только использует шифрование, но и обладает повышенной надёжностью. Это потому, что данные ws://
не зашифрованы, видны для любого посредника. Старые прокси-серверы не знают о WebSocket
, они могут увидеть «странные» заголовки и закрыть соединение.
С другой стороны, wss://
– это WebSocket поверх TLS
(так же, как HTTPS
– это HTTP поверх TLS), безопасный транспортный уровень шифрует данные от отправителя и расшифровывает на стороне получателя. Пакеты данных передаются в зашифрованном виде через прокси, которые не могут видеть, что внутри, и всегда пропускают их.
Как только объект WebSocket
создан, мы должны слушать его события. Их всего 4:
open
– соединение установлено,message
– получены данные,error
– ошибка,close
– соединение закрыто. …А если мы хотим отправить что-нибудь, то вызовsocket.send(data)
сделает это.
На фронте часто используются библиотеки Socket.IO и SockJS вместо нативного WebSocket. Они предоставляют дополнительные возможности, такие как автоматическое переподключение, поддержку старых браузеров и механизмы безопасности, что делает их более удобными для использования в реальных проектах. Устанавливаем библиотеку socket.io-client
Открытие веб-сокета
Когда new WebSocket(url)
создан, он тут же сам начинает устанавливать соединение.
Браузер, при помощи специальных заголовков, спрашивает сервер: «Ты поддерживаешь Websocket?» и если сервер отвечает «да», они начинают работать по протоколу WebSocket, который уже не является HTTP.
Запрос WebSocket нельзя эмулировать
Мы не можем использовать XMLHttpRequest
или fetch
для создания такого HTTP-запроса, потому что JavaScript не позволяет устанавливать такие заголовки.
Если сервер согласен переключиться на WebSocket, то он должен отправить в ответ код 101:
101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=X
После этого данные передаются по протоколу WebSocket, и вскоре мы увидим его структуру («фреймы»). И это вовсе не HTTP.
Передача данных
Поток данных в WebSocket состоит из «фреймов», фрагментов данных, которые могут быть отправлены любой стороной, и которые могут быть следующих видов:
- «текстовые фреймы» – содержат текстовые данные, которые стороны отправляют друг другу.
- «бинарные фреймы» – содержат бинарные данные, которые стороны отправляют друг другу.
- «пинг-понг фреймы» используется для проверки соединения; отправляется с сервера, браузер реагирует на них автоматически.
- также есть «фрейм закрытия соединения» и некоторые другие служебные фреймы.
В браузере мы напрямую работаем только с текстовыми и бинарными фреймами.
Ограничение скорости
Представим, что наше приложение генерирует много данных для отправки. Но у пользователя медленное соединение, возможно, он в интернете с мобильного телефона и не из города.
Мы можем вызывать socket.send(data)
снова и снова. Но данные будут буферизованы (сохранены) в памяти и отправлены лишь с той скоростью, которую позволяет сеть.
Свойство socket.bufferedAmount
хранит количество байт буферизованных данных на текущий момент, ожидающих отправки по сети.
Мы можем изучить его, чтобы увидеть, действительно ли сокет доступен для передачи.
// каждые 100мс проверить сокет и отправить больше данных,
// только если все текущие отосланы
setInterval(() => {
if (socket.bufferedAmount == 0) {
socket.send(moreData())
}
}, 100)
Закрытие подключения
Обычно, когда сторона хочет закрыть соединение (браузер и сервер имеют равные права), они отправляют «фрейм закрытия соединения» с кодом закрытия и указывают причину в виде текста.
Метод для этого:
socket.close([code], [reason])
code
– специальный WebSocket-код закрытия (не обязателен).reason
– строка с описанием причины закрытия (не обязательна).
Состояние соединения
Чтобы получить состояние соединения, существует дополнительное свойство socket.readyState
со значениями:
0
– «CONNECTING»: соединение ещё не установлено,1
– «OPEN»: обмен данными,2
– «CLOSING»: соединение закрывается,3
– «CLOSED»: соединение закрыто.
Пример чата
Давайте рассмотрим пример чата с использованием WebSocket API и модуля WebSocket сервера Node.js https://github.com/websockets/ws. Основное внимание мы, конечно, уделим клиентской части, но и серверная весьма проста.
HTML: нам нужна форма <form>
для отправки данных и <div>
для отображения сообщений:
<!-- форма сообщений -->
<form name="publish">
<input type="text" name="message">
<input type="submit" value="Отправить">
</form>
<!-- div с сообщениями -->
<div id="messages"></div>
Выводы
WebSocket – это современный способ иметь постоянное соединение между браузером и сервером.
- Нет ограничений, связанных с кросс-доменными запросами.
- Имеют хорошую поддержку браузерами.
- Могут отправлять/получать как строки, так и бинарные данные.
const socket = new WebSocket("ws://just-test.com");
socket.onopen = function() {
alert("[open] Connected!");
socket.send("Hello world!");
};
socket.onmessage = function(e) {
alert(`[message] Data is received from server: ${e.data}`);
};
socket.onclose = function(e) {
e.wasClean
? alert(`[close] Connection closed cleanly, code=${e.code} reason=${e.reason}`)
: alert('[close] Connection interrupted');
};
socket.onerror = function(e) {
alert(`[error] ${e.message}`);
};
Плюсы:
- Двусторонняя связь: WebSocket обеспечивает полнодуплексную двустороннюю связь между клиентом и сервером, что позволяет как клиенту, так и серверу инициировать отправку сообщений в любой момент времени. Это делает WebSocket очень эффективным для реализации приложений, которым нужно быстро реагировать на изменения данных и взаимодействовать с пользователями в режиме реального времени.
- Низкая задержка: WebSocket обеспечивает быструю и эффективную связь между клиентом и сервером. Он работает на основе протокола TCP, который обеспечивает низкую задержку и малое количество потерь данных при передаче.
- Масштабируемость: WebSocket может обрабатывать большое количество одновременных подключений, что делает его идеальным для создания масштабируемых приложений, таких как онлайн-игры, чат-приложения и многие другие.
- Безопасность: WebSocket поддерживает защищенное соединение SSL/TLS, которое обеспечивает безопасную передачу данных между клиентом и сервером. Кроме того, WebSocket обеспечивает защиту от атак типа XSS и CSRF.
- Простота использования: WebSocket легко интегрируется с различными языками программирования и платформами, что делает его доступным для широкого круга разработчиков. Кроме того, многие фреймворки и библиотеки, такие как Socket.IO для Node.js и SignalR для .NET, предоставляют простой и удобный интерфейс для работы с WebSocket.
Минусы
- Не поддерживается всеми браузерами
- Требует дополнительной настройки на стороне сервера для поддержки постоянного соединения
- Более высокая нагрузка на сервер в сравнении с HTTP при передаче большого количества данных