Java Sockets
Ouvrez des connexions TCP clientes en Java avec la classe Socket.
Sous HTTP et tout autre protocole applicatif se trouve le socket : une connexion TCP brute et bidirectionnelle entre deux endpoints. La classe java.net.Socket de Java représente le côté client — vous en créez une, vous la connectez à un hôte et un port, puis vous lisez et écrivez des octets via des InputStream/OutputStream ordinaires. Il s'agit du niveau de réseau le plus bas auquel vous avez recours lorsque vous implémentez un protocole personnalisé ou que vous communiquez avec un service qui n'est pas HTTP.
Ce chapitre couvre ce qu'un socket connecté vous offre, les deux façons de se connecter (et pourquoi l'une est plus sûre), comment lire et écrire du texte et des octets bruts, un exemple complet exécutable d'un client communiquant avec un serveur, les timeouts et les pièges qui affectent le code réel, et où les sockets se situent par rapport au HttpClient de niveau supérieur que vous avez déjà rencontré.
Ce qu'un Socket vous offre
Un Socket connecté est un tuyau avec deux flux :
socket.getOutputStream()— les octets que vous écrivez voyagent vers l'autre extrémité.socket.getInputStream()— les octets écrits par l'autre extrémité arrivent ici.
TCP garantit que les octets arrivent de manière fiable et dans l'ordre. Il n'impose pas de structure de message — un socket est un flux d'octets, pas de messages. Le cadrage (où un message se termine et où le suivant commence) est votre travail : utilisez des sauts de ligne, des préfixes de longueur, ou un protocole de niveau supérieur. Si vous avez besoin que les limites de messages soient préservées plutôt qu'un flux, c'est le rôle des sockets en datagrammes (UDP), qui échangent l'ordre et la fiabilité contre des paquets discrets.
Les flux proviennent directement du système I/O de Java : getInputStream() retourne un InputStream simple et getOutputStream() un OutputStream simple, donc tous les wrappers que vous connaissez — BufferedReader, PrintWriter, DataInputStream — fonctionnent sur un socket exactement comme sur un fichier.
Connexion
// Style 1: connect in the constructor
Socket socket = new Socket("example.com", 80);
// Style 2: create then connect with a timeout (preferred)
Socket socket = new Socket();
socket.connect(new InetSocketAddress("example.com", 80), 2000);La deuxième forme vous permet de définir un timeout de connexion — sans lui, un hôte inaccessible peut bloquer le thread pendant la durée par défaut du système d'exploitation (souvent une minute ou plus). Une fois connecté, enveloppez les flux dans des readers/writers mis en mémoire tampon et choisissez explicitement un jeu de caractères.
Lecture et écriture de texte
var out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
var in = new BufferedReader(
new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
out.println("ping"); // autoFlush=true sends it immediately
String reply = in.readLine(); // blocks until a line arrivesreadLine() bloque jusqu'à ce que des données (ou la fin du flux) arrivent — la caractéristique de l'API socket bloquante classique. Fermez toujours le socket (try-with-resources) pour libérer la connexion.
Lecture d'octets bruts
Le texte est pratique, mais de nombreux protocoles sont binaires — données d'image, trames préfixées par la longueur, format de transmission personnalisé. Dans ce cas, ignorez les readers et travaillez directement avec les flux :
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress("example.com", 80), 2000);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
out.write("PING".getBytes(StandardCharsets.UTF_8));
out.flush(); // streams are not auto-flushed
byte[] buffer = new byte[4096];
int n = in.read(buffer); // bytes read, or -1 at end-of-stream
if (n > 0) {
String chunk = new String(buffer, 0, n, StandardCharsets.UTF_8);
System.out.println(chunk);
}
}Deux faits conditionnent chaque lecture au niveau des octets :
read(byte[])retourne le nombre d'octets réellement reçus, ce qui n'est pas nécessairement ce que vous avez demandé. Une seule écriture de l'autre côté peut arriver en plusieurs lectures, et plusieurs écritures peuvent arriver comme une seule lecture — TCP fusionne et divise à sa guise. Pour obtenir un nombre fixe d'octets, vous devez boucler, ou envelopper le flux dansDataInputStreamet appelerreadFully().- Une valeur de retour de
-1signifie que le pair a fermé son extrémité (fin de flux), pas "pas de données pour le moment". C'est votre signal pour arrêter de lire.
Les timeouts importants
Un socket bloquant peut se bloquer en deux endroits distincts, et ils nécessitent deux timeouts distincts :
Socket socket = new Socket();
socket.connect(new InetSocketAddress("example.com", 80), 2000); // connect timeout
socket.setSoTimeout(5000); // read timeout- Le timeout de connexion (le deuxième argument de
connect) limite la durée du handshake TCP. Sans lui, un hôte inaccessible bloque le thread pendant la durée par défaut du système d'exploitation — souvent une minute ou plus. - Le timeout de lecture (
setSoTimeout) limite la durée pendant laquelle toutread/readLinepeut bloquer en attendant des données. Lorsqu'il expire, l'appel lèveSocketTimeoutExceptionsans fermer le socket, vous pouvez donc décider de réessayer ou d'abandonner. Sans lui, un pair silencieux vous bloque indéfiniment.
Le code réseau réel devrait définir les deux. Les deux erreurs que vous devez être prêt à intercepter sont UnknownHostException (DNS n'a pas pu résoudre le nom) et la famille générique IOException (connexion refusée, réinitialisée ou expirée) ; voir les exceptions Java pour les modèles de gestion.
Un exemple concret : un client communiquant avec un serveur echo loopback
Ce programme démarre un serveur echo à usage unique sur un thread d'arrière-plan lié à l'adresse loopback, puis — le vrai sujet du chapitre — connecte un Socket client, envoie une ligne et lit la réponse. Il s'agit d'une conversation TCP complète à l'intérieur d'une seule JVM, sans réseau externe.
Ce qu'il faut retenir de l'exécution :
- Le côté client se résume à trois étapes : construire un
Socket, leconnect()à une adresse et un port, puis lire et écrire ses flux. Tout ce que HTTP faisait pour vous dans les chapitres précédents — lignes de requête, en-têtes, codes de statut — a disparu ; un socket déplace des octets bruts et rien de plus. connect(new InetSocketAddress(...), 2000)a défini un timeout de connexion de 2 secondes. Le constructeur sans timeoutnew Socket(host, port)bloquerait sur la durée par défaut du système d'exploitation si l'hôte était inaccessible, donc la forme avec timeout explicite est la meilleure habitude pour tout vrai réseau.- Le protocole était une convention, pas une fonctionnalité : le client a écrit une ligne et en a lu une parce que les deux côtés avaient convenu que les lignes sont des messages. TCP a fourni un flux d'octets ordonné ; le cadrage par saut de ligne qui l'a transformé en "messages" était entièrement défini par l'application.
readLine()a bloqué jusqu'à l'arrivée de la réponse du serveur. Ce modèle un-thread-par-connexion, bloquer-jusqu'aux-données est simple et correct, et c'est exactement le coût que les threads virtuels visent à rendre bon marché lorsque le nombre de connexions augmente.getRemoteSocketAddress()etgetLocalSocketAddress()ont montré les deux extrémités de la connexion active — le port loopback du serveur et le port local attribué par le système d'exploitation au client. Chaque connexion TCP est identifiée par cette paire d'endpoints. LeServerSocketcôté serveur construit l'écouteur qui a accepté cette connexion.
Quand utiliser un socket brut
Un Socket est le bon outil lorsqu'il n'existe pas de bibliothèque qui parle déjà votre protocole :
- Vous implémentez ou consommez un protocole TCP personnalisé (un serveur de jeux, un format de transmission de broker de messages, un port d'administration basé sur des lignes).
- Vous avez besoin de communiquer avec un service non HTTP — SMTP, un protocole texte de style Redis, un serveur de lignes hérité.
Pour tout ce qui est HTTP, ne créez pas manuellement des requêtes via un socket. Utilisez le HttpClient moderne, qui vous offre gratuitement le pooling de connexions, les redirections, HTTP/2 et TLS. La relation est la même qu'entre les flux d'octets bruts et les readers de niveau supérieur construits dessus : descendez au niveau inférieur uniquement lorsque le niveau supérieur ne peut pas exprimer ce dont vous avez besoin.