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.