JavaScript WebSocket API
JavaScript WebSocket API: жизненный цикл соединения и readyState, отправка текста, JSON и бинарных данных, переподключение и безопасность с примером Echo Chat.
Введение в технологию WebSocket
WebSocket — это протокол, обеспечивающий полнодуплексный (двунаправленный) канал связи между браузером и сервером через одно долгоживущее TCP-соединение. После открытия соединения любая из сторон может отправить сообщение в любой момент, не ожидая запроса от другой — чего обычный HTTP делать не умеет.
На этой странице рассматривается, какие задачи решают WebSockets, жизненный цикл соединения, отправка текстовых и бинарных данных, работа с JSON-сообщениями, переподключение, безопасность и случаи, когда вместо «сырого» API стоит использовать библиотеку. Всё изложено на основе исполняемого примера Echo Chat.
Почему WebSockets, а не HTTP?
При обычном HTTP (в том числе через Fetch API и XMLHttpRequest) клиент должен инициировать каждый обмен. Чтобы получать актуальные данные, приходится опрашивать сервер — постоянно спрашивать «есть ли что-то новое?» — что расходует полосу пропускания и увеличивает задержку. WebSockets меняют эту схему: сервер может отправлять данные мгновенно, как только они появляются, практически без накладных расходов на каждое сообщение.
| Подход | Направление | Накладные расходы | Лучше всего для |
|---|---|---|---|
| HTTP запрос/ответ | Клиент спрашивает, сервер отвечает | Полные заголовки на каждый запрос | Единичные запросы, REST API |
| Опрос (Polling) | Клиент опрашивает по таймеру | Много лишних запросов | Простые обновления с низкой частотой |
| WebSocket | Обе стороны отправляют свободно | Одно рукопожатие, маленькие фреймы | Чат, живые ленты, игры, дашборды |
Используйте WebSockets, когда серверу нужно говорить первым или сообщения поступают постоянно: чат, многопользовательские игры, совместные редакторы, торговые тикеры и интерактивные дашборды. Для нечастых однонаправленных обновлений от сервера к клиенту подойдут Server-Sent Events или Push API.
Жизненный цикл соединения WebSocket
Соединение открывается путём передачи URL с протоколом ws:// (незашифрованный) или wss:// (зашифрованный) в конструктор WebSocket. Затем соединение проходит четыре состояния, доступные через socket.readyState:
| Константа | Значение | Смысл |
|---|---|---|
WebSocket.CONNECTING | 0 | Рукопожатие выполняется (начальное состояние) |
WebSocket.OPEN | 1 | Готово к отправке и получению |
WebSocket.CLOSING | 2 | Запрошено закрытие соединения |
WebSocket.CLOSED | 3 | Соединение закрыто или не удалось открыть |
На переходы между состояниями реагируют четыре события: open, message, error и close. Ключевое правило: вызывать socket.send() можно только тогда, когда readyState равен OPEN — отправка до срабатывания open вызовет ошибку.
const socket = new WebSocket("wss://echo.websocket.events");
socket.addEventListener("open", () => {
console.log("open, readyState =", socket.readyState); // 1
socket.send("hello");
});
socket.addEventListener("message", (event) => {
console.log("received:", event.data); // "hello" (echoed back)
});
socket.addEventListener("close", (event) => {
console.log("closed, code =", event.code); // e.g. 1000
});Создание Echo Chat на WebSocket
Базовая HTML-структура
Сначала создадим интерфейс на HTML: область отображения сообщений, поле ввода и кнопки управления.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>WebSocket Echo Chat</title>
</head>
<body>
<textarea id="chatBox" readonly style="width: 100%; height: 300px"></textarea><br />
<input type="text" id="messageInput" placeholder="Type a message..." style="width: 75%" />
<button onclick="sendMessage()">Send</button>
<button onclick="closeConnection()">Close Connection</button>
</body>
</html>Интеграция WebSocket с JavaScript
JavaScript для управления WebSocket-коммуникацией — ключевой компонент для организации взаимодействия в реальном времени.
<script>
// Accessing the chat box and message input elements from the HTML.
const chatBox = document.getElementById("chatBox");
const messageInput = document.getElementById("messageInput");
// Establishing a WebSocket connection to the echo server.
const socket = new WebSocket("wss://echo.websocket.events");
// When the connection is open, display a connected message in the chat box.
socket.addEventListener("open", function (event) {
chatBox.value += "Connected to the echo server\n";
});
// Handle incoming messages by adding them to the chat box.
socket.addEventListener("message", function (event) {
chatBox.value += "Echoed back: " + event.data + "\n";
});
// Handle connection errors.
socket.addEventListener("error", function (event) {
chatBox.value += "Connection error occurred.\n";
});
// Handle connection closure.
socket.addEventListener("close", function (event) {
chatBox.value += "Connection closed. Code: " + event.code + "\n";
});
// Function to send a message when the send button is clicked.
function sendMessage() {
const message = messageInput.value; // Get the message from the input field.
if (!message) return; // If there's no message, don't do anything.
socket.send(message); // Send the message to the server.
chatBox.value += "You: " + message + "\n"; // Show the message in the chat box.
messageInput.value = ""; // Clear the message input field.
}
// Function to close the WebSocket connection.
function closeConnection() {
if (socket.readyState === WebSocket.OPEN) {
socket.close(1000, "The user closed the connection"); // Close the connection normally.
chatBox.value += "Connection closed by user\n"; // Inform the user in the chat box.
} else {
alert("Connection is not open or already closed."); // Alert if the connection can't be closed.
}
}
// Ensure the WebSocket is closed properly when the webpage is closed or reloaded.
window.addEventListener("beforeunload", function () {
if (socket.readyState === WebSocket.OPEN) {
socket.close(1000, "The page is unloading"); // Close the connection normally.
}
});
</script>Этот скрипт реализует чат на веб-странице, которая подключается к серверу через WebSockets. Разберём его части:
- Обращение к HTML-элементам: скрипт получает элементы чата и поля ввода, чтобы взаимодействовать с ними.
- Соединение WebSocket: открывается соединение с сервером, который возвращает сообщения обратно. Всё, что вы отправите, будет отправлено обратно вам.
- Отображение статуса соединения: при успешном подключении в чате появляется сообщение о том, что соединение с echo-сервером установлено.
- Обработка входящих сообщений: все ответы от сервера добавляются в чат, демонстрируя эхо-ответ сервера.
- Отправка сообщений: функция отправляет текст из поля ввода. Если там что-то написано, сообщение передаётся на сервер и отображается в чате.
- Закрытие соединения: предусмотрена функция закрытия WebSocket-соединения — когда пользователь явно нажимает кнопку или закрывает страницу.
Такая схема позволяет взаимодействовать с сервером в реальном времени и наглядно демонстрирует работу приложений для обмена сообщениями.
А теперь соберём всё вместе и посмотрим на результат:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>WebSocket Echo Chat</title>
</head>
<body>
<textarea id="chatBox" readonly style="width: 100%; height: 300px">
</textarea>
<br />
<input
type="text"
id="messageInput"
placeholder="Type a message..."
style="width: 75%"
/>
<button onclick="sendMessage()">Send</button>
<button onclick="closeConnection()">Close Connection</button>
</body>
<script>
const chatBox = document.getElementById("chatBox");
const messageInput = document.getElementById("messageInput");
const socket = new WebSocket("wss://echo.websocket.events");
socket.addEventListener("open", function (event) {
chatBox.value += "Connected to the echo server\n";
});
socket.addEventListener("message", function (event) {
chatBox.value += "Echoed back: " + event.data + "\n";
});
socket.addEventListener("error", function (event) {
chatBox.value += "Connection error occurred.\n";
});
socket.addEventListener("close", function (event) {
chatBox.value += "Connection closed. Code: " + event.code + "\n";
});
function sendMessage() {
const message = messageInput.value;
if (!message) return;
socket.send(message);
chatBox.value += "You: " + message + "\n";
messageInput.value = "";
}
function closeConnection() {
if (socket.readyState === WebSocket.OPEN) {
socket.close(1000, "The user closed the connection");
chatBox.value += "Connection closed by user\n";
} else {
alert("Connection not open or already closed.");
}
}
window.addEventListener("beforeunload", function () {
if (socket.readyState === WebSocket.OPEN) {
socket.close(1000, "The page is unloading");
}
});
</script>
</html>В примере выше: как только вы подключитесь к серверу, можно начинать отправлять сообщения — вы будете получать обратно именно то, что напечатали. Нажатие кнопки «Close Connection» завершает WebSocket-соединение, и эхо-ответы больше не поступают.
Продвинутые возможности и техники WebSocket
Отправка структурированных данных в формате JSON
Реальные приложения редко пересылают обычные строки — они передают object-ы. Поскольку по сети можно передавать только текст или бинарные данные, object-ы сериализуются через JSON.stringify() перед отправкой и разбираются обратно с помощью JSON.parse() при получении. Подробнее о формате читайте в разделе Работа с JSON.
// Sending an object
const payload = { type: "chat", user: "Ann", text: "Hi!" };
socket.send(JSON.stringify(payload));
// Receiving and parsing it
socket.addEventListener("message", (event) => {
const msg = JSON.parse(event.data);
console.log(msg.user + ": " + msg.text); // "Ann: Hi!"
});Распространённый паттерн — поле type, которое позволяет одному соединению мультиплексировать разные типы сообщений: chat, presence, typing и другие, — каждый обрабатывается своей ветвью.
Работа с бинарными данными
WebSockets не ограничены текстом. Они также поддерживают бинарные фреймы, что полезно для передачи аудио, изображений или игрового состояния. Задайте socket.binaryType, чтобы управлять тем, в каком виде поступают входящие бинарные данные: "blob" (по умолчанию) или "arraybuffer".
socket.binaryType = "arraybuffer";
// Send raw bytes
const bytes = new Uint8Array([72, 73]); // "HI"
socket.send(bytes);
socket.addEventListener("message", (event) => {
if (typeof event.data === "string") {
console.log("text frame:", event.data);
} else {
const view = new Uint8Array(event.data);
console.log("binary frame, length:", view.length);
}
});Автоматическое переподключение
Сети разрываются. «Сырой» WebSocket не переподключается самостоятельно — когда событие close срабатывает неожиданно, вы должны повторно открыть соединение, желательно с нарастающей (экспоненциальной) задержкой, чтобы не перегружать сервер.
let delay = 1000; // start at 1 second
function connect() {
const socket = new WebSocket("wss://echo.websocket.events");
socket.addEventListener("open", () => {
delay = 1000; // reset back-off after a successful connection
});
socket.addEventListener("close", () => {
setTimeout(connect, delay);
delay = Math.min(delay * 2, 30000); // cap at 30 seconds
});
}
connect();Обеспечение безопасности WebSocket
Безопасность имеет первостепенное значение при работе с WebSockets:
- Используйте WSS: всегда применяйте WebSocket Secure (WSS), который шифрует данные, передаваемые между клиентом и сервером.
- Аутентификация: реализуйте аутентификацию на основе токенов, чтобы устанавливать WebSocket-соединения могли только авторизованные пользователи.
- Валидация: тщательно проверяйте все данные, отправляемые на сервер, чтобы защититься от распространённых уязвимостей — XSS или SQL-инъекций.
WebSocket и паттерн Pub/Sub
Паттерн публикация/подписка — популярная модель в сервисах реальных данных, где сообщения рассылаются по каналам. Такие WebSocket-сервисы, как PubNub, предлагают API с поддержкой модели pub/sub, расширяя возможности WebSocket за счёт управления соединениями, шифрования данных и широковещательной рассылки на основе каналов.
Библиотеки и фреймворки для WebSocket
Ряд JavaScript-библиотек упрощает работу с WebSockets и делает её более надёжной:
- Socket.IO: предоставляет дополнительные возможности — автоматическое переподключение, обработку событий и управление комнатами.
- WebSocket-Node: реализация WebSocket-сервера для Node.js.
- ReconnectingWebSocket: небольшая библиотека, добавляющая поддержку переподключения к обычным WebSockets.
Заключение
Технология WebSocket — фундаментальный строительный блок для создания интерактивных веб-приложений реального времени. Интегрируя WebSockets в свои приложения, вы обеспечиваете прямое двустороннее взаимодействие между клиентами и серверами. В этом руководстве мы рассмотрели жизненный цикл соединения, пример Echo Chat, JSON и бинарные сообщения, переподключение и безопасность — достаточно для создания надёжных функций реального времени.
Смотрите также
- Fetch API — для разовых HTTP-запросов, когда постоянный канал не нужен.
- XMLHttpRequest — более старый API запросов, концептуальной основой которого пользуются WebSockets и Fetch.
- Push API — сообщения от сервера к клиенту даже при закрытой странице.
- Работа с JSON — стандартный формат для структурированных WebSocket-сообщений.