Habitualmente un programa utiliza la entrada-salida estandar para comunicarse con el usuario. Por ejemplo, el módulo Chatbot::Eliza establece un diálogo con el usuario imitando a un psiquiatra:
nereida:~/LGRID_Machine/lib/GRID$ perl -wde 0 main::(-e:1): 0 DB<1> use Chatbot::Eliza DB<2> $bot = Chatbot::Eliza->new DB<3> $bot->command_interface() Eliza: Is something troubling you? you: The future Eliza: I'm not sure I understand you fully. you: Nothing stands, everything changes Eliza: I'm not sure I understand you fully. you: Is very honest from you to say that Eliza: Oh, I to say that? you: bye! Eliza: Goodbye. It was nice talking to you.
La solución está en redirigir la entrada/salida del programa hacia el socket tal y como hacíamos en la sección 1.5.
milogin@beowulf:~/src/perl/NETWORKING$ cat -n eliza_server.pl
1 #!/usr/bin/perl
2 use strict;
3 use Chatbot::Eliza;
4 use IO::Socket;
5 use POSIX 'WNOHANG';
6
7 my $PORT = shift || 1026;
8
9 my $quit = 0;
10
11 # signal handler for child die events
12 $SIG{CHLD} = sub { while ( waitpid(-1,WNOHANG)>0 ) { } };
13
14 # signal handler for interrupt key and TERM signal (15)
15 $SIG{INT} = sub { $quit++ };
16
17 my $listen_socket = IO::Socket::INET->new(LocalPort => $PORT,
18 Listen => 20,
19 Proto => 'tcp',
20 Reuse => 1,
21 Timeout => 60*60,
22 );
23 die "Can't create a listening socket: $@" unless $listen_socket;
24 warn "Server ready. Waiting for connections...\n";
25
26 while (!$quit) {
27
28 next unless my $connection = $listen_socket->accept;
29
30 defined (my $child = fork()) or die "Can't fork: $!";
31 if ($child == 0) {
32 $listen_socket->close;
33 interact($connection);
34 exit 0;
35 }
36
37 $connection->close;
38 }
39
40 sub interact {
41 my $sock = shift;
42 STDIN->fdopen($sock,"<") or die "Can't reopen STDIN: $!";
43 STDOUT->fdopen($sock,">") or die "Can't reopen STDOUT: $!";
44 STDERR->fdopen($sock,">") or die "Can't reopen STDERR: $!";
45 $|=1;
46 my $bot = Chatbot::Eliza->new;
47 $bot->command_interface();
48 }
El método command_interface del objeto $bot (línea 47) utiliza
los ficheros STDIN y STDOUT. Pero ahora, al reconvertir el
método a un servicio queremos que use el socket $sock.
El método fdopen en IO::Handle funciona de manera parecida a open
pero su primer parámetro es un descriptor de fichero. El método cierra el
fichero y lo reabre utilizando el manejador $sock proveído como
argumento. Después de las tres llamadas en las líneas 42-44 escribir en
STDOUT y STDERR es enviar los datos vía el socket.
Leer desde STDIN es leer desde el socket.
Funciona de manera parecida a
las aperturas con los descriptores ">&" (escritura) y "<&"
(lectura).
casiano@beowulf:~/src/perl/NETWORKING$ ./eliza_server.pl Server ready. Waiting for connections... .............................. milogin@beowulf:~/src/perl/netserver$ pkill eliza_server |
milogin@micasa:~/LPOP$ telnet beowulf 1026 Trying 123.123.123.123... Connected to beowulf. Escape character is '^]'. Eliza: Please tell me what's been bothering you. you: Hey! it works! Eliza: Tell me more about that. you: You are server. You work Eliza: Do you sometimes wish you were server? you: No Eliza: Are you saying no just to be negative? you: quit Eliza: Life is tough. Hang in there! Connection closed by foreign host. |
Hay otro problema con la conversión de Eliza en un servicio.
Eliza esta pensado para trabajar con un sólo usuario y por ello
sólo termina cuando la entrada contiene palabras como quit
o bye. En otro caso se queda en un bucle a la espera
por mas entrada del usuario. Incluso si se genera un final de
fichero pulsando CTRL-D no termina.
Para lograr que el programa termine cuando se alcanza el final de fichero
Lincoln Stein propone sustituir el método privado _test_quit del objeto por este:
sub Chatbot::Eliza::_testquit {
my ($self,$string) = @_;
return 1 unless defined $string; # test for EOF
foreach (@{$self->{quit}}) { return 1 if $string =~ /\b$_\b/i };
}
Casiano Rodríguez León
