Для многих клиент-серверных веб-приложений старая модель HTTP-запроса-ответа имеет свои ограничения. Информация должна передаваться с сервера клиенту между запросами, а не только по запросу.
Ряд «хаков» использовался в прошлом для обхода этой проблемы, например, длинный опрос и комета. Однако потребность в стандартном, двунаправленном и дуплексном канале между клиентами и сервером только возросла.
В 2011 году IETF стандартизировал протокол WebSocket как RFC 6455. С тех пор большинство веб-браузеров реализуют клиентские API, поддерживающие протокол WebSocket. Кроме того, был разработан ряд библиотек Java, которые реализуют протокол WebSocket.
Протокол WebSocket использует технологию обновления HTTP для обновления HTTP-соединения до WebSocket. После обновления соединение способно отправлять сообщения (кадры данных) в обоих направлениях, независимо друг от друга (полный дуплекс). Не требуются заголовки или файлы cookie, что значительно снижает требуемую пропускную способность. Как правило, WebSockets используются для периодической отправки небольших сообщений (например, нескольких байтов). Дополнительные заголовки часто делают издержки больше, чем полезная нагрузка.
JSR 356
JSR 356, Java API для WebSocket, определяет API, который разработчики Java могут использовать, когда они хотят интегрировать WebSockets в свои приложения — как на стороне сервера, так и на стороне клиента Java. Каждая реализация протокола WebSocket, которая претендует на совместимость с JSR 356, должна реализовывать этот API. Как следствие, разработчики могут писать свои приложения на основе WebSocket независимо от базовой реализации WebSocket. Это огромное преимущество, поскольку оно предотвращает блокировку от поставщиков и предоставляет больше возможностей для выбора и свободы библиотек и серверов приложений.
JSR 356 является частью будущего стандарта Java EE 7; следовательно, все серверы приложений, совместимые с Java EE 7, будут иметь реализацию протокола WebSocket, которая соответствует стандарту JSR 356. После того, как они установлены, клиент и сервер WebSocket становятся симметричными. Поэтому разница между клиентским API и серверным API минимальна. JSR 356 также определяет клиентский API Java, который является подмножеством полного API, требуемого в Java EE 7.
Клиент-серверное приложение, использующее WebSockets, обычно содержит серверный компонент и один или несколько клиентских компонентов, как показано на рисунке 1:
фигура 1
В этом примере серверное приложение написано на Java, а детали протокола WebSocket обрабатываются реализацией JSR 356, содержащейся в контейнере Java EE 7.
Клиент JavaFX может полагаться на любую реализацию клиента, совместимую с JSR 356, для решения проблем протокола, специфичных для WebSocket. Другие клиенты (например, клиент iOS и клиент HTML5) могут использовать другие (не Java) реализации, совместимые с RFC 6455, для взаимодействия с серверным приложением.
Модель программирования
Группа экспертов, которая определила JSR 356, хотела поддерживать шаблоны и методы, которые являются общими для разработчиков Java EE. Как следствие, JSR 356 использует аннотации и инъекции.
В общем, поддерживаются две разные модели программирования:
- Аннотация инициативы. Используя аннотированные POJO, разработчики могут взаимодействовать с событиями жизненного цикла WebSocket.
- Интерфейс с приводом. Разработчики могут реализовать
Endpoint
интерфейс и методы, которые взаимодействуют с событиями жизненного цикла.
События жизненного цикла
Типичное событие жизненного цикла взаимодействия WebSocket происходит следующим образом:
- Один узел (клиент) инициирует соединение, отправляя запрос HTTP-рукопожатия.
- Другой узел (сервер) отвечает ответом рукопожатия.
- Соединение установлено. Отныне соединение полностью симметрично.
- Оба узла отправляют и получают сообщения.
- Один из пиров закрывает соединение.
Большинство событий жизненного цикла WebSocket могут быть сопоставлены с методами Java как в подходах, основанных на аннотациях, так и в интерфейсах.
Подход, основанный на аннотациях
Конечной точкой, принимающей входящие запросы WebSocket, может быть POJO, аннотированный @ServerEndpoint
аннотацией. Эта аннотация сообщает контейнеру, что данный класс следует рассматривать как конечную точку WebSocket. Обязательный value
элемент указывает путь к конечной точке WebSocket.
Рассмотрим следующий фрагмент кода:
@ClientEndpoint public class MyClientEndpoint {}
Инициирование соединения WebSocket в Java с использованием подхода POJO, управляемого аннотациями, может быть выполнено следующим образом:
javax.websocket.WebSocketContainer container = javax.websocket.ContainerProvider.getWebSocketContainer(); container.conntectToServer(MyClientEndpoint.class, new URI("ws://localhost:8080/tictactoeserver/endpoint"));
В дальнейшем классы, помеченные @ServerEndpoint
или @ClientEndpoint
будут называться аннотированными конечными точками.
Как только соединение WebSocket установлено, Session
создается и вызывается метод, аннотированный @OnOpen
на аннотированной конечной точке. Этот метод может содержать ряд параметров:
javax.websocket.Session
Параметр, указав созданныйSession
EndpointConfig
Экземпляр , содержащий информацию о конфигурации конечной точки- Ноль или более строковых параметров, помеченных
@PathParam
, ссылаясь на параметры пути на пути конечной точки
Следующая реализация метода напечатает идентификатор сеанса, когда WebSocket будет «открыт»:
@OnOpen public void myOnOpen (Session session) { System.out.println ("WebSocket opened: "+session.getId()); }
Session
Экземпляр действует до тех пор , как WebSocket не закрыта. Session
Класс содержит ряд интересных методов , которые позволяют разработчикам получить более подробную информацию о подключении. Кроме того, он Session
содержит привязку к данным конкретного приложения с помощью getUserProperties()
метода, возвращающего a Map<String, Object>
. Это позволяет разработчикам заполнять Session
экземпляры информацией о сеансах и приложениях, которая должна быть распределена между вызовами методов.
Когда конечная точка WebSocket получает сообщение, вызывается метод, помеченный @OnMessage
как. Метод с комментариями @OnMessage
может содержать следующие параметры:
javax.websocket.Session
Параметр.- Ноль или более строковых параметров, помеченных с помощью
@PathParam
ссылки на параметры пути на пути конечной точки. - Само сообщение. Ниже приведен обзор возможных типов сообщений.
Когда текстовое сообщение было отправлено другим узлом, содержимое сообщения будет напечатано с помощью следующего фрагмента кода:
@OnMessage public void myOnMessage (String txt) { System.out.println ("WebSocket received message: "+txt); } If the return type of the method annotated with @OnMessage is not void, the WebSocket implementation will send the return value to the other peer. The following code snippet returns the received text message in capitals back to the sender: @OnMessage public String myOnMessage (String txt) { return txt.toUpperCase(); }
Другой способ отправки сообщений через соединение WebSocket показан ниже:
RemoteEndpoint.Basic other = session.getBasicRemote(); other.sendText ("Hello, world");
В этом подходе мы начинаем с Session
объекта, который можно получить из методов обратного вызова жизненного цикла (например, метода, помеченного как @OnOpen
). getBasicRemote()
Способ по Session
примеру возвращает представление другой части WebSocket, с RemoteEndpoint
. Этот RemoteEndpoint
экземпляр можно использовать для отправки текстовых или других типов сообщений, как описано ниже.
Когда соединение WebSocket закрывается, вызывается метод, помеченный @OnClose
как. Этот метод может принимать следующие параметры:
javax.websocket.Session
Параметр. Обратите внимание, что этот параметр нельзя использовать, когда WebSocket действительно закрыт, что происходит после@OnClose
возврата аннотированного метода.javax.websocket.CloseReason
Параметр , описывающий причину для закрытия WebSocket, например, нормального замыкание, ошибки протокола, перегруженное обслуживание, и так далее.- Ноль или более строковых параметров, помеченных с помощью
@PathParam
ссылки на параметры пути на пути конечной точки.
Следующий фрагмент кода напечатает причину закрытия WebSocket:
@OnClose public void myOnClose (CloseReason reason) { System.out.prinlnt ("Closing a WebSocket due to "+reason.getReasonPhrase()); }
Чтобы завершить, есть еще одна аннотация жизненного цикла: в случае получения ошибки @OnError
будет вызван метод, помеченный как.
Управляемый интерфейсом подход
Подход, основанный на аннотациях, позволяет нам аннотировать класс и методы Java аннотациями жизненного цикла. Используя подход интерфейса с приводом, разработчик расширяет javax.websocket.Endpoint
и отменяет onOpen
, onClose
и onError
методы:
public class myOwnEndpoint extends javax.websocket.Endpoint { public void onOpen(Session session, EndpointConfig config) {...} public void onClose(Session session, CloseReason closeReason) {...} public void onError (Session session, Throwable throwable) {...} }
Для перехвата сообщений в реализации javax.websocket.MessageHandler
необходимо зарегистрировать onOpen
:
public void onOpen (Session session, EndpointConfig config) { session.addMessageHandler (new MessageHandler() {...}); }
MessageHandler
интерфейс с двумя подинтерфейсами: MessageHandler.Partial
и MessageHandler.Whole
. MessageHandler.Partial
Интерфейс должен быть использован , когда разработчик хочет получать уведомления о частичных поставках сообщений, а также реализация MessageHandler.Whole
должна быть использована для уведомления о прибытии полного сообщения.
Следующий фрагмент кода прослушивает входящие текстовые сообщения и отправляет версию текстового сообщения в верхнем регистре другому узлу:
public void onOpen (Session session, EndpointConfig config) { final RemoteEndpoint.Basic remote = session.getBasicRemote(); session.addMessageHandler (new MessageHandler.Whole<String>() { public void onMessage(String text) { try { remote.sendString(text.toUpperCase()); } catch (IOException ioe) { // handle send failure here } } }); }
Типы сообщений, кодеры и декодеры
Java API для WebSocket является очень мощным, поскольку позволяет отправлять или получать любой объект Java в виде сообщения WebSocket.
В основном, есть три разных типа сообщений:
- Текстовые сообщения
- Двоичные сообщения
- Сообщения Pong, касающиеся самого соединения с WebSocket
При использовании модели, управляемой интерфейсом, каждый сеанс может регистрировать не более одного MessageHandler
для каждого из этих трех различных типов сообщений.
При использовании модели на основе аннотаций для каждого отдельного типа сообщения @onMessage
допускается один аннотированный метод. Допустимые параметры для указания содержимого сообщения в аннотированных методах зависят от типа сообщения.
В Javadoc для @OnMessage
аннотации четко указаны допустимые параметры сообщения в зависимости от типа сообщения (из Javadoc указано следующее):
- «если метод обрабатывает текстовые сообщения:
String
получить целое сообщение- Примитив Java или эквивалент класса для получения всего сообщения, преобразованного в этот тип
String
и логическая пара для получения сообщения по частямReader
получить сообщение целиком в виде блокирующего потока- любой параметр объекта, для которого конечная точка имеет текстовый декодер (
Decoder.Text
илиDecoder.TextStream
).
- если метод обрабатывает двоичные сообщения:
byte[]
илиByteBuffer
получить сообщение целикомbyte[]
и логическая пара, илиByteBuffer
логическая пара, чтобы получить сообщение по частямInputStream
получить сообщение целиком в виде блокирующего потока- любой параметр объекта, для которого конечная точка имеет двоичный декодер (
Decoder.Binary
илиDecoder.BinaryStream
).
- если метод обрабатывает сообщения pong:
PongMessage
для обработки сообщений понг
Любой объект Java может быть закодирован в текстовое или двоичное сообщение с использованием кодера. Это текстовое или двоичное сообщение передается другому узлу, где оно может быть снова декодировано в Java-объект или может быть интерпретировано другой библиотекой WebSocket. Часто XML или JSON используются для передачи сообщений WebSocket, а затем кодирование / декодирование сводится к маршалированию объекта Java в XML или JSON и обратно.
Кодер определен как реализация javax.websocket.Encoder
интерфейса, а декодер — реализация javax.websocket.Decoder
интерфейса. Каким-то образом экземплярам конечной точки необходимо знать, каковы возможные кодеры и декодеры. Использование подхода аннотаций привода, список кодеров и декодеров пропускают через кодер и декодер элементов в @ClientEndpoint
и @ServerEndpoint
аннотации.
Код в листинге 1 показывает, как зарегистрировать MessageEncoder
класс, который определяет преобразование экземпляра MyJavaObject
в текстовое сообщение. MessageDecoder
Класс зарегистрирован для преобразования противоположного.
@ServerEndpoint(value="/endpoint", encoders = MessageEncoder.class, decoders= MessageDecoder.class) public class MyEndpoint { ... } class MessageEncoder implements Encoder.Text<MyJavaObject> { @override public String encode(MyJavaObject obj) throws EncodingException { ... } } class MessageDecoder implements Decoder.Text<MyJavaObject> { @override public MyJavaObject decode (String src) throws DecodeException { ... } @override public boolean willDecode (String src) { // return true if we want to decode this String into a MyJavaObject instance } }
Листинг 1
Encoder
Интерфейс имеет ряд субинтерфейсов:
Encoder.Text
для преобразования объектов Java в текстовые сообщенияEncoder.TextStream
для добавления объектов Java в поток символовEncoder.Binary
для преобразования объектов Java в двоичные сообщенияEncoder.BinaryStream
для добавления объектов Java в двоичный поток
Аналогично, Decoder
интерфейс имеет четыре подинтерфейса:
Decoder.Text
для преобразования текстового сообщения в объект JavaDecoder.TextStream
для чтения объекта Java из потока символовDecoder.Binary
для преобразования двоичного сообщения в объект JavaDecoder.BinaryStream
для чтения объекта Java из двоичного потока
Вывод
Java API для WebSocket предоставляет разработчикам Java стандартный API для интеграции со стандартом IETF WebSocket. Таким образом, веб-клиенты или собственные клиенты, использующие любую реализацию WebSocket, могут легко обмениваться данными с серверной частью Java.
Java API легко настраивается и гибок, и позволяет разработчикам Java использовать их предпочтительные шаблоны.
Смотрите также
- JSR 356 на jcp.org
- Спецификация JSR 356 работает на java.net
- Tyrus (справочная реализация JSR 356) на java.net
- JSR 342, спецификация Java EE 7 на jcp.org
- GlassFish, эталонная реализация для Java EE
об авторе
Энтузиаст как встраиваемых, так и корпоративных разработок, Vos фокусируется на сквозной Java с использованием JavaFX и Java EE. Узнайте больше в его блоге или следуйте за ним по адресу http://twitter.com/johanvos .