Ir al contenido principal

Socket en perl 3/3




$sudo apt-get install xinetd
$ sudo nano echo
           
# default: off
            # description: An xinetd internal service which echo's characters back to
            # clients.
            # This is the tcp version.
            service echo
            {
            disable         = no
            type            = INTERNAL
            id              = echo-stream
            socket_type     = stream
            protocol        = tcp

            user            = root
            wait            = no
            }
# This is the udp version.
service echo
{
        disable         = yes
        type            = INTERNAL
        id              = echo-dgram
        socket_type     = dgram
        protocol        = udp
        user            = root
        wait            = yes
}

$ sudo netstat -putan|grep xine
           
tcp        0      0 0.0.0.0:7               0.0.0.0:*               ESCUCHAR    3583/xinetd

$ telnet localhost 7
           
Escribo string y este me lo repite
Abro nuevos telnets de la misma forma en terminales distintos y hace lo mismo. Escribo string y me lo contesta repitiéndolo.
Si este telnet lo hago al puerto 80, este esperará una petición asiciado a su protocolo, espera una petición web.
Telnet es mi vehículo para conectar con el servidor, por lo tanto se usa en la parte cliente.




$ sudo netstat -putan|grep xin


Telnet a la linea que das agrega \r\n. Si escribimos papa, al pulsar intro, nos devuelve papa\r\n.
1º - Telnet envía agregando siempre \r\n
2º - flush buffers cuando usamos funciones librerías para escribir en un stream(canal)
buferring es en la memoria del servidor, al escribir lo descarga, mientras que unbuffering es en memoria del PC

xinetd sirve para ahorrar recursos.
Si tengo 3, o 5 o 10 servicios esperando peticiones, significa que tengo 3, 5, o 10 procesos o demonios escuchando y esperando petición.
Con xinetd puedo tener la misma funcionalidad, pero con un unico servidor en memoria. Estará previamente configurado para esperar las peticiones de varios programas.
Cuando recibe una petición, por ejemplo de correo, este levanta el servidor de correo para atenderlo.

Echo es un claro ejemplo de como un servidor atiende varias peticiones iniciando un nuevo proceso. Pero nuestros códigos socket comienza un proceso, lo atiende y acaba. Cada proceso que comienza un servidor sería igual que el funcionamiento del socket.
Podríamos variar mi programa para que cuando el cliente lo llama, no se lo de este y acabe, sino que este cree un nuevo proceso, le atienda y acabe dicho proceso, pero mi programa sigue escuchando.
Un nuevo proceso, o un nuevo hilo.

Especificaciones de la ACTIVIDAD:
          #Escribir un servidor de echo con puerto 7777
          #prueba con telnet \r\n
          #Recordar flush
          #Acaba cuando el cliente envia "fin"
          #Acaba el server con el string 99


  
CLIENTE 1: echo telnet - use IO::Socket::UNIX;
#!/usr/bin/perl
use IO::Socket::UNIX;
#uso libreria socket UNIX = socket local
my $socket = IO::Socket::UNIX->new(
            Type => SOCK_STREAM,
            Peer => '/tmp/mysocket',
);
die "Can't create socket: $!" unless $socket;
#SE ESTABLECE LA CONEXIÓN CON EL SERVIDOR
#Si la conexión del socket da error, termina
$socket->autoflush;
while() {
#Mientras se escriba entradas por teclado,...
            print $socket $_;
            #Envia al servidor por SOCKET estas entradas
            if(my $line=<$socket>) {
           
#Si se guarda en variable lo que trae el canal desde el servidor, ...
                        print $line;
           
            #Imprime esta variable
                        my $salir="fin";
           
            #Guardo en variable string salir
                        if ($line =~ $salir) {
                       
#Si en la variable se guarda el string salir, ...
                                   close $socket;
                                  
#Cierra canal socket
                                   exit();
                                  
#Salir
                        }
            }         
}


SERVIDOR 1: echo telnet - use IO::Socket::UNIX;
#!/usr/bin/perl -w
use IO::Socket::UNIX;
#uso libreria socket UNIX = socket local
my $socket_path = '/tmp/mysocket';
unlink $socket_path if -e $socket_path;
my $socket = IO::Socket::UNIX->new(
            Local  => $socket_path,
            Type   => SOCK_STREAM,
            Listen => SOMAXCONN,
);
die "Can't create socket: $!" unless $socket;
#Si la conexión del socket da error, termina
while (1) {
#Se crea un bucle infinido. Significa que si queremos salir deberemos poner un exit().
            my $connection = $socket->accept;
            #ACEPTO LA CONEXIÓN
            # se crea el tunel de conexión.
            $connection->autoflush(1);
            while (my $line = <$connection>) {
            #Mientras se guarde en variable lo que manda el cliente por el SOCKET
            chomp($line);
            $nueva="$line" . "\n";
            print $connection $nueva;
           
#MANDA al CLIENTE por SOCKET la variable

            my $salir="fin";
           
#En variable guarda string fin
                        if ($line =~ $salir){
           
            #Si lo que manda cliente es salir, ...
                                   print "99\n";
                       
            #Imprime por pantalla 99
                                   exit();
                                  
#Sale del programa
                        }
            }         
}


CLIENTE 2: echo telnet - use Socket;
#!/usr/bin/perl
use Socket;
use IO::Handle;
socket(S,PF_INET,SOCK_STREAM,6);
$dest=sockaddr_in(7777,inet_aton('127.0.0.1')) ;
$paddr = connect(S, $dest);
S->autoflush;
while() {
            print S $_;
            if($line=) {
                        print $line;
            }
            else { last; }
}
close S;


SERVIDOR 2: echo telnet - use Socket;
#!/usr/bin/perl
use Socket;
socket (Server, PF_INET, SOCK_STREAM, 6) || die ("socket");
$pbind = sockaddr_in(7777,INADDR_ANY) || die ("sockaddr_in");
bind(Server,$pbind) || die ("bind");
listen(Server,SOMAXCONN) || die ("listen");
my $connection;
while ($connection = accept(Client, Server)) {
            ($port,$iaddr) = sockaddr_in($connection);
            $name = gethostbyaddr($iaddr,AF_INET);
            print "Atiendo petición desde puerto ", $port, " de máquina ",$name, " dirección ",inet_ntoa($iaddr),"\n";
            Server->autoflush;
            while (my $line=) {
                        chomp($line);
                        $nueva="$line" .
"\n";
                        print $nueva;
            }
            print "99\n";
}


Especificaciones de la ACTIVIDAD:
Un cliente que permita conversar con un servidor.
Pensar, aquí en el cliente hay poca variación, la adaptación viene en el SERVIDOR.
Pasaríamos de tener un servidor de echo a un servidor talk.
El usuario que opera el SERVIDOR lee lo que recibe el servidor y escribe una respuesta, por teclado claro. Esto es lo que no hace tu servidor de echo

CLIENTE 2: talk
#!/usr/bin/perl -W
use IO::Socket;
$host=;
$port=;
$cliente = IO::Socket::INET->new(
                        Proto       => 'tcp',
                        PeerAddr    => $host,
                        PeerPort    =>  $port,
                        Reuse       =>  1,
) || die print "Error, no se pudo conectar al servidor :(";
print "Se conecto correctamente :)\n";
while ($cliente) {
                        print "->";
                        $msg=;
                        print "Enviaste:$msg";
                        $cliente->send($msg);
                        $serv=<$cliente>;
                        print "Servidor:$serv";
}
close $cliente;

  
SERVIDOR 2: echo telnet - use Socket;
#!/usr/bin/perl  -W
use IO::Socket;
$port=;
$server=IO::Socket::INET->new(
                        PeerAddr => 127.0.0.1,
                        LocalPort => $port,
                        Listen => 1,
                        Reuse => 1,
)|| die "Error no se pudo crear el servidor";
print "Servidor Corriendo, esperando a cliente...\n";
while ($conexion = $server->accept() ) {
                        print "Conectado:",inet_ntoa((unpack_sockaddr_in(getpeername $conexion))[1])," \n";
            while ($conexion) {
                       $msg=<$conexion>;
                       print "Cliente: $msg";
                       print "->";$env=;
                       print "Enviaste:$env";
                       $conexion->send($env) || die "No se pudo enviar el mensaje.Por favor reinicie el servidor";
                       }
}
close($conexion);

Los servidores no suelen responder a la conexión del cliente, es decir, suele llamar el cliente y el servidor no le suele contestar “que quieres” ni nada similar.
Cuando se conecta el cliente al servidor, este crea algo similar a un Open de File. Abrir un socket es el equivalente a un Open.
Leer FILE:
#!/usr/bin/perl
open(FILE,"
@source = ;
foreach $linea (@source){
print $linea;
}
close(FILE);
Leer y Escribir en un FILE:
#!/usr/bin/perl
open(FILE,">archivo.txt");
while ($contador >= 5){
$contador++;
print FILE "Nuestra linea numero $contador\n";
}
close(FILE);
exit(1);

En socket: connect (SOCK, $paddr);
Guarda en SOCK , la conexión de dirección y puerto de esta conexión.
En file: open(FILE,"
Guarda en FILE, el contenido del fichero.


En el SERVIDOR:
Cuando el servidor hace la aceptación de la conexión (accept), este es bloqueante.
Un servidor siempre tiene un bucle, esperando la conexión.
while(1) es igual que while(accept(Z,S))
            while() Mientras fluye el accept, es un read
            Ahora el servidor bloquea el canal y espera leer del cliente
En el CLIENTE:
           
connect(S, …)
           
while()
                        print S
                        print S
                        print S

IPC mediante socket:
El bucle de interacción es el que puede variar según protocolo.
Server                                                Cliente
connect                                              while(accept){
while(){                                                         while(){

                                   IO
funcion librería                       call system                  en perl
funcion real                            read                 →        sysread
                                             write               →        syswrite
Las funciones se dividen en 2, funciones de sistema y funciones de librerías.


Qué pasa si el canal para la transferencia de datos creado, en el script Servidor, es cerrado con la función 'close', y el cliente va a ejecutar una lectura.
  connect                     <---------------->           accept
   escribe al canal        <---------------->           lee del canal
   lee del canal             <---------------->           escribe al canal
   escribe al canal        <---------------->           lee del canal
    ---- hasta aquí todo bien, ambos están coordinados ----
   lee del canal             <---------------->           close el canal.
Como vemos el cliente esta ejecutando una lectura (está bloqueado, esperando) mientras que el Servidor nunca escribirá, ya que lo que hace es cerrar el canal con lo cual 'no hay más canal'.
En el Servidor está todo bien, podrá ir a recibir una nueva conexión cliente, es decir un nuevo ciclo del bucle.
En el cliente "no están bien las cosas", el kernel reacciona ....
Si llamamos a close() no se podrá escribir o leer usando ese socket, y si alguien trata de hacerlo recibirá un mensaje de error(excepción).
El kernel genera la señal |broken(señal PIPE).
Cuando el servidor cierra el socket (close()), es END FILE.

  
Las señales a los programas los envía los kernels, por ejemplo con kill -9 proceso, en realidad se le está diciendo al kernel, envía la señal -9 al programa. Las señales siempre son para procesos.
La señal que se envía al kernel realiza una acción por defecto, por ejemplo, el CONTROL-C envía también una señal (kill -2 proceso).
            #!/usr/bin/perl
            print "Escribe: ";
            while(){
                        print $_;
                        print "Escribe: ";
            }

            #Se corta la entrada de datos(STDIN) con control-D

            #!/usr/bin/perl
            open(F, $ARGV[1]) or die "ERROR de apertura de fichero";
            while(){
                        print $_;
            }
            close F;

            #Se corta la entrada de datos

  
Especificaciones de la ACTIVIDAD:
Definir una función en el programa cliente que en ningún momento se va a llamar, pero se va a tener que agregar que equivale a signal (man 2 signal), que indique al kernel que debe ejecutar.
#!/usr/bin/env perl
$SIG{'INT'} = \&sigIntHandler;
sleep 100;
sub sigIntHandler {
            print "INT caught!\n";
            exit;
}

#kill -s SIGINT proceso = kill -2 proceso

#!/usr/bin/env perl
$SIG{'INT'} = \&sigIntHandler;
sleep 100;
print hola;
sleep 20;
print "luego del sleep\n\n";
#Este es tu programa
sub sigIntHandler {
            print "INT caught!\n";
            print "NO ME MATAS, JEJEJEJE t!\n";
}
#kill -s SIGINT proceso = kill -2 proceso
#Si se hace CONTROL-C, llama a la función  y muestra el print
#La señal -9, -17, -23 no puede ser atrapada



Socket UDP
El cliente crea la primera función, función socket
use Socket;
socket (S, ….)
…....
…. Canal creado, etc
….
Trabajo, interactúo con el servidor
…. acabo
Ahora no me sirve para conectar con un nuevo servidor.
No puede usarse para iniciar y conectar con un servidor distinto una vez terminado con el actual.
ESTO OCURRE EN TCP!!!
Hay que crear de nuevo el socket.
Mientras que en UDP si te sirve, ...



Como funcionan los servicios:
En los servicios, existe una conexión del cliente con el servidor, pero luego este servidor le pasa el “tubo” a un hijo para que pueda este interactuar con el cliente, mientras el servidor padre siguen en la conexión y a la escucha.
 while (1) {
     dfClient = accept ( dfServer, puntSockClient, &longClient );
                          
   /* acepta la conexion cliente */
     printf ("\n acepto la conexion \n");
     if ( fork() == 0 )  /* crea hijo y envia fichero */
       {
         escribeFichero ( dfClient );
         close ( dfClient );     
  /* cierra el socket */
         exit ( 0 );  
        }else {
       close ( dfClient );    
 /* cierra el descriptor cliente */
   }                                
   /* en el padre */

/*El padre le retorna al hijo 0 y el hijo le retorna al padre su PID*/


#!/usr/bin/perl
$pid = fork();

if( $pid == 0 ){
print "Esto es un proceso HIJO ",$$,"\n";
sleep 50;
print "Proceso HIJO acabado\n";
exit 0;
}
print "**Esto es un proceso PADRE $$ y HIJO y su PID es $pid **\n";
sleep 30;
print "Proceso PADRE acabado\n";
exit 0;


Comentarios