<div class="page"> <div class="cover text-center"> <img class="mx-auto" src=/itb/images/logo_mislata.png alt="logo"> # Comunicació entre processos <div class="text-end fit-content ms-auto my-3 mt-auto pt-3"> <p><strong>Autor:</strong> Joan Puigcerver Ibáñez</p> <p><strong>Correu electrònic:</strong> j.puigcerveribanez@edu.gva.es</p> <p><strong>Curs:</strong> 2024/2025</p> </div> <div> <p class="fw-bold mb-0">Llicència: BY-NC-SA</p> <p class="d-none d-md-block">(Reconeixement - No Comercial - Compartir Igual)</p> <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.ca" target="_blank"> <img class="mx-auto" src="/itb/images/license.png" alt="Licence"/> </a> </div><!--license--> </div><!--cover--> </div><!--page--> {:toc} ::: warning Aquest material està realitzat per a sistemes __Linux__. En sistemes __Windows__, podeu executar els exemples mitjançant WSL: ```java ProcessBuilder pb = new ProcessBuilder({"wsl.exe", "program", "arg1", "arg"}); ``` ::: ## Flux de dades estàndard Els __fluxos de dades estàndard__ son uns canals de comunicació interconectats entre un programa i el seu entorn en moment d'execució. Hi ha tres canals d'E/S: __l'entrada estàndard (standard input o stdin)__, __l'eixida estàndard (standard output o stdout)__ i __l'eixida d'error (standard error o (stderr)__. Aquests fluxos estan connectats a la consola del sistema. Quan un programa s'executa mitjançant una _shell interactiva_, aquests canals estan connectats a la terminal, però poden ser redirigits a altres fitxers o altres processos. - __Entrada estàndard (stdin)__: És un flux de dades d'on el programa pot rebre i llegir dades d'entrada. Si no ha segut redirigit, aquest flux reb les dades des del teclat. En Java, aquest flux de dades està asociat a `System.in` i pot ser llegit mitjançant la classe `Scanner` ```java Scanner in = new Scanner(System.in); while(in.hasNextLine()){ String data = in.nextLine(); } ``` - __Eixida estàndard (stdout)__: És un flux de dades on el programa pot escriure les dades d'eixida. Si no ha segut redirigit, aquest flux mostra les dades per la terminal. En Java, aquest flux de dades està asociat a `System.out` i pot escriure dades utilitzant els seus mètodes, com `print` o `println`. ```java System.out.println("Dades que s'escriuren a STDOUT"); ``` - __Eixida d'error (stderr)__: És un flux de dades on el programa pot escriure les dades d'error. Si no ha segut redirigit, aquest flux també mostra les dades per la terminal. En Java, aquest flux de dades està asociat a `System.err` i pot escriure dades utilitzant els seus mètodes, com `print` o `println`. ```java System.err.println("Dades que s'escriuren a STDERR"); ``` ## Redirecció fluxos de dades Els fluxos de dades treballen per defecte amb el __teclat__ (stdin) o la __terminal__ (stdout i stderr). No obstant això, aquests fluxos es poder redirigir cap a altres fonts utilitzant diferens operadors. ### Operadors de redirecció - __Redirecció eixida `>`__: Crea un nou fitxer contenint l'eixida estàndard (no la d'error!) del programa executat. Atenció, si el fitxer existeix, aquest és sobreescrit (i es perden les dades existents) - _Exemple en Bash_: ![](../img/stdout.png) > Aquest exemple comença amb la comanda `ls`, que mostra els continguts del directori actual, on es pot veure que està buit. > > Després, s'executa `echo`, que funciona com un `print` i mostra per pantalla (__stdout__) la informació: "Aquesta informació s'imprimeix per stdout". Després es torna a executar, redirigint l'eixida al fitxer "stdout.txt" amb l'operador de redirecció `>`. > > Finalment, es mostra el contingut del fitxer "stdout.txt", amb la informació anterior. - __Redirecció afegir eixida `>>`__: Afegeix (append) un fitxer l'eixida estàndard (no la d'error!). Si el fitxer no existeix es crea. Les noves dades s'afegeixen al final del fitxer i no s'eliminen les dades existents. - _Exemple en Bash_: ![](../img/stdout_append.png) > Aquest exemple comença a partir del exemple anterior, on es mostra el contingut del fitxer "stdout.txt". > > Després, s'utilitza la comanda `echo` afegir una informació al final del fitxer "stdout.txt" mitjançant la redirecció `>>`. > > Finalment, es mostra el contingut del fitxer "stdout.txt", amb la nova informació al final del fitxer. - __Redirecció error `2>`__: Crea un nou fitxer contenint l'eixida d'error del programa executat. Atenció, si el fitxer existeix, aquest és sobreescrit (i es perden les dades existents) - _Exemple en Bash_: ![](../img/stderr.png) > Aquest exemple comença amb la comanda `ls`, que mostra els continguts del directori actual, on es pot veure que està buit. > > Després, s'executa `rm`, que intenta eliminar un fitxer anomenat "inexistent", el qual no existeix. Com es veu en l'imatge, aquesta comanda falla i mostra per pantalla l'error (__stderr__). Es torna a repetir la comanda anterior, però esta vegada redirigint l'error al fitxer "stderr.txt" amb l'operador `2>`. > > Finalment, es mostra el contingut del fitxer "stderr.txt", amb l'error anterior. - __Redirecció afegir error `2>>`__: Afegeix (append) un fitxer l'eixida d'error. Si el fitxer no existeix es crea. Les noves dades s'afegeixen al final del fitxer i no s'eliminen les dades existents. - _Exemple en Bash_: ![](../img/stderr_append.png) > Aquest exemple comença a partir del exemple anterior, on es mostra el contingut del fitxer "stderr.txt". > > Després, s'utilitza la comanda `cd`, que intenta accedir al directori "noexisteix", el qual no existeixCom es veu en l'imatge, aquesta comanda falla i mostra per pantalla l'error (__stderr__). Es torna a repetir la comanda anterior, però esta vegada redirigint l'error al final del fitxer "stderr.txt" amb l'operador `2>>`. > > Finalment, es mostra el contingut del fitxer "stdout.txt", amb el nou error al final del fitxer. - __Redirecció eixida i error `&>` i `&>>`__: Funciona de la mateixa manera que els operadors anteriors, amb la diferència que redirigeix els dos fluxos, el d'exida i el d'error. - _Exemple en Bash_: ![](../img/stdout_stderr.png) > Aquest exemple comença amb la comanda `ls`, que mostra els continguts del directori actual, on es pot veure que està buit. > > Després, s'executa `echo`, on es redirigeix l'eixida (__stdout__) al fitxer "out.txt" mitjançant l'operador `&>`. > > A continuació, s'executa la comanda `rm inexistent`, on l'error d'aquesta també es redirigeix al fitxer "out.txt". En aquest cas, es gasta l'operador `&>>` per afegir-ho al final. > > Finalment, es mostra el contingut del fitxer "out.txt", on es pot observar que els operadors `&>` i `&>>` rederigeixen els dos fluxos: __stdout__ i __stderr__. - __Redirecció entrada `<`__: Passa les dades d'un fitxer com a entrada estàndard del programa. - _Exemple en Bash_: ![](../img/stdin.png) > Aquest exemple comença amb un fitxer de text que conté un text qualsevol. > > Després, s'executa `tr aeiou AEIOU`, que tradueix totes les vocals minúscules a vocals majúscules. Aquesta comanda pot rebre la informació des de l'entrada estàndard (__stdin__). En aquest cas, li la passem des del fitxer "text.txt" mitjançant la redirecció `<`. ### Canonades (pipes) Les __canonades (o pipes)__ són un mecanisme per a la comunicació entre processos utilitzant els fluxos de dades estàndard, de tal manera que el __stdout__ de cada process es passa al procés següent com a __stdin__. Les canonades es representen mitjançant el símbol __`|`__. ![](../img/pipe.png){.center} ![](../img/pipe_example.png){.center} ## Fluxos de dades en Java Quan un programa Java crea un procés fill, el procés pare pot gestionar els fluxos de dades del fill per poder comunicar-se amb ell o que aquest es comunique amb altres processos. Per fer-ho, s'utilitzarà la classe [`Process`](https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html), que permet obtindre un objecte diferent per cada flux de dades: - [`InputStream Process.getInputStream()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html#getInputStream--): Retorna un objecte de la classe [`InputStream`](https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html) que està connectat a __l'eixida estàndard o stdout__ del procès fill. - [`InputStream Process.getErrorStream()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html#getErrorStream--): Retorna un objecte de la classe [`InputStream`](https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html) que està connectat a __l'eixida d'error o stderr__ del procès fill. - [`OutputStream Process.getOutputStream()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html#getOutputStream--): Retorna un objecte de la classe [`OutputStream`](https://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html) que està connectat a __l'entrada estàndard o stdin__ del procès fill. Tots aquests objectes `Stream`, llegeixen i escriuren les dades en binari. Per facilitar la gestió d'aquestes dades, existeixen les classes [`InputStreamReader`](https://docs.oracle.com/javase/7/docs/api/java/io/InputStreamReader.html) i [`OutputStreamWriter`](https://docs.oracle.com/javase/7/docs/api/java/io/OutputStreamWriter.html) que treballen en caràcters en compte de bytes. A més, aquests objectes poden ser combinats amb buffers, que emmagatzena les dades de manera temporal i ens proporciona mètodes per facilitar la gestió d'aquestes dades. [`BufferedReader`](https://docs.oracle.com/javase/7/docs/api/java/io/BufferedReader.html) per llegir les dades i [`BufferedWriter`](https://docs.oracle.com/javase/7/docs/api/java/io/BufferedWriter.html) per escriure'n. També existeix la classe [`PrintWriter`](https://docs.oracle.com/javase/8/docs/api/java/io/PrintWriter.html), que permet escriure en un `Stream` amb mètodes similars als que ja coneixem: `print`, `println`, `printf`,... ### Exemple: RunProcessOutput Aquest programa crea un nou programa a partir dels arguments passats al programa RunProcessOutput. El procés és creat mitjançant la classe ProcessBuilder. Una vegada creat, el procés pare espera a que acabe el procés fill i imprimeix per pantalla el codi de retorn, l'eixida estàndard i l'error que ha produït. - <a href="/itb/DAM-PSP/files/ud1/examples/RunProcessOutput.java" download="RunProcessOutput.java">RunProcessOutput.java</a> ```java package ud1.examples; import java.io.IOException; import java.util.Arrays; import java.io.BufferedReader; import java.io.InputStreamReader; public class RunProcessOutput { public static void main (String[] args) { String[] program = {"wsl", "echo", "Hello world!"}; ProcessBuilder pb = new ProcessBuilder(program); try { Process process = pb.start(); // Objectes per poder llegir l'eixida estàndard i l'error BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream())); BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream())); int codiRetorn = process.waitFor(); System.out.println("L'execució de "+ Arrays.toString(program) +" ha acabat amb el codi: "+ codiRetorn); String line; System.out.println("Stdout:"); while ((line = stdout.readLine()) != null) System.out.printf(" %s\n", line); System.out.println("Stderr:"); while ((line = stderr.readLine()) != null) System.out.printf(" %s\n", line); } catch (IOException ex) { System.err.println("Excepció d'E/S:"); System.err.println(ex.getMessage()); System.exit(-1); } catch (InterruptedException ex) { System.err.println("El procés fill ha finalitzat de manera incorrecta."); System.exit(-1); } } } ``` _Exmeple d'execució_: - `java RunProcessOutput ls`: El programa `RunProcessOutput` executa la comanda `ls`, que mostra els continguts del directori actual. Es pot veure a l'imatge que mostra els mateixos continguts per __stdout__ que la comanda inicial. - `java RunProcessOutput rm inexistent`: El programa `RunProcessOutput` executa la comanda `rm inexistent`, que intenta eliminar un fitxer que no existeix. Es pot veure a l'imatge que mostra el mateix missatge d'error que la comanda original. ![](/itb/DAM-PSP/UD1/img/RunProcessOutput.png) ### Exemple: RunProcessInput Aquest programa crea un nou programa a partir dels arguments passats al programa RunProcessOutput. El procés és creat mitjançant la classe ProcessBuilder. Una vegada creat, el procés pare llegeix des d'entrada estàndard (teclat) i li passa aquestes dades al procès fill com a entrada estàndard. Quan s'introdueix una línia en blanc, espera a que el fill acababe i imprimeix per pantalla el codi de retorn, l'eixida estàndard i l'error que ha produït. - <a href="/itb/DAM-PSP/files/ud1/examples/RunProcessInput.java" download="RunProcessInput.java">RunProcessInput.java</a> ```java package ud1.examples; import java.util.Arrays; import java.util.Scanner; import java.util.Locale; import java.io.IOException; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; public class RunProcessInput { public static void main (String[] args) { String[] program = {"wsl", "tr", "a", "A"}; ProcessBuilder pb = new ProcessBuilder(program); try { Process process = pb.start(); // Objecte per poder llegir l'entrada estàndard del programa pare Scanner in = new Scanner(System.in).useLocale(Locale.US); // Objecte per poder escriure a l'entrada estàndard del procés fill PrintWriter stdin = new PrintWriter(process.getOutputStream()); System.out.println("Stdin:"); String line; while(!(line = in.nextLine()).isEmpty()) stdin.println(line); // Quan acabem de introduir dades, cal tancar el stream stdin.close(); // Objectes per poder llegir l'eixida estàndard i l'error BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream())); BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream())); int codiRetorn = process.waitFor(); System.out.println("L'execució de "+ Arrays.toString(program) +" ha acabat amb el codi: "+ codiRetorn); System.out.println("Stdout:"); while ((line = stdout.readLine()) != null) System.out.printf(" %s\n", line); System.out.println("Stderr:"); while ((line = stderr.readLine()) != null) System.out.printf(" %s\n", line); } catch (IOException ex) { System.err.println("Excepció d'E/S."); System.err.println(ex.getMessage()); System.exit(-1); } catch (InterruptedException ex) { System.err.println("El procés fill ha finalitzat de manera incorrecta."); System.exit(-1); } } } ``` _Exmeple d'execució_: - `java RunProcessInput tr aeiou AEIOU`: El programa `RunProcessInput` executa la comanda `tr aeiou AEIOU`, que traduirà totes les vocals minúscules del text que rep per entrada estàndard a vocals majúscules. ![](/itb/DAM-PSP/UD1/img/RunProcessInput.png) ## Canonades en Java Una __canonada (o pipe)__ consisteix en encadenar multiples programes, de tal manera que la eixida estàndard d'un es passat al següent com a entrada estàndard. Java permet realitzar aquesta operació utilitzant el mètode [`List<Process> startPipeline(List<ProcessBuilder> builders)`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/ProcessBuilder.html#startPipeline(java.util.List)) que executa una llista de programes (`List<ProcessBuilder>`) on conecta tots els programes mitjançant canonades. Aquest mètode retorna una llista de processos (`List<Process>`) què conté els objectes necessaris per controlar-los. ### Exemple: RunPiplineColors Aquest exemple realitzarà la canonada que es mostra a les imatges. ![](../img/pipe.png){.center} ![](../img/pipe_example.png){.center} - <a href="/itb/DAM-PSP/files/ud1/examples/RunPipelineColors.java" download="RunPipelineColors.java">RunPipelineColors.java</a> - <a href="/itb/DAM-PSP/files/files/ud1/colors.txt" download="colors.txt">colors.txt</a>: Situa aquest fitxer en el directori __/files/ud1/colors.txt__ a partir de l'arrel del teu projecte. ```java package ud1.examples; import java.io.IOException; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.List; import java.util.ArrayList; import java.util.Arrays; public class RunPipelineColors { public static void main (String[] args) { ArrayList<ProcessBuilder> programs = new ArrayList<>(); programs.add(new ProcessBuilder("sort", "files/ud1/colors.txt")); programs.add(new ProcessBuilder("uniq", "-c")); programs.add(new ProcessBuilder("sort", "-r")); programs.add(new ProcessBuilder("head", "-3")); try { List<Process> processes = ProcessBuilder.startPipeline(programs); for(Process p : processes) p.waitFor(); Process last = processes.get(processes.size() - 1); BufferedReader stdout = new BufferedReader(new InputStreamReader(last.getInputStream())); String line; System.out.println("Stdout:"); while ((line = stdout.readLine()) != null) System.out.printf(" %s\n", line); } catch (IOException ex) { System.err.println("Excepció d'E/S."); System.out.println(ex.getMessage()); System.exit(-1); } catch (InterruptedException ex) { System.err.println("El procés fill ha finalitzat de manera incorrecta."); System.exit(-1); } } } ``` _Resultat de l'execució_: ![](/itb/DAM-PSP/UD1/img/RunPipelineColors.png) ## Blibliografia - https://en.wikipedia.org/wiki/Standard_streams - https://en.wikipedia.org/wiki/Pipeline_(Unix)