DESARROLLO DE APLICACIONES DISTRIBUIDAS A TRAVS DE SOCKETS
1. Los sockets en OSI El modelo OSI estructura los diferentes elementos que conforman una red de comunicacin en 7 niveles. Cada capa ofrece sus servicios (funcionalidades propias) a los componentes del nivel superior y a su vez usa los servicios de la capa inferior. La manera estandarizada en la que una capa accede a los servicios de la inmediatamente inferior se denomina interfaz. Entre capas homlogas se intercambia informacin siguiendo un determinado protocolo (estructura de la informacin propia de esa capa). Los 7 niveles OSI: 1. Capa FSICA: Se ocupa de la transmisin de bits a lo largo de un canal de comunicacin. 2. Capa de ENLACE: Transforma el medio de transmisin (por naturaleza ruidoso) en una lnea sin errores. 3. Capa de RED: Obtiene paquetes del origen y los encamina por la red hasta su destino. 4. Capa de TRANSPORTE: Acepta los datos de la capa de sesin, los divide en unidades ms pequeas y los pasa a la capa de red asegurndose de que llegan a su destino. 5. Capa de SESIN: Permite que los usuarios de distintas mquinas puedan abrir sesiones en mquinas remotas. Por ejemplo, conectarse a un sistema remoto con TELNET o transportar ficheros de una mquina a otra con FTP. 6. Capa de PRESENTACIN: Se encarga de unificar la sintaxis y la semntica de la informacin que se transmite. Por ejemplo, resuelve los problemas que puedan surgir de codificaciones de caracteres distintas como ASCII y EBCDIC, o distinta representacin de los coma flotante, etc. 7. Capa de APLICACIN: Ofrece sus servicios al usuario resolviendo los problemas de ms alto nivel, como por ejemplo la compatibilidad entre terminales, etc. Un ejemplo es el sistema de ventanas X-WINDOWS. Los SOCKETS de UNIX son un API (Application Program Interface) que permite al programador hacer uso desde sus aplicaciones de los servicios de comunicacin. Este API se sita en la capa de TRANSPORTE, de forma que la aplicacin deber resolver lo referente a las capas de sesin y presentacin, adems de lo referente a la propia aplicacin. El uso de Sockets libera al programador de preocuparse por el canal fsico (nivel fsico), los errores de la
transmisin (nivel enlace), los nodos de la red por donde deben encaminarse las tramas de bits (nivel red), y de la formacin de paquetes de informacin desde el origen al destino (nivel transporte). 2. Familias y tipos de conexin La familia (o dominio) agrupa a los sockets que comparten una serie de caractersticas comunes como convenios para formar direcciones de red o para nombrar los objetos de la red. Las familias ms comunes en los sistemas UNIX son: a) Familia AF_UNIX: Que usa protocolos internos de Unix. Se suele emplear para comunicar procesos que se ejecutan en una misma mquina ya que no realiza accesos a la red. b) Familia AF_INET: Que usa protocolos de Internet, como el TCP (Transmisin Control Protocol) orientado a conexin UDP (User Datagram Protocol) no orientado a conexin. Esta familia de sockets es la que usa para comunicar procesos entre mquinas distintas a travs de la red. c) Familia AF_CCITT: Que usa la Norma X.25 de CCITT. d) Familia AF_NS: Que usa protocolos NS de XEROX. e) Familia AF_SNA: Protocolos IBM SNA. f) Familia AF_DECnet. g) Familia AF_APPLETALK. El tipo de conexin se refiere al tipo de circuito que se establece entre los dos procesos que se van a comunicar. Hay 2 tipos de circuitos: a) VIRTUAL (orientado a conexin): Mediante la bsqueda de enlaces libres, se establece un circuito virtual entre los dos procesos que se van a comunicar de tal forma que esta conexin ya es permanente hasta el final de la comunicacin. Una vez se establece el circuito, los datos se pueden enviar secuencialmente sin preocuparse del circuito por donde pasen (conocido como STREAM o corriente de datos). El tipo de socket que usa el circuito virtual se define como SOCK_STREAM. b) DATAGRAMA (no orientado a conexin): Los datagramas no trabajan con circuitos permanentes. La transmisin se realiza al nivel de paquetes de tal forma que cada paquete puede seguir una ruta distinta y, por lo tanto, no garantiza la recepcin secuencial de la informacin. El uso de datagramas mejora las prestaciones del sistema ya que la red est menos tiempo ocupada aunque obliga al programador a establecer algn mecanismo para
garantizar la recepcin secuencial de la informacin. El tipo de socket que usa datagramas se define como SOCK_DGRAM. c) Otros tipos de sockets son los SOCK_RAW que usan el protocolo IP de mas bajo nivel. Estos sockets solo estn disponibles para el Superusuario. 3. Direcciones de la red La direccin de red distingue unvocamente a cada nodo de la red. Para hacer referencia a los nodos origen y destino de la conexin, algunas llamadas a los sockets necesitan usar un puntero a una estructura de direccin de socket. Esta estructura est definida en el fichero de cabecera <sys/socket.h>:
struct sockaddr { u_short sa_family; /* familia de sockets: AF_xxxx */ char sa_data[14]; /* 14 bits con la direccin.*/ /* Su codificacin depende de la familia */ };
Esta estructura general depende de la familia de sockets que se est usando. Sin embargo, algunas familias han definido su propia estructura para las direcciones de red. As para la familia AF_INET (con protocolos de Internet) se define en el fichero <netinet/in.h>:
struct in_addr{ u_long s_addr; /* 32 bits con la identificacin de la red y el host (en binario) */ }; struct sockaddr_in { short sin_family; /*en este caso, AF_INET */ u_short sin_port; /* 16 bits con el nmero de puerto */ in_addr sin_addr; /* id. red y host */ char sin_zero[8]; /* 8 bytes sin usar */ };
El nmero de puerto se usa para asociar el socket al proceso que se est ejecutando. Si en una misma mquina hay distintos sockets abiertos cada uno de ellos deber llevar un nmero de puerto distinto, puesto que todos llevarn la misma direccin de red. El nmero de puerto puede tomar valores entre 1024 y 65535. Del nmero 1 al 1024 estn reservados al sistema y no pueden ser usados por aplicaciones de usuario. 4. Llamadas para el manejo de sockets Socket - Apertura de un punto terminal en un canal.
#include <sys/types.h> #include <sys/socket.h> int socket (int af, int type, int protocol);
Socket crea un punto terminal para conectarse a bidireccional, devolviendo un descriptor para ese canal.
o o o
un
canal
af - Es la familia: AF_UNIX, AF_INET, AF_CCITT, AF_NS, ... type - El tipo de conexin: SOCK_STREAM (para virtual), SOCK_DGRAM (para datagramas) protocol - Es el protocolo particular que se desea usar. Normalmente cada tipo de socket slo tiene un protocolo, pero si hay ms de uno se puede especificar. Si no, se pone un 0.
Si todo funciona bien, socket devuelve un descriptor de fichero. En caso contrario devuelve -1, quedando codificado el error en la variable global errno. Bind - Nombre de un socket. Bind une un socket a una direccin de red determinada. Su declaracin es:
#include <sys/socket.h> #include <sys/un.h> /* para la familia AF_UNIX */ #include <netinet/in.h> /* para la familia AF_INET */ int bind(int sfd, const void *addr,int addrlen) o Bind hace que el socket con descriptor sfd se una a la direccin de socket que especifica la estructura apuntada por addr y de longitud addrlen. La estructura a la que apunta addr deber ser struct sockaddr_un en el caso de la familia AF_UNIX, y ser struct sockaddr_in en el caso de la familia AF_INET. Si la llamada funciona correctamente, bind devuelve 0, en caso contrario -1.
Listen. Disponibilidad para recibir peticiones de servicio. En los sockets orientados a conexin, los programas servidores usan listen para indicar que el programa est preparado para recibir peticiones de conexin.
o #include <sys/socket.h> int listen (int sfd, int backlog) Listen habilita una cola asociada al socket descrito por sfd para alojar las peticiones de conexin que le realicen otros programas. La longitud de esa cola se especifica en backlog. Listen slo tiene sentido en sockets del tipo SOCK_STREAM. Si listen funciona correctamente devuelve 0, y en caso de error -1.
o o
Connect. Peticin de conexin. Es la llamada que hace un proceso cliente para empezar a comunicar a travs de un socket.
#include <sys/socket.h> #include <sys/un.h> /* para la familia AF_UNIX */ #include <sys/in.h> /* para la familia AF_INET */ int connect (int sfd,const void *addr,int addrlen) o o o o o sfd es el descriptor del socket. addr un puntero a la estructura que contiene la direccin del socket remoto con el que se quiere conectar. addrlen especifica el tamao de la estructura. Si el socket es del tipo SOCK_DGRAM, connect especifica la direccin del socket remoto, pero no se conecta con l. Si el socket es del tipo SOCK_STREAM, connect intenta una conexin entre el ordenador local y el remoto, quedando la llamada bloqueada hasta que se
consigue la conexin o devolviendo un error si no se consigue (cuando est activo el modo de acceso no bloqueante O_NDELAY). La conexin correcta devuelve un 0. Se devuelve -1 en caso de error.
Accept. Aceptacin de conexin. Los servidores que usan sockets orientados a conexin aceptan las demandas de servicio por medio de la llamada accept.
#include <sys/socket.h> int accept(int sfd, void *addr, int *addrlen) o o sfd es el descriptor de socket creado por la llamada socket y unido a una direccin con bind. accept extrae la primera llamada de la cola que ha creado listen sobre el socket, y despus crea un nuevo socket con las mismas propiedades que sfd, reservndole un nuevo descriptor de fichero. Si no hay peticiones para extraer de la cola, accept permanecer bloqueada a menos que est activo el control de acceso no bloqueante (O_NDELAY) en cuyo caso devolver un error. addr debe apuntar a una estructura local de direccin de socket. La propia llamada accept rellenar esa estructura con la direccin del socket remoto que ha pedido la conexin. addrlen debe ser un puntero para permitir que la llamada sobrescriba sobre l el tamao real de la direccin leda. Si no se produce error, accept devolver el descriptor del nuevo socket creado que corresponder al socket remoto aceptado. En caso de error devuelve -1.
o o
Read, readv, recv, recvfrom, recvmsg: Lectura o recepcin demensajes de un socket.
a) int read (int sfd, char* buf, unsigned nbyte) Read lee nbyte bytes del socket sfd y los almacena en el buffer apuntado por buf. Si no hay error, devuelve el nmero de bytes ledos. b) #include <sys/uio.h> ssize_t readv (int sfd, const struct iovec* iov, size_t iovcnt) Readv lee los datos del socket sfd y los distribuye entre los distintos buffers especificados por el array iov. El total de elementos de iov viene especificado por iovcnt: iov[0], .., iov[iovcnt-1]. Readv lee hasta encontrar un fin de fichero o completar los buffers, devolviendo el nmero total de bytes ledos. c) #include <sys/socket.h> int recv (int sfd, void *buf, int len, int flags) int recvfrom (int sfd, void *buf, int len, int flags, void * from, int fromlen) int recvmsg (int sfd, struct msghdr msg[], int flags) sfd : descriptor del socket del que se lee. buf : puntero al buffer donde se escriben los datos ledos. len : nmero mximo de bytes que cabe en el buffer. flags: es una combinacin de 2 bits - En los sockets SOCK_STREAM, las llamadas slo pueden usarse despus de haber establecido una conexin con CONNECT. En los SOCK_DGRAM no es necesario, si se hace, entonces se recibe siempre del mismo, si no, se recibe de cualquiera.
- En los sockets SOCK_STREAM, recvfrom devuelve la misma informacin que recv ya que al establecer una conexin no devolver la direccin del socket origen. - En los sockets no orientados a conexin, el mensaje debe leerse en un solo bloque, ya que en otro caso queda truncado.
Write, writev, send, sendto y sendmsg: escritura o envo de mensajes a un socket. Son las funciones complementarias de read, readv, recv, recvfrom y recvmsg
a) int write (int sfd, char* buf, unsigned nbyte) Write escribe nbyte bytes del buffer apuntado por buf en el socket sfd. Si no hay error, devuelve el nmero de bytes escritos realmente. b) #include <sys/uio.h> ssize_t writev (int sfd, const struct iovec* iov, size_t iovcnt) Writev escribe los datos en el socket sfd tomndolos de los distintos buffers especificados por el array iov. El total de elementos de iov viene especificado por iovcnt: Writev devuelve el nmero total de bytes escritos. c) #include <sys/socket.h> int send (int sfd, void *buf, int len, int flags) int sendto (int sfd, void *buf, int len, int flags, void * to, int tolen) int sendmsg (int sfd, struct msghdr msg[], int flags) sfd : descriptor del socket donde se escribe. buf : puntero al buffer de donde se leen los datos a escribir. len : nmero de bytes en el buffer. flags: puede valer 0 o MSG_OOB. (MSG_PEEK no tiene sentido aqu) En los sockets SOCK_STREAM, las llamadas slo pueden usarse despus de haber establecido una conexin con CONNECT. En los SOCK_DGRAM no es necesario.
Close. Cierre del canal.
int close (int sfd) La llamada a close cierra el socket en los dos sentidos de recepcin y envo.
Shutdown. Cierre de socket en un sentido. Cierra un socket en un nico sentido.
int shutdown(int sfd, int how) Si how=0 cierra recepcin de datos, 1 cierra envo de datos y 2 cierra ambos.
5. Modelo Cliente-Servidor Servidor es un proceso que se ejecuta en cualquier nodo de una red y que gestiona el acceso a un determinado recurso. Cliente es un proceso que se ejecuta en cualquier nodo de la red y que realiza peticiones de servicio al servidor. Segn la forma de atender el servicio, se puede hacer la siguiente clasificacin de los servidores: a) Servidores interactivos: Servidor que adems de recoger la peticin de servicio se encarga de atenderla l mismo.
b) Servidores concurrentes: Servidor que recoge cada una de las peticiones de los clientes y crea procesos para que se encarguen de atenderlas. Esto es especialmente til en los servidores que han de atender un gran volumen de peticiones. Esquema general del modelo Cliente-Servidor
Esquema conexin
Cliente-Servidor
con
sockets
no
orientados