Un Ejemplo: Una Aplicación Tipo talk

Ejemplo de Ejecución

La aplicación que desarrollaremos en esta sección permite establecer un diálogo entre dos usuarios. El cuadro 5.1 muestra el resultado de una ejecución


Tabla: Ejecución coordinada
lhp@nereida:~/Lperl/src/expect/kibitz$ mytalk.pl pp2
asking pp2 to type: /tmp/rYQ4jPDKQx/mytalk.pl -16340 /tmp/rYQ4jPDKQx
write: write: you have write permission turned off.

* Hola!
Acuerdate de enviarme la referencia de la que me hablaste
* De acuerdo. Lo hago ahora
Chao

pp2@nereida:~/src$ /tmp/rYQ4jPDKQx/mytalk.pl -16340 /tmp/rYQ4jPDKQx
Hola!
* Acuerdate de enviarme la referencia de la que me hablaste
De acuerdo. Lo hago ahora
* Chao


El primer usuario inicia el diálogo con el comando $ mytalk.pl user. El otro usuario contesta con un comando np5.pl -PIDNUMBER /tmp/tmpdir.

El Programa Principal

Este ejemplo muestra como usar dos FIFOs o pipes con nombre para establecer una comunicación bidireccional entre dos procesos. Los dos procesos usan los dos pipes en direcciones opuestas.

lhp@nereida:~/Lperl/src/expect/kibitz$ cat -n mytalk.pl
  1  #!/usr/bin/perl -w
  2  use strict;
  3  use IO::Handle;
  4  use File::Basename;
  5  use File::Temp qw/tempdir/;
  6  use IO::Select;
  7
  8  my ($pgm)=basename($0);
  9  my $inf;
 10  my $outf;
 11  my @handler = (\&user1, \&user2);
 12
 13  &usage unless @ARGV;
 14  # user 1 invokes mytalk.pl as "mytalk.pl user"
 15  # user 2 writes "mytalk.pl /tmp/cSmOql4G3i/mytalk.pl -16080 /tmp/cSmOql4G3i" (some pid).
 16  my $user=shift;
 17  # User who originated mytalk.pl session has $usernum == 1.
 18  # User who is responding to mytalk.pl has $usernum == 2.
 19
 20  my $usernum = $user =~ /^-(\d+)/ ? 2 : 1;
 21  my $pid = ($usernum == 1)?$$:$1;
 22  STDOUT->autoflush(1);
 23  STDIN->blocking(0);
 24
 25  $handler[$usernum-1]->($pid);
 26  exit;

El contenido de usage es trivial:

110  sub usage {
111    warn "Usage: $pgm user\n";
112    exit 1;
113  }

El Primer Usuario

El usuario que inicia la comunicación (user1) crea los dos FIFOS con los permisos adecuados.

Después se contacta mediante el programa write al otro usuario (user2). Para que el mensaje se reciba es conveniente que el receptor habilite las escrituras en su terminal mediante el comando mesg .

 59  sub user1 {
 60    my $pid=shift;
 61
 62    my $tmpdir = tempdir();
 63    chmod chmod 0755, $tmpdir;
 64
 65    system("cp $0 $tmpdir/");
 66
 67    $inf="$tmpdir/exp0.$pid";
 68    $outf="$tmpdir/exp1.$pid";
 69
 70    my $fifocmd = '/usr/bin/mkfifo';
 71    system("$fifocmd $inf") == 0
 72      or die "$pgm: could not make fifo 'inf': $?\n";
 73    system("$fifocmd $outf") == 0
 74      or unlink($inf), die "$pgm: could not make fifo '$outf': $?\n";
 75    chmod 0666,$inf,$outf;
 76
 77    print "asking $user to type: $tmpdir/$pgm -$pid $tmpdir\n";
 78    open WQ,"|/usr/bin/write $user"
 79      or unlink($inf,$outf), warn "$pgm: write command failed: $!\n";
 80    print WQ "Can we talk?  Run: $pgm -$pid $tmpdir\n" or warn "Could'nt warn $user\n";
 81    close WQ;
 82
 83    open IN,$inf or die "$pgm: read pipe open failed: $!\n";
 84    IN->autoflush(1);
 85    IN->blocking(0);
 86
 87    open OUT,">$outf" or die "$pgm: write pipe open failed: $!\n";
 88    OUT->autoflush(1);
 89
 90    &dialog();
 91  }

Nombres Únicos para Los Pipes

Para hacerlos únicos, los nombres de los FIFOs incluyen el PID del proceso user1. Se crea un directorio /tmp/tmpdir/ único. La ubicación en /tmp evita la posibilidad de estar trabajando en un sistema NFS.

lhp@nereida:~/Lperl/src/expect/kibitz$ ls -ltr /tmp/KmTWYAneMz
total 4
-rwxr-xr-x 1 lhp lhp 2616 2008-04-18 13:01 mytalk.pl
prw-rw-rw- 1 lhp lhp    0 2008-04-18 13:01 exp1.16405
prw-rw-rw- 1 lhp lhp    0 2008-04-18 13:01 exp0.16405

El Método blocking

Una llamada de la forma $io->blocking ( [ BOOL ] ) hace que el manejador funcione en modo no bloqueante si BOOL es falso. Si no se especifica parámetro se devolvera el valor actual del parámetro. Si ocurre un error devuelve undef y deja el código de error en $!.

El Segundo Usuario

El usuario receptor (user2), siguiendo las instrucciones recibidas, ejecuta el programa usando como argumento el PID del proceso user1 y el directorio temporal los cuales permiten identificar los FIFOs.

El Orden de Apertura

El segundo usuario lee desde "/tmpdir/exp1.$pid" y escribe en "/tmpdir/exp0.$pid" mientras que user1 lo hace a la inversa.

El orden en que se abren los ficheros es importante ya que open puede atascarse. Si abrimos un pipe para lectura O_RDONLY el open se bloquea hasta que otro proceso abre el FIFO para escritura (a menos que se especificara O_NONBLOCK en la apertura con sysopen, en cuyo caso la apertura podrá realizarse con éxito). Así pues, si en uno de los procesos se abre primero el de lectura en el otro debe abrirse primero el de escritura. De manera análoga, si abrimos un pipe para escritura (O_WRONLY) la apertura se bloqueará hasta que el otro proceso abra el FIFO para lectura.

 93  sub user2 {
 94    my $pid = shift;
 95
 96    my $tmpdir = shift @ARGV || die "directory wasn't specified\n";
 97
 98    $outf="$tmpdir/exp0.$pid";
 99    open OUT,">$outf" or die "$pgm: write pipe open failed: $!\n";
100    OUT->autoflush(1);
101
102    $inf="$tmpdir/exp1.$pid";
103    open IN,$inf or die "$pgm: read pipe open failed: $!\n";
104    IN->autoflush(1);
105    IN->blocking(0);
106
107    &dialog();
108  }

El Diálogo

El diálogo (sub dialog) hace uso de comunicaciones sin buffer ( sysread , syswrite , sección 1.7) y sin bloqueo. Mediante el método blocking hemos desactivado el bloqueo en las lecturas de STDIN (llamadas a STDIN->blocking(0) y a IN->blocking(0)). El módulo IO::Select nos provee de los mecanismos para saber que manejadores estan listos para una operación de entrada salida. Al añadir los manejadores de entrada mediante $s->add(\*STDIN) y $s->add(\*IN) indicamos al selector el conjunto de manejadores que deberá consultar. En la línea (my @read_from = $s->can_read()) obtenemos la lista de manejadores que estan preparados para una operación de lectura. A continuación procedemos a leer de cada uno de los que estan preparados.

28  sub dialog {
29    my $s = IO::Select->new();
30    $s->add(\*STDIN);
31    $s->add(\*IN);
32
33    FOREVER: {
34      my @read_from = $s->can_read();
35      foreach my $file (@read_from) {
36        if ($file == \*STDIN) {
37          my $bytes = sysread(STDIN, $_, 1024);
38          if (defined($bytes) and ($bytes == 0)) {
39            close(OUT);
40            unlink $inf,$outf;
41            exit 0;
42          }
43          syswrite(OUT,"* $_") if $bytes;
44        }
45        if ($file == \*IN) {
46          my $bytes = sysread(IN, $_, 1024);
47          if (defined($bytes) and ($bytes == 0)) {
48            close(OUT);
49            unlink $inf,$outf;
50            exit 0;
51          }
52          syswrite(STDOUT, $_);
53        }
54      }
55      redo FOREVER;
56    }
57  }

Lectura sin Bloqueo

Cuando el manejador esta siendo usado sin bloqueo, la función sysread devuelve undef si no hay entrada disponible. En tal caso la variable $! contiene el código de error EWOULDBLOCK (definida en el módulo POSIX). Esta situación no debe confundirse con la devolución de un cero, la cual indica la presencia del final de fichero.

En mas detalle, hay varias posibilidades cuando se llama a sysread con un manejador sin bloqueo:

  1. Se solicitaron $ N$ caracteres y se obtuvieron $ N$ . En ese caso sysread llena el buffer y devuelve $ N$ .
  2. Se solicitaron $ N$ caracteres y se obtuvieron $ r < N$ . En ese caso sysread llena el buffer y devuelve $ r$ .
  3. Se solicitaron $ N$ caracteres y no hay nada disponible. En ese caso sysread devuelve undef y pone $! a EWOULDBLOCK.
  4. Al alcanzar el final de fichero sysread devuelve 0.
  5. En cualquier otro caso sysread devuelve undef y pone $! al código de error apropiado.

Código Típico de una Lectura sin Buffers ni Bloqueo

El código típico de una lectura sin buffers ni bloqueo suele tener este aspecto:

my $r = sysread(R, $data, $bytes);
if (defined $r) {
  if ($r > 0) {
    Lectura con éxito ...
  }
  else {
    close(R);
    ... código a ejecutar si EOF
  }
}
elsif ($! == EWOULDBLOCK) {
  # Nada en la entrada. Decidir que se hace.
}
else { 
  die "Error en sysread: $!";
}

Escrituras Sin Bloqueo

El manejo de escrituras sin bloqueo es similar. El retorno de un valor undef señala la imposibilidad de escribir. Si la escritura sólo se ha podido hacer parcialmente es responsabilidad del programador intentarlo posteriormente.

Ejercicio 5.1.1   Las escrituras en el ejemplo anterior ¿Se están haciendo con bloqueo?



Subsecciones
Casiano Rodríguez León
2012-02-29