DOCTORADO EN INFORMÁTICA
SOCKETS: COMUNICACIÓN ENTRE PROCESOS DISTRIBUIDOS
Miguel Rueda Barranco
mrueda@lsi.us.es
Junio 1.996

1.- El concepto de red. 

2.- Redes Ethernet. 

3.- Internet. 
    3.1.- El modelo TCP/IP. 
    3.2.- Arquitectura y direccionamiento. 

4.- Los Sockets. 
    4.1.- Tipos de sockets. 
    4.2.- El dominio de un socket. 
    4.3.- Folosofía Cliente-Servidor: el Servidor. 
    4.4.- El Cliente. 




Comparación sockets-pipes.

Ejemplo de comunicación mediante Sockets UNIX ( en la misma máquina ).

Ejemplo de comunicación con sockets INET ( diferentes máquinas ).

Código fuente de las principales funciones de la Shell de comunicaciones. 






1.- EL CONCEPTO DE RED. 

     Por red entendemos un sistema de ordenadores interconectados, a través de los cuales se podrá compartir recursos e intercambiar información entre las diferentes máquinas. El concepto modo distribuido en contraposición a modo repartido con respecto a los recursos de una red va tomando cada vez más auge en las redes. En el modo repartido, los recursos deben estar localizados explícitamente. En el modo distribuido, el usuario no tiene por qué saber donde se localiza cada uno de los recursos: para ellos cada tipo de recurso es un único recurso virtual, que englobará a todos los recursos de ese tipo repartidos a través de sus distintas localizaciones. Nace así el concepto de Sistemas Abiertos introducido por la ISO ( International Standards Organization ) definiendo las diferentes interacciones entre los equipos que integran una red.  

Como consecuencia de ello, ISO define el modelo OSI (Open Systems Interconnection), una arquitectura de red basada en la descomposición en siete niveles de abstracción de red, cada uno de los cuales se apoya en los servicios que lo ofrece el nivel inmediatamente inferior. Cada nivel tiene una función bien definida. El conjunto de reglas que rigen el diálogo ( virtual ) entre un nivel de un sistema y su homólogo en otro sistema se conoce como el protocolo de ese nivel. El modelo OSI publicó estándares para cada uno de estos niveles. Las máquinas UNIX se basan en estos niveles, que son: 


     * Nivel 1: Nivel Físico. 
     * Nivel 2: Nivel de Enlace de Datos. 
     * Nivel 3: Nivel de Red. 
     * Nivel 4: Nivel de Transporte. 
     * Nivel 5: Nivel de Sesión. 
     * Nivel 6: Nivel de Presentación. 
     * Nivel 7: Nivel de Aplicación. 

Sólo nos interesará conocer algo sobre los niveles 3 ( de Red ), nivel 4 ( de Transporte ) y los niveles 5, 6 y 7. 

     - Nivel de Red: 

          En él se solucionan los problemas de encaminamiento de la información a través de las distintas subredes. Por tanto, se necesita conocer la topología de la red y la interconexión de redes. Encontrar una ruta para la comunicación será su misión fundamental. 
      
     Aporta dos tipos de servicios: 
     Servicio sin conexión, recibiendo del nivel superior paquetes de información o datagramas. Se recalcula la ruta a seguir por cada paquete recibido. INTERNET posee este mecanismo. 

     Servicio orientado a conexión, que establece un circuito virtual entre las máquinas correspondientes. Así se asegura el flujo y el control de errores. X25.PLP adopta este esquema. 


     - Nivel de Transporte: 
          Aquí se establece la comunicación virtual entre aplicaciones y no ya entre máquinas ( comunicación extremo a extremo ). El servicio más importante que presenta es el modo conectado ( conexión segura, control de flujo, ensamblado,... ). Mediante la multiplexación se podrá establecer varias conexiones a nivel de transporte mediante el uso de una sola conexión de red. Los protocolos TCP y UDP pertenecen a este nivel en la familia ARPA (INTERNET). 


     - Niveles de Sesión-Presentación-Aplicación: 

          Ofrecen servicios orientados al usuario y no ya exclusivamente orientados a un nivel superior. El establecimiento de una sesión de comunicación, la gestión del diálogo y la sincronización son servicios que aportan estos niveles. También se ubican en este nivel las RPC ( llamadas a procedimientos remotos ), XDR y sistemas de ficheros tipo NFS. 


2.- REDES ETHERNET. 

     Las Redes Ethernet fueron desarrolladas por Xerox a principio de los sesenta, estandarizándose por otros fabricantes posteriormente. Físicamente la red la compone un cable coaxial pasivo, con una longitud máxima de 500 metros antes de tener que instalar un repetidor, siendo dos el número máximo de repetidores que pueden situarse entre dos puntos. La conexión de una máquina a la red se realiza por medio de un transceiver. La topología de este red difusora es en bus, con 10 Mbits/sg. de capacidad y protocolo CSMA/CD. Las direcciones a través de esta red son de 48 bits (6 grupos de 2 dígitos hexadec. hh:hh:hh:hh:hh:hh). 
Estas direcciones las administra el IEEE. 


Las redes Ethernet son LANs ( Local Area Networks ) que mediante interconexiones a través de bridges ( puentes ), routers ( enrutadores ) y gateways ( pasarelas ) forman lo que se conoce como WANs ( large Wide Area Networks ). La necesidad de intercambiar información entre todas estas LANs y WANs genera un esquema de direccionamiento y enrutado estándar para todas ellas: Internet agrupa al mayor número de redes actualmente ( no todas son Ethernet ) y todas ellas utilizan el protocolo de comunicación TCP/IP. 


3.- INTERNET. 

     Internet se origina gracias a DARPA ( Defense Advanced Research Projects Agency ) y a su idea de crear una red lógica que esconda la heterogeneidad de las redes que se interconectan. Debido a este afán, se definen un modelo de interconexión basado en el protocolo internet TCP/IP, que describe su estructura de comunicaciones de red utilizando cinco niveles, a diferencia de lo marcado por el modelo de referencia OSI. 

3.1.- El modelo TCP/IP. 

     Los cinco niveles del modelo TCP/IP son: 
                  TCP/IP                OSI
            +------------------+--------------------+
    (NFS)   |                  | 7. APLICACION      |
            |                  +--------------------+
    (XDR)   | 5. APLICACION    | 6. PRESENTACION    |
            |                  +--------------------+
    (RPC)   |                  | 5. SESION          |
            +------------------+--------------------+
 (TCP/UDP)  | 4. TRANSPORTE    | 4. TRANSPORTE      |
            +------------------+--------------------+
 (IP/ICMP)  | 3. INTERNET      | 3. RED             |
            +------------------+--------------------+
 TRAMA ETHER| 2. INTERFAZ RED  | 2. ENLACE DE DATOS |
            +------------------+--------------------+
 RED ETHER  | 1. HARDWARE      | 1. FISICO          |
            +------------------+--------------------+


         
Nivel Interfaz de Red y Hardware: 

     agrupa los bits en tramas para el manejo de la información y se ocupa de las características técnicas de la red ( voltajes, pines, ... ) 

- Nivel Internet: 

     se corresponde con el nivel de Red OSI y controla el direccionamiento de la información. El IP se trata de un protocolo para el intercambio de datagramas en modo no conectado. Esto no garantiza la llegada de mensajes ( cosa que se hará con el TCP ).  El algoritmo de direccionamiento de Internet se basa en tablas de direccionamiento de los datagramas difundidos por los gateways. 

- Nivel de Transporte: 

     se corresponde con el de Transporte OSI, garantizando la seguridad de la conexión y el control del flujo. Incluye el Protocolo de Control de Transmisión ( TCP ) y el Protocolo de Datagrama de Usuario ( UDP ).  

     * El TCP es un protocolo orientado a conexión que transporta de forma segura grupos de octetos ( segmentos ) modo duplex (en los dos sentidos). 

     Utiliza el mecanismo de puerto ( al igual que el protocolo de transporte UDP, pero que actúa en modo datagrama no conectado ). Este mecanismo se basa en la asignación para cada uno de los protocolos del nivel de transporte (TCP o UDP) de un conjunto de puertos de E/S identificados mediante un número entero. Así TCP y UDP multiplexarán las conexiones por medio de los números de los puertos. Existen una serie de puertos reservados a aplicaciones estándares Internet (ECHO - puerto 7, FTP - puerto 21, TELNET - puerto 23). El archivo /etc/services contiene la lista de los puertos estándar. En UNIX están reservados los números de puerto inferiores a 1024. El resto pueden ser utilizados, cualidad fundamental que aprovechan los programas definidos por el usuario para el establecimiento de comunicaciones entre hosts. 


- Nivel de Aplicación: 

     Incluye los niveles OSI de sesión, presentación y aplicación. Ejemplos de estos niveles son el telnet, ftp o el sistema de ficheros de red NFS para el nivel de aplicación, el lenguaje de descripción de información XDR para el nivel de presentación o la interfaz de llamada a procedimientos remotos RPC. 

     -> Por ejemplo, el formato de una trama telnet sería: 
                              /etc/services
    +--------------+--------+--------+----------------+
    | Dir. Ethernet|   IP   |   TCP  |    telnetd     |
    +--------------+--------+--------+----------------+
       /etc/host    /etc/protocols       inetd.conf


3.2.- Arquitectura y direccionamiento. 

Con respecto a la arquitectura de Internet, la unión de las redes se basa en la existencia de gateways entre ellas. Pero lo más importante dentro de esta incompatibilidad de redes es otorgar a los hosts implicados una dirección lógica que se componga de: 
          - una dirección de red 
          - una dirección de la máquina dentro de la red 

La dirección completa ocupa 32 bits, dándose en forma de 4 octetos: 
                    n1.n2.n3.n4 
Cada campo es un número decimal entre 0 y 255. 

     - La dirección nula se refiere a la red. 
     - Para direccionar un mensaje a todas las máquinas ( broadcast ), se usa una dirección dentro de la red en la cual todos sus bits son iguales a 1. 


     - El valor 127 en el primer campo se llama loopback y se refiere a una interfaz que permite al host enviarse paquetes a sí mismo. 

Existen varias clase de redes: 

     Redes clase A:     gran tamaño. El primer bit es 0, [1-127], con lo que su dirección son los 7 siguientes bits de la dirección de 32 bits (permite 127 redes de clase A). Los 24 bits restantes se usan para direccionar sus hosts locales.  



     Redes clase B:     tamaño medio. Los dos primeros bits son 10, [128-191], y su dirección la componen los siguientes 14 bits de la dirección de 32 bits ( 16384 redes de clase B ). Los 16 bits restantes se usan para direccionar sus hosts locales. 

  

     Redes clase C:     pequeño tamaño. Su dirección la componen los siguientes 21 bits a los tres primeros, 110, característicos [192-223] ( 2097152 redes de clase C ). Los 8 bits restantes se usan para direccionar sus hosts locales ( 256 hosts: del 0 al 255 ). 



     Redes clase D: Dirección multicast: 

          Sus primeros 4 bits son 1110 [224-239], indicando que s etrata de una dirección multicast. Los restantes 28 bits comprenden un grupo específico multicast. Esta dirección es una dirección destino para una o varias máquinas ( a diferencia de las clases anteriores, que se referían sólo a una máquina ) 



Para simplificar el direccionamiento, se utilizan direcciones simbólocas del tipo  
       host.organización ( dominios ) 
El fichero /etc/hosts muestra correspondencia entre las direcciones IP y los nombres de los hosts definidos por defecto. 

4.- LOS SOCKETS. 

     Los sockets no son más que puntos o mecanismos de comunicación entre procesos que permiten que un proceso hable ( emita o reciba información ) con otro proceso incluso estando estos procesos en distintas máquinas. Esta característica de interconectividad entre máquinas hace que el concepto de socket nos sirva de gran utilidad. Esta interfaz de comunicaciones es una de las distribuciones de Berkeley al sistema UNIX, implementándose las utilidades de interconectividad de este Sistema Operativo ( rlogin, telnet, ftp, ... ) usando sockets. 

     Un socket es al sistema de comunicación entre ordenadores lo que un buzón o un teléfono es al sistema de comunicación entre personas: un punto de comunicación entre dos agentes ( procesos o personas respectivamente ) por el cual se puede emitir o recibir información. La forma de referenciar un socket por los procesos implicados es mediante un descriptor del mismo tipo que el utilizado para referenciar ficheros. Debido a esta característica, se podrá realizar redirecciones de los archivos de E/S estándar (descriptores 0,1 y 2) a los sockets y así combinar entre ellos aplicaciones de la red. Todo nuevo proceso creado heredará, por tanto, los descriptores de sockets de su padre.  

     La comunicación entre procesos a través de sockets se basa en la filosofía CLIENTE-SERVIDOR: un proceso en esta comunicación actuará de proceso servidor creando un socket cuyo nombre conocerá el proceso cliente, el cual podrá "hablar" con el proceso servidor a través de la conexión con dicho socket nombrado. 

     El proceso crea un socket sin nombre cuyo valor de vuelta es un descriptor sobre el que se leerá o escribirá, permitiéndose una comunicación bidireccional, característica propia de los sockets y que los diferencia de los pipes, o canales de comunicación unidireccional entre procesos de una misma máquina. El mecanismo de comunicación vía sockets tiene los siguientes pasos: 

     1º) El proceso servidor crea un socket con nombre y espera la 
          conexión.  
     2º) El proceso cliente crea un socket sin nombre. 
     3º) El proceso cliente realiza una petición de conexión al socket 
          servidor. 
     4º) El cliente realiza la conexión a través de su socket mientras el 
          proceso servidor mantiene el socket servidor original con 
          nombre. 


     Es muy común en este tipo de comunicación lanzar un proceso hijo, una vez realizada la conexión, que se ocupe del intercambio de información con el proceso cliente mientras el proceso padre servidor sigue aceptando conexiones. Para eliminar esta característica se cerrará el descriptor del socket servidor con nombre en cuanto realice una conexión con un proceso socket cliente. 

-> Todo socket viene definido por dos características fundamentales: 

     - El tipo del socket, que indica la naturaleza del mismo, el tipo de comunicación que puede generarse entre los sockets. 

     - El dominio del socket especifica el conjunto de sockets que pueden establecer una comunicación con el mismo. 


Vamos a estudiar con más detalle estos dos aspectos: 

4.1.- Tipos de sockets. 

     Define las propiedades de las comunicaciones en las que se ve envuelto un socket, esto es, el tipo de comunicación que se puede dar entre cliente y servidor. Estas pueden ser: 
     - Fiabilidad de transmisión. 
     - Mantenimiento del orden de los datos. 
     - No duplicación de los datos. 
     - El "Modo Conectado" en la comunicación. 
     - Envío de mensajes urgentes. 

Los tipos disponibles son los siguientes: 

     * Tipo SOCK_DGRAM:     sockets para comunicaciones en modo no conectado, con envío de datagramas de tamaño limitado ( tipo telegrama ). En dominios Internet como la que nos ocupa el protocolo del nivel de transporte sobre el que se basa es el UDP. 

     * Tipo SOCK_STREAM:     para comunicaciones fiables en modo conectado, de dos vías y con tamaño variable de los mensajes de datos. Por debajo, en dominios Internet, subyace el protocolo TCP. 

     * Tipo SOCK_RAW:     permite el acceso a protocolos de más bajo nivel como el IP ( nivel de red ) 

     * Tipo SOCK_SEQPACKET: tiene las características del    SOCK_STREAM pero además el tamaño de los mensajes es fijo. 

4.2.- El dominio de un socket. 

     Indica el formato de las direcciones que podrán tomar los sockets y los protocolos que soportarán dichos sockets.  
     La estructura genérica es 

     struct sockaddr { 
               u__short     sa__family;         /* familia */ 
               char         sa__data[14];       /* dirección */ 
          }; 

Pueden ser: 

     * Dominio AF_UNIX ( Address Family UNIX ): 

               El cliente y el servidor deben estar en la misma máquina. Debe incluirse el fichero cabecera /usr/include/sys/un.h. La estructura de una dirección en este dominio es: 
               struct sockaddr__un { 
               short          sun__family;  /* en este caso AF_UNIX */ 
               char          sun__data[108]; /* dirección */ 
                    }; 

     * Dominio AF_INET ( Address Family INET ): 
               El cliente y el servidor pueden estar en cualquier máquina de la red Internet. Deben incluirse los ficheros cabecera /usr/include/netinet/in.h, /usr/include/arpa/inet.h, /usr/include/netdb.h. La estructura de una dirección en este dominio es: 

               struct in__addr { 
                    u__long     s__addr; 
               }; 

               struct sockaddr__in { 
               short          sin_family;  /* en este caso AF_INET */ 
               u__short     sin_port;   /* numero del puerto */ 
               struct in__addr          sin__addr; /* direcc Internet */ 
               char          sin_zero[8];    /* campo de 8 ceros */ 
                    }; 

          Estos dominios van a ser los utilizados en xshine. Pero existen otros como: 
     * Dominio AF_NS: 
          Servidor y cliente deben estar en una red XEROX. 
     * Dominio AF_CCITT: 
          Para protocolos CCITT, protocolos X25, ... 


4.3.- FILOSOFIA CLIENTE-SERVIDOR: el Servidor. 

     Vamos a explicar el proceso de comunicación servidor-cliente en modo conectado, modo utilizado por las aplicaciones estándar de Internet (telnet, ftp). El servidor es el proceso que crea el socket no nombrado y acepta las conexiones a él. El orden de las llamadas al sistema para la realización de esta función es: 

1º) int socket ( int dominio, int tipo, int protocolo 
          crea un socket sin nombre de un dominio, tipo y p 
rotocolo específico 
          dominio   : AF_INET, AF_UNIX 
          tipo      : SOCK__DGRAM, SOCK__STREAM 
          protocolo : 0 ( protocolo por defecto ) 

2º) int bind ( int dfServer, struct sockaddr* direccServer, int longDirecc ) 

          nombra un socket: asocia el socket no nombrado de descriptor dfServer con la dirección del socket almacenado en direccServer. La dirección depende de si estamos en un dominio AF_UNIX o AF_INET. 

3º) int listen ( int dfServer, int longCola ) 

          especifica el máximo número de peticiones de conexión pendientes. 

4º) int accept ( int dfServer, struct sockaddr* direccCliente, int* longDireccCli) 

          escucha al socket nombrado servidor dfServer y espera hasta que se reciba la petición de la conexión de un cliente. Al ocurrir esta incidencia, crea un socket sin nombre con las mismas características que el socket servidor original, lo conecta al socket cliente y devuelve 
un descriptor de fichero que puede ser utilizado para la comunicación con el cliente. 


4.4.- El Cliente. 

     Es el proceso encargado de crear un socket sin nombre y posteriormente enlazarlo con el socker servidor nombrado. O sea, es el proceso que demanda una conexión al servidor. La secuencia de llamadas al sistema es: 

1º) int socket ( int dominio, int tipo, int protocolo 

          crea un socket sin nombre de un dominio, tipo y protocolo específico 
          dominio   : AF_INET, AF_UNIX 
          tipo      : SOCK__DGRAM, SOCK__STREAM 
          protocolo : 0 ( protocolo por defecto ) 

2º) int connect ( int dfCliente, struct sockaddr* direccServer, int longDirecc ) 

          intenta conectar con un socket servidor cuya dirección se encuentra incluida en la estructura apuntada por direccServer. El descriptor dfCliente se utilizará para comunicar con el socket servidor. El tipo de estructura dependerá del dominio en que nos encontremos. 

Una vez establecida la comunicación, los descriptores de ficheros serán utilizados para almacenar la información a leer o escribir. 


SERVIDOR                                                      .                           CLIENTE 
descrServer = socket ( dominio, SOCK_STREAM,PROTOCOLO)  descrClient = socket (dominio, SOCK_STREAM,PROTOCOLO) 
bind (descrServer, PuntSockServer,longServer) 
do { 
listen (descrServer, longCola) 
descrClient = accept (descrServer,PuntSockClient,longClient)          result = connect (descrClient, PuntSockServer,longserver) 
[  close (descrServer) ]            } while ( result == -1 )   
< DIALOGO >   < DIALOGO >  
close (descrClient)    close (descrClient)  

   
COMPARACION SOCKETS-PIPES COMO MECANISMOS DE COMUNICACION ENTRE PROCESOS 

SOCKETS                                      PIPES
refenciado por descriptores  referenciado  por array de descriptores 
admite comunicación entre procesos de distintas máquinas  sólo admite comunicación entre procesos de la misma máquina 
comunicación bidireccional  comunicación unidireccional 
filosofía cliente-servidor  simple intercambio de información 

         
EJEMPLO DE COMUNICACION MEDIANTE SOCKETS TIPO UNIX ( EN LA MISMA MAQUINA ) 

/************************************************************/ 
/**************    servidor.c          **********************/ 
/************************************************************/ 
/*********   proceso servidor con sockets AF_UNIX  **********/ 
/************************************************************/ 


#include <stdio.h> 
#include <signal.h> 
#include <sys/types.h> 
#include <sys/socket.h> 

#include <sys/un.h>           /*  para sockets UNIX  */ 

#define PROTOCOLO_DEFECTO 0 

/****************************************************/ 
main() 
{ 
 int dfServer, dfClient, longServer, longClient; 

 struct sockaddr_un dirUNIXServer; 
 struct sockaddr_un dirUNIXClient; 

 struct sockaddr* puntSockServer; 
 struct sockaddr* puntSockClient; 
 signal ( SIGCHLD, SIG_IGN );    /*  para no crear zombies */ 

 puntSockServer = ( struct sockaddr* ) &dirUNIXServer; 
 longServer = sizeof ( dirUNIXServer ); 
 puntSockClient = ( struct sockaddr* ) &dirUNIXClient; 
 longClient = sizeof ( dirUNIXClient ); 

 dfServer = socket ( AF_UNIX, SOCK_STREAM, PROTOCOLO_DEFECTO ); 
                 /* se crea un socket UNIX, bidireccional */ 
 dirUNIXServer.sun_family = AF_UNIX;    /* tipo de dominio */ 
 strcpy ( dirUNIXServer.sun_path, "fichero" );   /* nombre */ 
 unlink ( "fichero" ); 
 bind ( dfServer, puntSockServer, longServer );   /* crea el fichero */ 
                                          /* o sea, nombra el socket */ 
 printf ("\n estoy a la espera \n"); 
 listen ( dfServer, 5 ); 
 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 */ 
} 




/******** funcion escribeFichero( df ) ***************/ 
escribeFichero ( int df ) 
{ 
 static char* linea1 = "esta es la linea 1, "; 
 static char* linea2 = "y esta la linea 2. "; 

 write ( df, linea1, strlen (linea1) + 1 );  
  write ( df, linea2, strlen (linea2) + 1 ); 
} 
  
/******************   fin de servidor.c   ***********************/ 



/************************************************************/ 
/**************                         cliente.c           **********************/ 
/************************************************************/ 
/*********                   proceso cliente con sockets AF_UNIX   **********/ 
/************************************************************/ 

#include <stdio.h> 
#include <signal.h> 
#include <sys/types.h> 
#include <sys/socket.h> 

#include <sys/un.h>           /*  para sockets UNIX  */ 

#define PROTOCOLO_DEFECTO 0 

/****************************************************/ 

main() 
{ 
 int dfClient, longServer, resultado; 
 struct sockaddr_un dirUNIXServer; 
 struct sockaddr* puntSockServer; 

 puntSockServer = ( struct sockaddr* ) &dirUNIXServer; 
 longServer = sizeof ( dirUNIXServer ); 

 dfClient = socket ( AF_UNIX, SOCK_STREAM, PROTOCOLO_DEFECTO ); 
                 /* se crea un socket UNIX, bidireccional */ 

 dirUNIXServer.sun_family = AF_UNIX;    /* tipo de dominio server */ 
 strcpy ( dirUNIXServer.sun_path, "fichero" );   /* nombre server */ 

 do   
  { 
   resultado = connect ( dfClient, puntSockServer, longServer ); 
   if ( resultado == -1 ) sleep (1);   /* reintento */ 
 } 
 while ( resultado == -1 ); 

 leeFichero ( dfClient );     /* lee el fichero */ 
 close ( dfClient );      /* cierra el socket */ 
 exit (0);     /* buen fin */ 

} 




/*************     leeFichero ( df )  *****************/ 

leeFichero ( int df ) 
{ 
 char cad[200]; 
 while ( leeLinea ( df, cad ) )      /* lee hasta fin de la entrada */ 
   printf ("%s\n", cad );            /* e imprime lo leido */ 

} 


/*************   leeLinea ( df, cad )  ******************/ 
leeLinea ( int df, char *cad ) 
{ 
 int n; 
 do 
  { 
    n = read ( df, cad, 1 );          /* lectura de un caracter */ 
  } 
 while ( n > 0 && *cad++ != NULL );   /* lee hasta NULL o fin entrada */ 

 return ( n > 0 );   /* devuelve falso si fin de entrada */ 

} 



/************* fin de cliente.c ****************************/ 




EJEMPLO DE COMUNICACION CON SOCKETS INET 
( ENTRE DIFERENTES MAQUINAS ) 



/*************************************************/ 
/****************   hora.c ***********************/ 
/*************************************************/ 
/*    visualiza el dia y la hora de un host      */ 
/*************************************************/ 

#include <stdio.h>  
#include <signal.h>  
#include <ctype.h>  
#include <sys/types.h>  
#include <sys/socket.h>  

#include <netinet/in.h>          /* socket INET */ 
#include <arpa/inet.h>  
#include <netdb.h>  


#define PUERTO_HORA     13 
#define PROTOCOLO_DEFECTO 0 

unsigned long promptForINETAddress (); 
unsigned long nameToAddr (); 
/***********************************/ 
main () 
{ 
int clientFd; 
int serverLen; 
int result; 



struct sockaddr_in serverINETAddress; 
struct sockaddr* serverSockAddrPtr; 
unsigned long inetAddress; 

serverSockAddrPtr = (struct sockaddr *) &serverINETAddress; 
serverLen = sizeof (serverINETAddress); 

while (1) 
 { 
  inetAddress = promptForINETAddress (); 
  if (inetAddress == 0) break; 

  bzero ((char *)&serverINETAddress , sizeof(serverINETAddress)); 
  serverINETAddress.sin_family = AF_INET; 
  serverINETAddress.sin_addr.s_addr = inetAddress; 
  serverINETAddress.sin_port = htons ( PUERTO_HORA ); 

  clientFd = socket ( AF_INET,  SOCK_STREAM,  PROTOCOLO_DEFECTO ); 

  do 
   { 
    result = connect( clientFd, serverSockAddrPtr, serverLen ); 
    if (result == -1) sleep(1); 
   } 
  while (result == -1); 

  readTime(clientFd); 
  close(clientFd); 
 } 
 exit(0);  
 } 



/******************  promptForINETAddress () **************************/ 
unsigned long promptForINETAddress () 
{ 
 char hostName [100]; 
 unsigned long inetAddress; 
  
  do 
  { 
   printf ("Nombre maquina (q = salir, s = maquina propia): "); 
   scanf("%s",hostName); 
   if ( strcmp (hostName,"q") == 0 ) return(0); 
   inetAddress = nameToAddr (hostName); 
   if (inetAddress == 0 ) printf ("\n Maquina no encontrada\n"); 
  } 
 while ( inetAddress == 0 ); 
} 

/*******************  nameToAddr ( name ) *************************/ 
unsigned long nameToAddr (char *name) 
{ 
 char hostName[100]; 
 struct hostent* hostStruct; 
 struct in_addr* hostNode; 

 if (isdigit (name[0])) return (inet_addr (name)); 
 if (strcmp (name,"s") == 0) 
  { 
   gethostname (hostName,100); 
   printf("Nombre de la propia maquina es %s\n",hostName); 
  } 
 else 
  strcpy(hostName,name); 



 hostStruct = gethostbyname (hostName); 
 if (hostStruct == NULL) return (0);                         /** maquina no encontrada **/ 

 hostNode = (struct in_addr*) hostStruct->h_addr;   /* saca la dir. IP de la struct */ 
 printf("Direccion IP = %s\n", inet_ntoa (*hostNode)); 
 return (hostNode->s_addr);                                     /* devuelve la dir IP */ 
} 

/******************** readTime ( fd ) **********************/ 

readTime (int fd) 
{ 
 char str[200]; 

 printf("La hora en el puerto destino es "); 

 while (readLine (fd,str)) 
  printf("%s\n",str); 
} 

/************   readLine ( fd, str ) ******************************/ 
readLine (int fd,char* str) 
{ 
 int n; 
 do 
  { 
   n = read(fd,str,1); 
 } 
 while (n>0 && *str++ != '\n'); 
 return (n>0); 
} 


CODIGO FUENTE DE LAS PRINCIPALES FUNCIONES 
DE COMUNICACION DE LA SHELL DE COMUNICACIONES 

/* 
*----------------------------------------------------------------- 
* 
*   NOMBRE:      xshine.c 
*                Moisés Fernández Andrés & Miguel Rueda Barranco 
*               
 *   DESCRIPCION: Xwindow Shell for Internet network 
* 
*   FECHA:       06.09.94 
* 
*------------------------------------------------------------------ 
*/ 

/* 
*---------------------------------------------------------------- 
*   NOMBRE:      shell.h 
*                 
 *   DESCRIPCION: cabeceras para la shell de comunicaciones  
 *                de xshine 
*           
 *   FECHA:       05.09.94  
 * 
*--------------------------------------------------------------- 
*/ 

/* 
*------------------------------------------------- 
*      includes necesarios 
*------------------------------------------------- 
*/ 

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <signal.h> 
#include <ctype.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <sys/file.h>   
#include <sys/ioctl.h>  
#include <sys/socket.h> 
#include <sys/socketvar.h> 
#include <sys/un.h>  
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <netdb.h> 

#ifdef RS6000         /* necesario para sistemas RS/6000 */ 
#include <malloc.h> 
#endif 


/* 
*------------------------------------------- 
*       definicion de constantes                     
 *------------------------------------------- 
*/ 


#define MAX_STRING          50 
#define MAX_TOKENS          100 
#define LONG_MAX_TOKEN      30 
#define MAX_SIMPLE          3 
#define MAX_PIPES           3 
#define NO_ENCONTRADO       -1 
#define REGULAR             -1 
#define PERMISO_DEFECTO     0660 
#define PROTOCOLO_DEFECTO   0 
#define LONG_COLA_DEFECTO   5 
#define SOCKET_DORMIR       1 
#define FALSE               0 
#define TRUE                1 


/* 
*----------------------------------------- 
*             macros                          
 *----------------------------------------- 
*/ 



#define obtienePuntNodo(a)  (a *) malloc(sizeof(a))      /* para la asignacion */ 
                                                        /* de memoria         */ 


/* 
*------------------------------------------ 
*            tipos enumerados                  
 *------------------------------------------ 
*/ 


enum descriptorEnum { STDIN, STDOUT, STDERR };  

enum pipeEnum { READ, WRITE }; 

enum IOEnum { NO_REDIREC, REDIREC_FICHERO, 
              REDIREC_SERVIDOR, REDIREC_CLIENTE, REDIREC_PANT }; 

enum socketEnum { CLIENTE, SERVIDOR }; 

enum tiposock{ SOCKET_DOS_VIAS, SOCKET_ENTRADA, SOCKET_SALIDA }; 


/* 
*---------------------------------------------- 
*                 estructuras                     
 *---------------------------------------------- 
*/ 


struct simple 
 { 
   char *token[MAX_TOKENS];            /* tokens del comando      */ 
   int contoken;                       /* numero de tokens        */ 
   int redirecSalida;                  /* tipo de IO_enum         */ 
   int redirecEntrada;                 /* tipo de IO_enum         */ 
   int adicion;                        /* VERDAD para modo añadir */ 
   char fichSalida[MAX_STRING];        /* nombre fichero salida   */ 
   char fichEntrada[MAX_STRING];       /* nombre fichero entrada  */ 
   char socketSalida[MAX_STRING];      /* nombre socket salida    */ 
   char socketEntrada[MAX_STRING];     /* nombre socket entrada   */ 
 }; 


struct pipeline 
 { 
   struct simple simple [MAX_SIMPLE];      /* comandos en pipe           */ 
   int contcom;                            /* numero de comandos simples */ 
 }; 

struct secuencia 
 { 
   struct pipeline pipeline[MAX_PIPES];    /* pipes en secuencia  */ 
   int contpipe;                           /* numero de pipes     */ 
   int background;                         /* en background o no  */ 

 }; 

/* 
*------------------------------------------------------------ 
 
 *   PROTOTIPO:   void ejecutaSimple ( struct simple* ) 
* 
*   DESCRIPCION: ejecuta un comando simple         
 * 
*-------------------------------------------------------------- 
*/ 


void ejecutaSimple ( struct simple *p ) 

{ 

       

      if (redireccion (p) == TRUE)  
      { 

     ejecutaPrimitiva (p);  

      }       
      

}  /* fin de ejecutaSimple */ 


/* 
*------------------------------------------------------------------ 
 
 *   PROTOTIPO:   void ejecutaPrimitiva ( struct simple* ) 
* 
*   DESCRIPCION: ejecuta una primitiva simple a través del comando        
 *                "execvp" 
* 
*------------------------------------------------------------------ 
*/ 

void ejecutaPrimitiva ( struct simple *p )  

{   
   
   if ( ((p->redirecSalida != REDIREC_SERVIDOR) && (p->redirecEntrada != REDIREC_SERVIDOR)) 
      
     && 
          


     (  
       (p->redirecSalida == REDIREC_CLIENTE)  || 
       (p->redirecEntrada == REDIREC_CLIENTE) || 
       (p->redirecSalida == REDIREC_FICHERO)  || 
       (p->redirecEntrada == REDIREC_FICHERO)  
     )  
      ) 
        
       mandaPadre(SIGDIBUJAR); 

      execvp (p->token[0], p->token); 
   
   
      if (errno != 0)   /* ha ocurrido un error */ 
     { 

       if (raiz->background == TRUE)         
         { 
            mandaPadreExit(SIGERRCMD); 
         } 
       else 
         { 
            mandaPadre(SIGERRCMD); 
         } 

     }  
     
}  /* fin de ejecutaPrimitiva */ 
    


/* 
*------------------------------------------------------------ 
 
 *   PROTOTIPO:   int redireccion ( struct simple* ) 
* 
*   DESCRIPCION: implementa la redirección correspondiente         
 * 
*-------------------------------------------------------------- 
*/ 


int redireccion ( struct simple *p ) 

{ 
 int masc; 




 switch (p->redirecEntrada) 
   { 

     case REDIREC_FICHERO: 
       
      if (!dupDf (p->fichEntrada, O_RDONLY, STDIN))  
         return (FALSE); 
      break; 
       

      case REDIREC_SERVIDOR: 
      
      if (raiz->background == FALSE)     /* se ha mandado un socket servidor en foreground */ 
        { 
          mandaPadre(SIGERRSERBACK); 
          return(FALSE); 
        }   
        
      if (!servidor (p->socketEntrada, SOCKET_ENTRADA)) 
         return(FALSE);  
      break; 
       

      case REDIREC_CLIENTE: 
       
      if (!cliente (p->socketEntrada, SOCKET_ENTRADA)) 
          return (FALSE); 
      break; 
         
    } 

  
  switch (p->redirecSalida) 
   { 
    
      case REDIREC_FICHERO: 
      
      masc = O_CREAT|O_WRONLY|(p->adicion?O_APPEND:O_TRUNC); 
      if (!dupDf (p->fichSalida, masc, STDOUT)) 
          return (FALSE);  
      break; 
      

     case REDIREC_SERVIDOR: 

         if (raiz->background == FALSE)     /* se ha mandado un socket servidor en foreground */ 
        { 
          mandaPadre(SIGERRSERBACK); 
          return(FALSE); 
        }  
       
      if (!servidor (p->socketSalida, SOCKET_SALIDA))  
         return (FALSE); 
      break; 
       

      case REDIREC_CLIENTE: 
       
     if (!cliente (p->socketSalida, SOCKET_SALIDA))  
          return (FALSE); 
      break; 
      
      case REDIREC_PANT: 
      
       masc=O_CREAT|O_WRONLY|O_TRUNC; 

       if (!dupDf ("resultado", masc, STDOUT)) 
          return (FALSE); 
      
       break; 

   }  
   
  return (TRUE); 

 


/* 
*----------------------------------------------------------------- 
 
 *   PROTOTIPO:   int dupDf ( char *, int, int ) 
* 
*   DESCRIPCION: realiza la redirección duplicando los descriptores         
 *                de ficheros 
* 
*----------------------------------------------------------------- 
*/ 



int dupDf ( char *nombre , int mascara , int dfStd )  

{ 
 int df; 

 df = open (nombre, mascara, PERMISO_DEFECTO); 

 if (df == -1) 
   { 

     mandaPadre(SIGERRREDIREC); 
     return (FALSE);  

    } 

 dup2 (df, dfStd);  /* copia sobre el descriptor de fichero estándar */ 
  
  close (df);        /* cierra el original */ 

 return (TRUE);  

}  /* fin de dupDf */ 




/* 
*-------------------------------------------------------- 
* 
*    PROTOTIPO:   int direccionInternet ( char* ) 
* 
*    DESCRIPCION: obtiene la dirección Internet del host 
* 
*-------------------------------------------------------- 
*/ 



int direccionInternet ( char *nombre )      

{ 
 return (strpbrk (nombre, "01234567890") != NULL); 
} 



/* 
*--------------------------------------------------------------- 
* 
*    PROTOTIPO:   void obtenerHostyPuerto ( char* , char*, int* ) 
* 
*    DESCRIPCION: almacena en "nombre" y "puerto" el nombre del  
 *                 nodo y su puerto de comunicaciones respectivamente 
*                 a partir de la variable "cad" 
* 
*-------------------------------------------------------------- 
*/ 




void obtenerHostyPuerto ( char *cad, char *nombre, int* puerto) 

{ 
 char *tok1,  *tok2;        /* decodifica Host y Puerto de una */ 
                           /* entrada cadena tipo NOMBRE.PUERTO */ 
 tok1 = strtok (cad, "."); 
 tok2 = strtok (NULL,"."); 

if (tok2 == NULL )       /* nombre perdido y toma por defecto el local */ 
   { 
     strcpy (nombre, ""); 
     sscanf (tok1, "%d", puerto); 
   } 
 else 
   { 
     strcpy (nombre, tok1); 
     sscanf (tok2, "%d", puerto); 
   } 
} 


/* 
*-------------------------------------------------------- 
* 
*    PROTOTIPO:   int cliente ( char* , int ) 
* 
*    DESCRIPCION: implementa el manejo del socket cliente 
* 
*-------------------------------------------------------- 
*/ 


int cliente ( char *nombre, int tipo ) 

{ 
 int dfCliente, resultado, internet, dominio, longServidor, puerto, time_out=0; 
 char nombreHost[100]; 
 struct sockaddr_un direccUNIXServidor; 
 struct sockaddr_in direccINETServidor; 
 struct sockaddr* puntDireccSockServidor; 
 struct hostent* estructHost; 
 struct in_addr* nodoHost; 

/*  Abriremos un socket cliente con un nombre y tipo específico */ 

  internet = direccionInternet ( nombre );   /* ¿es socket internet? */ 
 dominio = internet ? AF_INET : AF_UNIX; 

 dfCliente = socket (dominio, SOCK_STREAM, PROTOCOLO_DEFECTO); 

 if (dfCliente == -1) 
   { 
     mandaPadre(SIGERRCLISOCK);  
      return(FALSE); 

   } 

 if (internet)    /* es un socket internet */ 
   { 

     obtenerHostyPuerto (nombre, nombreHost, &puerto); 
     if (nombreHost[0] == NULL) gethostname (nombreHost,100); 
     direccINETServidor.sin_family = AF_INET; 
     estructHost = gethostbyname (nombreHost); 

     if (estructHost==NULL) 
      { 

        mandaPadre(SIGERRCLISOCK);  
         return (FALSE); 

      } 

     nodoHost = (struct in_addr*) estructHost->h_addr; 
     /*printf("Direccion IP %s \n", inet_ntoa (*nodoHost));*/ 
      
      direccINETServidor.sin_addr = *nodoHost;    /* nos da la direccion IP */ 
     direccINETServidor.sin_port = puerto;       /* nos da el puerto de comunic */  
      puntDireccSockServidor = (struct sockaddr*) &direccINETServidor; 
     longServidor = sizeof (direccINETServidor); 

   } 

 else    /* socket del dominio UNIX */ 
   { 

     direccUNIXServidor.sun_family = AF_UNIX; 
     strcpy (direccUNIXServidor.sun_path, nombre); 
     puntDireccSockServidor = (struct sockaddr*) &direccUNIXServidor; 
     longServidor = sizeof (direccUNIXServidor); 

   } 

 do  /* conexion a un servidor */ 
   { 
  
      resultado = connect (dfCliente, puntDireccSockServidor, longServidor); 
     if (resultado == -1) { sleep (SOCKET_DORMIR); time_out++; } 
     if (time_out == 2 ) { 
                         mandaPadre(SIGERRCLISOCK);  
                          return(FALSE);  
                          } 
   } 
 while (resultado == -1); 

 if (tipo == SOCKET_SALIDA) dup2 (dfCliente, STDOUT); 
 if (tipo == SOCKET_ENTRADA) dup2 (dfCliente, STDIN);   
     
  close (dfCliente);  /* cierra el descriptor del fichero cliente original */ 
 return (TRUE); 

}  /* fin de cliente */ 

/* 
*-------------------------------------------------------- 
* 
*    PROTOTIPO:   int servidor (char* , int ) 
* 
*    DESCRIPCION: implementa el manejo del socket servidor 
* 
*-------------------------------------------------------- 
*/ 


int servidor ( char *nombre, int tipo ) 

{ 
 int dfServidor, dfCliente, longServidor, longCliente; 
 int dominio, internet, puerto; 
 char nombreHost[100]; 
 struct sockaddr_un direccUNIXServidor; 
 struct sockaddr_un direccUNIXCliente; 
 struct sockaddr_in direccINETServidor; 
 struct sockaddr_in direccINETCliente; 
 struct sockaddr* puntDireccSockServidor; 
 struct sockaddr* puntDireccSockCliente; 


  internet = direccionInternet (nombre); 
 dominio = internet ? AF_INET : AF_UNIX; 

 dfServidor = socket (dominio, SOCK_STREAM, PROTOCOLO_DEFECTO); 

 if (dfServidor == -1) 
   { 
     mandaPadre (SIGERRSERSOCK); 
     return(FALSE); 
   } 

 mandaPadre (SIGDIBUJAR);   /* envia la señal de dibujar */ 

 if (internet)   /* es un socket internet */ 
   { 
  
      sscanf (nombre, "%d", &puerto);  /* obtiene numero y puerto */ 
      
      longServidor = sizeof (direccINETServidor); 
     bzero ((char*) &direccINETServidor, longServidor); 
     direccINETServidor.sin_family = AF_INET; 
     direccINETServidor.sin_addr.s_addr = htonl (INADDR_ANY);    
      direccINETServidor.sin_port = htons (puerto); 
     puntDireccSockServidor = (struct sockaddr*) &direccINETServidor; 

   } 

 else   /* socket dominio UNIX */ 
   { 

     direccUNIXServidor.sun_family = AF_UNIX; 
     strcpy (direccUNIXServidor.sun_path, nombre); 
     puntDireccSockServidor = (struct sockaddr*) &direccUNIXServidor; 
     longServidor = sizeof (direccUNIXServidor); 
      
      unlink (nombre);  /* borra el socket si ya existe */ 

   } 


 if (bind (dfServidor, puntDireccSockServidor, longServidor) == -1) 
   { 

     mandaPadre (SIGERRSERSOCK); 
     return (FALSE); 

   } 

 if (listen (dfServidor, LONG_COLA_DEFECTO) == -1) 
   { 

     mandaPadre (SIGERRSERSOCK); 
     return (FALSE);  

    } 

 if (internet) 
   { 

     longCliente = sizeof (direccINETCliente); 
     puntDireccSockCliente = (struct sockaddr*) &direccINETCliente; 

   } 

 else 
   { 

     longCliente = sizeof (direccUNIXCliente); 
     puntDireccSockCliente = (struct sockaddr*) &direccUNIXCliente; 

   } 


   
  dfCliente = accept (dfServidor, puntDireccSockCliente, &longCliente); 

 close (dfServidor); 

 if (dfCliente == -1) 
   { 

     mandaPadre (SIGERRSERSOCK); 
     return (FALSE); 

   } 

 if (tipo == SOCKET_SALIDA) dup2 (dfCliente, STDOUT); 
 if (tipo == SOCKET_ENTRADA) dup2 (dfCliente, STDIN);   
     
  close (dfCliente);  /* cierra el descriptor del fichero cliente original */ 
 return (TRUE); 
    
}  /* fin de servidor */  


/* 
*-------------------------------------------------------- 
* 
*    PROTOTIPO:   int borraServer ( char* ) 
* 
*    DESCRIPCION: desactiva un socket servidor creado 
* 
*-------------------------------------------------------- 
*/ 


int borraServer ( char *nombre ) 

{ 
 int dfCliente, resultado, internet, dominio, longServidor, puerto, time_out=0; 
 char nombreHost[100]; 
 struct sockaddr_un direccUNIXServidor; 
 struct sockaddr_in direccINETServidor; 
 struct sockaddr* puntDireccSockServidor; 
 struct hostent* estructHost; 
 struct in_addr* nodoHost; 

/*  Abriremos un socket cliente con un nombre y tipo específico */ 

  internet = direccionInternet ( nombre );   /* ¿es socket internet? */ 
 dominio = internet ? AF_INET : AF_UNIX; 

 dfCliente = socket (dominio, SOCK_STREAM, PROTOCOLO_DEFECTO); 

 if (dfCliente == -1) 
   { 
     Xerror(ERRORBORRAR);  
      return(FALSE); 

   } 

 if (internet)    /* es un socket internet */ 
   { 

     obtenerHostyPuerto (nombre, nombreHost, &puerto); 
     if (nombreHost[0] == NULL) gethostname (nombreHost,100); 
     direccINETServidor.sin_family = AF_INET; 
     estructHost = gethostbyname (nombreHost); 

     if (estructHost==NULL) 
      { 

        Xerror(ERRORBORRAR);  
         return (FALSE); 

      } 
           
      nodoHost = (struct in_addr*) estructHost->h_addr; 
     /*printf("Direccion IP %s \n", inet_ntoa (*nodoHost));*/ 
      
      direccINETServidor.sin_addr = *nodoHost;    /* nos da la direccion IP */ 
     direccINETServidor.sin_port = puerto;       /* nos da el puerto de comunic */  
      puntDireccSockServidor = (struct sockaddr*) &direccINETServidor; 
     longServidor = sizeof (direccINETServidor); 

   } 

 else    /* socket del dominio UNIX */ 
   { 

     direccUNIXServidor.sun_family = AF_UNIX; 
     strcpy (direccUNIXServidor.sun_path, nombre); 
     puntDireccSockServidor = (struct sockaddr*) &direccUNIXServidor; 
     longServidor = sizeof (direccUNIXServidor); 

   } 

 do  /* conexion a un servidor */ 
   { 



  
      resultado = connect (dfCliente, puntDireccSockServidor, longServidor); 
     if (resultado == -1) { sleep (SOCKET_DORMIR); time_out++; } 
     if (time_out == 2 ) { 
                         Xerror(ERRORBORRAR);  
                          return(FALSE);  
                          } 
   } 
 while (resultado == -1); 
    
  close (dfCliente);  /* cierra el descriptor del fichero cliente */ 
 return (TRUE); 

}  /* fin de borraServer */ 






/* 
*--------------------------------------------------------------------- 
* 
*   PROTOTIPO:   void trataSignal ( void ) 
* 
*   DESCRIPCION: especifica la acción a tomar al llegar cada  
 *                una de las señales definidas posibles 
* 
*--------------------------------------------------------------------- 
*/ 


void trataSignal ( void ) 
 { 
   signal ( SIGPANT, XmanejaPantalla ); 
   signal ( SIGERRCMD, manejaErrorCmd ); 
   signal ( SIGERRSERBACK, manejaErrorSerBack ); 
   signal ( SIGERRCLISOCK, manejaErrorCliSock ); 
   signal ( SIGERRSERSOCK, manejaErrorSerSock ); 
   signal ( SIGERRREDIREC, manejaErrorRedirec ); 
   signal ( SIGDIBUJAR, manejaDibujar ); 
 }