W3docs

Java ServerSocket

Acceptez des connexions TCP entrantes en Java avec ServerSocket et créez un serveur simple.

L'extrémité client d'une connexion TCP est Socket. L'extrémité serveur est java.net.ServerSocket : elle se lie à un port, écoute les connexions entrantes et vous fournit un Socket ordinaire pour chaque client qui se connecte. Un seul ServerSocket accepte de nombreux clients ; chaque appel à accept() retourne une connexion distincte.

Ce chapitre explique comment lier et accepter des connexions, comment servir plusieurs clients simultanément, le rôle du backlog de connexions, et la discipline de cycle de vie qu'un serveur à longue durée de vie nécessite.

Lier, accepter, servir

ServerSocket server = new ServerSocket(8080);   // bind to port 8080
while (true) {
    Socket client = server.accept();            // blocks until a client connects
    handle(client);                             // read/write the client's streams
}
  • new ServerSocket(port) lie et commence à écouter. Utilisez 0 pour laisser l'OS choisir un port libre (puis relisez-le avec getLocalPort()).
  • accept() bloque jusqu'à ce qu'un client se connecte, puis retourne un Socket représentant cette connexion. Le socket serveur lui-même continue d'écouter pour le suivant.

La connexion retournée par accept() est un Socket ordinaire — identique à celui qu'un client crée — donc la lecture et l'écriture de ses flux fonctionnent exactement de la même façon.

Par défaut, accept() bloque indéfiniment. Appelez server.setSoTimeout(ms) au préalable si vous souhaitez qu'il lève SocketTimeoutException après un délai d'attente, ce qui permet à un thread serveur de vérifier un indicateur « dois-je continuer à tourner ? » au lieu de se bloquer sur un port silencieux.

Gestion des clients en parallèle

accept() est bloquant et chaque client peut maintenir sa connexion ouverte, donc une boucle mono-thread ne peut servir qu'un seul client à la fois. La solution classique est un thread (ou une tâche du pool) par connexion :

ExecutorService pool = Executors.newFixedThreadPool(8);
while (running) {
    Socket client = server.accept();
    pool.submit(() -> handle(client));   // serve this client on a worker thread
}

La boucle d'acceptation reste libre de prendre la connexion suivante pendant que les workers servent les connexions existantes. Consultez le framework Executor et les pools de threads pour savoir comment dimensionner et gérer le pool. Avec les threads virtuels (Java 21+), « un thread par connexion » reste peu coûteux même avec des dizaines de milliers de clients, vous pouvez donc souvent vous passer du pool et soumettre chaque connexion à son propre thread virtuel.

Le backlog

new ServerSocket(port, backlog) définit le backlog — combien de connexions l'OS peut mettre en file d'attente pendant que votre code est occupé entre les appels à accept(). Au-delà, les nouvelles connexions sont refusées. La valeur par défaut est généralement 50.

Un constructeur à quatre arguments ajoute l'adresse de liaison : new ServerSocket(port, backlog, address). Passer InetAddress.getLoopbackAddress() rend le serveur accessible uniquement depuis la même machine (127.0.0.1) — pratique pour les services locaux uniquement et pour l'exemple ci-dessous.

Libérer le port : SO_REUSEADDR

Lorsqu'un serveur s'arrête, son port d'écoute peut rester dans l'OS dans un état TIME_WAIT, et un redémarrage peut échouer avec « Adresse déjà utilisée ». Définir SO_REUSEADDR permet à un nouveau ServerSocket de se lier à un port encore en TIME_WAIT :

ServerSocket server = new ServerSocket();      // unbound
server.setReuseAddress(true);
server.bind(new InetSocketAddress(8080));      // now bind explicitly

C'est pourquoi les serveurs en production créent généralement un ServerSocket non lié, définissent les options, puis appellent bind() — plutôt que de passer le port au constructeur.

Un exemple complet : un serveur loopback concurrent

Ce programme lie un ServerSocket à l'interface loopback, accepte trois clients sur un thread en arrière-plan, et sert chacun sur un pool de threads — puis envoie trois clients vers lui. Chaque worker accueille son client et nomme le thread qui l'a servi, rendant ainsi la simultanéité visible.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • Le travail du serveur est entièrement : lier → accept() → servir, en boucle. accept() s'est bloqué jusqu'à ce que chaque client se connecte, puis a retourné un Socket ordinaire pour ce seul client, tandis que le ServerSocket est resté ouvert pour accepter le suivant — un seul écouteur, de nombreuses connexions.
  • Se lier au port 0 a laissé l'OS choisir un port libre, relu via getLocalPort() et transmis aux clients. Le troisième argument du constructeur a également épinglé le serveur à getLoopbackAddress(), de sorte qu'il n'écoutait que sur 127.0.0.1 — la même paire adresse-port que les clients ont composée.
  • Chaque connexion acceptée a été confiée à un pool de threads, de sorte que la boucle d'acceptation ne s'est jamais bloquée à servir un client lent. Les réponses nommaient différents threads workers (pool-1-thread-1, -2, -3), rendant concrète la simultanéité par connexion : trois clients ont été servis en parallèle, pas l'un après l'autre.
  • handle() utilisait try-with-resources sur le socket client (try (client; …)), garantissant que chaque connexion est fermée après son échange. Un serveur qui oublie de fermer les sockets acceptés perd des descripteurs rapidement, car il en ouvre un par client.
  • L'arrêt était explicite et ordonné : arrêter d'accepter, pool.shutdown(), attendre la fin, puis server.close(). Un serveur à longue durée de vie doit libérer délibérément son port d'écoute, et les tâches workers en cours doivent être autorisées à se terminer — la même discipline s'applique de cette démo à trois clients jusqu'à un vrai serveur.

Quand utiliser ServerSocket

ServerSocket est le bon outil lorsque vous avez besoin d'un serveur orienté connexion (TCP) que vous contrôlez au niveau octet/flux : un protocole personnalisé, un backend de chat ou de jeu, un proxy, ou un exercice d'apprentissage. Pour les requêtes/réponses sur HTTP, vous utiliseriez normalement un framework serveur de plus haut niveau plutôt que d'écrire la boucle d'acceptation à la main. Si vous avez besoin de messagerie sans connexion (UDP) — où il n'y a pas d'accept() et chaque paquet est indépendant — utilisez les sockets datagrammes. Pour des informations générales sur les adresses, ports et la pile protocolaire, consultez l'introduction au réseau.

Pratique

Pratique
Un serveur utilise une boucle mono-thread : 'while (true) { Socket c = server.accept(); handle(c); }' où 'handle' maintient la connexion ouverte pendant toute la session du client. Sous charge, de nouveaux clients se connectent mais n'obtiennent aucune réponse tant que les précédents ne se déconnectent pas. Quelle est la cause et la solution standard ?
Un serveur utilise une boucle mono-thread : 'while (true) { Socket c = server.accept(); handle(c); }' où 'handle' maintient la connexion ouverte pendant toute la session du client. Sous charge, de nouveaux clients se connectent mais n'obtiennent aucune réponse tant que les précédents ne se déconnectent pas. Quelle est la cause et la solution standard ?
Was this page helpful?