Préambule
Ce travail pratique vous permettra de découvrir les mécanismes essentiels de la gestion des processus système depuis une application Java. Vous apprendrez à lancer des programmes externes, contrôler leurs flux d’entrée/sortie, et gérer leur cycle de vie.
Étape 0 : Code de base et concepts fondamentaux
Contexte technique
En Java, il existe deux façons principales de lancer un processus externe : ProcessBuilder et Runtime.exec(). La classe ProcessBuilder est l’approche moderne et recommandée, car elle offre un contrôle plus fin sur l’environnement d’exécution, une gestion simplifiée des flux d’entrée/sortie et une API fluide pour préparer et lancer un processus. Par exemple, pour exécuter la commande ls -l sur un système Unix :
ProcessBuilder builder = new ProcessBuilder("ls", "-l");
Process process = builder.start();
À l’inverse, Runtime.exec() est une méthode héritée qui fonctionne toujours mais présente plusieurs limitations : difficulté à gérer des arguments contenant des espaces, contrôle limité de l’environnement, et manipulation des flux moins intuitive. Voici l’équivalent avec Runtime.exec() :
Process process = Runtime.getRuntime().exec("ls -l");
Organisation du projet
OS_Nom1_Nom2/
├─ .gitignore # fichiers à ignorer (*.class, *.jar, etc.)
├─ README.md # informations additionnelles
└─ ProcessManager/
├─ .gitignore
├─ README.md
├─ ProcessController.java # gestionnaire principal des processus
└─ Main.java # Vos tests et démonstrations
ProcessController.java
import java.io.*;
/**
* Gestionnaire principal pour le lancement et le contrôle de processus externes.
* Cette classe encapsule les fonctionnalités de ProcessBuilder en offrant
* une interface simplifiée pour la gestion des processus.
*/
public class ProcessController {
public static final int DEFAULT_TIMEOUT_SECONDS = 30;
// Variables d'instance
private ProcessBuilder processBuilder;
private Process currentProcess;
public ProcessController() {
this.processBuilder = new ProcessBuilder();
this.currentProcess = null;
}
/**
* Lance un processus simple avec une commande et ses arguments.
* Cette méthode constitue le point d'entrée basique pour l'exécution
* de programmes externes.
*
* @param command commande à exécuter (ex: "ls", "python3", "notepad.exe")
* @param args arguments optionnels de la commande
* @return le processus lancé
* @throws IOException si le lancement échoue
*/
public Process executeSimple(String command, String[] args) throws IOException {
// TODO Créer un tableau pour stocker la commande complète
String[] fullCommand = null;
// TODO Si args est null, fullCommand = tableau avec juste command
// TODO Sinon, fullCommand = tableau avec command + tous les args
// TODO Configurer le ProcessBuilder avec fullCommand
// processBuilder.command(fullCommand);
// TODO Lancer le processus avec processBuilder.start()
currentProcess = null;
System.out.println("Lancement de : " + command);
return currentProcess;
}
/**
* Lance un processus avec redirection des flux vers des fichiers.
* Permet de capturer facilement les sorties standard et d'erreur.
*
* @param command commande à exécuter
* @param outputFile fichier pour la sortie standard (null = pas de redirection)
* @param errorFile fichier pour la sortie d'erreur (null = pas de redirection)
* @param args arguments de la commande
* @return le processus configuré et lancé
* @throws IOException si la configuration ou le lancement échoue
*/
public Process executeWithRedirection(String command, File outputFile,
File errorFile, String[] args) throws IOException {
// TODO Utiliser executeSimple pour lancer le processus de base
Process process = null;
// TODO Si outputFile n'est pas null, configurer la redirection
// processBuilder.redirectOutput(outputFile);
// TODO Si errorFile n'est pas null, configurer la redirection d'erreur
// processBuilder.redirectError(errorFile);
System.out.println("Redirection configurée - Sortie: " + outputFile + ", Erreur: " + errorFile);
// TODO Relancer le processus avec les redirections
currentProcess = null;
return currentProcess;
}
/**
* Lance un processus interactif permettant l'envoi de données via l'entrée standard
* et la lecture temps réel des sorties.
*
* @param command commande à lancer
* @param args arguments
* @return le processus interactif
* @throws IOException si le lancement échoue
*/
public Process executeInteractive(String command, String[] args) throws IOException {
// TODO Utiliser executeSimple pour lancer le processus
// (Les flux restent accessibles par défaut)
System.out.println("Mode interactif activé pour : " + command);
return null;
}
/**
* Attend la fin d'exécution d'un processus avec un timeout optionnel.
* Retourne le code de sortie.
*
* @param process processus à attendre
* @param timeoutSeconds délai maximum d'attente (0 = pas de timeout)
* @return code de sortie du processus (-1 si timeout)
* @throws InterruptedException si l'attente est interrompue
*/
public int waitForProcess(Process process, int timeoutSeconds) throws InterruptedException {
if (timeoutSeconds <= 0) {
// TODO Attendre indéfiniment avec process.waitFor()
return 0;
} else {
// TODO Utiliser process.waitFor(timeoutSeconds, java.util.concurrent.TimeUnit.SECONDS)
// TODO Si le processus se termine dans les temps, retourner process.exitValue()
// TODO Sinon, appeler process.destroyForcibly() et retourner -1
return -1;
}
}
/**
* Envoie des données à l'entrée standard d'un processus interactif.
*/
public void sendInput(Process process, String input) throws IOException {
// TODO Obtenir l'OutputStream du processus
OutputStream outputStream = null;
if (outputStream != null) {
// TODO Écrire les données + retour à la ligne
// TODO Appeler flush() pour forcer l'envoi
}
System.out.println("Envoi vers le processus : " + input);
}
/**
* Lit la sortie standard d'un processus de manière non-bloquante.
*/
public String readOutput(Process process) throws IOException {
// TODO Obtenir l'InputStream du processus
InputStream inputStream = null;
if (inputStream != null) {
// TODO Vérifier s'il y a des données avec inputStream.available()
// TODO Si oui, les lire et les retourner comme String
}
return "";
}
// Getters
public Process getCurrentProcess() {
return currentProcess;
}
}
Attention : Les parties marquées TODO constituent le cœur de votre travail. Ne modifiez pas la structure générale des classes, concentrez-vous sur l’implémentation des fonctionnalités demandées.
Étape 1 : Lancement simple de processus
La méthode ProcessBuilder.command() accepte une liste d’arguments où le premier élément est toujours la commande, et les suivants sont ses paramètres. Cette séparation évite les problèmes de parsing des espaces et caractères spéciaux.
Exemple concret :
// Correct
String[] cmd = {"ls", "-la", "/home"};
ProcessBuilder pb = new ProcessBuilder(cmd);
// Aussi correct
ProcessBuilder pb = new ProcessBuilder("ls", "-la", "/home");
// Incorrect (parsing problématique)
ProcessBuilder pb = new ProcessBuilder("ls -la /home");
Complétez la méthode executeSimple() dans ProcessController :
- Créez une
List<String>et ajoutez-y la commande puis tous les arguments - Configurez le
ProcessBuilderavecprocessBuilder.command(listComplete) - Lancez le processus avec
processBuilder.start() - Stockez le processus dans
currentProcesset retournez-le
Test recommandé : Testez avec "echo", "Hello World" sous Linux/Mac ou "cmd", "/c", "echo Hello World" sous Windows.
Étape 2 : Redirection des flux
ProcessBuilder propose plusieurs modes de redirection des flux :
redirectOutput(File): redirige la sortie standard vers un fichierredirectError(File): redirige la sortie d’erreur vers un fichierredirectErrorStream(true): fusionne erreur et sortie standardinheritIO(): hérite des flux du processus parent (affichage direct)
Pourquoi rediriger ? Cela permet de :
- Sauvegarder les résultats d’exécution
- Séparer les messages d’erreur des données utiles
- Éviter que les buffers de sortie se remplissent et bloquent le processus
Complétez la méthode executeWithRedirection() :
- Construisez la liste complète commande + arguments
- Si
outputFilen’est pas null :processBuilder.redirectOutput(outputFile) - Si
errorFilen’est pas null :processBuilder.redirectError(errorFile) - Lancez le processus et retournez-le
Exemple :
ProcessBuilder pb = new ProcessBuilder("ls", "-la", "/");
pb.redirectOutput(new File("listing.txt")); // Sortie dans listing.txt
pb.redirectError(new File("errors.txt")); // Erreurs dans errors.txt
Process process = pb.start();
Étape 3 : Interaction avec des processus
Un processus interactif maintient ses flux d’entrée/sortie ouverts pour permettre un dialogue. Les méthodes clés sont :
process.getOutputStream(): pour envoyer des données AU processusprocess.getInputStream(): pour lire les données DU processusprocess.getErrorStream(): pour lire les erreurs du processus
Attention à la confusion : L’OutputStream vous permet d’écrire VERS le processus (son entrée), tandis que l’InputStream vous permet de lire DEPUIS le processus (sa sortie). Dit autrement c’est la sortie de VOTRE prgramme.
Complétez les méthodes executeInteractive(), sendInput(), et readOutput() :
Pour executeInteractive() :
- Ne configurez aucune redirection automatique (pas de
redirectOutput()) - Lancez simplement le processus pour garder les flux accessibles
Pour sendInput() :
- Obtenez l’OutputStream du processus
- Écrivez les données +
\n - Appelez
flush()pour l’envoi immédiat
Pour readOutput() :
- Obtenez l’InputStream du processus
- Vérifiez
available() > 0pour éviter le blocage - Lisez les données disponibles dans un buffer
Test interactif : Lancez python3 ou un autre programme interactif et envoyez-lui des instructions comme print("Hello").
Étape 4 : Synchronisation et terminaison des processus
Chaque processus se termine avec un code de sortie (exit code) :
0: succèsnon-zéro: erreur (la valeur indique le type d’erreur)
La méthodewaitFor()bloque jusqu’à la fin du processus, tandis quewaitFor(timeout, unit)permet d’éviter l’attente infinie.
Gestion des processus bloqués : Si un timeout survient, il faut impérativement appeler process.destroy() ou process.destroyForcibly() pour libérer les ressources système.
Complétez la méthode waitForProcess() :
- Si
timeoutSeconds == 0: utilisezprocess.waitFor()et retournez le code - Sinon : utilisez
process.waitFor(timeoutSeconds, TimeUnit.SECONDS) - Si
waitFor()retournetrue: le processus s’est terminé, retournezprocess.exitValue() - Si
waitFor()retournefalse: timeout, appelezprocess.destroyForcibly()et lancezTimeoutException
Test de timeout : Lancez sleep 60 avec un timeout de 2 secondes pour vérifier la gestion.