Préambule
Nous avons étudié les processus comme unités d’exécution indépendantes avec leur propre espace mémoire. Les threads constituent une évolution de ce concept en permettant le partage de ressources au sein d’un même processus. Cependant, cette solution reste limitée aux threads d’un même processus sur une seule machine. Les sockets offrent une solution élégante pour la communication entre processus séparés, localement ou à travers le réseau.
L’isolation mémoire des processus, bien qu’essentielle pour la stabilité et la sécurité du système, crée un défi fondamental : comment permettre à des processus indépendants d’échanger des données efficacement ? Cette problématique a donné naissance aux mécanismes de Communication Inter-Processus (IPC - Inter-Process Communication), dont les sockets constituent l’une des solutions les plus universelles.
Introduction aux Sockets
Concept Fondamental
Un socket est un point d’extrémité de communication bidirectionnelle entre deux processus, qu’ils s’exécutent sur la même machine ou sur des machines distantes. Cette abstraction révolutionnaire unifie la communication locale et réseau sous une API cohérente, permettant aux développeurs de traiter indifféremment les connexions locales et distantes.
Les sockets implémentent naturellement le paradigme client-serveur, modèle architectural fondamental de la communication réseau moderne. Cette approche asymétrique définit des rôles clairs et des responsabilités distinctes.
Types de Sockets et Domaines de Communication
Domaines de Communication :
- AF_INET (Internet Protocol v4) : Communication via réseau IP standard, adressage par IP + Port, support mondial
- AF_INET6 (Internet Protocol v6) : Évolution d’IPv4 avec adresses 128 bits et fonctionnalités avancées
- AF_UNIX/AF_LOCAL (Unix Domain Sockets) : Communication locale uniquement, performance optimale, sécurité renforcée
Protocoles de Transport :
- SOCK_STREAM (TCP) : Connexion orientée, fiabilité garantie, ordre préservé, contrôle de flux
- SOCK_DGRAM (UDP) : Sans connexion, rapide, non-fiable, idéal pour applications temps-réel
Implémentation Java : API Socket
Socket TCP avec InputStream/OutputStream
Java propose une approche orientée flux pour la gestion des sockets via InputStream et OutputStream, unifiant l’accès aux données réseau avec le modèle de fichiers.
Serveur TCP basique :
import java.net.*;
import java.io.*;
// Exemple adapté de la documentation Oracle
try (ServerSocket serverSocket = new ServerSocket(4444)) {
Socket clientSocket = serverSocket.accept();
// Obtention des flux d'E/S
InputStream input = clientSocket.getInputStream();
OutputStream output = clientSocket.getOutputStream();
// Lecture des données client
BufferedReader in = new BufferedReader(new InputStreamReader(input));
PrintWriter out = new PrintWriter(output, true);
String inputLine = in.readLine();
out.println("Echo: " + inputLine);
}
Client TCP :
import java.net.*;
import java.io.*;
// Communication client selon documentation Oracle
try (Socket socket = new Socket("hostname", 4444)) {
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
out.println("Hello Server");
String response = in.readLine();
System.out.println("Server response: " + response);
}
Socket UDP avec DatagramPacket
UDP utilise un modèle de datagrammes indépendants :
Serveur UDP :
import java.net.*;
try (DatagramSocket socket = new DatagramSocket(4445)) {
byte[] buffer = new byte[256];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
// Traitement et réponse
InetAddress address = packet.getAddress();
int port = packet.getPort();
String response = "Hello Client";
DatagramPacket responsePacket = new DatagramPacket(
response.getBytes(), response.length(), address, port);
socket.send(responsePacket);
}
Client UDP :
import java.net.*;
try (DatagramSocket socket = new DatagramSocket()) {
InetAddress address = InetAddress.getByName("hostname");
String message = "Hello Server";
DatagramPacket packet = new DatagramPacket(
message.getBytes(), message.length(), address, 4445);
socket.send(packet);
// Réception de la réponse
byte[] buffer = new byte[256];
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
}
Unix Domain Sockets (Java 16+)
Depuis Java 16, les Unix Domain Sockets sont nativement supportés :
import java.net.*;
import java.nio.channels.*;
import java.nio.file.Path;
// Serveur UDS
Path socketPath = Path.of("/tmp/java.sock");
UnixDomainSocketAddress address = UnixDomainSocketAddress.of(socketPath);
try (ServerSocketChannel serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX)) {
serverChannel.bind(address);
SocketChannel clientChannel = serverChannel.accept();
// Communication via channels
}
// Client UDS
try (SocketChannel channel = SocketChannel.open(StandardProtocolFamily.UNIX)) {
channel.connect(UnixDomainSocketAddress.of(socketPath));
// Communication établie
}
Communication via Pipes
Pipes Anonymes avec ProcessBuilder
Java permet la communication via pipes entre processus avec ProcessBuilder :
import java.io.*;
ProcessBuilder pb = new ProcessBuilder("grep", "pattern");
Process process = pb.start();
// Écriture vers l'entrée du processus
try (PrintWriter writer = new PrintWriter(process.getOutputStream())) {
writer.println("line with pattern");
writer.println("another line");
}
// Lecture depuis la sortie du processus
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Output: " + line);
}
}
Pipes Nommés (Named Pipes/FIFOs)
Sur systèmes Unix, les pipes nommés peuvent être utilisés via l’API fichier standard :
import java.io.*;
import java.nio.file.*;
// Création du pipe nommé (via commande système)
ProcessBuilder.start("mkfifo", "/tmp/mypipe").waitFor();
// Écriture dans le pipe
try (FileWriter writer = new FileWriter("/tmp/mypipe")) {
writer.write("Message via pipe nommé\n");
}
// Lecture du pipe (dans un autre processus)
try (BufferedReader reader = Files.newBufferedReader(
Paths.get("/tmp/mypipe"))) {
String message = reader.readLine();
System.out.println("Reçu: " + message);
}
Gestion des Ressources et Bonnes Pratiques
Try-with-Resources
Java recommande l’utilisation de try-with-resources pour la gestion automatique des sockets :
// Gestion automatique des ressources
try (ServerSocket serverSocket = new ServerSocket(port);
Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(
clientSocket.getOutputStream(), true)) {
// Communication
String inputLine = in.readLine();
out.println("Response: " + inputLine);
} // Fermeture automatique des ressources
Gestion des Exceptions Réseau
try {
Socket socket = new Socket("hostname", port);
// Communication
} catch (UnknownHostException e) {
System.err.println("Hôte inconnu: " + e.getMessage());
} catch (IOException e) {
System.err.println("Erreur I/O: " + e.getMessage());
} catch (SecurityException e) {
System.err.println("Accès refusé: " + e.getMessage());
}
Références et Ressources
Documentation Oracle Java
- Socket Programming - Guide officiel Oracle sur les sockets Java
- DatagramSocket API - Documentation UDP Java
- Unix Domain Sockets - Guide Java 16+ pour UDS
- ProcessBuilder - API de gestion de processus
- NIO.2 File API - Manipulation de fichiers et pipes
- Try-with-Resources - Gestion automatique des ressources