Ein Handbuch zur Java-API für WebSocket

1. Übersicht

WebSocket bietet eine Alternative zur Einschränkung einer effizienten Kommunikation zwischen dem Server und dem Webbrowser, indem bidirektionale Vollduplex-Echtzeit-Client / Server-Kommunikation bereitgestellt wird. Der Server kann jederzeit Daten an den Client senden. Da es über TCP ausgeführt wird, bietet es auch eine Kommunikation auf niedriger Ebene mit geringer Latenz und reduziert den Overhead jeder Nachricht .

In diesem Artikel werfen wir einen Blick auf die Java-API für WebSockets, indem wir eine Chat-ähnliche Anwendung erstellen.

2. JSR 356

JSR 356 oder die Java-API für WebSocket gibt eine API an, mit der Java-Entwickler WebSockets in ihre Anwendungen integrieren können - sowohl auf der Serverseite als auch auf der Java-Clientseite.

Diese Java-API bietet sowohl server- als auch clientseitige Komponenten:

  • Server : alles im Paket javax.websocket.server .
  • Client : Der Inhalt des Pakets javax.websocket , das aus clientseitigen APIs und gemeinsamen Bibliotheken für Server und Client besteht.

3. Erstellen eines Chats mit WebSockets

Wir werden eine sehr einfache Chat-ähnliche Anwendung erstellen. Jeder Benutzer kann den Chat über einen beliebigen Browser öffnen, seinen Namen eingeben, sich im Chat anmelden und mit allen mit dem Chat verbundenen Personen kommunizieren.

Zunächst fügen wir der Datei pom.xml die neueste Abhängigkeit hinzu :

 javax.websocket javax.websocket-api 1.1 

Die neueste Version finden Sie hier.

Um Java- Objekte in ihre JSON-Darstellungen zu konvertieren und umgekehrt, verwenden wir Gson:

 com.google.code.gson gson 2.8.0 

Die neueste Version ist im Maven Central-Repository verfügbar.

3.1. Endpunktkonfiguration

Es gibt zwei Möglichkeiten, Endpunkte zu konfigurieren: annotationsbasiert und erweiterungsbasiert. Sie können entweder die Klasse javax.websocket.Endpoint erweitern oder Anmerkungen auf Methodenebene verwenden. Da das Annotationsmodell im Vergleich zum programmatischen Modell zu saubererem Code führt, ist die Annotation zur herkömmlichen Wahl der Codierung geworden. In diesem Fall werden WebSocket-Endpunkt-Lebenszyklusereignisse durch die folgenden Anmerkungen behandelt:

  • @ServerEndpoint: Wenn der Container mit @ServerEndpoint dekoriert ist, stellt er die Verfügbarkeit der Klasse als WebSocket- Server sicher, der einen bestimmten URI-Bereich abhört
  • @ClientEndpoint : Eine mit dieser Annotation dekorierte Klasse wird als WebSocket- Client behandelt
  • @OnOpen : Eine Java-Methode mit @OnOpen wird vom Container aufgerufen, wenn eine neue WebSocket- Verbindung initiiert wird
  • @OnMessage : Eine mit @OnMessage kommentierte Java-Methode empfängt die Informationen vom WebSocket- Container, wenn eine Nachricht an den Endpunkt gesendet wird
  • @OnError : Eine Methode mit @OnError wird aufgerufen, wenn ein Problem mit der Kommunikation vorliegt
  • @OnClose : Wird verwendet, um eine Java-Methode zu dekorieren, die vom Container aufgerufen wird, wenn die WebSocket- Verbindung geschlossen wird

3.2. Schreiben des Server-Endpunkts

Wir deklarieren einen WebSocket- Serverendpunkt der Java-Klasse, indem wir ihn mit @ServerEndpoint kommentieren . Wir geben auch den URI an, in dem der Endpunkt bereitgestellt wird. Der URI ist relativ zum Stammverzeichnis des Servercontainers definiert und muss mit einem Schrägstrich beginnen:

@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 } }

Der obige Code ist das Server-Endpunkt-Skelett für unsere Chat-ähnliche Anwendung. Wie Sie sehen können, haben wir 4 Anmerkungen, die den jeweiligen Methoden zugeordnet sind. Unten sehen Sie die Implementierung solcher Methoden:

@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(); } } }); } }

Wenn sich ein neuer Benutzer anmeldet ( @OnOpen ), wird er sofort einer Datenstruktur aktiver Benutzer zugeordnet. Anschließend wird eine Nachricht erstellt und mithilfe der Broadcast- Methode an alle Endpunkte gesendet .

Diese Methode wird auch verwendet, wenn eine neue Nachricht von einem der verbundenen Benutzer gesendet wird ( @OnMessage ) - dies ist der Hauptzweck des Chats.

Wenn irgendwann ein Fehler auftritt, wird er von der Methode mit der Annotation @OnError behandelt. Mit dieser Methode können Sie die Informationen zum Fehler protokollieren und die Endpunkte löschen.

Wenn ein Benutzer nicht mehr mit dem Chat verbunden ist, löscht die Methode @OnClose den Endpunkt und sendet an alle Benutzer, dass ein Benutzer getrennt wurde.

4. Nachrichtentypen

Die WebSocket-Spezifikation unterstützt zwei On-Wire-Datenformate - Text und Binär. Die API unterstützt beide Formate und bietet Funktionen für die Arbeit mit Java-Objekten und Integritätsprüfungsnachrichten (Ping-Pong), wie in der Spezifikation definiert:

  • Text : Alle Textdaten ( java.lang.String , Grundelemente oder ihre entsprechenden Wrapper-Klassen)
  • Binär : Binärdaten (z. B. Audio, Bild usw.), dargestellt durch einen java.nio.ByteBuffer oder ein Byte [] (Byte-Array)
  • Java-Objekte : Die API ermöglicht es, mit nativen (Java-Objekt-) Darstellungen in Ihrem Code zu arbeiten und benutzerdefinierte Transformatoren (Encoder / Decoder) zu verwenden, um sie in kompatible On-Wire-Formate (Text, Binär) zu konvertieren, die vom WebSocket-Protokoll zugelassen werden
  • Ping-Pong : Eine javax.websocket.PongMessage ist eine Bestätigung, die von einem WebSocket-Peer als Antwort auf eine Ping-Anforderung (Health Check) gesendet wird

Für unsere Anwendung verwenden wir Java-Objekte. Wir erstellen die Klassen zum Codieren und Decodieren von Nachrichten.

4.1. Encoder

Ein Encoder nimmt ein Java-Objekt und erzeugt eine typische Darstellung, die für die Übertragung als Nachricht geeignet ist, z. B. JSON, XML oder binäre Darstellung. Encoder können durch Implementierung der Schnittstellen Encoder.Text oder Encoder.Binary verwendet werden.

Im folgenden Code definieren wir die zu codierende Klasse Message und in der Methodencodierung verwenden wir Gson zum Codieren des Java-Objekts in 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. Decoder

Ein Decoder ist das Gegenteil eines Encoders und wird verwendet, um Daten wieder in ein Java-Objekt umzuwandeln. Decoder können über die Schnittstellen Decoder.Text oder Decoder.Binary implementiert werden.

Wie wir beim Encoder gesehen haben, nehmen wir bei der Decodierungsmethode den in der an den Endpunkt gesendeten Nachricht abgerufenen JSON und transformieren ihn mit Gson in eine Java-Klasse namens 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. Einstellen von Encoder und Decoder im Server-Endpunkt

Lassen Sie uns alles zusammenfügen, indem wir die Klassen hinzufügen, die zum Codieren und Decodieren der Daten auf Klassenebene erstellt wurden. Annotation @ServerEndpoint :

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

Jedes Mal, wenn Nachrichten an den Endpunkt gesendet werden, werden sie automatisch entweder in JSON- oder Java-Objekte konvertiert.

5. Schlussfolgerung

In diesem Artikel haben wir uns angesehen, was die Java-API für WebSockets ist und wie sie uns beim Erstellen von Anwendungen wie diesem Echtzeit-Chat helfen kann.

Wir haben die beiden Programmiermodelle zum Erstellen eines Endpunkts gesehen: Anmerkungen und programmatisch. Wir haben einen Endpunkt mithilfe des Anmerkungsmodells für unsere Anwendung zusammen mit den Lebenszyklusmethoden definiert.

Um zwischen Server und Client hin und her kommunizieren zu können, haben wir festgestellt, dass wir Encoder und Decoder benötigen, um Java-Objekte in JSON zu konvertieren und umgekehrt.

Die JSR 356-API ist sehr einfach und das auf Anmerkungen basierende Programmiermodell, mit dem sich WebSocket-Anwendungen sehr einfach erstellen lassen.

Um die im Beispiel erstellte Anwendung auszuführen, müssen Sie lediglich die War-Datei auf einem Webserver bereitstellen und die URL: // localhost: 8080 / java-websocket / aufrufen. Den Link zum Repository finden Sie hier.