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.

sequenceDiagram participant C as 👤 Client participant OS as 🖥️ OS Client participant Network as 🌐 Réseau participant OSS as 🖥️ OS Serveur participant S as 🏢 Serveur Note over S: Phase d'Initialisation S->>OSS: socket() - Créer socket S->>OSS: bind() - Associer port S->>OSS: listen() - Mode écoute Note over S,C: Phase de Connexion S->>OSS: accept() - Attendre clients Note over OSS: Serveur en attente... C->>OS: socket() - Créer socket C->>OS: connect(IP, Port) OS->>Network: SYN (demande connexion) Network->>OSS: SYN OSS->>S: Nouvelle connexion détectée Note over OSS,OS: Handshake TCP (3-way) OSS->>Network: SYN-ACK Network->>OS: SYN-ACK OS->>Network: ACK Network->>OSS: ACK S->>OSS: accept() retourne socket client Note over S,C: Phase de Communication C->>OS: send(données) OS->>Network: Paquet TCP Network->>OSS: Paquet TCP OSS->>S: recv() reçoit données S->>OSS: send(réponse) OSS->>Network: Paquet TCP Network->>OS: Paquet TCP OS->>C: recv() reçoit réponse Note over S,C: Phase de Fermeture C->>OS: close() OS->>Network: FIN Network->>OSS: FIN S->>OSS: close()

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