Socket

download Socket

of 24

Transcript of Socket

E.T.S INGENIERA INFORMATICA

Comunicacin de Socket de Tipo SOCK_STREAMWaseem M. Haider, Erik Nieto Tovar, Hugo E. Camacho Cruz, Ral Hernndez Palacios Departamento de Arquitectura y Tecnologa de Computadores Universidad De Granada (Espaa) ( mhaider, enieto, hcamacho, rpalacios )@atc.ugr.es

Introduccin: 1. Definicin de Sockets:Antes de iniciar el anlisis de los distintos tipos de Sockets y sus algoritmos de procesamiento, definiremos lo que es un socket. Un socket (zcalo) es la unidad mnima de un sistema de comunicacin, el cual se usa para entablar una comunicacin entre dos equipos o entre dos procesos. Dicho esto, la comunicacin entre dos equipos puede ser sincrona o asncrona, esto quiere decir, que cuando una comunicacin es asncrona, el host emisor enva datos hacia el host destino sin un previo aviso, es decir, no es necesario que el destino este enterado de que le llegaran dichos datos. A diferencia de la transmisin sncrona, en la cual el host emisor advierte al host destino que quiere entablar una comunicacin, enviando la cantidad de informacin que se enviara entre otros datos, de esta manera, el host destino le enva una seal de que esta enterado de dicha peticin, y adems de que la acepta, esta sincrona le brinda seguridad a la comunicacin entre dos host. Esta pequea introduccin es para mencionar que los sockets puedes ser de tipo STREAM los cuales utilizan la sincrona, y por lo regular utilizando el protocolo TCP dentro de la capa de transporte de la pila de protocolos TCP/IP, y de tipo DATAGRAMA que es de tipo asncrona, y utiliza el protocolo UDP dentro de la misma pila de protocolos. Existe otro tipo de socket llamado RAW, pero este trabajo se centrara solamente sobre los sockets de tipo STREAM.

1

E.T.S INGENIERA INFORMATICA

2 Secuencia de Funciones:Network Byte Order & Host Byte Order. Cuando se estn enviando datos por algn tipo de red, estos deben de viajar en un orden tal, que puedan ser ledos, por lo regular se convierten al tipo Network Byte Order (Orden de Byte de Red) o tambin conocida como Big-Endian, y cuando esos datos se procesan en un ordenador se deben de convertir a Host Byte Order (Orden de Byte de Red). Esto se puede hacer con funciones predefinidas dentro de las API (Applicatioon Programming Interface) de Socket como son htons(), htonl(), ntohs() y ntohl() descritas a continuacin:

htons(): Host to Network Short. Convierte de un short int de host byte order a network byte order. htonl(): Host to Network Long de igual manera que la anterior convierte un long int de host byte order a network byte order. ntohs(): Network to Host Short. Convierte un short int de network byte order a host byte order. ntohl(): Network to Host long. Convierte un long int de network byte order a host byte order.

A continuacin veamos el siguiente ejemplo para comprenderlo un poco mejor: El flujo de los bits a travs de la red es secuencial y directo, as que cuando pretendemos enviar un byte tipo carcter con sus respectivos 8 bits seria:

A={1 0 0 0 0 0 0}Al pasarlo por la red seguira el orden: 1 1 2 3 4 5 6 7 8 representacin 1 0 0 0 0 0 0 0Nmero de Bit

0 0 0 0 Letrta A 0 0 0

Al llegar la secuencia de bits al host Destino se acomodaran en la memoria del usuario de la siguiente forma:

2

E.T.S INGENIERA INFORMATICA

1 2 3 4 5 6 7 8 representacin 0 0 0 0 0 0 0 1 Representando el carcter con el nmero 1 dentro del cdigo ASCII, de esta manera se demuestra el uso de las funciones de conversin tanto a Network byte order como Host Byte Order. Cuando se desea convertir una direccin IP a Network byte order se hace de la siguiente manera. htonl(154.198.52.200). una forma sencilla de obtener la direccin IP del equipo donde se esta ejecutando la aplicacin con sockets, y cambiarla a Network byte order es: htonl(INADDR_ANY); de esta manera se podr obtener la direccin IP del host local y convertirla a la secuencia Network Byte Order aunque algunos autores comentan que esto no es necesario debido que realmente lo que regresa INADDR_ANY es un cero, y al poner socket.sin_addr.s_addr = 0 (el dato socket.sin_addr.s_addr es un campo de las estructuras de socket que se explicaran mas adelante), automticamente se toma la direccin IP del sistema local, pero otros autores comentan que en algunos sistemas INADDR_ANY tomara valores diferentes de cero, solo para indicar que se debe tomar la direccin IP local, y por razones de portabilidad deberemos escribirlo en nuestros programas de la forma htonl(INADDR_ANY); esto ya lo comentaremos mas adelante dentro del ejemplo que se realice. De igual manera para indicar un puerto en secuencia de Network Byte Order se hara de forma: Puerto= htons(3490); Ntese que para las direcciones IP se toma htonl() ya que se trata de cifras de tipo long, y cuando se hace solo con un puerto y sus valores estn entre 1 hasta 65535 se trata de una cantidad de tipo short, as que hay que tener cuidado en utilizar las funciones correctas para los distintos tipos de datos que se envan por la red.

Nmero de Bit

3. Creacin de un Socket:Para crear un socket dentro de una aplicacin se utiliza la funcin socket(), cuyos parmetros se indican enseguida:

socket(int dominio, int tipo, int protocolo);int dominio: con este parmetro se indica en que tipo de dominio estar comunicando el socket, ya que el dominio puede variar de acuerdo hacia donde se comunique, se puede comunicar dentro de procesos que se estn ejecutando en sistemas Unix, o hacia otras aplicaciones que se estn ejecutando dentro de Internet. Existen dos tipos mas utilizados dentro de los sockets (hay que dejar claro que existen mas tipos de dominio) PF_UNIX y PF_INET, el primero se utiliza para hacer un socket dentro de un dominio de host, es decir para comunicar dos procesos que se estn ejecutando dentro de un host, y PF_INET, el cual se utiliza para 3

E.T.S INGENIERA INFORMATICA

indicar que el dominio ser la red, que el socket se comunicara con otros socket que estn en un host conectado a Internet. int tipo: este parmetro indica el tipo de socket que se estar utilizando. Este parmetro es muy importante ya que al indicaremos de que forma viajaran los datos por la red, o por los buses del host local. Los cuatro tipos de sockets mas importantes se indican en la tabla1:

TIPOSOCK_STREAM SOCK_DGRAM SOCK_RAW SOCK_SEQPACKET

DEFINICINSocket de tipo stream Socket de tipo Datagrama Solo protocolos con privilegios de root. Comunicacin con datagramas fiable.Tabla 1. Tipos de sockets

Cada uno de los tipos de socket por lo regular se asocia a un tipo de protocolo, por ejemplo SOCK_STREAM esta asociado al protocolo TCP. El cual brinda seguridad en la transmisin de datos, seguridad en la recepcin, en la integridad y en la secuencia, entre otras cosas. El tipo de socket SOCK_DGRAM esta asociado al protocolo UDP, y este indica que los paquetes viajaran en tipo datagramas, el cual como ya se indico tiene una comunicacin asncrona, y ofrece bajo overhead en la creacin de los datagramas y en el procesamiento de estos.

Por lo que respecta a los dos tipos de sockets restantes, SOCK_RAW indica que se usaran protocolos con privilegios de root para acceder directamente a la interfaz de red del host y SOCK_SEQPACKET ofrece una comunicacin con datagramas fiable. Int protocolo: En cuanto al tercer parmetro de la funcin socket() int protocolo se indica el tipo de protocolo de la capa de transporte que se usara para la comunicacin, como se ya comentamos antes, si se usa un socket de tipo SOCK_STREAM se asocia al protocolo TCP y si usamos un socket de tipo SOCK_DGRAM se asocia al protocolo UDP, pues si ponemos el parmetros int protocolo a O, indicaramos el tipo de protocolo por omisin(TCP para comunicacin por omisin y UDP para comunicacin con datagramas). As que si usamos: Int sock = socket (PF_INET, SOCK_DGRAM, 0); Indicamos que el socket estar en el dominio de Internet, que ser de tipo datagrama y con el 0 asociamos el tipo de protocolo a UDP, ya que el tipo SOCK_DAGRAM coasocia de forma indirecta. Tambin podemos indicar el tipo de protocolo con la funcin getprotobyname(), esta funcin lee el archivo etc/protocols y devuelve una estructura que contiene lo siguiente: Char *p_name /nombre oficial del protocolo 4

E.T.S INGENIERA INFORMATICA

Char **p_aliases /lista de Alias Int p_proto / nmero de protocolo. Un ejemplo de esta funcione s: pp=getprotobyname(tcp): sock=socket(AF_INET,SOCK_STREAM, pp ->p_proto); La funcin socket() devuelve un descriptor de fichero el cual es de tipo int, en el ejemplo anterior a sock le asignamos el valor de dicho descriptor, y de all en adelante del programa, para reverenciarse al socket se utilizara sock, ya que este contendr el descriptor del socket generado.

4. Estructuras de datos utilizadasAntes de continuar con las funciones que se utilizan para la creacin de socket definiremos las estructuras que se utilizan para manejar los datos de dichos Sockets. La primera estructura es struct sockaddr. Esta estructura contiene los siguientes datos: Struct sockaddr { Unsigned short sa_family; // Aqu se indica la familia de la que ser el socket creado. Char sa_data[14]; //Direccin del protocolo }; La segunda estructura y sus campos es: Struct sockaddr_in { Short int son_family; //Familia a la que pertenece AF_INET Unsigned short sin_port; // nmero de Puerto utilizado. Struct in_addr sin_addr// Direccin de IP Unsigned char sin_zero[8]; //Relleno }; La estructura que se usa para guardar la direccin IP es: Struct in_addr { Unsigned long s_addr; // Campo para la direccion IP 4 bytes };

5

E.T.S INGENIERA INFORMATICA

5. Funcin bind()Esta funcin se encarga de darle nombre al socket, ahora describiremos por que. Con esta funcin se le asigna una direccin IP y un nmero de puerto al socket especificado. Al especificarle una direccin IP le estamos indicando por cual interfaz fsica escuchara (el sistema puede tener distintas interfaces fsicas.). Es importante remarcar que por lo regular la funcin bind() se utiliza para programar servidores, por que por lo regular cuando se programan clientes no se utiliza esta funcin, el kernel propio le asignara la direccin IP y el numero de puerto disponible al llamar a ciertas funciones, por ejemplo cuando se llama a conect() para conectarse a un sistema remoto. En un servidor es necesario llamar a bind() debido a que el numero de puerto debe de ser conocido por los clientes para que puedan conectarse con el, por ejemplo si se programa un servidor de telnet debemos llamar a bind() para asignarle el puerto 23. En el cliente se puede llamar a bind y asignarle un puerto, pero aunque se haga esto nadie intentara ni podr conectarse con el. La funcin bind trabaja de la siguiente manera: Int bind(int sock, struct sockaddr *my_addr, int addrlen); Es una funcin que trabaja con tres parmetros los cuales reciben lo siguiente: int sock: Este es el descriptor del fichero devuelto por la funcin socket(). *my_addr: Este es un puntero a la estructura sockaddr que contiene la IP del host local y el nmero de puerto que se le asignara al socket. Addrlen: Este se debe establecer al tamao de la estructura sockaddr sizeof(struct sockaddr).

Ejemplo de la funcin bind:

. . .Int sock = socket (PF_INET, SOCK_DGRAM, 0); Struct sockaddr_in sin

.Int bind(sock, (struct sockaddr *)&sin, sizeof(sin));

. . .6

E.T.S INGENIERA INFORMATICA

En el segundo parmetro vemos que se debe de pasar un puntero referenciando la direccin de memoria de my_addr, la sintaxis (struct sockaddr *)&sin enva como puntero struct sockaddr y llevando la direccin de la estructura sin con el, el cual es la variable de tipo struct sockaddr_in. Y al final como ultimo parmetro se pone sizeof(sin), el cual contiene el tamao total de la estructura sockaddr_in.

6. Caso para establecer una comunicacin con el protocolo TCP y el tipo de socket SOCK_STREAM.En la figura 1 se muestra los pasos que se deben de realizar para realizar una comunicacin con el protocolo TCP. Se muestra claramente que la comunicacin dentro de este protocolo debe de ser sincrona, ya que se utiliza la funcin connect() en el cliente y la funcin accept() en el servidor. Como se comento al inicio de este trabajo, la comunicacin sincrona debe de trabajar de esta manera, el host origen que desea comunicarse con un host destino, debe de enviar primero una seal para que el destino acepte la comunicacin, en caso de que el destino no regrese la seal de aceptacin, el origen no enviara datos. Los motivos por los que el destino no regrese una seal pueden ser distintas, y mientras tanto el host origen debe de esperar un tiempo razonable para enviar de nuevo la seal de conexin, y el algoritmo decidir si desiste de establecer la comunicacin o vuelve a intentar la comunicacin.

Figura 1. Modelo de comunicacin de sockets tipo SOCK_STREAM.

7

E.T.S INGENIERA INFORMATICA

Realizando un estudio de la figura 1 podemos decir que: 1.- Ambos host, tanto el cliente y el servidor deben de abrir un socket con la funcin socket() (Paso 1 en la figura 1), para poder intercambiar datos, aqu ya los dos quedaran en posibilidades de intercambiar datos por los sockets, pero se debe inicializar los sockets para indicar tanto la direccin IP para encontrarse y el puerto para definir por donde entraran datos al host. 2.- Dentro del servidor se llama a la funcin bind() (paso 2 en la figura 1) para darle un nombre al socket, es decir, establecer tanto la direccin IP del socket, y el puerto por el cual escuchara dicho socket. En el caso de que se tratara de un servidor telnet se asignara el puerto 23, ya que este es el puerto preestablecido para telnet. Es importante indicar que para el cliente no es necesario establecer un nmero de puerto ya que no recibir intentos de conexin. 3.- En el servidor llama la opcin listen() (paso 3 en la figura 1), para que el socket por donde escuchar se habilite, ahora con esta funcin el servidor quedara atento para que en cuanto reciba una seal de conexin la conteste con otra seal de conexin aceptada. En el cliente no es necesario hacer un listen() ya que no debe de estar atento a que alguien se conecte con el. 4.- El servidor ejecuta la funcin accept () (paso 4 en la figura 1) y queda en estado de espera, la funcin acept() no retorna hasta que alguien intente conectarse con el servidor. Para iniciar la comunicacin el cliente utiliza la funcin connect() (paso 3 en la figura 1) en ese momento la funcin accept() retorna con un parmetro que es un nuevo descriptor de socket, el cual se utiliza para comenzar la transferencia de datos con el cliente. 5.- Una vez establecida la conexin se utilizan las funciones send() y recv() (pasos 3 en el cliente y 5 en el servidor de la figura 1) con el nuevo descriptor de socket del paso anterior para realizar las transferencias de datos. 6.- Ya que la transferencia de datos ha terminado se deben de cerrar los descriptores de sockets, esto se realiza con las funciones close() y shutdown() (pasos 4 y 6 en el cliente y servidor respectivamente de la figura 1). Es importante mencionar que las funciones close() y shutdown() trabajan de manera diferente, en secciones posteriores se explicara.

6.1 Descripcin de la funcin listen()Esta funcin solo se aplica a sockets de tipo STREAM (TCP), y habilita al socket para que pueda recibir conexiones. Hablando de forma hipottica que pasara si de alguna manera quieres gestionar las conexiones entrantes, y de esta manera aceptar o no aceptar dichas conexiones, pues esto se hara de forma que primero escuchas quien toca a la puerta y luego decides si abres o no, es decir primero se pone a escuchar al servidor con la funcin listen() y despus se acepta la conexin con accept(). La llamada listen() tiene implcita cierta sencillez, y ahora explicaremos de como trabaja y los parmetros que necesita para funcionar. La funcin listen() se llama de la siguiente manera:

8

E.T.S INGENIERA INFORMATICA

int listen(int sock, int backlog);Los parmetros int sock e int backlog son: int sock: Es el descriptor de socket que nos fue devuelto por la funcin socket. int backlog: Este parmetro determina el numero mximo de conexiones que se podrn aadir a la cola de entrada. Es decir, es el numero mximo de conexiones que podrn esperar a que las aceptes (accept()), y la mayora de los sistemas limitan esta cola a 20, un numero de 5 a 20 ser suficiente dependiendo del numero de clientes que pueda tener el servidor. Como ya se pudo ver en la figura 1, es necesario llamar primero a la funcin bind() que la funcin listen(), por que de otra manera el kernel nos tendr esperando en un nmero de puerto aleatorio. As que la secuencia de funciones correctas seria la siguiente: { socket(); bind(); listen(); accept() }

6.2 Caso de la funcin connect()La funcin connect() inicia la conexin con el host remoto, esta funcin es usada por el cliente para conectarse. Esta funcin necesita 3 parmetros que a continuacin se explicaran. La forma de llamar a esta funcin es: connect(int sock, struct sockaddr *serv_addr, int addrlen); int sock: este es ya famoso descriptor de socket devuelto por la funcin socket(). serv_addr: es una estructura de tipo struct sockaddr que contiene tanto la direccin IP y el puerto de destino del servidor. addrlen: a este parmetro se le asigna el valor de la estructura sockaddr mediante sizeof(struct sockaddr). Un ejemplo del uso de esta funcin sera: #include #include #include #include #include #define DEST_IP 10.12.110.57 9

E.T.S INGENIERA INFORMATICA

#define DEST_PORT 23 main() { int sock, new_sock; struct sockaddr_in direc_remota; if (sock=socket(AF_INET, SOCK_STREAM, 0) == -1); { perror(Error al crear socket); exit(1); } direc_remota.sin_family = AF_INET; direc_remota.sin_port=htons(DEST_PORT); direc_remota.sin_addr.s_addr=inet_addr(DEST_IP); memset(&(mi_direc.sin_zero), \0, 8 ); if (conect(sock, (struct sockaddr *)&direc_remota, sizeof(struct sockaddr)) == -1); { perror(Conectando); exit(1); }

. . .En el ejemplo anterior el cliente tratara de comunicarse con el servidor el cual tiene la direccin DEST_IP 10.12.110.57 y el puerto DEST_PORT 23, para de esta manera mediante la funcin connect() se le dan los datos al socket para que realice dicha conexin.

6.3 Caso de la funcin accept()Esta funcin trabaja de una manera un poco difusa, pero solo con un poco de explicacin quedara todo entendido. La llamada a accept() se realiza cuando algn host realiza un connect() hacia el servidor, mientras tanto el servidor estar a la escucha de las conexiones con la llamada listen(). Esta conexin pasara a la cola de conexiones, esperando sea atendida. Cuando en el servidor se llama a la funcin accept() se esta tomando en cuenta que se desea atender a una nueva conexin y sacarla de la cola de espera. La llamada a accept() regresa un nuevo descriptor de socket, es decir, que ahora ya se tienen dos distintos descriptores, en el proceso, y este nuevo descriptor esta listo para enviar y recibir datos por el puerto indicado. La llamada a la funcin accept() es como sigue: int accept(int sock, void *addr, int addrlen);

10

E.T.S INGENIERA INFORMATICA

como se puede ver la funcin accept es de tipo int y sus parmetros son los siguientes: int sock: es el descriptor de socket que nos devolvi la funcin socket() usado por todas las dems funciones. Void *addr: Este es un puntero a la estructura sockaddr_in, en esta se almacenara informacin de la conexin entrante, devuelve informacin de que host esta llamando y desde que puerto. Addrlen: Este parmetro debe de estar igualado al tamao de la estructura sockaddr (sizeof(sockaddr)) La funcin accept() no escribir mas de addrlen bytes en addr. Si pone menos cambiara el valor de addrlen para que refleje el nmero exacto de bytes almacenados. Para poder comprender este concepto de accept() haremos el siguiente ejemplo: /* Este es un trozo de cdigo para plasmar el ejemplo de la funcin accept() */ #include #include #include #include #include #include #define MYPORT 3490 #define BACKLOG 10 main() { int sock, new_sock; struct sockaddr_in mi_direc; struct sockaddr_in direc_remota; int sin_size; if (sock=socket(AF_INET, SOCK_STREAM, 0) == -1); { perror(Error al crear socket); exit(1); } mi_direc.sin_family = AF_INET; mi_direc.sin_port=htons(MYPORT); mi_direc.sin_addr.s_addr=INADDR_ANY; memset(&(mi_direc.sin_zero), \0, 8 ); if (bind(sock, (struct sockaddr *)&mi_direc, sizeof(struct sockaddr)) == -1); { perror(Asociando socket); exit(1); }

11

E.T.S INGENIERA INFORMATICA

if (listen(sock, BACKLOG) == -1) { perror(Error al abrir la escucha); exit(1); } sin_size = sizeof( struct sockaddr_in); if ( new_sock=accept(sock, (struct sockaddr *) & direc_remota, &sin_size) == -1)

. . .} En la ltima lnea de este trozo de cdigo, se representa la llamada a la funcin accept(), la cual es asignada a new_sock, el cual contendr el nuevo descriptor de socket, para poder entablas las comunicaciones con el host remoto. Como se puede ver, los datos del host remoto se guardaran dentro de la estructura sock_addr referenciada por direc_remota. En el caso de que solo se espere una conexin remota en el servidor se puede cerrar el descriptor sock con la funcin close(), la cual ser explicada un poco mas adelante, esto para evitar que otras conexiones traten de conectarse con este socket.

6.4 Funciones de envo y recepcin con send() y recv()Despus de haber hecho todo lo anterior con las distintas funciones de comprobacin de conexin, de escucha, de conexin, se realiza el envi y la recepcin de datos, que es verdaderamente el objetivo final de los sockets. Estas operaciones se pueden ejecutar mediante las funciones send() y recv(). Estas funciones se ejecutan directamente con sockets de tipo STREAM, y es necesario dejar claro que para realizar la comunicacin mediante sockets de tipo DATAGRAM se utilizan las funciones sendto() y recvfrom(), las cuales trabajan de forma distinta. La sintaxis de la funcin send() es de la siguiente forma: send(int sock, const void *msg, int len, int flag ); donde: sock: Es el descriptor de socket por el cual se realizara la comunicacin, solamente que aqu puede ser, o bien, el que nos fue devuelto por la funcin socket() o el que nos fue devuelto por la funcin connect() (new_sock). 12

E.T.S INGENIERA INFORMATICA

Const void *msg: este elemento es un parmetro que apunta hacia los datos que deseamos enviar. len: este parmetro representa la longitud en bytes que deseamos enviar de datos. flag: La nica diferencia entre la funcin send() y la funcin write() es la presencia del parmetro flag, Con un valor de 0 puesto al parmetro flag, la funcin send() es equivalente a la funcin write. Tambin, send(s, buf, len) es equivalente a sendto(s, buf, len, NULL, 0). (Para conocer mas sobre estas funciones, puedes consultar la pagina web: http://www.penguinsoft.com/penguin/man/2/send.html). La funcin send() retornara la cantidad de datos enviados, la cual podra ser menor a la cantidad de datos que se posicionaron en el buffer de envo. Esta funcin enviara la cantidad mayor posible y retornara el total de bytes enviados, aqu restara al programador verificar que realmente se enviaron todos los datos que se queran, y si no es as, reenviar solo los que hicieron falta en una nueva llamada a send(). Un ejemplo de la sintaxis de la funcin send() podra ser: Char *msg = Este es el mensaje que deseamos enviar; int len, bytes_enviados;

. .len = strlen(msg); bytes_anviados=send(sock, msg, len, 0);

. .La funcin recv() es similar en muchos aspectos veamos su sintaxis: recv(int sock, void *buf, int len, unsigned int flag); Los parmetros de la funcin send() son los siguientes: sock: este es el descriptor de fichero devuelto por la funcin socket(). buf: este parmetro apunta hacia el espacio de memoria o buffer en el cual se guardaran los datos obtenidos por recv(). int len: Este es el tamao total del espacio de memoria en el que se depositaran los datos. flag: Como en la parte de arriba puede ir puesta a cero. La funcin recv() retorna el numero total de bytes que se leyeron y respectivamente, retorna un -1 en caso de que existiera un error en la lectura de dichos datos.

13

E.T.S INGENIERA INFORMATICA

Tambin la funcin recv() puede retornar un 0, esto solo puede deverse a una cosa, y es que el host remoto cerro la conexin con nosotros, un valor de cero es una seal de que ha sucedido lo anterior. Si no hay datos que recibir en el socket, la llamada a recv() no retorna (se bloquea) hasta que lleguen datos, ahora que el socket se puede establecer como no bloqueante de manera que cuando no hay datos para recibir datos retorne un -1 y establece la variable errno a EWOULDBLOCK, esto se hace mediante la funcin fcntl() la cual se explicara enseguida.

6.5 Caso de la Funcin fcntl().Cuando creamos un socket, y nos conectamos a un servidor, sabemos que el socket nos provee una comunicacin bidireccional, podemos enviar y recibir datos simultneamente. Si llamamos a la funcion recv() para recibir datos, estamos seguros que los recibir, pero que pasa en el caso de que el servidor en ese momento no tiene ningn dato para enviar, entonces la funcin recv() no retorna, se bloquea, como algunos autores lo llaman. Otros usan el tecnicismo de dormir, es decir el socket se mantiene dormido a la espera de que algn dato llegue para poder guardarlo en el buffer de memoria. Mientras el socket permanece dormido el programa se queda bloqueado y no continua en su ejecucin. Muchas de las funciones que se usan en los sockets se bloquean, como accept(), o recv(), y lo hacen por que as estn configuradas, es decir, el kernel las define como bloqueantes y mientras no se diga lo contrario as permanecern.

De alguna manera se debe de establecer que las funciones sean no bloqueantes y retornen aun que no existan datos para procesar por parte del servidor. Para establecer el socket como no bloqueante se utiliza la funcin fcntl(), el cual tiene la siguiente sintaxis: int fcntl(int sock, int cmd, long arg); donde: sock: descriptor de archive sobre el que estamos realizando las operaciones. cmd: Determina el comando que se va a aplicar, en este caso se usara el comando F_SETL, dicho comando establece los flags del descriptor al valor especificado en arg. arg: Argumentos que necesita el comando para establecer el socket como no bloqueante sera O_NONBLOCK. En esta funcin se produce un error y retorna un -1 y se especifica errno especificando el error.

14

E.T.S INGENIERA INFORMATICA

Como ejemplo utilizaremos el siguiente ejemplo: #include #include

. .sock= socket(AF_INET, SOCK_STREAM, 0); fcntl(sock, F_SETFL, O_NONBLOCK);

. .Mediante esta ejecucin se intenta leer el socket, ahora no bloqueante y si no hay datos, la funcin no esta autorizada para bloquearse retorna un -1 y le asignara a errno el valor EWOULDBLOCK. Mediante este procedimiento se escuchara de forma constante al socket para saber si hay datos para leer, pero en concepto de eficiencia no es muy buena forma de hacerlo, ya que si se pone en un bucle indefinido a la CPU a escuchar al socket se estara desperdicienando mucho tiempo de procesamiento, y no es una manera muy elegante de realizarlo. Para poder aplicarlo de una manera mas eficiente existe la funcin select(), que a continuacin se explica.

6.6 Funcin Select: Multiplexado de E/S sncrona.Esta funcin resulta muy eficaz cuando en un servidor se desean escuchar nuevas conexiones entrantes, al mismo tiempo que se siguen leyendo conexiones que ya existen. En este caso el uso de accept() y send()/recv() no seria la mejor opcin, por que se puede dar el caso que la funcin accept() se quede bloqueada en espera de nuevos datos. En el mejor de los casos se pueden usar sockets de tipo no bloqueantes, y monitorearlos cada determinado periodo de tiempo, pero esto ocasionara que la CPU trabajara de forma excesiva, y el tiempo de procesamiento consumido seria considerable. Una de las mejores opciones para estos casos es el uso de la funcin select(), la cual se encarga de monitorear un conjunto de descriptores de sockets para saber cuales estn listos para leer, para escribir o cuales produjeron excepciones . La funcin revisa los descriptores de ficheros guardados dentro de las listas readfds, writefds y exceptfds, y retorna los descriptores que estn listos para leer(readfds) para escribir (writefds) y cuales provocaron excepciones (exceptfds). La sintaxis de la funcin select() es como se muestra a continuacin: int select(int num_desc, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,struct timeval *timeout); Como ejemplo podemos verificar la funcin select() con la entrada estndar y con el descriptor de ficheros devuelto por la funcin socket(), aadiendo ambos al conjunto readfds. Por

15

E.T.S INGENIERA INFORMATICA

lo regular la entrada estndar es el descriptor de ficheros 0, as que dentro de los parmetros de la funcin select(), num_desc deber tener el valor del descriptor mayor + 1, es decir si solo se desea leer la entrada estndar el valor de num_desc debera ser 0+1. En el caso de que se desea leer el descriptor sock, el parmetro num_desc debera tener el valor sock + 1, ya que seguramente tendr como mnimo un valor mayor que el descriptor de la entrada estandar(0). Cuando select() regresa, los descriptores quedaran incluidos dentro de las listas readfds(lectura), writefds(escritura) y exceptfds(excepciones) respectivamente. Para poder aadir, borrar, limpiar y leer el conjunto de descriptores de ficheros se utilizan cuatro macros que son de suma importancia conocerlas: FD_ZERO (fd_set *set) Borra un conjunto de descriptores de fichero. FD_SET (int fd, fd_set *set) Aade el descriptor fd al conjunto de ficheros *set FD_CLR (int fd, fd_set *set) Elimina el descriptor fd al conjunto de ficheros *set. FD_ISSET (int fd, fd_set *set) Verifica si el descriptor fd esta en el conjunto de ficheros *set. Es importante destacar que todos los conjuntos de descriptores de ficheros, como se puede ver en el conjunto de parmetros de la funcin select() son de tipo fd_set. Bueno ahora que ya definimos la mayora de los parmetros de la funcin select() queda por definir la estructura de tipo timeval. Esta estructura se usa para establecer el tiempo que se esperara a leer un descriptor de ficheros, ya que si no se define el tiempo la funcin nuevamente se puede quedar bloqueada. Este tiempo servir para esperar a que algn descriptor se comunique y si el tiempo expira la funcin regresara para que se pueda seguir procesando. Struct timeval { int tv_sec; int tv_usec; }; En los campos definidos dentro de la estructura timeval, se definen tv_sec, que al darle un valor a este campo sern los segundos que la funcin select() debe de esperar, y el campo tv_usec se utiliza para establecer los microsegundos que se esperaran, es decir la funcin esperara tv_sec segundos y tv_usec microsegundos. Se defini tv_usec por que la u representa la letra del alfabeto griego (mu), que suele usarse para definir el prefijo micro, como p significa microprocesador. Se le pueden dar valores predefinidos al parmetro struct timeval para que realice distintos procesos, si se establece a 0 los campos tv_sec y tv_usec regresara inmediatamente de haber monitoreado todos los descriptores de ficheros incluidos en los conjuntos, si se le da el

16

E.T.S INGENIERA INFORMATICA

valor de NULL al parmetro timeout nunca expirara el temporizador y se esperara hasta que un descriptor este listo para trabajar.

7. Otras funciones utilizadas dentro de la programacin de sockets. NOMBRE:setsockopt - Establece las opciones de socket.

SINOPSIS:#include sys/socket.h>

int setsockopt(int socket,int level, int option_name, const void *option_value, socklen_t option_len);

DESCRIPCIN:La funcin setsockopt ( ) pondr la opcin especificada por el argumento option_name, en el nivel de protocolo especificado por el argumento de nivel, al valor sealado por el argumento option_value para el socket asociado con el descriptor de archivo especificado por el argumento de socket. El argumento de nivel especifica el nivel de protocolo en el cual la opcin reside. Para poner opciones en el nivel de socket, especifique el argumento de nivel como SOL_SOCKET. Para poner opciones en otros niveles, suministre el identificador de nivel apropiado para el protocolo controlando la opcin. Por ejemplo, para indicar que una opcin es interpretada por el TCP (Transport Control Protocol), ponga el nivel a IPPROTO_TCP el cual esta definido en el header . El argumento option_name especifica una sola opcin para ponerse. El argumento option_name y cualquier opcin especificada son pasados no interpretados al mdulo de protocolo apropiado para interpretaciones. El header define las opciones de nivel de socket. Las opciones son como sigue: SO_DEBUG Enciende la grabacin de eliminar fallos de la informacin. Esta opcin permite o incapacita la depuracin en los mdulos de protocolo subyacentes. Esta opcin toma un valor de int. Este es una opcin Booleana. SO_BROADCAST

17

E.T.S INGENIERA INFORMATICA

Los permisos que envan de mensajes de emisin, si este es apoyado por el protocolo. Esta opcin toma un valor de int. Este es una opcin Booleana. SO_REUSEADDR Especifica que las reglas usadas en la convalidacin de direcciones suministradas para bind ( ) deberan permitir la reutilizacin de direcciones locales, si este es apoyado por el protocolo. Esta opcin toma un valor de int. Este es una opcin Booleana.

SO_KEEPALIVE Guarda conexiones activas permitiendo a la transmisin peridica de mensajes, si este es apoyado por el protocolo. Esta opcin toma un valor de int. Si el socket conectado fallo de responder a estos mensajes, est conexin rota y escritura de hilos a aquel socket son notificados con una seal de SIGPIPE. Este es una opcin Booleana. Para opciones Booleanas, 0 indica que la opcin es minusvlida y 1 indica que la opcin es permitida. Las opciones en otros niveles de protocolo varan en formato y nombre.

VALOR DE VUELTA:Sobre la finalizacin acertada, setsockopt() regresara 0. Por otra parte,-1 ser devuelto y errno set indicar el error. ERRORES: La funcin setsockopt ( ) fallar si: [EBADF] El argumento de socket no es un descriptor de archivo vlido. [EDOM] Enviar y recibe valores de intervalo de espera son demasiado grandes para caber en los campos de intervalo de espera en la estructura del socket. [EINVAL] La opcin especificada es el invlido en el nivel del socket especificado o el socket ha sido cerrado.

18

E.T.S INGENIERA INFORMATICA

[EISCONN] El socket est relacionado ya, y una opcin especificada no puede ser puesta mientras el socket est relacionado. [ENOPROTOOPT] La opcin no es apoyada por el protocolo. [ENOTSOCK] El argumento del socket no se refiere a un socket . El setsockopt ( ) funcin puede fallar si: [ENOMEM] Haba memoria insuficiente disponible para la operacin para completar. [ENOBUFS] Los recursos insuficientes estn disponibles en el sistema para completar la llamada.

USO DE APLICACIN:El setsockopt ( ) funcin provee un programa de uso de los medios de controlar el comportamiento del socket. Un programa de uso puede usar setsockopt ( ) para asignar buffer space, intervalos de espera de control, o emisiones de datos del socket de permiso. El header define las opciones de nivel del socket disponibles a setsockopt ( ). Las opciones pueden existir en niveles de protocolo mltiples. Las opciones SO_ estn siempre presentes en el nivel del socket ms alto.

Funcin Close ( ):Ests listo para cerrar la conexin de tu descriptor de socket. Solo hay normalmente la funcin close ( ) que cierra descriptores de ficheros : Ejemplo de la funcin close(): Close (sockfd); Esto impedir ms lecturas y escritures al socket, cualquiera que intente leer o escribir sobre el socket en el extremo recibir un error. que usar

19

E.T.S INGENIERA INFORMATICA

Shutdown ( ):En el caso que quieras un poco ms de control sobre cmo se cierra el socket puedes usar la funcin shutdown ( ). Te permite cortar la comunicacin en un cierto sentido, o en los dos ( tal y como lo hace close() . Sintaxis: int shutdown (int sockfd, int how) ; sockfd es el descriptor de socket que quieres desconectar, y how es uno de los siguiente valores : 0--- No se permite recibir ms datos . 1--- No se permite enviar ms datos . 2--- No se permite enviar ni recibir ms datos (lo mismo que close ()). Shutdown() devuelve 0 si tiene xito, y en caso de error (con errno establecido adecuadamente ). Si usas shutdown() en un socket de datagramas sin conexin, simplemente inhabilitar el socket para posteriores llamadas a send() y recv() .

Dos ejemplos del uso de sockets de tipo Stream, usando el protocolo TCP se ilustraran a continuacin. Inicialmente se detallara un cliente, el cual abrir la comunicacin por el puerto 3490. El programa recibir como parmetros la direccin IP, el puerto por donde se establecern las comunicaciones y un mensaje, el cual ser un cadena de texto que ser enviado al servidor. En el caso del servidor solo tendr como parmetro el puerto por donde escuchara. El mensaje deber llegar hasta la Terminal en donde se correr el programa de servidor. Observemos los ejempos:

/*Ejemplo de un Cliente TCP */#include #include #include #include #include #include /* la funcin MAIN recibir los siguientes parmetros:*/ /* IP destino, puerto , mensaje */ main(int argc, char *argv[]) { int s;

20

E.T.S INGENIERA INFORMATICA

struct sockaddr_in bs,des; char resp[255]; int *sd; if (argc == 4) { // Creamos el socket s = socket(AF_INET,SOCK_STREAM,0); if (s != -1) { bs.sin_family = AF_INET; bs.sin_port = htons(0); //Asigna un puerto disponible dela mquina bs.sin_addr.s_addr = htonl(INADDR_ANY);//Asigna IP de la mquina //Asigna un nombre local al socket if( bind(s,(struct sockaddr*)&bs, sizeof(bs)) != -1) { //Se prepara el nombre de la mquina remota des.sin_family = AF_INET; des.sin_addr.s_addr = inet_addr(argv[1]); des.sin_port = htons(atoi(argv[2])); //Establece la conexin con la mquina remota connect(s,(struct sockaddr*)&des,sizeof(des)); //Enva el mensaje send(s,argv[3],strlen(argv[3])+1,0); printf("\n\n->Enviando: %s, a: %s en el puerto: %s \n",argv[3], argv[1], argv[2]); //Recibe la respuesta recv(s,resp, sizeof(resp) ,0); printf("