W3docs

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.CONNECTING0Рукопожатие выполняется (начальное состояние)
WebSocket.OPEN1Готово к отправке и получению
WebSocket.CLOSING2Запрошено закрытие соединения
WebSocket.CLOSED3Соединение закрыто или не удалось открыть

На переходы между состояниями реагируют четыре события: 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. Разберём его части:

  1. Обращение к HTML-элементам: скрипт получает элементы чата и поля ввода, чтобы взаимодействовать с ними.
  2. Соединение WebSocket: открывается соединение с сервером, который возвращает сообщения обратно. Всё, что вы отправите, будет отправлено обратно вам.
  3. Отображение статуса соединения: при успешном подключении в чате появляется сообщение о том, что соединение с echo-сервером установлено.
  4. Обработка входящих сообщений: все ответы от сервера добавляются в чат, демонстрируя эхо-ответ сервера.
  5. Отправка сообщений: функция отправляет текст из поля ввода. Если там что-то написано, сообщение передаётся на сервер и отображается в чате.
  6. Закрытие соединения: предусмотрена функция закрытия 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-сообщений.

Практика

Практика
Каковы основные преимущества использования WebSockets в веб-приложениях?
Каковы основные преимущества использования WebSockets в веб-приложениях?
Практика
В каком состоянии readyState можно безопасно вызывать socket.send()?
В каком состоянии readyState можно безопасно вызывать socket.send()?
Практика
Как отправить JavaScript object через WebSocket?
Как отправить JavaScript object через WebSocket?
Was this page helpful?