Ein Handbuch zur Java-API für WebSocket

Eine Anleitung zur Java-API für WebSocket

1. Überblick

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 Client-seitige Komponenten:

  • Server: alles im Paketjavax.websocket.server.

  • Client: Der Inhalt des Paketsjavax.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 Personen kommunizieren, die mit dem Chat verbunden sind.

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


    javax.websocket
    javax.websocket-api
    1.1

Die neueste Version kannhere gefunden werden.

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


    com.google.code.gson
    gson
    2.8.0

Die neueste Version ist im Repository vonMaven Centralverfügbar.

3.1. Endpunktkonfiguration

Es gibt zwei Möglichkeiten, Endpunkte zu konfigurieren:annotation-basiert und erweiterungsbasiert. Sie können entweder die Klassejavax.websocket.Endpointerweitern oder dedizierte Annotationen auf Methodenebene verwenden. Da das Annotationsmodell im Vergleich zum programmatischen Modell zu einem saubereren Code führt, ist die Annotation zur herkömmlichen Wahl der Codierung geworden. In diesem Fall werden WebSocket-Endpunkt-Lebenszyklusereignisse von den folgenden Anmerkungen behandelt:

  • @ServerEndpoint: Wenn der Container mit@ServerEndpoint, dekoriert ist, stellt er die Verfügbarkeit der Klasse alsWebSocket-Server sicher, der einen bestimmten URI-Bereich abhört

  • @ClientEndpoint: Eine mit dieser Annotation dekorierte Klasse wird alsWebSocket-Client behandelt

  • @OnOpen: Eine Java-Methode mit@OnOpen wird vom Container aufgerufen, wenn eine neueWebSocket-Verbindung initiiert wird

  • @OnMessage: Eine mit@OnMessage, versehene Java-Methode empfängt die Informationen aus dem ContainerWebSocket, 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 Verbindung vonWebSocketgeschlossen wird

3.2. Schreiben des Server-Endpunkts

Wir deklarieren den Serverendpunkt einer Java-KlasseWebSocket, indem wir ihn mit@ServerEndpoint versehen. Wir geben auch den URI an, unter dem der Endpunkt bereitgestellt wird. Der URI wird 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-Gerüst für unsere Chat-ähnliche Anwendung. Wie Sie sehen können, haben wir 4 Anmerkungen, die den jeweiligen Methoden zugeordnet sind. Nachfolgend 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 mit der Methodebroadcastan 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@OnErrorbehandelt. Mit dieser Methode können Sie die Informationen zu dem 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 drahtgebundene Datenformate - Text und Binär. Die API unterstützt beide Formate und fügt Funktionen für die Arbeit mit Java-Objekten und Integritätsprüfungsnachrichten (Ping-Pong) hinzu, wie in der Spezifikation definiert:

  • Text: Alle Textdaten (java.lang.String, Grundelemente oder ihre entsprechenden Wrapper-Klassen)

  • Binary: Binärdaten (z. Audio, Bild usw.) dargestellt durch einjava.nio.ByteBuffer oder einbyte[] (Byte-Array)

  • Java objects: 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 zugelassen werden Protokoll

  • Ping-Pong: Einjavax.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 wirJava Objects.. Wir erstellen die Klassen zum Codieren und Decodieren von Nachrichten.

4.1. Encoder

Ein Encoder nimmt ein Java-Objekt und erstellt eine typische Darstellung, die für die Übertragung als Nachricht geeignet ist, z. B. als JSON-, XML- oder Binärdarstellung. Encoder können durch Implementieren der SchnittstellenEncoder.Text<T> oderEncoder.Binary<T> verwendet werden.

Im folgenden Code definieren wir die zu codierende KlasseMessage und in der Methodeencode 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 SchnittstellenDecoder.Text<T> oderDecoder.Binary<T> implementiert werden.

Wie wir beim Encoder gesehen haben, verwenden wir bei der Methodedecodeden in der an den Endpunkt gesendeten Nachricht abgerufenen JSON und transformieren ihn mit Gson in eine Java-Klasse namensMessage:

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

Fügen wir alles zusammen, 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 in JSON- oder Java-Objekte konvertiert.

5. Fazit

In diesem Artikel haben wir uns mit der Java-API für WebSockets befasst und erklärt, 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 in der Lage zu sein, zwischen dem Server und dem Client hin und her zu kommunizieren, 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 Annotationen basierende Programmiermodell, das das Erstellen von WebSocket-Anwendungen sehr einfach macht.

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