Programación de procesos con Java

De MediaWiki
Ir a la navegación Ir a la búsqueda

Introducción

A máquina virtual de Java, a linguaxe Java e a biblioteca de clases estándar de Java foron diseñadas para soportar programación concurrente.

A funcionalidade básica para procesos a proporciona a clase Process.

A funcionalidade básica para fíos a proporciona a clase Thread, que será obxecto de estudo na próxima sección. Esta clase implementa la interfaz Runnable.

O paquete java.util.concurrent proporciona funcionalidade de alto nivel para programación concurrente. Inclúe varios frameworks e algunhas clases adicionais.

A clase Process é abstracta; porén non se poden crear obxectos dela. Debe ser de subclases súas que de feito implementan procesos nativos (procesos de plataformas específicas que se executan nun sistema operativo particular para un tipo de procesador particular). A clase Process ten métodos para:

  • lanzar un proceso
  • obter información acerca do seu estado
  • controlar a súa execució.

Pódense crear obxectos da clase Process con métodos doutras clases, en particular con ProcessBuilder.start() e Runtime.exec().


A clase Runtime

Cada programa de Java ten asociada unha instancia da clase Runtime que lle permite obter información do seu entorno de execución e interactuar con él. Este pódese recuperar co método estático getRuntime(). As diversas variantes do método exec() permiten lanzar novos procesos.

Algóns dos métodos desta clase:




Método Funcionalidade
static Runtime getRuntime() Devolve o obxecto Runtime asociado coa aplicación de Java actual.
Process exec(String command)
Process exec(String[] comandoYArgs)
Process exec(String[] comandoYArgs, String[] envp)
Process exec(String[] cmdarray, String[] envp, File dir)
Executa un comando. Algunhas variantes permiten especificar o entorno de execución e o directorio para executar o comando.
void exit(int status)
void halt(int status)
Termina a ejecución da máquina virtual (exit de xeito ordenado e harl de xeito abrupto)
int availableProcessors() Devolve o número de procesadores disponibles para a máquina virtual.
long freeMemory() Devolve a cantidade de memoria disponible para a máquina virtual.

As clases Process e ProcessBuilder

Process é unha clase abstracta. Pódense obter instancias de subclases súas que proporcionan implementaciones para unha plataforma particular, con métodos da clase ProcessBuilder .

Con ProcessBuilder pódese configurar previamente o entorno de execución dos procesos que crea e, en particular, como se verá en breve, redirixir a súa entrada e saída. Algúns dos seus métodos

Método Funcionalidade
ProcessBuilder(String...comando)
ProcessBuilder(List<String> comando)
Constrúe un ProcessBuilder para un comando cos correspondentes argumentos de líña de comandos.
ProcessBuilder directory(File directorio)
File directory()
O primeiro método establece o directorio de traballo do proceso e o segundo método o devolve. Por defecto, o segundo método normalmente devolve null . Se é así, pódese obter o directorio de execución do proceso actual con System.getProperty(“user.dir”) .
Map<String,String> environment() Devolve o entorno de traballo do proceso, que é una lista de asignación de valores para variables de entorno.
Process start() Crea e inicia un proceso.

Algúns métodos da clase Process:

Método Funcionalidade
void destroy() public
Process destroyForcibly()
Termina i proceso. (destroy permite unha terminación limpa e ordenada e destroyForcibly o termina inmediatamente.
int exitValue() Devolve o valor de saída (o código de retorno do proceso). Por convención, un valor 0 indica terminación normal, e outro valor se interpretará como un código de erro. En Java pódese terminar un programa cun código de retorno distinto de 0 con System.exit(código).
ProcessHandle.Info info() Devolve a información actual do proceso.
boolean isAlive() Comproba se o proceso está vivo.
long pid() Devolve o PID (identificador de proceso).
int waitFor() Fai que o fío en execución espere ata que o proceso teña rematado. Devuelve o valor de saída do proceso. Un valor cero se entiende que corresponde a una ejecución sin errores, mientras que un valor distinto de cero corresponde a un código de error.
boolean waitFor(long timeout, TimeUnit unit) Fai que o fío en execución espere ata que o proceso teña rematado durante un tempo máximo indicado por timeout. Devolve true se o proceso terminou por sí mismo antes do tiempo máximo indicado, e false en caso contrario.

Calquiera dos múltiples erros que se poden producir durante a creación e execución do proceso fará que se produza unha excepción (en xeral da clase IOException).

Redirección de entrada e saída estándares e de erro

As clases ProcessBuilder e Process proporcionan diversos mecanismos para redireccionar e xestionar a saída e entrada dos procesos. Esto podería non funcionar ben nalgunas plataformas debido a limitacións nos buffers utilizados para elas polo sistema operativo particular. Estos mecanismos aproveitanse en algúns dos programas de exemplo que se presentan a continuación.

Os conceptos de entrada e saída estándares e de saída de error introduxeronse no sistema operativo UNIX e implementaronse posteriormente en Linux e Windows.

Cualquier proceso ten acceso a varios streams ou flujos de bytes:

  • Entrada estándar ou stdin (standard input). É un stream de entrada do que pode ler datos. Normalmente lense datos sobre os que se realiza algún procesamento. Por defecto, adoita estar asociada ó teclado, de xeito que o programa pode ler todo o texto que se introduce por teclado.
  • Saída estándar ou stdout (standard output). É un stream de saída no que se poden escribir datos. Un xeito sinxelo no que un programa pode xerar a súa saída é escribila na saída estándar. Por defecto, adoita estar asociada á pantalla, de xeito que todo o texto que se envía á salida estándar escríbese na pantalla.
  • Saída de error ou stderr (standard output). É un stream de saída no que se poden escribir datos. Tamén adoita estar asociada á pantalla, de xeito que todo o texto que se envía á saída de error escríbese na pantalla.

O proceso pode ser calquer programa en execución. Para os seguientes exemplos utilizaranse comandos existentes en Linux.

Mecanismos de redirección da clase ProcessBuilder

A clase ProcessBuilder proporciona mecanismos para a redirección da entrada e saída dos novos procesos creados.

Nos programas de exemplo amosados utilizouse o método inheritIO() para enlazar a entrada e a saída estándares e de erro dos subprocesos coas do proceso principal, e por iso puídose ver a súa saída.

Os mecanismos proporcionados por ProcessBuilder para iso son:

1. O método inheritIO(). Esta é a solución máis sinxela e a utilizada anteriormente. Con este método, a entrada e a saída estándares e de error dos procesos creados enlázanse coas do proceso actual.

2. Os métodos redirectInput() , redirectOutput() y redirectError() son útiles cando se precisa unha sinxela redirección, desde ou hacia un ficheiro, ou ben a /dev/ null para ignorar a saída. En detalle:

  • redirectInput() redirixe a entrada desde:
    • A entrada estándar do proceso pai con redirect.INHERIT.
    • El contenido dun ficheiro con un objeto File.
  • redirectOutput() redirixea saída estándar dos novos procesos creados hacia:
    • A saída estándar do proceso pai con redirect.INHERIT .
    • Un ficheiro, borrando contidos actuales do ficheiro con f, de clase File .
    • Un ficheiro, engadindo os seus contidos actuais con Redirect.appendTo(f) .
    • Ninguhna parte, descartanto con Redirect.DISCARD .
  • redirectError() funciona como redirectOutput() , pero para a saída de error.

As equivalencias entre os diversos métodos de redirección para a clase ProcessBuilder de Java e mecanismos de redirección disponibles para a execución de comandos nunha shell de Linux e así:

Método Líña de comandos de Linux
redirectInput(new File (fichero)) comando < fichero
redirectOutput(new File(fichero)) comando > fichero
redirectOutput(Redirect.appendTo(new File(fichero))) comando >> fichero
redirectOutput(Redirect.DISCARD) comando > /dev/null
redirectError(new File(fichero)) comando 2> fichero
RedirectError(Redirect.appendTo(new File(fichero))) comando 2>> fichero
redirectError(Redirect.DISCARD)) comando 2> /dev/null

Os métodos para redirección de entrada y saída explicados anteriormente:

Método Funcionalidade
ProcessBuilder inheritIO() Redirige la salida estándar y de error de los subprocesos creados hacia las del proceso padre, y su entrada estándar desde la del proceso padre.
ProcessBuilder redirectInput(File f)
ProcessBuilder redirectInput( ProcessBuilder.Redirect fuente)
ProcessBuilder redirectOutput(File f) redirectOutput( ProcessBuilder.Redirect destino)
ProcessBuilder redirectError(File f) redirectError(
ProcessBuilder.Redirect destino)
Redirixe a entrada, salida estándar e de error, respectivamente, desde ou hacia:
– Un fichero (con File f ).
– A correspondiente do proceso pai (conRedirect.INHERIT ).
– A saída estándar e de error pódense descartar (con Redirect.DISCARD).
ProcessBuilder redirectErrorStream( boolean redir) Co valor true para redir , dirixe a salida de error hacia a estándar. Esto fai innecesario xestionar por separado a saída de error. Ten o inconvinte de que non se pode distinguir entre ambas. Pero ten a ventaja de que evita que ambas aparezcan entremezcladas, ou que dificulta a súa lectura e interpretación.
static List<Process> startPipeline( List<ProcessBuilder> builders) Inicia un proceso para cada ProcessBuilder , e crea unha secuencia de procesos cuxas saída e entradas están enlazadas mediante tuberías ou pipes, de xeito que a saída dun proceso diríxese á entrada do seguente na secuencia. Só se poden redirixir a entrada do primer proceso e a salida do último; o resto de entradas e salidas están conectadas mediante pipes dun proceso ó siguiente e como é lóxico non se poden redirixir.

Mecanismos de redirección a streams da clase Process

A clase Process ten métodos que devolven streams asociados á entrada estándar do proceso, á saída estándar e á saída de error. Isto permite enviar datos á entrada estándar do proceso e ler da saída estándar e de error, o que permite un absoluto control e flexibilidade, máis alá dos sinxelos mecanismos de redirección vistos anteriormente.

Os métodos de Process para obter streams asociados a entrada estándar e a saída estándar e de erro son:

Método Funcionalidade
InputStream getInputStream() Devolve un stream de entrada conectado coa saída estándar do proceso.
OutputStream getOutputStream() Devuelve un stream de saída conectado coa entrada estándar do proceso.
InputStream getErrorStream() Devuelve un stream de entrada conectado coa salida de error do proceso.

Sobre estos streams binarios podense construir streams de texto (un InputStreamReader sobre un InputStream ), e sobre estos pódense construir streams con buffering, que permiten ler ou escribir líñas enteras.

O seguiente programa de exemplo executa un comando que se proporciona como argumento de líña de comandos. A saída do proceso p obtense con p.getInputStream() .

Sobre o InputStream obtido constrúese un BufferedReader , para poder obter a saída como texto e líña a líña.

 1 package lanzaprocesocapturasalida;
 2 import java.util.Arrays;
 3 import java.io.InputStream;
 4 import java.io.InputStreamReader;
 5 import java.io.BufferedReader;
 6 import java.io.IOException;
 7 public class LanzaProcesoCapturaSalida {
 8   public static void main(String[] args) {
 9     if (args.length <= 0) {
10       System.out.println(“Debe indicarse comando a executar.”);
11       System.exit(1);
12     }
13     ProcessBuilder pb = new ProcessBuilder(args);
14     try {
15       Process p = pb.start();
16       try (InputStream is = p.getInputStream();
17         InputStreamReader isr = new InputStreamReader(is);
18         BufferedReader br = new BufferedReader(isr)) {
19         int codRet = p.waitFor();
20         System.out.println(“A execución de “ + Arrays.toString(args)
21           + “ devuelve “ + codRet
22           + “ “ + (codRet == 0 ? “(execución correcta)” : “(ERROR)”)
23         );
24         System.out.println(“Saída do proceso”);
25         System.out.println(“------------------”);
26         String linea = null;
27         while ((linea = br.readLine()) != null) {
28           System.out.println(linea);
29           }
30         System.out.println(“------------------”);
31       }
32     } catch (IOException e) {
33       System.err.println(“Erro durante execución do proceso”);
34       System.err.println(“Información detallada”);
35       System.err.println(“---------------------”);
36       e.printStackTrace();
37       System.err.println(“---------------------”);
38       System.exit(2);
39     } catch (InterruptedException ex) {
40       System.err.println(“Proceso interrumpido”);
41       System.exit(3);
42     }
43   }
44 }

Exemplos

Execución dunha aplicación de windows

 1 publie class Exemplol { 
 2    public static void main(String[] args) {
 3       Runtime r = Runtime.getRuntimeO;
 4       String comando = "NOTEPAD";
 5       Process p;
 6       try {
 7          p = r.exec( comando );
 8       } catch (Exception e) {
 9            System.out.println ("Erro en: "+comando );
10            e.printStackTrace () ;
11       }
12    }
13 }// Exemplo1

Execución de un comando de windows que non ten executable

É necesario utilizar o comando CMD.EXE para os comandos de Windows que non teñen executable (coma por exemplo DIR). Porén, sería igual que no exemplo anterior, pero cambiando a variable comando por:

String comando= "CMD /C DIR";

Para executar un comando, entre outros parámetros posibles:

/C executa o comando especificado e logo finaliza
/K executa o comando especificado e segue activo

Para ver a sintaxe do comando CMD:

HELP CMD

Con todo... funciona correctamente o programa? Non exactamente, xa que non obteremos ningunha saída. Iso é así porque a saída do comando redirixe ó noso programa Java e non á pantalla.

Para ler a saída (o que nos devolve o método exec do Runtime temos que usar o obxecto Process:

p = r.exec( comando );

A clase Process posee o método getImputStream() que nos permite ler o stream de saída do proceso, isto é, podemos ler o que o comando que executamos escribíu na consola. Definiremos así o stream:

Process p = Runtime.getRuntime().exec("CMD /C DIR");
InputStream is = p.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));

Para ler a saída usamos o método readLine() de BufferedReader que nos devolve unha líña de texto.

En resumo, a versión final sería: publie class Exemplo2 {

  public static void main(String[] args) {
     Runtime r = Runtime.getRuntimeO;
     String comando = "NOTEPAD";
     Process p=null;
     try {
        p = r.exec( comando );
        Inputstream is = P.getlnPutStreanrO ,'
        BufferedReader br = neYr BufferedReader(new InPutstreamReader (is) ) ;
        String linea;
        while ((linea = br.readline() ) !=null) //Le unha líña
           System. out.Println (linea) ;
       br.close();
     } 
     catch (Exception e) {
        System.out.println ("Erro en: "+comando );
        e.printStackTrace () ;
     }
     // COMPROBACIÓN DE ERRO - 0 ben - 1 mal
     int exitVal;
     try {
        exitVal = p.waitFor();
        System.out.println ("Va1or de salída: " + exitVal);
        } catch (InterruptedException e) {
             e.printstackTrace ();
        )
  }

}// Exemplo2 </syntaxhighlight >

Resumo

  • A clase Process permite xestionar procesos.
    • É unha clase abstracta, e as súas instancias podense crear con métodos das clases Runtime e ProcessBuilder .
    • A clase ProcessBuilder ten métodos para redirixir a entrada e a saída dos procesos que crea.
    • A clase Process ten métodos que devolven streams que permiten enviar datos directamente á entrada estándar dun proceso e ler da súa saída estándar e de error.

Exercicios propostos

0. Crea un programa que execute un comando en Linux utilizando la clase Runtime . O comando debe proporcionarse nos parámetros da liña de comandos. Verifica que o comando se ejecuta, aínda non se amose a súa saída. Prueba a executar un comando que non existe.

1. Crea un programa que:

  • amose un prompt progcontfich1>
  • para cada nome de ficherio introducido amose o número de liñs, palabras e caracteres obtidos mediante unha chamada ó comando wc .
  • para cada nome de ficheiro proporcionado, debe dirixirse a entrada estándar dun proceso creado para o comando wc desde o ficheiro indicado.
  • a execución do programa rematará cando se introduzca unha liña vacía.
  • a saída estándar e a saída de error do proceso creado deben redirixirse hacia as do proceso pai; isto é, hacia as do programa desenvolto, mediante os métodos redirectOutput e redirectError .

2. Crea un programa que debe funcionar como el anterior desde o punto de vista del usuario, pero que en troques de amosar directamente a saída do comando wc, o amose do seguinte xeito:

ficheiro ten num_lin líñas, núm_pal palabras e núm_car caracteres

Para iso debe obterse un InputStream asociado á saída estándar do proceso. Para obter o número de líñas, palabras e caracteres a partir da saída do procesowc, podes utilizar o método split da clase String .

3. Crea un programa que:

  • amose un prompt progcontfich3>
  • permita introducir líñas ata que se introduzca unha líña vacía.
  • para cada lia introducida débese amosar o número de palabras e caracteres obtido dun proceso creado para o comando wc, a cuxa entrada dirixirase o contido da liña leída.
  • a entrada ó proceso débese realizar utilizando un OutputStream obtido mediante getOutputStream().
  • a saída de wc amosarase sen cambios tal como a xenera o comando wc .

4. Crea un programa similar o anterior pero que:

  • amose un prompt progcontfich4> .
  • deben lerse liñas ata que se introduzca unha vacía.
  • o comando wc debe proporcionar información para o conxunto de liñas introducidas ata antes de introducirse a última liña, que estará vacía.
  • débese crear un so proceso para o comando wc , e se lle deben dirixir todas las líneas leídas, unha a unha, ata que se introduzca unha liña vacía. Entón debe amosarse a información correspondente ó conjunto de líñas que se introduciron
  • a entrada ó proceso débese realizar tamén utilizando un OutputStream obtido mediante getOutputStream() .

Créditos e Referencias