Développement d'une communication WebSocket bidirectionnelle en Java

Le protocole WebSocket permet d'établir une communication full-duplex sur une seule connexion TCP. Contrairement au modèle requête-réponse de HTTP, il maintient un canal ouvert, ce qui le rend idéal pour les applications nécessitant des mises à jour de données en temps réel, telles que les messageries instantanées, les tableaux de bord financiers ou les jeux multijoueurs.

Implémentation du serveur avec l'API JSR 356

Java prend en charge WebSocket côté serveur via la spécification JSR 356 (javax.websocket). Cette approche utilise des annotations pour définir les points de terminaison et gérer le cycle de vie des connexions. Notez que cette classe doit être déployée dans un conteneur de servlets compatible (comme Apache Tomcat, Jetty ou Undertow).

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;

@ServerEndpoint("/flux-donnees")
public class ServeurFlux {

    @OnOpen
    public void initialiserConnexion(Session session) {
        System.out.println("Nouvelle session établie : " + session.getId());
    }

    @OnMessage
    public void traiterMessage(String payload, Session session) throws IOException {
        System.out.println("Données reçues du client : " + payload);
        
        // Utilisation de l'envoi asynchrone pour ne pas bloquer le thread du conteneur
        String reponse = "Accusé de réception : " + payload;
        session.getAsyncRemote().sendText(reponse);
    }

    @OnClose
    public void fermerConnexion(Session session, CloseReason motif) {
        System.out.println("Session terminée : " + session.getId() + " | Motif : " + motif.getReasonPhrase());
    }

    @OnError
    public void gererErreur(Session session, Throwable erreur) {
        System.err.println("Erreur sur la session " + session.getId() + " : " + erreur.getMessage());
    }
}

Dans cette implémentation, l'annotation @ServerEndpoint définit l'URI relative du point de terminaison. Les méthodes annotées avec @OnOpen, @OnMessage, @OnClose et @OnError interceptent respectivement les événements de connexion, de réception de données, de déconnexion et d'exception. L'utilisation de getAsyncRemote() est recommandée pour les opérations d'écriture afin d'éviter de bloquer les threads du serveur.

Implémentation du client avec l'API Java 11+

Pour le client, nous pouvons utiliser l'API java.net.http introduite dans Java 11, qui fournit un client HTTP et WebSocket natif et performant. Cette approche élimine la nécessité de dépendances externes pour le client.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.CountDownLatch;

public class ClientFlux implements WebSocket.Listener {
    
    private final CountDownLatch latch = new CountDownLatch(1);

    @Override
    public void onOpen(WebSocket webSocket) {
        System.out.println("Connexion au serveur réussie.");
        // Envoi du premier message au serveur
        webSocket.sendText("Bonjour Serveur WebSocket!", true);
        WebSocket.Listener.super.onOpen(webSocket);
    }

    @Override
    public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
        System.out.println("Réponse du serveur : " + data);
        latch.countDown(); // Libère le thread principal après réception
        webSocket.request(1); // Demande le prochain message
        return WebSocket.Listener.super.onText(webSocket, data, last);
    }

    @Override
    public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {
        System.out.println("Connexion fermée par le serveur. Code : " + statusCode);
        latch.countDown();
        return WebSocket.Listener.super.onClose(webSocket, statusCode, reason);
    }

    @Override
    public void onError(WebSocket webSocket, Throwable error) {
        System.err.println("Erreur de communication : " + error.getMessage());
        latch.countDown();
        WebSocket.Listener.super.onError(webSocket, error);
    }

    public static void main(String[] args) throws InterruptedException {
        ClientFlux client = new ClientFlux();
        HttpClient httpClient = HttpClient.newHttpClient();
        
        WebSocket ws = httpClient.newWebSocketBuilder()
                .buildAsync(URI.create("ws://localhost:8080/flux-donnees"), client)
                .join();

        // Maintient le thread principal actif jusqu'à la réception d'une réponse ou la fermeture
        client.latch.await();
    }
}

Le client implémente l'interface WebSocket.Listener pour gérer les événements de manière asynchrone. La méthode onText traite les messages entrants, tandis que CountDownLatch est utilisé dans la méthode main pour empêcher l'application de se terminer prématurément avant la fin de l'échange.

Déploiement et exécution

Pour exécuter ce système, le serveur (ServeurFlux) doit être compilé et intégré dans une application web (WAR) ou un projet Spring Boot/Quarkus, puis déployé sur un serveur d'applications écoutant sur le port 8080. Une fois le serveur démarré et le point de terminaison /flux-donnees accessible, vous pouvez exécuter la classe ClientFlux directement depuis votre IDE ou via la ligne de commande pour observer l'échange de messages en temps réel.

Étiquettes: Java WebSocket jsr-356 java-http-client real-time

Publié le 11 octobre à 22h09