WebSocket на Java: создание real-time приложений

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему WebSocket — стандарт для real-time приложений на Java

Современные веб-приложения требуют мгновенного обмена данными между клиентом и сервером. Традиционный HTTP-протокол, основанный на модели «запрос-ответ», плохо подходит для задач, где важна минимальная задержка: онлайн-чаты, биржевые котировки, многопользовательские игры или панели мониторинга в реальном времени.

Протокол WebSocket решает эту проблему, устанавливая постоянное двунаправленное соединение между клиентом и сервером. После начального HTTP-рукопожатия соединение «апгрейдится» до WebSocket, и данные передаются в виде фреймов без дополнительных заголовков. Java, благодаря мощной стандартной библиотеке и фреймворкам, предоставляет отличные инструменты для работы с WebSocket.

В этой статье мы рассмотрим, как создать полноценное real-time приложение на Java с использованием Jakarta WebSocket API (ранее Java EE WebSocket). Вы узнаете о настройке сервера, обработке сообщений, управлении сессиями и типовых архитектурных решениях.

Основы работы с WebSocket в Java: Jakarta WebSocket API

Начиная с Java EE 7 (Jakarta EE), в стандартную платформу был добавлен JSR 356 — Java API для WebSocket. Это означает, что для создания WebSocket-сервера не требуется подключать сторонние библиотеки — достаточно сервлет-контейнера, поддерживающего Jakarta EE (например, Apache Tomcat 9+, Jetty 10+, WildFly).

Создание WebSocket-эндпоинта

Основной элемент — это класс-эндпоинт, аннотированный @ServerEndpoint. Внутри него мы определяем методы, которые будут вызываться при подключении, получении сообщения, ошибке или закрытии соединения.

import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

@ServerEndpoint("/chat")
public class ChatEndpoint {

    private static final Set<Session> sessions = new CopyOnWriteArraySet<>();

    @OnOpen
    public void onOpen(Session session) {
        sessions.add(session);
        broadcast("Пользователь подключился. Всего: " + sessions.size());
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        broadcast("[" + session.getId() + "]: " + message);
    }

    @OnClose
    public void onClose(Session session) {
        sessions.remove(session);
        broadcast("Пользователь отключился. Осталось: " + sessions.size());
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        System.err.println("Ошибка в сессии " + session.getId() + ": " + throwable.getMessage());
    }

    private void broadcast(String message) {
        for (Session s : sessions) {
            if (s.isOpen()) {
                try {
                    s.getBasicRemote().sendText(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

В этом примере:

  • @ServerEndpoint("/chat") — указывает, что эндпоинт доступен по относительному пути /chat.
  • CopyOnWriteArraySet — потокобезопасная коллекция для хранения активных сессий.
  • @OnOpen — вызывается при установке соединения.
  • @OnMessage — вызывается при получении текстового сообщения.
  • @OnClose — вызывается при закрытии соединения.
  • @OnError — обработка ошибок.

Клиентский JavaScript

Для тестирования сервера достаточно простого HTML-файла с JavaScript:

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Chat</title>
</head>
<body>
    <div id="messages"></div>
    <input type="text" id="msgInput" />
    <button onclick="sendMessage()">Отправить</button>
    <script>
        const ws = new WebSocket("ws://localhost:8080/chat");
        ws.onmessage = function(event) {
            const div = document.getElementById("messages");
            div.innerHTML += "<p>" + event.data + "</p>";
        };
        function sendMessage() {
            const input = document.getElementById("msgInput");
            ws.send(input.value);
            input.value = "";
        }
    </script>
</body>
</html>

Архитектура real-time приложения: от чата до push-уведомлений

Простой чат — это лишь вершина айсберга. Рассмотрим три типовых сценария использования WebSocket в Java.

1. Групповой чат (Broadcast)

Как показано в примере выше, все сообщения рассылаются всем подключенным клиентам. Это основа для комнатных чатов, где нужно разделить пользователей по каналам. Для этого можно использовать Map<String, Set<Session>>, где ключ — имя комнаты.

2. Push-уведомления и мониторинг

Сервер может инициировать отправку данных без запроса клиента. Например, бэкенд отслеживает изменения в базе данных и отправляет обновления на WebSocket:

// Внутри метода, который вызывается при изменении данных
public void notifyClients(String eventType, String payload) {
    String json = String.format("{\"event\":\"%s\",\"data\":\"%s\"}", eventType, payload);
    for (Session s : sessions) {
        if (s.isOpen()) {
            s.getBasicRemote().sendText(json);
        }
    }
}

Клиент обрабатывает JSON и обновляет UI без перезагрузки страницы.

3. Многопользовательская игра

Для игр критична низкая задержка. WebSocket позволяет передавать координаты, действия и состояние игроков в реальном времени. Важно использовать session.getBasicRemote().sendText() для синхронной отправки или getAsyncRemote() для асинхронной, если требуется высокая пропускная способность.

Продвинутые техники: управление сессиями, безопасность и масштабирование

При создании production-приложения важно учитывать несколько аспектов.

Управление сессиями и идентификация

По умолчанию Session не содержит информации о пользователе. Для идентификации можно: