Ръководство за Java API за WebSocket

1. Общ преглед

WebSocket предоставя алтернатива на ограничаването на ефективната комуникация между сървъра и уеб браузъра, като осигурява двупосочна, пълна дуплексна комуникация клиент / сървър в реално време. Сървърът може да изпраща данни на клиента по всяко време. Тъй като работи през TCP, той също така осигурява комуникация на ниско ниво на латентност и намалява режийните разходи за всяко съобщение .

В тази статия ще разгледаме Java API за WebSockets, като създадем приложение, подобно на чат.

2. JSR 356

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

Този Java API предоставя както сървърни, така и клиентски компоненти:

  • Сървър : всичко в пакета javax.websocket.server .
  • Клиент : съдържанието на пакета javax.websocket , който се състои от клиентски приложни програмни интерфейси (API) и също общи библиотеки както за сървъра, така и за клиента.

3. Изграждане на чат с помощта на WebSockets

Ще изградим много просто приложение, подобно на чат. Всеки потребител ще може да отвори чата от всеки браузър, да въведе името му, да влезе в чата и да започне да комуникира с всички, свързани с чата.

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

 javax.websocket javax.websocket-api 1.1 

Най-новата версия може да бъде намерена тук.

За да конвертираме Java Objects в техните JSON представления и обратно, ще използваме Gson:

 com.google.code.gson gson 2.8.0 

Най-новата версия е достъпна в хранилището на 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 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 ), той веднага се съпоставя със структурата на данни на активните потребители. След това се създава съобщение и се изпраща до всички крайни точки, използвайки метода на излъчване .

Този метод се използва и винаги, когато се изпраща ново съобщение ( @OnMessage ) от някой от свързаните потребители - това е основната цел на чата.

Ако в даден момент възникне грешка, методът с анотацията @OnError се справя с нея. Можете да използвате този метод, за да регистрирате информацията за грешката и да изчистите крайните точки.

И накрая, когато потребител вече не е свързан с чата, методът @OnClose изчиства крайната точка и излъчва на всички потребители, че даден потребител е прекъснат.

4. Видове съобщения

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

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

За нашето приложение ще използваме Java Objects. Ще създадем класовете за кодиране и декодиране на съобщения.

4.1. Енкодер

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

В кода по-долу дефинираме класа 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. Декодер

A decoder is the opposite of an encoder and is used to transform data back into a Java object. Decoders can be implemented using the Decoder.Text or Decoder.Binary interfaces.

As we saw with the encoder, the decode method is where we take the JSON retrieved in the message sent to the endpoint and use Gson to transform it to a Java class called 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. Setting Encoder and Decoder in Server Endpoint

Let's put everything together by adding the classes created for encoding and decoding the data at the class level annotation @ServerEndpoint:

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

Every time messages are sent to the endpoint, they will automatically either be converted to JSON or Java objects.

5. Conclusion

In this article, we looked at what is the Java API for WebSockets and how it can help us building applications such as this real-time chat.

We saw the two programming models for creating an endpoint: annotations and programmatic. We defined an endpoint using the annotation model for our application along with the life cycle methods.

Also, in order to be able to communicate back and forth between the server and client, we saw that we need encoders and decoders to convert Java objects to JSON and vice versa.

The JSR 356 API is very simple and the annotation based programming model that makes it very easy to build WebSocket applications.

За да стартираме приложението, което изградихме в примера, всичко, което трябва да направим, е да разположим военния файл в уеб сървър и да отидем на URL: // localhost: 8080 / java-websocket /. Можете да намерите връзката към хранилището тук.