Руководство по API Java для WebSocket

Руководство по API Java для WebSocket

1. обзор

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

В этой статье мы рассмотрим Java API для WebSockets, создав приложение, похожее на чат.

2. JSR 356

JSR 356 или Java API для WebSocket определяет API, который разработчики Java могут использовать для интеграции WebSockets со своими приложениями - как на стороне сервера, так и на стороне клиента Java.

Этот Java API предоставляет компоненты как на стороне сервера, так и на стороне клиента:

  • Server: все в пакетеjavax.websocket.server.

  • Client: содержимое пакетаjavax.websocket, который состоит из клиентских API, а также общих библиотек для сервера и клиента.

3. Создание чата с использованием WebSockets

Мы создадим очень простое приложение, похожее на чат. Любой пользователь сможет открыть чат из любого браузера, ввести свое имя, войти в чат и начать общаться со всеми, кто подключен к чату.

Начнем с добавления последней зависимости в файлpom.xml:


    javax.websocket
    javax.websocket-api
    1.1

Последнюю версию можно найтиhere.

Чтобы преобразовать JavaObjects в их представления JSON и наоборот, мы будем использовать Gson:


    com.google.code.gson
    gson
    2.8.0

Последняя версия доступна в репозиторииMaven Central.

3.1. Конфигурация конечной точки

Существует два способа настройки конечных точек: на основеannotation- и на основе расширений. Вы можете либо расширить классjavax.websocket.Endpoint, либо использовать специальные аннотации на уровне метода. Поскольку модель аннотации приводит к более чистому коду по сравнению с программной моделью, аннотация стала обычным выбором кодирования. В этом случае события жизненного цикла конечной точки WebSocket обрабатываются следующими аннотациями:

  • @ServerEndpoint: Если оформлен@ServerEndpoint,, контейнер обеспечивает доступность класса в качестве сервераWebSocket, прослушивающего определенное пространство URI

  • @ClientEndpoint: класс, украшенный этой аннотацией, рассматривается как клиентWebSocket

  • @OnOpen: метод Java с@OnOpen вызывается контейнером, когда инициируется новое соединениеWebSocket

  • @OnMessage: метод Java, помеченный@OnMessage,, получает информацию из контейнераWebSocket, когда сообщение отправляется в конечную точку

  • @OnError: метод с@OnError вызывается, когда есть проблема со связью

  • @OnClose: используется для украшения метода Java, который вызывается контейнером при закрытии соединенияWebSocket

3.2. Написание конечной точки сервера

Мы объявляем конечную точку сервера Java-классаWebSocket, аннотируя ее с помощью@ServerEndpoint. Мы также указываем URI, где развернута конечная точка. URI определяется относительно корня контейнера сервера и должен начинаться с косой черты:

@ServerEndpoint(value = "/chat/{username}")
public class ChatEndpoint {

    @OnOpen
    public void onOpen(Session session) throws IOException {
        // Get session and WebSocket connection
    }

    @OnMessage
    public void onMessage(Session session, Message message) throws IOException {
        // Handle new messages
    }

    @OnClose
    public void onClose(Session session) throws IOException {
        // WebSocket connection closes
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        // Do error handling here
    }
}

Приведенный выше код представляет собой скелет конечной точки сервера для нашего приложения, похожего на чат. Как видите, у нас есть 4 аннотации, сопоставленные с их соответствующими методами. Ниже вы можете увидеть реализацию таких методов:

@ServerEndpoint(value="/chat/{username}")
public class ChatEndpoint {

    private Session session;
    private static Set chatEndpoints
      = new CopyOnWriteArraySet<>();
    private static HashMap users = new HashMap<>();

    @OnOpen
    public void onOpen(
      Session session,
      @PathParam("username") String username) throws IOException {

        this.session = session;
        chatEndpoints.add(this);
        users.put(session.getId(), username);

        Message message = new Message();
        message.setFrom(username);
        message.setContent("Connected!");
        broadcast(message);
    }

    @OnMessage
    public void onMessage(Session session, Message message)
      throws IOException {

        message.setFrom(users.get(session.getId()));
        broadcast(message);
    }

    @OnClose
    public void onClose(Session session) throws IOException {

        chatEndpoints.remove(this);
        Message message = new Message();
        message.setFrom(users.get(session.getId()));
        message.setContent("Disconnected!");
        broadcast(message);
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        // Do error handling here
    }

    private static void broadcast(Message message)
      throws IOException, EncodeException {

        chatEndpoints.forEach(endpoint -> {
            synchronized (endpoint) {
                try {
                    endpoint.session.getBasicRemote().
                      sendObject(message);
                } catch (IOException | EncodeException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

Когда новый пользователь входит в систему (@OnOpen) немедленно отображается в структуру данных активных пользователей. Затем создается сообщение, которое отправляется всем конечным точкам с помощью методаbroadcast.

Этот метод также используется всякий раз, когда любое из подключенных пользователей отправляет новое сообщение (@OnMessage) - это основная цель чата.

Если в какой-то момент происходит ошибка, метод с аннотацией@OnError обрабатывает ее. Вы можете использовать этот метод для регистрации информации об ошибке и очистки конечных точек.

Наконец, когда пользователь больше не подключен к чату, метод@OnClose очищает конечную точку и транслирует всем пользователям, что пользователь был отключен.

4. Типы сообщений

Спецификация WebSocket поддерживает два формата данных в сети - текстовый и двоичный. API поддерживает оба этих формата, добавляет возможности для работы с объектами Java и сообщениями о проверке работоспособности (пинг-понг), как определено в спецификации:

  • Text: любые текстовые данные (java.lang.String, примитивы или их эквивалентные классы-оболочки)

  • Binary: двоичные данные (например, аудио, изображение и т. д.), представленныеjava.nio.ByteBuffer илиbyte[] (массив байтов)

  • Java objects: API позволяет работать с собственными представлениями (объектами Java) в вашем коде и использовать настраиваемые преобразователи (кодировщики / декодеры) для преобразования их в совместимые интерактивные форматы (текстовые, двоичные), разрешенные WebSocket. протокол

  • Ping-Pong:javax.websocket.PongMessage - это подтверждение, отправленное одноранговым узлом WebSocket в ответ на запрос проверки работоспособности (ping)

Для нашего приложения мы будем использоватьJava Objects.. Мы создадим классы для кодирования и декодирования сообщений.

4.1. кодировщик

Кодировщик берет объект Java и создает типичное представление, подходящее для передачи в виде сообщения, такое как JSON, XML или двоичное представление. Кодеры можно использовать, реализовав интерфейсыEncoder.Text<T> илиEncoder.Binary<T>.

В приведенном ниже коде мы определяем классMessage для кодирования, а в методеencode мы используем Gson для кодирования объекта Java в JSON:

public class Message {
    private String from;
    private String to;
    private String content;

    //standard constructors, getters, setters
}
public class MessageEncoder implements Encoder.Text {

    private static Gson gson = new Gson();

    @Override
    public String encode(Message message) throws EncodeException {
        return gson.toJson(message);
    }

    @Override
    public void init(EndpointConfig endpointConfig) {
        // Custom initialization logic
    }

    @Override
    public void destroy() {
        // Close resources
    }
}

4.2. дешифратор

Декодер является противоположностью кодера и используется для преобразования данных обратно в объект Java. Декодеры могут быть реализованы с использованием интерфейсовDecoder.Text<T> илиDecoder.Binary<T>.

Как мы видели с кодировщиком, методdecode - это то место, где мы берем JSON, полученный в сообщении, отправленном в конечную точку, и используем Gson для преобразования его в класс Java с именемMessage:.

public class MessageDecoder implements Decoder.Text {

    private static Gson gson = new Gson();

    @Override
    public Message decode(String s) throws DecodeException {
        return gson.fromJson(s, Message.class);
    }

    @Override
    public boolean willDecode(String s) {
        return (s != null);
    }

    @Override
    public void init(EndpointConfig endpointConfig) {
        // Custom initialization logic
    }

    @Override
    public void destroy() {
        // Close resources
    }
}

4.3. Настройка кодировщика и декодера в конечной точке сервера

Давайте соберем все вместе, добавив классы, созданные для кодирования и декодирования данных, в аннотацию уровня класса@ServerEndpoint:

@ServerEndpoint(
  value="/chat/{username}",
  decoders = MessageDecoder.class,
  encoders = MessageEncoder.class )

Каждый раз, когда сообщения отправляются в конечную точку, они автоматически конвертируются в объекты JSON или Java.

5. Заключение

В этой статье мы рассмотрели, что такое Java API для WebSockets и как оно может помочь нам в создании приложений, таких как чат в реальном времени.

Мы видели две модели программирования для создания конечной точки: аннотации и программирование. Мы определили конечную точку, используя модель аннотации для нашего приложения вместе с методами жизненного цикла.

Кроме того, чтобы иметь возможность обмениваться данными между сервером и клиентом, мы увидели, что нам нужны кодеры и декодеры для преобразования объектов Java в JSON и наоборот.

JSR 356 API - это очень простая и основанная на аннотациях модель программирования, которая упрощает создание приложений WebSocket.

Чтобы запустить приложение, которое мы создали в этом примере, все, что нам нужно сделать, это развернуть файл war на веб-сервере и перейти по URL-адресу:http://localhost:8080/java-websocket/.. Вы можете найти ссылку на репозиторийhere.