CodeGym /Cours /JAVA 25 SELF /Interaction interactive avec des processus

Interaction interactive avec des processus

JAVA 25 SELF
Niveau 61 , Leçon 3
Disponible

1. Introduction

Un processus interactif est un programme qui ne se contente pas d’exécuter quelque chose tout seul, mais attend que vous « discutiez » avec lui. Il reçoit la saisie de l’utilisateur et répond en retour.

Exemples classiques — des interpréteurs comme Python, bash ou PowerShell. Ils patiently attendent des commandes, les exécutent et affichent le résultat. Il y en a d’autres : des utilitaires interactifs comme une calculatrice, des bases de données en console (psql, sqlite3) ou même des éditeurs de texte comme vi et nano. Parfois, un script ordinaire peut aussi devenir interactif s’il vous demande des paramètres ou des réponses pendant son exécution.

Lancer de tels processus depuis Java — ce n’est pas une promenade de santé. Il ne suffit pas de « donner une commande et récupérer le résultat ». Il faut établir un véritable dialogue : envoyer les données à temps et lire les réponses, comme dans une conversation réelle.

Imaginez que vous écriviez à un ami dans une messagerie. Vous envoyez un message, attendez la réponse, puis vous écrivez à nouveau. C’est à peu près ainsi que ressemble l’interaction de Java avec un processus externe, si vous configurez correctement le travail avec les flux.

Organisation d’un échange bidirectionnel

Chaque processus dispose de trois « canaux de communication » : l’entrée (stdin), la sortie (stdout) et les erreurs (stderr). Par le premier, vous pouvez transmettre quelque chose au processus, par le second — recevoir le résultat, et le troisième sert aux messages d’erreur.

L’essentiel — ne pas laisser le processus « se figer » en attente. Si vous ne lisez que la sortie normale et ignorez le flux d’erreurs, le processus peut se bloquer, comme s’il attendait que vous prêtiez attention à ses plaintes. Et inversement : si vous envoyez quelque chose sur l’entrée sans lire les réponses, le processus peut se retrouver bloqué, ne sachant pas où mettre les données accumulées.

Ainsi, lors du travail avec des processus interactifs, il est important de maintenir un échange bidirectionnel — comme dans une conversation normale où les deux parties écoutent et répondent, et ne parlent pas dans le vide.

Schéma d’échange interactif

+---------------------+
|   Programme Java    |
+---------------------+
   |           ^
   v           |
stdin      stdout/stderr
   |           ^
+---------------------+
| Processus (ext)     |
+---------------------+
  • Java écrit dans le flux d’entrée du processus (stdin du processus).
  • Java lit depuis stdout et stderr du processus.
  • Tout cela peut se produire simultanément !

2. Pratique : échange interactif avec un processus externe

Examinons un exemple pratique : nous allons lancer un processus externe (par exemple, l’interpréteur Python ou un simple script echo), lui envoyer des lignes et lire les réponses.

Exemple 1 : lancer un script Python qui attend une saisie

Commençons par créer un script Python simple (nommons-le echo_bot.py) :

# echo_bot.py
while True:
    try:
        line = input()
        if line == "exit":
            print("Bye!")
            break
        print("Echo:", line)
    except EOFError:
        break

Ce script attend une saisie, et pour chaque ligne il répond "Echo: ...". Si vous entrez "exit", il termine son travail.

Comment lancer et « discuter » avec ce script depuis Java ?

1. Démarrer le processus
ProcessBuilder builder = new ProcessBuilder("python", "echo_bot.py");
Process process = builder.start();
2. Préparer les flux pour communiquer
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
3. Mettre en place le dialogue
// Envoyer une ligne au processus
writer.write("Hello, process!\n");
writer.flush();

// Lire la réponse
String response = reader.readLine();
System.out.println("Réponse du processus: " + response);
4. Répétez jusqu’à ce que vous en ayez assez

Mettons cela sous forme d’une boucle simple afin que l’utilisateur puisse écrire dans la console, Java — envoyer cela au processus et afficher la réponse.

Exemple complet : chat Java avec un processus externe

import java.io.*;

public class InteractiveProcessDemo {
    public static void main(String[] args) throws IOException {
        ProcessBuilder builder = new ProcessBuilder("python", "echo_bot.py");
        Process process = builder.start();

        // Flux pour communiquer avec le processus
        BufferedWriter toProcess = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
        BufferedReader fromProcess = new BufferedReader(new InputStreamReader(process.getInputStream()));
        BufferedReader userInput = new BufferedReader(new InputStreamReader(System.in));

        System.out.println("Saisissez des lignes à envoyer au processus (exit pour quitter):");

        while (true) {
            // Lire une ligne de l'utilisateur
            String line = userInput.readLine();
            if (line == null) break;

            // L'envoyer au processus
            toProcess.write(line + "\n");
            toProcess.flush();

            // Lire la réponse du processus
            String response = fromProcess.readLine();
            System.out.println("Réponse du processus: " + response);

            if ("exit".equals(line)) break;
        }

        // Terminer le processus
        process.destroy();
    }
}

Commentaires sur le code :

  • Nous utilisons trois flux : pour la lecture depuis l’utilisateur (System.in), pour l’écriture vers le processus (process.getOutputStream()) et pour la lecture de la réponse (process.getInputStream()).
  • Après chaque envoi d’une ligne au processus, nous lisons immédiatement la réponse via readLine().
  • Si l’utilisateur saisit "exit", le programme termine la boucle et détruit le processus (process.destroy()).

3. Pourquoi la lecture et l’écriture simultanées sont-elles importantes ?

Dans les tâches réelles, le processus peut produire beaucoup de données, et parfois demander l’entrée plusieurs fois de suite. Si la sortie du processus n’est pas lue à temps, son tampon interne peut se remplir et le processus « se bloquer », en attendant que quelqu’un lise ses messages. De même, si vous n’envoyez pas de données à l’entrée, le processus peut attendre et « se figer » aussi.

Deadlock : quand tout est bloqué

Deadlock — c’est une situation où deux processus (ou un thread et un processus) s’attendent mutuellement, et personne ne peut continuer à travailler.

Exemple de deadlock :

  • Java attend que le processus écrive quelque chose dans stdout.
  • Le processus attend que Java lui écrive quelque chose sur stdin.
  • Les deux attendent — personne ne travaille.

Solution : threads séparés pour la lecture et l’écriture

Pour éviter un deadlock, on utilise souvent des threads séparés (Thread) pour lire simultanément stdout et stderr du processus. Par exemple, on peut créer deux threads : l’un lit stdout, l’autre — stderr, et le thread principal s’occupe d’écrire sur stdin.

Exemple minimal avec un thread séparé pour stderr

// Lire stderr dans un thread séparé
Thread errorThread = new Thread(() -> {
    try (BufferedReader errorReader = new BufferedReader(
            new InputStreamReader(process.getErrorStream()))) {
        String line;
        while ((line = errorReader.readLine()) != null) {
            System.err.println("stderr: " + line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
});
errorThread.start();

4. Pratique : échange interactif avec une calculatrice

Nous n’avons pas toujours Python. Essayons de lancer une commande standard présente presque partout — par exemple une calculatrice en ligne de commande. Sous Linux/Mac, cela peut être bc (basic calculator), sous Windows — cmd ou powershell.

Exemple : interaction avec bc (Linux/Mac)

ProcessBuilder builder = new ProcessBuilder("bc");
Process process = builder.start();

BufferedWriter toProcess = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
BufferedReader fromProcess = new BufferedReader(new InputStreamReader(process.getInputStream()));

toProcess.write("2 + 2\n");
toProcess.flush();

String result = fromProcess.readLine();
System.out.println("Réponse de la calculatrice: " + result);

toProcess.write("quit\n");
toProcess.flush();

process.destroy();

Sous Windows : vous pouvez essayer de lancer cmd et lui transmettre des commandes, mais ce sera plus compliqué (syntaxe différente). Pour une démonstration de l’interactivité, on utilise généralement des utilitaires disponibles sur les deux plates-formes, ou on écrit ses propres mini-scripts.

5. Problèmes et pièges

Mise en mémoire tampon des flux
Il arrive que le processus ne produise pas les données immédiatement, mais les accumule dans un tampon interne et « les délivre » uniquement quand le tampon est rempli ou après avoir reçu un saut de ligne ("\n"). Cela peut faire que vous ne voyiez pas de réponse pendant un moment, même si le processus l’a déjà « préparée ».

Conseil :

  • Terminez toujours les lignes par le caractère "\n" lors de l’écriture sur le stdin du processus.
  • Si vous écrivez vos propres scripts, utilisez flush() après chaque sortie.

Deadlock en cas de mauvaise organisation des flux
Si vous ne lisez pas stderr et que le processus y écrit beaucoup de données — il peut se bloquer en attendant que quelqu’un lise les erreurs.

Conseil :
Lisez toujours à la fois stdout et stderr du processus, de préférence dans des threads séparés.

Le processus s’est terminé — et vous écrivez encore
Si le processus est déjà terminé et que vous continuez d’écrire sur son entrée — vous obtiendrez une IOException : "Stream closed".

7. Bonnes pratiques : interaction avec des processus interactifs

Utiliser ExecutorService pour la lecture en parallèle

Plutôt que de gérer à la main des threads pour lire la sortie et les erreurs, il est pratique d’utiliser un ExecutorService (par exemple, un pool de deux threads) :

import java.util.concurrent.*;

ExecutorService executor = Executors.newFixedThreadPool(2);

executor.submit(() -> {
    try (BufferedReader out = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
        String line;
        while ((line = out.readLine()) != null) {
            System.out.println("stdout: " + line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
});

executor.submit(() -> {
    try (BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
        String line;
        while ((line = err.readLine()) != null) {
            System.err.println("stderr: " + line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
});

Fermeture correcte des flux

Après avoir terminé le travail avec le processus, fermez impérativement tous les flux (stdin, stdout, stderr) pour éviter les fuites de ressources.

toProcess.close();
fromProcess.close();
process.destroy();
executor.shutdown();

8. Exemple final : échange interactif avec un processus externe

Mettons tout ensemble. L’exemple ci-dessous fonctionne comme un « chat » interactif entre l’utilisateur et un processus externe (par exemple, un script Python ou une calculatrice).

import java.io.*;
import java.util.concurrent.*;

public class InteractiveProcessUniversal {
    public static void main(String[] args) throws IOException {
        // Remplacez par votre commande — par exemple, "python echo_bot.py" ou "bc"
        ProcessBuilder builder = new ProcessBuilder("python", "echo_bot.py");
        Process process = builder.start();

        ExecutorService executor = Executors.newFixedThreadPool(2);

        // Lecture du stdout du processus
        executor.submit(() -> {
            try (BufferedReader out = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = out.readLine()) != null) {
                    System.out.println("[process] " + line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        // Lecture du stderr du processus
        executor.submit(() -> {
            try (BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                String line;
                while ((line = err.readLine()) != null) {
                    System.err.println("[process-err] " + line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        // Thread principal: écrit sur le stdin du processus
        try (BufferedWriter toProcess = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
             BufferedReader userInput = new BufferedReader(new InputStreamReader(System.in))) {

            System.out.println("Saisissez des lignes pour le processus (exit pour quitter):");
            String line;
            while ((line = userInput.readLine()) != null) {
                toProcess.write(line + "\n");
                toProcess.flush();
                if ("exit".equals(line)) break;
            }
        }

        process.destroy();
        executor.shutdown();
    }
}

9. Erreurs courantes lors du travail interactif avec des processus

Erreur n° 1 : stderr du processus n’est pas lu. Si le processus écrit beaucoup d’erreurs mais que stderr n’est pas lu, le processus peut se bloquer. Même si vous êtes sûr qu’il n’y aura pas d’erreurs — lisez stderr !

Erreur n° 2 : les flux ne sont pas fermés. Si vous ne fermez pas les flux du processus, il peut y avoir des fuites de ressources, et parfois un blocage de la fin du processus.

Erreur n° 3 : différences de commandes selon la plate-forme. Les commandes et leur syntaxe diffèrent entre Windows et les systèmes de type Unix. Vérifiez l’OS et adaptez la commande en conséquence.

Erreur n° 4 : le programme « se fige » — la sortie n’est pas flushée. Si vous oubliez d’appeler flush() après l’écriture dans un flux, les données peuvent « rester » dans le tampon et ne pas parvenir au processus.

Erreur n° 5 : mise en tampon de la sortie du processus externe. Parfois, le processus ne produit pas les données tant qu’il n’a pas accumulé suffisamment de caractères ou reçu "\n". Si vous écrivez vos propres scripts — utilisez flush().

Erreur n° 6 : deadlock dû à une attente simultanée d’entrée/sortie. Si le thread principal attend la sortie du processus tandis que le processus attend une entrée, les deux peuvent « se figer ». Utilisez des threads séparés pour la lecture/écriture.

Erreur n° 7 : exception « Stream closed » lors de l’écriture vers un processus terminé. Vérifiez que le processus est vivant avant d’écrire sur son stdin. En cas d’erreur, vous obtiendrez une IOException : "Stream closed".

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION