Перейти к основному содержимому

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

· 7 мин. чтения

1. Обзор

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

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

2. JSR 356

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

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

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

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

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

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

<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
</dependency>

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

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

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.0</version>
</dependency>

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

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

Существует два способа настройки конечных точек: на основе аннотаций и на основе расширений. Вы можете либо расширить класс 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. Запись конечной точки сервера

Мы объявляем конечную точку сервера WebSocket класса Java , аннотируя ее с помощью @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<ChatEndpoint> chatEndpoints
= new CopyOnWriteArraySet<>();
private static HashMap<String, String> 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 ) немедленно сопоставляется со структурой данных активных пользователей. Затем создается сообщение, которое отправляется на все конечные точки с использованием широковещательного метода.

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

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

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

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

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

  • Text : любые текстовые данные ( java.lang.String , примитивы или их эквивалентные классы-оболочки)
  • Двоичные : двоичные данные (например, аудио, изображение и т. д.), представленные java.nio.ByteBuffer или byte[] (массив байтов) .
  • Объекты Java : API позволяет работать с собственными представлениями (объект Java) в вашем коде и использовать настраиваемые преобразователи (кодировщики/декодеры) для преобразования их в совместимые форматы (текстовые, двоичные), разрешенные протоколом WebSocket.
  • Ping-Pong : javax.websocket.PongMessage — это подтверждение, отправленное узлом WebSocket в ответ на запрос проверки работоспособности (ping).

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

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<Message> {

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> .

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

public class MessageDecoder implements Decoder.Text<Message> {

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 и наоборот.

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

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