Anda di halaman 1dari 12

RPC's en UNIX: sockets

En las primeras versiones de UNIX la nica comunicacin entre procesos se realizaba mediante los pipes o interconexin de entradas-salidas. Se conforman por instrucciones a nivel de usuario, no se les asigna un nombre y son unidireccionales. No son simples llamadas, pues aunque datos pasan de un proceso a otro, estos no regresan. El usuario establece una cadena de transferencia de datos, no llamadas que devolvern resultados. Sockets Los sockets son la manera desarrollada en BSD4.2 (Berkeley) para comunicar procesos, tanto locales como remotos. Un socket es un punto de referencia hacia donde los mensajes pueden ser enviados, o de donde pueden ser recibidos. Cualquier proceso puede crear un socket para comunicarse con cualquier otro proceso, y el socket permite que un mensaje sea mapeado directamente a su proceso. Protocolos en sockets Una vez creado un socket, se debe especificar el protocolo que ser utilizado. Existen dos protocolos de la capa de transporte para la utilizacin de sockets: Datagrama Se basa en el protocolo UDP (Unreliable Datagram Protocol), en el que los mensajes se dividen en paquetes, cada cual contiene informacin sobre su origen, su destino y su nmero secuencial. Cada paquete podr tomar el mismo camino o diferente, y no se garantiza que todos llegarn. Conexin Se basa en el protocolo TCP (Transmision Control Protocol), en el que al momento de abrir la comunicacin, se define un circuito virtual, en el cual los mensajes viajarn, en secuencia siempre y sin prdidas. Funciones con sockets Existe una cantidad de funciones necesarias para crear y utilizar sockets. En la siguiente tabla se muestran las funciones bsicas necesarias para el uso de sockets. Se requiere el uso de asignaciones y estructuras especiales, los cuales se vern en un ejemplo.
Cliente Crea el socket: sd=socket(AF_INET,SOCK_STREAM,0) Poner nombre al socket (servidor): bind(sd,&sockname,sizeof(sockname)) Poner el socket a escuchar (servidor): Servidor sd=socket(AF_INET,SOCK_STREAM,0)

listen(sd,1) Solicitar/aceptar conexin: connect(sd,&sockname,sizeof(sockname)) new_socket=accept(sd,&from,&from_len) Enva/recibe solicitud: send(sd,buffer,80,0) recv(new_socket,buffer,80,0) Recibe/enva respuesta: recv(sd,buffer,80,0) send(new_socket,buffer,80,0) Cierra conexin: close(new_socket)

La interfaz socket
La abstraccin de socket La base para la E/S de red en BSD de UNIX se centra en una abstraccin conocida como socket. Pensamos al socket como una generalizacin del mecanismo de acceso a archivos de UNIX que proporciona un punto final para la comunicacin. Al igual que con el acceso a archivos, los programas de aplicacin requieren que el sistema operativo cree un socket cuando se necesita. El sistema devuelve un entero pequeo que utiliza el programa de aplicacin para hacer referencia al socket recientemente creado. La diferencia principal entre los descriptores de archivos y los descriptores de socket es que el sistema operativo enlaza un descriptor de archivo a un archivo o dispositivo especfico cuando la aplicacin llama a open, pero puede crear sockets sin enlazarlos a direcciones de destino especficas. La aplicacin puede elegir abastecer una direccin de destino cada vez que utiliza el socket (es decir, cuando se envan datagramas) o elegir enlazar la direccin de destino a un socket y evadir la especificacin de destino repetidamente (es decir, cuando se hace una conexin TCP). Cada vez que tenga sentido, los sockets hacen ejecuciones exactamente iguales a los archivos o dispositivos de UNIX, de manera que pueden utilizarse con operaciones tradicionales, como read y write. Por ejemplo, una vez que

un programa de aplicacin crea un socket y una conexin TCP del socket a un destino externo, el programa puede hacer uso de write para mandar un flujo de datos a travs de la conexin (el programa de aplicacin en el otro extremo puede utilizar read para recibirlo). Para hacer posible que se utilicen las primitivas read y write con archivos y sockets, el sistema operativo ubica los descriptores de socket y los descriptores de archivo del mismo conjunto de enteros y se asegura de que, si un entero dado se ha ubicado como descriptor de archivo, no ser ubicado tambin como descriptor de socket. Creacin de un socket El sistema de llamada socket crea sockets cuando se le pide. Toma tres argumentos enteros y devuelve un resultado entero:
resultado = socket (pf, tipo, protocolo)

El argumento pf especifica la familia del protocolo que se va a utilizar con el socket. Esto quiere decir que especifica cmo interpretar la direccin cuando se suministra. Las familias actuales incluyen la red de redes TCP/IP (PF_INET), la Red de redes PUP de Xerox Corporation (PF_PUP), la red Appletalk de Apple Computer Incorporated (PF_APPLETALK) y el sistema de archivos UNIX (PF_UNIX) as como tambin muchos otros. El argumento tipo especifica el tipo de comunicacin que se desea. Los tipos posibles incluyen el servicio de entrega confiable de flujo (SOCK_STREAM) o servicio orientado a conexin, y el servicio de entrega de datagramas sin conexin (SOCK_DGRAM), as como tambin el tipo creado o no procesado (SOCK_RAW) que permite a los programas de privilegio accesar a los protocolos de bajo nivel o a las interfaces de red. Otros dos tipos adicionales ya se han planeado pero no han sido implantados. Aunque el acercamiento general de separacin de las familias y los tipos de protocolo puede parecer suficiente para manejar todos los casos con facilidad, no es as. En primer lugar puede ser que una familia de protocolos dada no soporte uno o ms de los tipos de servicio posibles. Por ejemplo, la familia UNIX tiene un mecanismo de comunicacin de interproceso, llamado pipe, que utiliza un servicio de entrega de flujo confiable, pero no posee mecanismo de entrega de paquetes secuencia dos. De este modo, no todas las combinaciones de familias de protocolos y tipos de servicio tienen sentido. En segundo lugar, algunas familias de protocolos poseen protocolos diversos que soportan un tipo de servicio. Por ejemplo podra ser que una sola familia de protocolos tuviera dos servicios de entrega de datagramas sin conexin. Para acomodar los diversos protocolos dentro de una familia, la llamada de socket tiene un tercer argumento, que se puede emplear para seleccionar un protocolo especfico. Para usar el tercer argumento, el programador debe conocer la familia de protocolos lo suficientemente bien como para saber el tipo de servicio que cada protocolo brinda. Herencia y finalizacin del socket UNIX utiliza las llamadas del sistema fork y exec para comenzar nuevos programas de aplicacin. Se trata de un procedimiento de dos pasos. En el primer paso, fork crea una copia separada del programa de aplicacin que se est ejecutando en ese momento. En el segundo paso, la nueva copia se reemplaza a s misma con el programa de aplicacin deseado. Cuando un programa llama a fork, la copia que se acaba de crear le hereda el acceso a todos los sockets abiertos, justo como si heredar el acceso a todos los archivos abiertos. Cuando el programa llama a exec, la nueva aplicacin retiene el acceso para todos los sockets abiertos. Veremos que los servidores maestros utilizan la herencia socket cuando crean servidores esclavos o manejan una conexin especfica. Internamente, el sistema operativo mantiene una cuenta de referencia asociada con cada socket de manera que se sabe cuntos programas de aplicacin (procesos) han accedido a l. Tanto los procesos viejos como los nuevos tienen los mismos derechos de acceso para los sockets existentes, y ambos tipos pueden accesar a los sockets. Luego pues, es responsabilidad del programador asegurar que los dos procesos empleen el significado del socket compartido. Cuando un proceso termina de utilizar un socket, ste llama a close (cerrar). Close tiene la forma:
close (socket)

donde el argumento socket especfica al descriptor de un socket para que cierre. Cuando un proceso se termina por cualquier razn, el sistema cierra todos los sockets que permanecen abiertos. Internamente, una llamada a close (cerrar) disminuye la cuenta de referencia para un socket y destruye al socket si la cuenta llega a cero. Especificacin de una direccin local Inicialmente, un socket se crea sin ninguna asociacin hacia direcciones locales o de destino. Para los protocolos TCP/IP, esto significa que ningn nmero de puerto de protocolo local se ha asignado y que ningn puerto de destino o direccin IP se ha especificado. En muchos casos, los programas de aplicacin no se preocupan por las direcciones locales que utilizan, ni estn dispuestos a permitir que el software de protocolo elija una para ellos. Sin embargo los procesos del servidor que operan en un puerto bien conocido deben ser capaces de especificar dicho puerto para el sistema. Una vez que se ha creado un socket, el servidor utiliza una llamada del sistema bind (enlace) para establecer una direccin local para ello. Bind tiene la siguiente forma:

bind(socket, localaddr, addrlen)

El argumento socket es el descriptor de enteros del socket que ha de ser enlazado. El argumento localaddr es una estructura que especfica la direccin local a la que el socket deber enlazarse, y el argumento addrlen es un entero que especifica la longitud de las direcciones medidas en octetos. En lugar de dar la direccin, solamente como una secuencia de octetos, los diseadores eligieron utilizar una estructura para las direcciones. Conexin de socket con direcciones de destino Inicialmente, un socket se crea en el unconnected state. (estado no conectado), lo que significa que el socket no est asociado con ningn destino externo. La llamada de sistema connect (conectar) enlaza un destino permanente a un socket, colocndolo en el connected state (estado conectado). Un programa de aplicacin debe llamar a connect para establecer una conexin antes de que pueda transferir datos a travs de un socket de flujo confiable. Los sockets utilizados con servicios de datagrama sin conexin necesitan no estar conectados ante de usarse, pero hacindolo as, es posible transferir datos sin especificar el destino en cada ocasin. La llamada de sistema connect tiene la forma:
connect(socket, destaddr, addrlen)

El argumento socket es el descriptor entero del socket que ha de conectarse. El argumento destaddr es una estructura de direccin socket en la que se especifica la direccin de destino a la que deber enlazarse el socket. El argumento addrlen especifica la longitud de la direccin de destino medida en octetos. El significado de connect depende de los protocolos subyacentes. La seleccin del servicio de entrega de flujo confiable en la familia PF_INET significa elegir al TCP. En el caso del servicio sin conexin, connect no hace nada ms que almacenar localmente la direccin de destino. Envo de datos a travs de un socket Una vez que el programa de aplicacin ha establecido un socket, puede usar el socket para transmitir datos. Hay cinco llamadas posibles de sistemas operativos de entre las que se puede escoger: send (mandar), sendto (mandar a), sendmsg (mandar mensage), write (escribir) y writev (vector escribir). Send, write y writev slo trabajan con sockets conectados pues no permiten que quin llama especifique una direccin de destino. Las diferencias entre los tres son menores. Write toma tres argumentos.
write(socket, buffer, length)

El argumento socket contiene un descriptor de socket entero (write puede tambin usarse con otros tipos de descriptores). El argumento buffer contiene la direccin de datos que se han de mandar y el argumento length especifica el nmero de octetos que se han de mandar. La llamada a write se bloquea hasta que los datos se pueden transferir (es decir, se bloquea si los bfers del sistema interno para el socket estn llenos). Como en la mayor parte de las llamadas de sistema en UNIX, write devuelve un cdigo de error para la aplicacin que llama, lo cual permite al programador saber si la operacin tuvo xito. La llamada de sistema send tiene la forma:
send(socket, message, length, flags)

donde el argumento socket especifica el socket que se ha de usar, el argumento message (mensaje) de la direccin de datos a ser mandados, el argumento length (longitud) especifica el nmero de octetos que se va a enviar y el argumento flags (banderas) controla la transmisin. Un valor para flags permite que quien enva especifique que el mensaje se deber enviar fuera de banda en los sockets que soporten tal nocin. Por ejemplo, se deca que los mensajes fuera de banda corresponden a la nocin de datos urgentes del TCP. Otro valor para flags permite que quien llama pida que el mensaje se enve sin necesidad de usar tablas de ruteo local. La intencin es permitir a quien llama que tome el control del ruteo, haciendo posible que se escriba en le software de depuracin de red. Por supuesto, no todos los sockets soportan todas las pertinencias y hay otros programas que sencillamente no se soportan en todos los sockets. Recepcin de datos a travs de un socket BSD de UNIX ofrece cinco llamadas de sistema, anlogas a las cinco diferentes operaciones de salida, que un proceso puede utilizar para recibir datos a travs de un socket: read (leer), ready (listo), recv (recibir), recvfrom (recibir de) y recvmsg (recibir mensaje). La operacin de entrada convencional de UNIX, read, slo se puede usar cuando un socket se conecta. Este tiene la forma:
recv(descriptor, buffer, length, flags)

donde el descriptor da el descriptor entero de un socket o el descriptor de archivo del que se puede leer los datos, el buffer especifica la direccin en memoria en la que se almacenan los datos y length especifica el nmero mximo de octetos que puedan leerse. Obtencin de direcciones socket locales y remotas Dijimos que los procesos que se crearon recientemente heredaron el conjunto de sockets abiertos del proceso que los cre. En algunas ocasiones, un proceso recin creado necesita determinar la direccin de destino a la que se conecta el socket. Un proceso puede tambin determinar la direccin local para un socket. Dos llamadas de sistema

proporcionan informacin como: getpeername (obtener nombre de pareja) y getsockname (obtener nombre de socket) (sin importar sus nombres, ambos tratan con lo que pensamos como "direcciones"). Un proceso llama a getpeername para determinar la direccin de la pareja (es decir, el extremo remoto) al que se conecta el socket. Tiene la forma:
getpeername(socket, destaddr, addrlen)

El argumento socket especifica el socket para el que se desea la direccin. El argumento destaddr es un apuntador de estructura de tipo sockaddr que recibir la direccin de socket. Por ltimo, el argumento addrlen es un apuntador de entero que recibir la longitud de la direccin. Getpeername slo trabaja con sockets conectados. La llamada de sistema getsockname devuelve la direccin local asociada con un socket. Tiene la forma:
getsockname(socket, localaddr, addrlen)

Como se esperaba, el argumento socket especifica el socket para el que se desea la direccin local. El argumento localaddr es un apuntador a una estructura de tipo sockaddr que contendr la direccin y el argumento addrlen es un apuntador a entero que contendr la longitud de direccin. Especificacin de una longitud de cola para un servidor Una de las opciones que se aplica a los sockets se utiliza con tanta frecuencia que se ha tenido que dedicar una llamada de sistema por separado a ella. Para comprender cmo surge, consideremos un servidor. El servidor crea un socket, lo enlaza a un puerto de protocolo bien conocido y espera las peticiones. Si el servidor emplea una entrega de flujo confiable, o si la computacin de una respuesta se lleva cantidades no triviales de tiempo, puede suceder que una nueva peticin legue antes de que el servidor termine de responder a una peticin anterior. Para evitar el rechazo de protocolos o la eliminacin de las peticiones entrantes, el servidor debe indicar al software de protocolo subyacente que desea tener dichas peticiones en cola de espera hasta que haya tiempo para procesarlas. La llamada de sistema listen (escuchar) permite que los servicios preparen un socket para las conexiones que vienen. En trminos de protocolos subyacentes, listen pone al socket en modo pasivo listo para aceptar las conexiones. Cuando el servidor invoca a listen, tambin informa al sistema operativo que el software de protocolo deber colocar en cola de espera las diversas peticiones simultneas que llegaron al socket. La forma es: El argumento socket indica al descriptor de un socket que debe estar preparado para que lo use un servidor y el argumento qlength especifica la longitud de la cola de espera para ese socket. Despus de la llamada, el sistema establece la cola de espera para que qlength solicite las conexiones. Si la cola de espera est llena cuando llega una peticin, el sistema operativo rechaza la conexin descartando la peticin. Listen se aplica slo a los sockets que han seleccionado el servicio de entrega de flujo confiable. Cmo acepta conexiones un servidor Como hemos visto, un proceso de servicio utiliza las llamadas de sistema socket, bind y listen para crear un socket, enlazarlo a un puerto de protocolo bien conocido y especificar la longitud de cola para las peticiones de conexin. Obsrvese que la llamada a bind asocia al socket con un puerto de protocolo bien conocido, pero el socket no est conectado a un destino exterior especfico. De hecho, el destino exterior debe especificar un wildcard (comodn), lo cual permite que el socket reciba peticiones de conexin de cualquier cliente. Una vez que se ha establecido un socket, el servidor necesita esperar la conexin. Para hacerlo, se vale de la llamada de sistema accept (aceptar). Una llamada accept se bloquea hasta que la peticin de conexin llega. Tiene la forma:
newsock = accept(socket, addr, addrlen)

El argumento socket especifica el descriptor del socket en el que va a esperar. El argumento addr es un apuntador a estructura de tipo sockaddr y addrlen es un apuntador a entero. Cuando llega una peticin, el sistema llena un argumento addr con la direccin del cliente que ha colocado la peticin y configura addrlen a la longitud de la direccin. Por ltimo, el sistema crea un nuevo socket que tiene su destino conectado hacia el cliente que pide, y devuelve el nuevo descriptor de socket a quien llama. El socket original todava tiene un comodn de destino externo y an permanece abierto. De este modo, el servidor maestro puede continuar aceptando las peticiones adicionales en el socket original. Cuando llega una peticin de conexin la llamada a accept reaparece. El servidor puede manejar las peticiones de manera concurrente o iterativa. Desde un enfoque iterativo, el servidor mismo maneja la peticin, cierra el nuevo socket y, despus, llama a accept para obtener la siguiente peticin de conexin. Desde el enfoque concurrente, despus de que la llamada a accept reapareci, el servidor maestro crea un esclavo para manejar la peticin (en terminologa UNIX el proceso se bifurca en el proceso hijo para manejar la peticin). El proceso esclavo hereda una copia del nuevo socket, de modo que puede proceder a servir a la peticin. Cuando termina, el esclavo cierra el socket y termina. El proceso del servidor original (maestro) cierra su copia de un nuevo socket despus de iniciar al esclavo. Despus, llama a accept para obtener la siguiente peticin de conexin. El diseo concurrente para servidores podra parecer confuso debido a que los diversos procesos usarn el mismo nmero local de puerto de protocolo. La clave para comprender el mecanismo descansa en la manera en que los

protocolos subyacentes tratan a los puertos de protocolos. Recordemos que en el TCP, un par de extremos definen una conexin. De este modo, no importa cuntos procesos se usen en un nmero local de puerto de protocolo, mientras se conecten a diferentes destinos. En el caso de un servidor concurrente, hay un proceso por cliente y un proceso adicional por destino externo, lo cual permite que se conecte con cualquier localidad externa. Cada socket conectado a la fuente de segmento. Si no existe tal socket, el segmento se mandar al socket que tenga un comodn para su destino externo. Adems, como el socket con un comodn de destino externo como nmero de puerto no tiene una conexin abierta, slo respetar a los segmentos TCP que pidan una nueva conexin.

Ejercicio 1
Sockets con lenguaje C El propsito de esta tarea es realizar la primera prueba de programacin cliente-servidor, en C bajo ambiente UNIX. De no contarse con equipo UNIX, deber investigarse cmo hacerlo bajo ambiente UNIX o alguna otra plataforma, pero en lenguaje C. Lo que se tiene que hacer en esta tarea de sockets es: a) Crear los archivos con los siguientes programas en lenguaje C: cliente.c
#include <stdio.h> #include <malloc.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> #include <sys/time.h> main() { int sd; struct sockaddr_in sockname; char buffer[80]; //Crear socket if((sd=socket(AF_INET,SOCK_STREAM,0))==-1) perror("Cliente:Socket"),exit(1); //Especificar parametros en la estructura sockname sockname.sin_family=AF_INET; sockname.sin_addr.s_addr=inet_addr("192.100.170.4"); /* ^^^^^^^^^^^^ direccin del servidor*/ sockname.sin_port=htons(5555); /* ^^^^ Sustituir por el puerto correspondiente */ //Conectar a servidor if(connect(sd,&sockname,sizeof(sockname))==-1) perror("Cliente:Connect"),exit(1); //Ciclo para transmitir mensajes, el cual termina con mensaje "FIN" do{ puts("Teclee el mensaje a transmitir"); gets(buffer); //Enviar mensajes if(send(sd,buffer,80,0)==-1) perror("Cliente:Send"),exit(1); }while(strcmp(buffer,"FIN")!=0); }

servidor.c
#include #include #include #include #include #include #include #include <stdio.h> <malloc.h> <sys/types.h> <netinet/in.h> <sys/socket.h> <netdb.h> <arpa/inet.h> <sys/time.h>

main() { int sd,from_len,new_socket; struct sockaddr_in from,sockname; char buffer[80]; //Crear socket if((sd=socket(AF_INET,SOCK_STREAM,0))==-1) perror("Servidor:Socket"),exit(1); //Especificar parametros en la estructura sockname sockname.sin_family=AF_INET; sockname.sin_addr.s_addr=INADDR_ANY; sockname.sin_port=htons(5555); /* ^^^^ Sustituir por el puerto correspondiente */ //Asociar parmetros a socket if(bind(sd,&sockname,sizeof(sockname))==-1) perror("Servidor:Bind"),exit(1); //Poner servidor a escuchar puerto if(listen(sd,1)==-1) perror("Servidor1:Listen"),exit(1); from_len=sizeof(from); //Esperar y aceptar conexin de cliente if((new_socket=accept(sd,&from,&from_len))==-1) perror("Servidor:Accept"),exit(1); //Ciclo para recibir mensajes, el cual termina con mensaje "FIN" do{ //Recibir mensajes if(recv(new_socket,buffer,80,0)==-1) perror("Servidor:Recv"),exit(1); printf("(El mensaje recibido fue: %s)\n",buffer); }while(strcmp(buffer,"FIN")!=0); }

b) Seleccionar un nmero de puerto, arriba de 1000 que sea el que siempre use. Es importante que el nmero elegido no corresponda al nmero de puerto de algn otro servicio corriendo en la computadora que ser usada como servidor. c) Editar servidor.c, y buscar la lnea donde est el nmero 5555, que es un nmero de puerto. Puede quedarse as, o elegir algn otro. Observa que la palabra clave para terminar el programa es la palabra "FIN" enviado como mensaje desde el cliente. d) Averiguar cul es la direccin IP de la mquina desde donde vas a correr el servidor. Las direcciones IP tiene una forma como esta: 192.100.170.1. Bajo UNIX se puede usar el comando nslookup para averiguar la direccin de una mquina:
nslookup nombre_maquina

donde nombre_maquina es la direccin por nombre de la mquina deseada. La ltima lnea en la respuesta indica el IP de la mquina en cuestin. e) Editar cliente.c. Buscar la lnea donde aparece tambin el nmero de puerto 5555 y cambiarlo al seleccionado. Tambin buscar la direccin que aparece como "192.100.170.4" y cambiarlo a la direccin de la mquina donde va a estar el servidor. Si el cliente ser puesto en ejecucin en la misma mquina en que estar ejecutndose el servidor, entonces se puede utilizar la direccin IP 127.0.0.1. f) Compilar tanto cliente como servidor, de la siguiente manera:
gcc servidor.c -o servidor gcc cliente.c -o cliente

Si el compilador es el de GNU, usen gcc en lugar de cc. Si hay algn problema con las bibliotecas, compilen incluyendo bibliotecas:
cc -lsocket -lnsl servidor.c -o servidor cc -lsocket -lnsl cliente.c -o cliente

g) Cambiar los permisos de ejecucin de sus programas as (esto no es necesario en cygwin, solo se necesita realizar si se est utilizando Linux realmente):
chmod 700 servidor chmod 700 cliente

h) Si todo sali bien, hay varias maneras para probar cliente y servidor: 1. Abrir dos ventanas (sesiones). En la primera, escribir
./servidor

de tal manera que se pondr a esperar los mensajes. Este programa no terminar hasta que reciba el mensaje "FIN", o sea interrumpido con Ctrl-C En la segunda ventana escribir
./cliente

Ahora el programa cliente estar listo para enviar mensajes. Escribirlos (que no pasen de 79 caracteres). Cuando se oprima -return- debern aparecer en la ventana del servidor. Puede repetirse. Esto se mantendr hasta que los programas sean detenidos con el mensaje (desde el cliente) "FIN", o abortados.

Sockets: Protocolos y Argumentos


Tcnicas con Sockets
Especificacin de direccin En el ejercicio visto previamente se especifica la direccin del servidor como parte del cdigo del cliente. Sin embargo, se puede especificar la direccin IP desde el cliente al momento de la ejecucin del programa. El siguiente listado muestra la utilizacin de la funcin gethostbyname(host) donde host es una cadena que contiene el nombre de la mquina donde se encuentra el servidor. La funcin devuelve un apuntador a una estructura con la informacin requerida.

Ejercicio 2
Mediante el editor de texto crear el archivo cliente2.c conteniendo el cdigo siguiente. Este cdigo es similar al del archivo cliente.c del ejercicio 1, pero permite que el usuario proporcione la direccin IP del servidor, en lugar de que dicha direccin ya est establecida de manera fija en el cdigo. Se pide compilar y probar la ejecucin de este cdigo (naturalmente, tanbin debe ponerse en ejecucin el servidor). #include <stdio.h> #include <malloc.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> #include <sys/time.h> main() { int sd; struct sockaddr_in sockname; char buffer[80]; char host[40]; struct hostent *hp, *gethostbyname();

//Pedir direccin (por nombre) al usuario puts("Direccion del servidor?"); gets(host); //Convertir y obtener apuntador a estructura con resultado hp = gethostbyname(host); //Copiar dato a estructura sockname bcopy(hp->h_addr, &sockname.sin_addr, hp->h_length); if((sd=socket(AF_INET,SOCK_STREAM,0))==-1) perror("Cliente:Socket"),exit(1); sockname.sin_family=AF_INET; /* sockname.sin_addr.s_addr=inet_addr("140.148.3.18"); */ /* ^^^^^^^^^^^^ cca */ sockname.sin_port=htons(5899); /* ^^^^ Sustituir por el puerto correspondiente */ if(connect(sd,&sockname,sizeof(sockname))==-1) perror("Cliente:Connect"),exit(1); do{ puts("Teclee el mensaje a transmitir"); gets(buffer); if(send(sd,buffer,80,0)==-1) perror("Cliente:Send"),exit(1); }while(strcmp(buffer,"FIN")!=0); }

Protocolo de sesin En el ejemplo bsico, se vi una simple transmisin del cliente al servidor. Las funciones send y receive pueden ser utilizadas tanto por el cliente como el servidor. Es importante que a cada send de una parte corresponda a un receive de la otra parte, para que la comunicacin sea efectiva. Si ambas partes intentan un receive a la vez, el sistema se bloquear, porque se quedarn esperando indefinidamente, a menos que se establezcan mecanismos de tiempo (timeout) o no-bloqueo. Al momento de construir y definir la secuencia de eventos de comunicacin entre un cliente y un servidor, se debe establecer un protocolo, para que cliente y servidor se pongan de acuerdo con el orden. Si bien es cierto que no se considera que el conjunto TCP/IP cuente con capa de sesin, se requiere un protocolo para coordinar la sesin entre cliente y servidor, y sta es definida por el diseador y desarrollador del sistema cliente/servidor. La secuencia ms usual de eventos es que el servidor espere conexiones. Al realizarse una conexin, el servidor espera que el cliente solicite un trabajo, ya sea el argumento para una tarea fija, o la especificacin de un servicio y el o los argumentos. La especificacin del servicio mas los argumentos se pueden enviar en varias llamadas a la funcin send, aunque es ms fcil y rpido utilizar una estructura para enviar toda la solicitud, y posteriormente la respuesta. Un cliente puede, adems, solicitar varios servicios, durante una conexin, hasta que le enve una seal al servidor para indicar que ya puede cerrar la conexin. En el ejemplo bsico se muestra como el servidor cierra la conexin y termina, cuando recibe la cadena "FIN" (lo cual es parte del protocolo definido para el ejemplo). Sin embargo, el programa servidor puede, despus de cerrar la conexin, esperar a otro cliente. Si un cliente intent conectarse mientras el servidor estaba atendiendo a otro cliente previo, el nuevo se pone en cola de espera, y ser atendido, cuando el servidor se ponga en disposicin de atender otra conexin, con la funcin accept. Paso de estructura como argumento Est visto que se pueden pasar varios datos en mltiples llamadas a send. Sin embargo, como ya se mencion, se puede enviar una estructura. Podemos suponer que la funcin send slo puede enviar datos tipo char, aunque se requiere especificar el tamao. Si bien es cierto que espera que sea tipo char, en realidad lo que necesita es saber en qu direccin de memoria se encuentra el dato a transmitir, y su tamao, as que podemos utilizar un "cast" para que considere una estructura en memoria como una cadena, e indicar el tamao de la estructura. Por supuesto, para que en el otro lado se reciba la estructura, debemos realizar las mismas consideraciones con la funcin receive. De la misma manera, es preciso que el receptor conozca la forma exacta de la estructura utilizada.

As que tanto en el cliente como en el servidor debemos definir la misma estructura, como en este ejemplo: struct message { int i; char mensaje[80]; }; struct message pt; donde se declara pt como instancia de la estructura message. Habiendo copiado algn valor entero a pt.i y una cadena a pt.mensaje, se puede enviar de la siguiente manera: if(send(sd,(char *)&pt,sizeof(pt),0)==-1) perror("Cliente:Send"),exit(1); Podemos ver que se pasa la direccin de pt como apuntador a cadena, y se especifica su tamao. De la misma manera, del otro lado se hace el receive as: if(recv(new_socket,(char *)&pt,sizeof(pt),0)==-1) perror("Servidor:Recv"),exit(1);

Ejercicio 3
Realizar un sistema cliente servidor donde el servidor puede ofrecer estos tres servicios: a) Recibir dos enteros, y devolver la suma de ellos. b) Recibir dos cadenas, y devolver la concatenacin de ellas. c) Recibir una cadena, y devolverla en puras maysculas El cliente debe poder conectarse a la direccin y puerto que el usuario especifique al momento de la ejecucin. El cliente presentar al usuario los servicios disponibles, y el usuario seleccionar el servicio deseado y dar los datos que correspondan al servicio. El cliente continuar conectado al servidor mientras el usuario quiera seguir usando el sistema. Una de las opciones del cliente ser terminar la ejecucin del cliente (nicamente). Otra de las opciones del cliente ser terminar tanto cliente como servidor. El servidor podr atender otra conexin cuando un cliente le indique el fin de la sesin, a menos que el cliente indique parar la ejecucin del servidor. Recomendacin: Practicar las diferentes tcnicas por separado, y luego conjuntarlas en un solo sistema.

Multiservidor
Forks
Puesto que un solo servidor podra no ser suficiente para atender a todos los clientes que podran requerir servicio, debemos considerar algunos mecanismos para que exista un nmero de procesos, atendiendo cada uno a un cliente. Llamada del sistema fork La nica manera por la cual se puede crear un nuevo proceso en UNIX es que un proceso ejecute la llamada del sistema fork: int fork(); Fork crea una copia del proceso que se est ejecutando. El proceso que ejecut el fork es llamado el proceso padre, y el proceso nuevo es llamado proceso hijo. El llamado a fork es llamado una vez (por el proceso padre) pero regresa (return ;) dos veces (una por el padre y otra por el hijo). La nica diferencia en el regreso de la llamada fork es que el valor de retorno en el proceso padre es el ID (identificador) del nuevo proceso hijo, mientras que el valor de retorno en el proceso hijo es cero. Si la llamada fork no es exitosa, el valor de retorno es -1. En caso de que el proceso hijo quiera obtener el ID del proceso padre, puede usar la llamada getppid. Un ejemplo de una llamada fork es el siguiente: main() {

int childpid; if( (childpid=fork() ) == -1) { perror("error"); exit (1); } else if (childpid == 0) { /* proceso hijo */ printf ("child: child pid=%p, parent pid=%d\n", getpid(),getppid()); exit (0); } else { /* proceso padre */ printf( "parent: child pid=%d, parent pid=%d/n", childpid, getpid() ); exit(0); } } } En este ejemplo, el programa, al correr, es un proceso que invoca la funcin fork, enviando un mensaje de error si no tuvo xito. Despus de la invocacin, el proceso principal tiene un valor diferente de 0 en la variable childpid, por lo que sabr que es el programa principal. Mientras tanto, el proceso hijo es creado como copia idntica del proceso padre. Tan idntico que necesita ver el valor de la variable childpid para saber que se trata del proceso hijo, por lo que efectuar una tarea especial. Un aspecto importante es que si el segmento de texto puede ser compartido, entonces ambos procesos, tanto el padre como el hijo pueden compartir el segmento de texto despus del fork. Tambin el proceso hijo copia el segmento de datos del proceso padre en el momento que se ejecuta el fork. Esta no es una copia del correspondiente archivo programa. Una caracterstica importante de la operacin fork es que los archivos que fueron abiertos por el proceso padre antes del fork son compartidos con el proceso hijo despus del fork. Esta caracterstica proporciona una manera fcil de pasar los archivos abiertos a los procesos hijos. Despus del fork el proceso padre cierra los archivos que fueron abiertos para el hijo, de manera que ambos procesos no compartan el mismo archivo. En cuanto a los valores de variables que son copiados del proceso padre al hijo tenemos los siguientes: real user ID real group ID effective user ID effective group ID process group ID terminal group ID root directory current working directory signal handling setting file mode creating mask El proceso hijo difiere del proceso padre en lo siguiente: el proceso hijo tiene un nuevo y nico ID de proceso, el proceso hijo tiene un diferente ID de proceso padre, el proceso hijo tiene sus propias copias de los descriptores de archivos del padre, el tiempo que queda en una alarma de reloj es puesto en cero en el hijo. Existen dos usos para la operacin fork: 1. Un proceso quiere hacer una copia de s mismo para que esa copia pueda manejar una operacin mientras la otra copia haga otra tarea. Esto es tpico para los servidores en una red. 2. Un proceso quiere ejecutar otro programa. Debido a que la nica manera en que se puede crear un nuevo proceso es con la operacin fork, el proceso debe primero realizar un fork para hacer una copia de s mismo, entonces una de las copias (tpicamente el proceso hijo) realiza una llamada exec para ejecutar el programa nuevo. Esto es tpico en programas como shells.

Servidor concurrente Un servidor concurrente se utiliza cuando la cantidad de trabajo requerida para manejar una peticin es desconocida, entonces el servidor comienza otro proceso para manejar cada una de las peticiones de los clientes. El servidor empieza a ejecutarse primero y tpicamente realiza los siguientes pasos: 1. Abre un canal de comunicacin e informa al host local de su deseo de aceptar peticiones de clientes en una direccin conocida con anterioridad. 2. Espera una peticin de algn cliente en la direccin conocida con anterioridad por el mismo. 3. Se crea un proceso nuevo para manejar esta peticin del cliente. Esto requiere de una llamada fork y posiblemente de una exec bajo UNIX, para empezar el nuevo proceso. Este nuevo proceso entonces maneja las peticiones del cliente y no tiene que responder a peticiones de otros clientes. Cuando este nuevo proceso es terminado, cierra su canal de comunicacin con el cliente y termina. 4. El servidor regresa al paso 2: espera otra peticin de algn cliente. Implcito en los pasos anteriores, el sistema de alguna manera forma en una cola las peticiones de los clientes mientras que el servidor procesa una peticin previa y realiza un fork. El proceso cliente realiza el siguiente conjunto de acciones: 1. Abre un canal de comunicaciones y se conecta a una direccin especfica conocida con anterioridad en un host especfico (el servidor). 2. Manda peticiones de servicio al servidor, y recibe las respuestas. Contina haciendo esto mientras sea necesario. 3. Cierra el canal de comunicaciones y termina. Cuando el servidor abre un canal de comunicaciones y espera por una peticin de algn cliente, se le denomina "pasive open". El ciente, por otro lado, ejecuta lo que es llamado "active open" debido a que cuenta con que el servidor est esperando alguna peticin. Un ejemplo de Cliente-Servidor El cliente lee el nombre de un archivo desde el standard input y lo escribe a un canal IPC (Interprocess Communication). El servidor lee este nombre de archivo del canal IPC y trata de abrir el archivo para lectura. Si el servidor puede abrir el archivo, responde leyendo el archivo y escribindolo al canal IPC, de otra manera el servidor responde con un mensaje de error ASCII describiendo que no pudo abrir el archivo. El cliente entonces lee del canal IPC, escribiendo lo que recibe hacia el standard output. Si el archivo no puede ser ledo por el servidor, el cliente lee un mensaje de error del canal IPC. De otra manera lee el contenido del archivo.

Multiservidor remoto por forks


Ahora que ya sabemos cmo crear procesos mltiples con forks, comunicables entre s mediante pipes, veremos ahora el uso de forks para que un servidor remoto atienda a mltiples clientes de manera simultnea. El principio es simple. Un proceso servidor define su socket para escuchar y aceptar conexiones de clientes. nicamente para eso usar dicho socket. Al aceptar, se define un socket para atender a la conexin entrante. Una vez definido, pone el socket a escuchar, e inicia un ciclo infinito en el que espera hasta aceptar una conexin. Tan pronto acepta una conexin, crea un proceso hijo, idntico a l. Tan pronto el proceso servidor comprueba que es el proceso padre, cierra la conexin, lo cual no afecta al cliente, porque el proceso hijo ser quien lo atienda. El proceso hijo, tan pronto comprueba que es el hijo, se hace cargo de atender la conexin. Cuando se termine la conexin entre el cliente y el hijo, el proceso hijo termina. #include <stdio.h> #include <malloc.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> #include <sys/time.h> main() { int sd,from_len,new_socket;

struct sockaddr_in from,sockname; char buffer[80]; //Crear socket y definirlo if((sd=socket(AF_INET,SOCK_STREAM,0))==-1) perror("Servidor:Socket"),exit(1); sockname.sin_family=AF_INET; sockname.sin_addr.s_addr=INADDR_ANY; sockname.sin_port=htons(5899); /* ^^^^ Sustituir por el puerto correspondiente */ if(bind(sd,&sockname,sizeof(sockname))==-1) perror("Servidor:Bind"),exit(1); //Poner socket a escuchar if(listen(sd,1)==-1) perror("Servidor1:Listen"),exit(1); from_len=sizeof(from); //Iniciar ciclo infinito para atender a cada cliente que llegue while( 1 ) { //Aceptar conexin de cliente if((new_socket=accept(sd,&from,&from_len))==-1) perror("Servidor:Accept"),exit(1); //Crear proceso hijo que atender al cliente if( fork() == 0 ) { do{ /* Soy el proceso hijo */ //El proceso hijo atiende al cliente if(recv(new_socket,buffer,80,0)==-1) perror("Servidor:Recv"),exit(1); printf("(Proceso %d, recibi: %s)\n",getpid(),buffer); }while(strcmp(buffer,"FIN")!=0); exit( 0 ); } else { //El proceso padre cierra conexin con cliente, disponindose a esperar siguiente if(close(new_socket)==-1) perror("Servidor: Close"),exit(1); } } }

Ejercicio 4
Crear el archivo servidorMC.c conteniendo este ltimo cdigo mostrado, y compilarlo. Probar su ejecucin junto con el cdigo cliente del ejerccio 2, poniendo en ejecucin varias instancias de dicho cdigo cliente, para observar que efectivamente el servidor presenta la capacidad de atender a varios clientes simultneamente.

Ejercicio 5
Basndose en este ltimo cdigo de servidor que se ha mostrado y que tiene la capacidad de atender a varios clientes a la vez, modifiquese el programa servidor del ejercicio 3 anterior, de manera que pueda atender a varios clientes ofreciendo los servicios que en l se han implementado.

Anda mungkin juga menyukai