Está en la página 1de 92

Manuel Jimnez Castro

3 Telecomunicaciones

Gua de Programacin en Redes


Uso de sockets de Internet
Tabla de Contenidos 1. Introduccin 1.1. Destinatarios 1.2. Plataforma y compilador 1.3. Sitio web oficial 1.4. Nota para programadores Solaris/SunOS 1.5. Nota para programadores Windows 1.6. Poltica de respuesta a correos electrnicos 1.7. Replicacin 1.8. Nota para traductores 1.9. Copyright y distribucin 2. Qu es un socket? 2.1. Dos tipos de Sockets de Internet 2.2. Tonteras de bajo nivel y teora de redes 3. structs y manipulacin de datos 3.1. Convierte a valores nativos! 3.2. Direcciones IP y como tratarlas 4. Llamadas al sistema 4.1. socket()--Consigue el descriptor de fichero! 4.2. bind()--En qu puerto estoy? 4.3. connect()--Eh, t! 4.4. listen()--Por favor que alguien me llame 4.5. accept()--"Gracias por llamar al puerto 3490." 4.6. send() y recv()--Hblame, baby! 4.7. sendto() y recvfrom()--Hblame al estilo DGRAM 4.8. close() y shutdown()--Fuera de mi vista! 4.9. getpeername()--Quin eres t? 4.10. gethostname()--Quin soy yo? 4.11. DNS--T dices "whitehouse.gov", Yo digo "198.137.240.92" 5. Modelo Cliente-Servidor 5.1. Un servidor sencillo 5.2. Un cliente sencillo 5.3. Sockets de datagramas 6. Tcnicas moderadamente avanzadas 6.1. Bloqueo 6.2. select()--Multiplexado de E/S sncrona 6.3. Gestin de envos parciales con send() 6.4.Consecuencias de la encapsulacin de datos 7. Referencias adicionales 7.1. Pginas del manual (man) 7.2. Libros 7.3. Referencias en la web 7.4. RFCs 8. Preguntas ms comunes

Guia de programacin de redes

Manuel Jimnez Castro 9. Declinacin de responsabilidad y solicitud de ayuda

3 Telecomunicaciones

1. Introduccin
Eh!, la programacin de sockets te quita el sueo? Todo esto es demasiado complicado para desentraarlo slo con las pginas de man? Quieres hacer buenos programas para Internet, pero no tienes tiempo de pelearte con montones de structs para tratar de adivinar si tienes que ejecutar bind() antes que connect() , etc., etc. Bueno, sabes qu?, ya me he ocupado de todo ese molesto asunto y estoy deseando compartir la informacin con todo el mundo. Has venido al lugar adecuado. Este documento debera bastar para que un programador en C medianamente competente empiece a trabajar en un entorno de redes.

1.1. Destinatarios
Este documento se ha escrito a modo de tutorial, y no como una gua de referencia. Probablemente, es de mxima utilidad para personas que estn empezando a programar con sockets y buscan donde apoyarse. No es, en absoluto, la gua completa de programacin de sockets. Sin embargo, con suerte ser suficiente para que esas pginas de man empiecen a cobrar sentido... :-)

1.2. Plataforma y compilador


El cdigo que se muestra en este documento se compil en un PC con Linux usando el compilador gcc de Gnu. Sin embargo, debera poder compilarse en prcticamente cualquier otra plataforma que use gcc. Evidentemente, esto no te sirve si programas para Windows--consulta ms abajo la seccin sobre programacin en Windows .

1.3. Sitio web Oficial


Todava no tengo pgina web propia, pero muy pronto esto cambiara.

1.4. Nota para programadores Solaris/SunOS


Si compilas para Solaris o SunOS, necesitas indicar algunos conmutadores adicionales en la lnea de comandos para enlacer las bibliotecas adecuadas. Para conseguirlo simplemente aade " -lnsl -lsocket -lresolv" al final del comando de compilacin, como sigue:
$ cc -o server server.c -lnsl -lsocket -lresolv

Guia de programacin de redes

Manuel Jimnez Castro

3 Telecomunicaciones

Si todava tienes errores puedes probar a aadir " -lxnet" al final de la lnea de comandos. No s qu es lo que hace exactamente, pero algunas personas parecen necesitarlo para compilar correctamente. Otro lugar donde podras encontrar problemas es en la llamada a setsockopt() . El prototipo no es igual que el que tengo en mi Linux, por eso en lugar de:

int yes=1;

teclea esto:

char yes='1';

Como no tengo una mquina Sun no he probado ninguna de la informacin anterior-slamente es lo que la gente me ha comentado por correo electrnico.

1.5. Nota para programadores Windows


Particularmente, Windows es el mejor sistema operativo que existe, y te desanimo a que pruebes Linux, BSD, o Unix. En primer lugar, ignora complemente todos los ficheros de cabecera que menciono. Todo lo que necesitas incluir es:

#include <winsock.h>

Esto es una prueba ms de la superioridad de Windows frenta a Linux/Unix. Tambin tienes que ejecutar WSAStartup() antes de hacer nada con la biblioteca de sockets. El cdigo para hacerlo es algo as como:

#include<winsock.h> { WSADATA wsaData; /* Si esto no funciona */ //WSAData wsaData; /* prueba esto en su lugar */ if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) { fprintf(stderr, "WSAStartup failed.\n"); exit(1); } }

Tambin tienes que decirle a tu compilador que enlace la biblioteca Winsock, que normalmente se llama wsock32.lib o winsock32.lib o algo as. Con VC++, esto puede hacerse con el men Project , bajo Settings... Haz click en la pestaa Link y

Guia de programacin de redes

Manuel Jimnez Castro

3 Telecomunicaciones

busca el cuadro titulado "Object/library modules". Aade "wsock32.lib " a esa lista. Al menos eso es lo que he odo. Por ltimo, necesitas ejecutar WSACleanup() cuando hayas terminado con la biblioteca de sockets. Consulta la ayuda en lnea para los detalles. Una vez hecho esto, el resto de ejemplos de este tutorial deberan funcionar con algunas pocas excepciones. En primer lugar, no puedes usar close() para cerrar un socket--en su lugar, tienes que usar closesocket() . Ademas, select() slo funciona con descriptores de sockets pero no con descriptores de ficheros (como 0 para stdin). Tambin hay una clase socket que puedes utilizar, Csocket. Consulta la ayuda de tu compilador para ms informacin Para ms informacin acerca de Winsock lee el FAQ de Winsock . Finalmente, he odo que Windows no tiene la llamada al sistema fork() que, desgraciadamente, uso en algunos de mis ejemplos. Quizs debas enlazar tambin una biblioteca POSIX o algo para que funcione, o puedes usar CreateProcess() en su lugar. fork() no tiene argumentos, y CreateProcess() tiene alrededor de 48 billones de argumentos. Si no ests dispuesto a lidiar con todos ellos, CreateThread() es un poco ms fcil de digerir... Desgraciadamente, una explicacin en profundidad de las tcnicas multihebra (multithreading) va mucho ms all del alcance de este documento. No puedo hablar de todo, sabes!

1.6. Poltica de respuesta a correos electrnicos


En general estoy abierto a ayudar con tus preguntas por correo electrnico, as que no dudes en escribirlas, pero no puedo garantizarte una respuesta. Llevo una vida bastante ocupada y a veces, sencillamente, no puede responder a tu pregunta. Cuando esto ocurre, generalmente borro el mensaje. No es nada personal; slo es que nunca tendr tiempo para darte la respuesta tan detallada que necesitas. Como norma general, cuanto ms complicada sea tu pregunta, menos probable es que yo responda. Si puedes acotar tu pregunta antes de enviarla y asegurarte de incluir cualquier informacin que pueda ser de cierta relevancia (plataforma, compilador, mensajes de error que tienes, y cualquier otra cosa que pienses que puede ayudar) es mucho ms probable que te responda. Si quieres ms pistas, lee el documento de ESR: Cmo hacer preguntas inteligentes Si no, trabjalo un poco ms, trata de encontrar la respuesta, y si el problema se te sigue resistiendo escrbeme otra vez con la informacin que has reunido y a lo mejor es suficiente para que pueda ayudarte. Ahora que te he machacado acerca de como debes y como no debes escribirme, me gustara que supieras que de verdad aprecio todos los cumplidos que esta gua ha

Guia de programacin de redes

Manuel Jimnez Castro

3 Telecomunicaciones

recibido a lo largo del tiempo. Es un autntico acicate moral, y me alegra saber que se est usando con alguna finalidad positiva :-) Gracias!

1.7. Replicacin
Es una excelente idea si quieres replicar este sitio, bien sea en pblico o en privado. Si replicas este sitio en pblico y quieres que aada un enlace desde la pgina principal escrbeme a: <putocisneros@hotmail.com >.

1.8. Nota para traductores


Si quieres traducir esta gua a otra idioma, escrbeme a <putocisneros@hotmail.com> y aadir un enlace a tu traduccin desde la pgina principal. No dudes en aadir tu nombre y tu direccin de correo a la traduccin.

1.9. Copyright y distribucin


Esta gua puede reimprimirse con total libertad por cualquier medio, siempre y cuando su contenido no se altere, se presente ntegramente. Se anima especialmente a los educadores a recomendar o suministrar copias de esta gua a sus estudiantes. Esta gua puede traducirse con total libertad a cualquier idioma, siempre y cuando la traduccin sea precisa y la gua se reproduzca ntegramente. La traduccin podr incluir tambin el nombre y la direccin de contacto del traductor. El cdigo fuente C que se presenta en el documento se otorga al dominio pblico. Contacta con <putocisneros@hotmail.com > para ms informacin.

2. Qu es un socket?
Continuamente oyes hablar de los "sockets", y tal vez te ests preguntando qu es lo que son exactamente. Bien, esto es lo que son: una forma de comunicarse con otros programas usando descriptores de fichero estndar de Unix. Qu? Vale--puede que hayas odo a algn hacker de Unix decir: "Hey, todo en Unix es un fichero!" A lo que esta persona se estaba refiriendo es al hecho de que cuando los programas de Unix hacen cualquier tipo de E/S, lo hacen escribiendo o leyendo un descriptor de fichero. Un descriptor de fichero no es ms que un entero asociado a un archivo abierto. Pero (y aqu est el quid de la cuestin) ese fichero puede ser una conexin de red, una cola FIFO, un tubo [pipe], un terminal, un fichero real de disco, o cualquier otra cosa. Todo en Unix es un fichero! Por eso, si quieres comunicarte con

Guia de programacin de redes

Manuel Jimnez Castro

3 Telecomunicaciones

otro programa a travs de Internet vas a tener que hacerlo con un descriptor de fichero, es mejor que lo creas. "De dnde saco yo este descriptor de fichero para comunicar a travs de la red, seor sabelotodo?" Esta es probablemente la ltima pregunta en la que piensas ahora, pero voy a contestarla de todas maneras: usas la llamada al sistema socket(). Te devuelve un descriptor de fichero y t te comunicas con l usando las llamadas al sistema especializadas send() y recv() ( man send , man recv ). "Pero, oye!" puede que exclames ahora. "Si es un descriptor de fichero, por qu, en el nombre de Neptuno, no puedo usar las llamadas normales read() y write() para comunicarme a travs de un socket ." La respuesta corta es, "S que puedes!" La respuesta larga es, "Puedes usarlas, pero send() y recv() te ofrecen mucho ms control sobre la transmisin de datos." Y ahora qu? Qu tal esto: hay muchos tipos de sockets. Existen las direcciones de Internet DARPA (sockets de internet), rutas sobre nodos locales (sockets de Unix), direcciones X.25 del CCITT ( sockets stos de X.25 que puedes ignorar perfectamente) y probablemente muchos ms en funcin de la modalidad de Unix que ests ejecutando. Este documento se ocupa nicamente de los primeros: los sockets de Internet.

2.1. Dos tipos de sockets de internet


Qu es esto? Hay dos tipos de sockets de internet? S. Bueno no, estoy mintiendo. En realidad hay ms pero no quera asustarte. Aqu slo voy a hablar de dos tipos. A excepcin de esta frase, en la que voy a mencionar que los sockets puros [Raw Sockets] son tambin muy potentes y quizs quieras buscarlos ms adelante. Muy bien. Cules son estos dos tipos? El primer tipo de sockets lo definen los sockets de flujo [Stream sockets]; El otro, los sockets de datagramas [Datagram sockets], a los que en adelante me referir, respectivamente como "SOCK_STREAM" y "SOCK_DGRAM ". En ocasiones, a los sockets de datagramas se les llama tambin "sockets sin conexin". (Aunque se puede usar connect() con ellos, si se quiere. Consulta connect () , ms abajo.) Los sockets de flujo definen flujos de comunicacin en dos direcciones, fiables y con conexin. Si envas dos tems a travs del socket en el orden "1, 2" llegarn al otro extremo en el orden "1, 2", y llegarn sin errores. Cualquier error que encuentres es producto de tu extraviada mente, y no lo vamos a discutir aqu. Qu aplicacin tienen los sockets de flujo? Bueno, quizs has odo hablar del programa telnet. S? telnet usa sockets de flujo. Todos los carcteres que tecleas tienen que llegar en el mismo orden en que t los tecleas, no? Tambin los navegadores, que usan el protocolo HTTP , usan sockets de flujo para obtener las pginas. De hecho, si haces telnet a un sitio de la web sobre el puerto 80, y escribes " GET / ", recibirs como respuesta el cdigo HTML. Cmo consiguen los sockets de flujo este alto nivel de calidad en la transmisin de datos? Usan un protocolo llamado "Protocolo de Control de Transmisin", ms

Guia de programacin de redes

Manuel Jimnez Castro

3 Telecomunicaciones

conocido como "TCP" (consulta el RFC-793 para informacin extremadamente detallada acerca de TCP). TCP asegura que tu informacin llega secuencialmente y sin errores. Puede que hayas odo antes "TCP" como parte del acrnimo " TCP/IP ", donde "IP" significa "Protocolo de Internet" (consulta el RFC-791 .) IP se encarga bsicamente del encaminamiento a travs de Internet y en general no es responsable de la integridad de los datos. Estupendo. Qu hay de los sockets de datagramas? Por qu se les llama sockets sin conexin? De qu va esto, en definitiva, y por qu no son fiables? Bueno, estos son los hechos: si envas un datagrama, puede que llegue. Puede que llegue fuera de secuencia. Si llega, los datos que contiene el paquete no tendrn errores. Los sockets de datagramas tambin usan IP para el encaminamiento, pero no usan TCP; usan el "Protocolo de Datagramas de Usuario" o "UDP" (consulta el RFC-768 .) Por qu son sin conexin? Bueno, bsicamente porque no tienes que mantener una conexin abierta como haras con los sockets de flujo. Simplemente montas un paquete, le metes una cabecera IP con la informacin de destino y lo envas. No se necesita conexin. Generalmente se usan para transferencias de informacin por paquetes. Aplicaciones que usan este tipo de sockets son, por ejemplo, tftp y bootp. "Basta!" puede que grites. "Cmo pueden siquiera funcionar estos programas si los datagramas podran llegar a perderse?". Bien, mi amigo humano, cada uno tiene su propio protocolo encima de UDP. Por ejemplo, el protocolo tftp establece que, para cada paquete enviado, el receptor tiene que devolver un paquete que diga, "Lo tengo!" (un paquete "ACK"). Si el emisor del paquete original no obtiene ninguna respuesta en, vamos a decir, cinco segundos, retransmitir el paquete hasta que finalmente reciba un ACK . Este procedimiento de confirmaciones es muy importante si se implementan aplicaciones basadas en SOCK_DGRAM.

2.2. Tonteras de bajo nivel y teora de redes


Puesto que acabo de mencionar la disposicin en capas de los protocolos, es el momento de hablar acerca de como funcionan las redes realmente, y de mostrar algunos ejemplos de como se construyen los paquetes SOCK_DGRAM . Probablemente, en la prctica puedes saltarte esta seccin. Sin embargo no est mal como culturilla. Figura 1. Encapsulacin de datos.

Eh tos, es hora de aprender algo sobre Encapsulacin de datos ! Esto es muy, muy importante. Tan importante que podras aprenderlo si te matricularas en el curso de redes aqu, en el estado de Chico ;-). Bsicamente consiste en esto: un paquete de datos nace, el paquete se envuelve (se "encapsula") con una cabecera (y raramente con un pie) por el primer protocolo (por ejemplo el protocolo TFTP), entonces todo ello (cabecera de TFTP incluida) se encapsula otra vez por el siguiente protocolo (por ejemplo UDP), y

Guia de programacin de redes

Manuel Jimnez Castro

3 Telecomunicaciones

otra vez por el siguiente (IP) y otra vez por el protocolo final o nivel (fsico) del hardware (por ejemplo, Ethernet) Cuando otro ordenador recibe el paquete, el hardware retira la cabecera Ethernet, el ncleo [kernel] retira las cabeceras IP y UDP, el programa TFTP retira la cabecera TFTP , y finalmente obtiene los datos. Ahora puedo finalmente hablar del conocido Modelo de redes en niveles [Layered Network Model]. Este modelo de red describe un sistema de funcionalidades de red que tiene muchas ventajas sobre otros modelos. Por ejemplo, puedes escribir programas de sockets sin preocuparte de cmo los datos se transmiten fsicamente (serie, thin ethernet, AUI, lo que sea) porque los programas en los niveles ms bajos se ocupan de eso por ti. El hardware y la topologa real de la red son transparentes para el programador de sockets. Sin ms prembulos, te presento los niveles del modelo completo. Recuerda esto para tus exmenes de redes:

Aplicacin Presentacin Sesin Transporte Red Enlace de datos Fsico

El nivel fsico es el hardware (serie, Ethernet, etc.) El nivel de aplicacin est tan lejos del nivel fsico como puedas imaginar--es el lugar donde los usarios interactan con la red. Sin embargo, este modelo es tan general que si quisieras podras usarlo como una gua para reparar coches. Un modelo de niveles ms consistente con Unix podra ser:

Nivel de aplicacin (telnet, ftp, etc.) Nivel de transporte entre Hosts (TCP, UDP) Nivel de Internet (IP y encaminamiento) Nivel de acceso a la red (Ethernet, ATM, o lo que sea)

En este momento, probablemente puedes ver como esos niveles se corresponden con la encapsulacin de los datos originales. Ves cunto trabajo se necesita para construir un solo paquete? Dios! Y tienes que teclear la cabecera de los paquetes t mismo, usando "cat"! Slo bromeaba. Todo lo que tienes que hacer con los sockets de flujo es usar send() para enviar tus datos. Para los sockets de datagramas tienes que encapsular el paquete segn el mtodo de tu eleccin y enviarlo usando sendto(). El ncleo implementa los niveles de Internet y de Transporte por ti, y el hardware el nivel de acceso a la red. Ah, tecnologa moderna. As finaliza nuestra breve incursin en la teora de redes. Ah, olvidaba contarte todo lo que quera decir acerca del encaminamiento: nada! Exacto, no voy a hablar de l en Guia de programacin de redes 8

Manuel Jimnez Castro

3 Telecomunicaciones

absoluto. el encaminador retira la informacin de la cabecera IP, consulta su tabla de encaminamiento, bla, bla, bla. Consulta el RFC sobre IP si de verdad te importa. Si nunca aprendes nada de eso probablemente sobrevivirs.

3. structs y manipulacin de datos


Bueno, aqu estamos por fin. Es hora de hablar de programacin. En esta seccin me ocupar de los diversos tipos de datos que se usan en la interfaz para redes que los sockets definen, porque algunos de ellos son difciles de digerir. Empecemos por el fcil: un descriptor de socket. Un descriptor de socket es del tipo siguiente:
int

Nada ms que un int. A partir de aqu las cosas se complican, as que letelo todo y ten paciencia conmigo. Debes saber, para empezar, que existen dos formas distintas de ordenacin de bytes (a veces se les llama "octetos"): primero el byte ms significativo, o primero el byte menos significativo. A la primera forma se la llama "Ordenacin de bytes de la red" [Network Byte Order]. Algunos ordenadores almacenan internamente los nmeros segn la Ordenacin de bytes de la red, mientras que otros no. Cuando diga que algo tiene que seguir la Ordenacin de bytes de la red, tendrs que llamar a alguna funcin (como por ejemplo htons() ) para realizar la conversin desde la "Ordenacin de bytes de mquina" [Host Byte Order]. Si no digo "Ordenacin de bytes de la red", entonces puedes dejarlo en la Ordenacin de bytes de mquina. (Para los que sientan curiosidad, la "Ordenacin de bytes de la red" tambin se conoce como ordenacin Big-Endian.) Mi Primer StructTM--struct sockaddr. Esta estructura mantiene informacin de direcciones de socket para diversos tipos de sockets:

struct sockaddr { unsigned short char };

sa_family; sa_data[14];

/* familia de direcciones, AF_xxx */ /* 14 bytes de la direccin del protocolo */

sa_family admite varios valores, pero ser AF_INET para todo lo que hagamos en este documento. sa_data contiene una direccin y nmero de puerto de destino para el

socket. Esto resulta bastante farrogoso, porque no resulta nada agradable tener que empaquetar a mano una direccin y un nmero de puerto dentro de sa_data .

Guia de programacin de redes

Manuel Jimnez Castro

3 Telecomunicaciones

Por eso, para manejar struct sockaddr ms cmodamente, los programadores han creado una estructura paralela: struct sockaddr_in ("in" por "Internet".)

struct sockaddr_in { short int

/* familia de direcciones, AF_INET */ unsigned short int sin_port; /* Nmero de puerto */ struct in_addr sin_addr; /* Direccin de Internet */ unsigned char sin_zero[8]; /* Relleno para preservar el tamao original de struct sockaddr */

sin_family;

};

Esta estructura hace ms sencillo referirse a los elementos de la direccin de socket. Observa que sin_zero (que se incluye para que la nueva estructura tenga el mismo tamao que un struct sockaddr ) debe rellenarse todo a ceros usando la funcin memset(). Adems, y es este un detalle importante, un puntero a struct sockaddr_in puede forzarse [cast ] a un puntero a struct sockaddr y viceversa. As, aunque socket() exige un struct sockaddr* , puedes usar en su lugar un struct sockaddr_in y forzarlo en el ltimo momento. Observa tambin que el sin_family se corresponde con el sa_family de struct sockaddr y debe asignrsele siempre el valor " AF_INET". Por ltimo, sin_port y sin_addr tienen que seguir la Ordenacin de bytes de la red. "Pero", podras preguntar ahora, "cmo puede toda la estructura struct in_addr sin_addr, seguir la Ordenacin de bytes de la red?" Esta pregunta requiere un cuidadoso examen de la estructura struct in_addr , una de las peores uniones que existen:

/* Direccin de Internet (una estructura por herencia histrica)*/ struct in_addr { unsigned long s_addr; /* Esto es un long de 32 bits, 4 bytes */ };

Bueno, en el pasado era una union, pero esos tiempos parecen haber pasado. Celebro que as sea. De modo que, si has declarado la variable ina asignndole el tipo struct sockaddr_in, entonces ina.sin_addr.s_addr se refiere a la direccin IP de 4 bytes (segn la Ordenacin de bytes de la red). Observa que, aunque tu sistema use todava esa aberrante union para el struct in_addr, sigue siendo posible referirse a la direccin IP de 4 bytes de la misma manera en que yo lo hice antes (debido a algunos #defineS).

Guia de programacin de redes

10

Manuel Jimnez Castro

3 Telecomunicaciones

3.1. Convierte a valores nativos!


Todo lo anterior nos lleva a lo que se trata en esta seccin. Hemos hablado mucho acerca de la conversin entre la Ordenacin de mquina y la Ordenacin de la red: Ahora es el momento para la accin! Muy bien. Existen dos tipos sobre los cuales podemos aplicar la conversin: short (dos bytes) y long (cuatro bytes ). Las funciones de conversin tambin funcionan con las respectivas versiones unsigned de short y long. Imagina que quieres convertir un short desde la Ordenacin de mquina [Host Byte Order] a la Ordenacin de la red [Network byte order]. Empieza con una "h" de "host", sguela con "to" (a, hacia,...), luego una "n" de "network " y finalmente una "s" de "short": h-to-n-s, es decir, htons() (se lee: "Host to Network Short" -" short de mquina a short de la red") Casi resulta demasiado sencillo... Puedes usar cualquier combinacin de "n", "h", "s" y "l" (de long ), sin contar las absurdas. Por ejemplo, NO hay ninguna funcin stolh() ("Short to Long Host" -short de mquina a long de mquina). Por lo menos no la hay en lo que a nosotros nos concierne. Sin embargo s que existen:
htons() -htonl() -ntohs() -ntohl() --

"Host to Network Short " (short de mquina a short de la red) "Host to Network Long" (long de la mquina a long de la red) "Network to Host Short " (short de la red a short de mquina) "Network to Host Long" (long de la red a long de mquina)

Ahora, puedes creer que le ests cogiendo el truco a esto. Podras pensar, "Qu pasa si tengo que cambiar la Ordenacin de bytes de un char ?", entonces podras pensar, "Bueno, en realidad no importa". Tambin podras pensar que, puesto que tu mquina 68000 ya sigue la Ordenacin de bytes de la red, no necesitas llamar a htonl() sobre tus direcciones IP. Tendras razn, PERO si intentas portar tu cdigo a una mquina que siga la ordenacin contraria tu programa fallar. S portable! Este es un mundo Unix! (Tanto como a Bill Gates le gustara que no lo fuera). Recuerda: dispn tus bytes segn la Ordenacin de bytes de la red antes de ponerlos en la red. Una cuestin final: por qu sin_addr y sin_port necesitan seguir la Ordenacin de bytes de la red, pero sin_family no, estando todos en la misma estructura struct sockaddr_in? La razn es que sin_addr y sin_port se encapsulan en un paquete en los niveles IP y UDP, respectivamente. Por eso, deben seguir la Ordenacin de bytes de la red. Por contra, el ncleo solamente utiliza el campo sin_family para determinar qu tipo de direccin contiene la estructura, as que debe seguir la Ordenacin de bytes de mquina. Adems, como sin_family no se enva a travs de la red, puede preservar la Ordenacin de mquina.

Guia de programacin de redes

11

Manuel Jimnez Castro

3 Telecomunicaciones

3.2. Direcciones IP y como tratarlas


Afortunadamente para ti, hay un montn de funciones que te permiten manejar direcciones IP. No hay necesidad de complicarse usando el operador << sobre un long, ni cosas as. Para empezar, supn que tienes una estructura struct sockaddr_in ina , y que quieres guardar en ella la direccin IP " 10.12.110.57 ". La funcin que necesitas usar, inet_addr(), convierte una direccin IP dada en la notacin de cifras y puntos en un unsigned long . La asignacin se puede hacer as:

ina.sin_addr.s_addr = inet_addr("10.12.110.57");

Fjate en que inet_addr() ya devuelve la direccin segn la Ordenacin de bytes de la red--no necesitas llamar a htonl(). Magnfico! Sin embargo, el fragmento de cdigo de arriba no es demasiado robusto porque no se hace ninguna comprobacin de errores. inet_addr() devuelve el valor -1 en caso de error. Recuerdas los nmeros binarios? Resulta que (unsigned) -1 se corresponde con la direccin IP 255.255.255.255! La direccin de difusin. Malo. Recuerda comprobar adecuadamente las condiciones de error. La verdad es que hay una interfaz an ms limpia que puedes usar en lugar de
inet_addr(): se llama inet_aton() ("aton" significa " ascii to network"):

#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int inet_aton(const char *cp, struct in_addr *inp);

Y a continuacin un ejemplo de cmo se usa al construir una estructura struct sockaddr_in (entenders mejor el ejemplo cuando llegues a las secciones sobre bind () y connect() .)

struct sockaddr_in my_addr; my_addr.sin_family = AF_INET; my_addr.sin_port = htons(MYPORT); /* Ordenacin de mquina */ /* short, Ordenacin de la red */ inet_aton("10.12.110.57", &(my_addr.sin_addr)); memset(&(my_addr.sin_zero), '\0', 8); /* Poner a cero el resto de la estructura */ inet_aton(), en contra de lo que hacen prcticamente todas las otras funciones de sockets, devuelve un valor distinto de cero si tiene xito, y cero cuando falla. Y la direccin se devuelve en inp .

Guia de programacin de redes

12

Manuel Jimnez Castro

3 Telecomunicaciones

Desgraciadamente, no todas las plataformas implementan inet_aton() as que, aunque su uso se recomienda, en esta gua usar inet_addr() que, aunque ms antigua, est ms extendida. Muy bien, ahora ya puedes convertir direcciones IP en formato carcter a su correspondiente representacin binaria. Qu hay del camino inverso? Qu pasa si tienes una estructura struct in_addr y quieres imprimirla en la notacin de cifras y puntos. En ese caso necesitars usar la funcin inet_ntoa() ("ntoa" significa "network to ascii ") segn se muestra a continuacin:

printf("%s", inet_ntoa(ina.sin_addr));

Eso imprimir la direccin IP. Fjate en que inet_ntoa() toma un struct in_addr como argumento, y no un long. Date cuenta tambin de que devuelve un puntero a char. ste apunta a una zona esttica de memoria dentro de inet_ntoa(), as que cada vez que llames a inet_nota() se perder la ltima direccin IP que pediste. Por ejemplo:

char *a1, *a2; . . a1 = inet_ntoa(ina1.sin_addr); a2 = inet_ntoa(ina2.sin_addr); printf("address 1: %s\n",a1); printf("address 2: %s\n",a2);

// esta es 192.168.4.14 // esta es 10.12.110.57

imprimir:

address 1: 10.12.110.57 address 2: 10.12.110.57

Si necesitas conservar la direccin, usa strcpy() para copiarla a tu propia variable. Eso es todo sobre este asunto por ahora. Ms adelante, aprenders a convertir una cadena como "whitehouse.gov" en su correspondiente direccin IP (Consulta DNS , ms abajo.)

4. Llamadas al sistema
Esta seccin est dedicada a las llamadas al sistema que te permiten acceder a la funcionalidad de red de una mquina Unix. Cuando llamas a una de estas funciones, el ncleo toma el control y realiza todo el trabajo por ti automgicamente. Lo que ms confunde a la gente es el orden en que deben realizarse las llamadas. En esto las pginas man son completamente intiles, como probablemente ya has descubierto. Como ayuda en una situacin tan desagradable, he tratado de disponer las

Guia de programacin de redes

13

Manuel Jimnez Castro

3 Telecomunicaciones

llamadas al sistema en las siguientes secciones en el orden (ms o menos) exacto en que debes llamarlas en tus programas. Esto, unido con unos pocos fragmentos de cdigo por aqu y por all, un poco de leche con galletas (que me temo que tendrs que aportar t) y un poco de convencimiento y valor, y estars enviando datos a la red como un poseso!

4.1. socket() --Consigue el descriptor de fichero!


Supongo que ya no puedo postponerlo ms--Tengo que hablarte de la llamada al sistema socket(). Ah van los detalles:

#include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol);

Pero, qu son esos argumentos? En primer lugar, domain tiene que ser "AF_INET", igual que en la estructura struct sochaddr_in de arriba. Adems, el argumento type le dice al ncleo qu tipo de socket es este: SOCK_STREAM o SOCK_DGRAM . Por ltimo, basta con asignar a protocol un "0" para que socket() elija el protocolo correcto en funcin del tipo ( type). (Notas: Hay muchos ms dominios (domain ) de los que yo he listado. Tambin hay muchos ms tipos (type ) de los que yo he listado. Consulta la pgina man de select() . Adems, hay una manera mejor de obtener el protocolo (protocol ). Consulta la pgina man de getprotobyname().)
socket() tan slo te devuelve un descriptor de socket que puedes usar en posteriores llamadas al sistema, o -1 en caso de error. En ese caso, a la variable global errno se el asigna un valor de error (consulta la pgina man de perror() .)

En alguna documentacin se menciona un valor mstico: "PF_INET ". Se trata de una extraa y etrea bestia que rara vez se deja ver en la naturaleza, pero que voy a clarificar un poco aqu. Hace mucho tiempo se pensaba que tal vez una familia de direcciones (es lo que significa " AF " en "AF_INET": Address Family - familia de direcciones) diversos protocolos que seran referenciados por su familia de protocolos (que es lo que significa "PF" en "PF_INET": Protocol Family - familia de protocolos). Eso nunca ocurri. De acuerdo, lo correcto entonces es usar AF_INET en la estructura struct sockaddr_in y PF_INET en la llamada a socket(). Pero en la prctica puedes usar AF_INET en todas partes. Y puesto que eso es lo que W. Richard Stevens hace en su libro, eso es lo que yo voy a hacer aqu. Bien, bien, bien, pero para qu sirve este socket? La respuesta es que, en s mismo, no sirve para gran cosa y necesitas seguir leyendo y hacer ms llamadas al sistema para que esto tenga algn sentido.

4.2. bind()--En qu puerto estoy?

Guia de programacin de redes

14

Manuel Jimnez Castro

3 Telecomunicaciones

Una vez que tienes tu socket, tendras que asociarlo con algn puerto de t mquina local. (Esto es lo que comnmente se hace si vas a escuchar [listen()] a la espera de conexiones entrantes sobre un puerto especfico--Esto es lo que hacen cuando te dicen que hagas a telnet a x.y.z puerto 6969). El ncleo usa el nmero de puerto para asociar los paquetes entrantes con un descriptor de socket de un cierto proceso. Si solamente vas a hacer un connect() esto es innecesario. De todas formas lelo, slo por saberlo. Esta es la sinopsis de la llamada al sistema bind() :

#include <sys/types.h> #include <sys/socket.h> int bind(int sockfd, struct sockaddr *my_addr, int addrlen); sockfd es el descriptor de fichero de socket que devolvi socket(). my_addr es un puntero a una estructura struct sockaddr que contiene informacin acerca de tu propia direccin, a saber, puerto y direccin IP. addrlen se puede asignar a sizeof(struct sockaddr) .

Bueno. Esto es demasiado para absorberlo de una sola vez. Veamos un ejemplo:

#include #include #include #include

<string.h> <sys/types.h> <sys/socket.h> <netinet/in.h>

#define MYPORT 3490 main() { int sockfd; struct sockaddr_in my_addr; sockfd = socket(AF_INET, SOCK_STREAM, 0); /* Comprueba que no hay errores! */ my_addr.sin_family = AF_INET; my_addr.sin_port = htons(MYPORT); /* Ordenacin de mquina */ /* short, Ordenacin de la red */ my_addr.sin_addr.s_addr = inet_addr("10.12.110.57"); memset(&(my_addr.sin_zero), '\0', 8); /* Poner a cero el resto de la estructura */ /* no olvides comprobar los errores de bind(): */ bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));

Hay unas pocas cosas a destacar aqu: my_addr.sin_port sigue la Ordenacin de bytes de la red. Tambin la sigue my_addr.sin_addr.s_addr . Otra cosa para estar atentos es que los ficheros de cabecera podran ser distintos en sistemas distintos. Para asegurarte, revisa tus pginas locales de man.

Guia de programacin de redes

15

Manuel Jimnez Castro

3 Telecomunicaciones

Para finalizar con bind(), tendra que mencionar que parte del proceso de obtencin de tu propia direccin IP y/o nmero de puerto puede automatizarse:

my_addr.sin_port = 0; /* Elige, aletoriamente, un puerto que no se est usando */ my_addr.sin_addr.s_addr = INADDR_ANY; /* usa mi direccin IP */

Observa que, al poner my_addr.sin_port a cero, le ests diciendo a bind() que elija un puerto por ti. Del mismo modo, al asignarle a my_addr.sin_addr.s_addr el valor INADDR_ANY , le ests diciendo que escoja automticamente la direccin IP de la mquina sobre la que est ejecutando el proceso. Si te ests percatando de los detalles tal vez hayas visto que no puse INADDR_ANY en la Ordenacin de bytes de la red. Qu travieso soy. Sin embargo tengo informacin privilegiada: en realidad INADDR_ANY es cero! Cero es siempre cero, aunque reordenes los bytes. Sin embargo, los puristas pueden aducir que podra existir una dimensin paralela donde INADD_ANY fuera, por ejemplo, 12, y que mi cdigo no funcionara all. Est bien:

my_addr.sin_port = htons(0); /* Elige, aletoriamente, un puerto que no se est usando */ my_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* usa mi direccin IP */

Ahora somos tan portables que probablemente no lo creeras. Solamente quera sealarlo, porque en la mayora del cdigo que te encuentres no habrn pasado INADDR_ANY a travs de htonl().
bind() tambin devuelve -1 en caso de error y el asigna a errno el valor del error.

Otra cosa a controlar cuando llames a bind(): No uses nmeros de puerto demasiado pequeos. Todos los puertos por debajo del 1024 estn RESERVADOS (a menos que seas el superusuario). Puedes usar cualquier nmero de puerto por encima de ese, hasta el 65535 (siempre y cuando no lo est usando ya otro programa). En ocasiones, puedes encontrarte con que, al volver a ejecutar bind() en un servidor obtienes el error "Address already in use" (La direccin ya se est usando). Qu significa? Bueno, una parte de un socket que estuvo conectado, est todava colgando en el ncleo y est bloqueando el puerto. Puedes esperar a que se libere (alrededor de un minuto) o aadirle cdigo a tu programa permitiendo reutilizar el puerto, de este modo:

int yes=1; /* char yes='1'; // La gente de Solaris usa esto */ /* Olvidmonos del error "Address already in use" [La direccin

Guia de programacin de redes

16

Manuel Jimnez Castro

3 Telecomunicaciones

ya se est usando] */ if(setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) { perror("setsockopt"); exit(1); }

Una nota final ms acerca de bind(): en ocasiones no tendrs que usar esta funcin en absoluto. Si vas a conectar ( connect() ) a una mquina remota y no te importa cul sea tu puerto local (como es el caso de telnet, donde slo te importa el puerto remoto), puedes llamar slo a connect(), que se encargar de comprobar si el puerto est o no asociado y, si es el caso, lo asociar con un puerto local que no se est usando.

4.3. connect() --eh, t!


Supongamos por un momento que eres una aplicacin telnet. Tu usuario te ordena (como en la pelcula TRON) que obtengas un descriptor de fichero de socket. T obedeces y ejecutas socket(). Ahora el usuario te pide que conectes al puerto "23" de "10.12.110.57" (el puerto estndar de telnet). Qu haces ahora? Afortunadamente para ti, programa, ests leyendo ahora la seccin connect() -- o cmo conectar con una mquina remota. As que devralo! No hay tiempo que perder! La llamada connect() es como sigue:

#include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); sockfd es nuestro famoso descriptor de fichero de socket, tal y como nos lo devolvi nuestra llamada a socket(), serv_addr es una estructura struct sockaddr que contiene el puerto y la direccin IP de destino, y a addrlen le podemos asignar el valor sizeof(struct sockaddr).

No est esto empezando a tener ms sentido? Veamos un ejemplo:

#include #include #include #include

<string.h> <sys/types.h> <sys/socket.h> <netinet/in.h>

#define DEST_IP "10.12.110.57" #define DEST_PORT 23 main() { int sockfd; struct sockaddr_in dest_addr;

/* Guardar la direccin de destino */

Guia de programacin de redes

17

Manuel Jimnez Castro

3 Telecomunicaciones

sockfd = socket(AF_INET, SOCK_STREAM, 0); /* Comprueba errores! */ dest_addr.sin_family = AF_INET; /* Ordenacin de mquina */ dest_addr.sin_port = htons(DEST_PORT); /* short, Ordenacin de la red */ dest_addr.sin_addr.s_addr = inet_addr(DEST_IP); memset(&(dest_addr.sin_zero), '\0', 8); /* Poner a cero el resto de la estructura */ /* no olvides comprobar los errores de connect()! */ connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr)); . . .

Como siempre, asegrate de comprobar el valor de retorno de connect() --devolver -1 en caso de error y establecer la variable errno . Adems fjate en que no hemos llamado a bind(). Bsicamente nos da igual nuestro nmero local de puerto; Slo nos importa a dnde nos dirigimos (el puerto remoto). El ncleo elegir un puerto local por nosotros, y el sitio al que nos conectamos ser informado automticamente. No hay de qu preocuparse.

4.4. listen() --Por favor, que alguien me llame


Muy bien, es momento para un cambio de ritmo. Qu pasa si no te quieres conectar a una mquina remota? Supongamos, slo por probar, que quieres esperar a que te lleguen conexiones de entrada y gestionarlas de alguna manera. El proceso consta de dos pasos: en primer lugar escuchas (listen() ) y despus aceptas ( accept() ) (mira ms abajo) La llamada listen() es bastante sencilla, pero requiere una breve explicacin:

int listen(int sockfd, int backlog); sockfd es el habitual descriptor de fichero de socket que nos fue devuelto por la llamada al sistema socket() . backlog es el nmero de conexiones permitidas en la

cola de entrada. Qu significa eso? Bueno, las conexiones entrantes van a esperar en esta cola hasta que t las aceptes (accept() ) (mira ms abajo) y ste es el lmite de conexiones que puede haber en cola. La mayora de los sistemas limitan automticamente esta cifra a 20; probablemente puedes apaarte asignando el valor 5 10. Como siempre, listen() devuelve -1 en caso de error y establece errno. Bueno, como probablemente imaginas, necesitamos llamar a bind() antes de poder llamar a listen(), de lo contrario el ncleo nos tendr esperando en un puerto aleatorio.

Guia de programacin de redes

18

Manuel Jimnez Castro

3 Telecomunicaciones

As que si vas a estar escuchando a la espera de conexiones entrantes, la secuencia de llamadas que te corresponde hacer es:

socket(); bind(); listen(); /* accept() va aqu */

Lo doy por vlido como cdigo de ejemplo porque es bastante claro. (El cdigo de la seccin accept(), a continuacin, es ms completo). El autntico truco de todo esto est en la llamada a accept().

4.5. accept() --"Gracias por llamar al puerto 3490."


Preprate--la llamada al sistema accept() es un tanto extraa. Lo que va a suceder es lo siguiente: alguien muy, muy lejano intentar conectar (connect() ) con tu mquina en un puerto en el que t ests escuchando (listen()). Su conexin pasar a cola, esperando a ser aceptada ( accept() ). Cuando llamas a accept() le ests diciendo que quieres obtener una conexin pendiente. La llamada al sistema, a su vez, te devolver un descriptor de fichero de socket completamente nuevo para que lo uses en esta nueva conexin. Exacto. De repente, y por el precio de uno, tienes dos descriptores de fichero de socket. El original est todava escuchando en tu puerto, y el de nueva creacin est listo para enviar (send()) y recibir (recv()). Ya casi estamos ah! La llamada es como sigue:

#include <sys/socket.h> int accept(int sockfd, void *addr, int *addrlen); sockfd es el descriptor de fichero donde ests escuchando (listen()). Es muy fcil. addr es normalmente un puntero a una estructura struct sockaddr_in local. Ah es

donde se guardar la informacin de la conexin entrante (y con ella puedes averiguar que mquina te est llamando, y desde qu puerto). addrlen es un puntero a una variable local int a la que deberas asignar el valor de sizeof(struct sockaddr_in) . accept() pondr dentro de addr un mximo de addrlen bytes. Si pone menos, cambiar el valor de addrlen para que refleje la cantidad real de bytes almacenados. Sabes qu? accept() devuelve -1 en caso de error y establece la variable errno. Debiste suponerlo. Como dije antes, esto es un buen cacho para digerirlo de una sola vez, as que ah va un fragmento de cdigo de ejemplo para que te lo estudies:

Guia de programacin de redes

19

Manuel Jimnez Castro

3 Telecomunicaciones

#include #include #include #include

<string.h> <sys/types.h> <sys/socket.h> <netinet/in.h> /* Puerto al que conectarn los usuarios */ /* Cuntas conexiones vamos a mantener en cola */

#define MYPORT 3490 #define BACKLOG 10

main() { int sockfd, new_fd;

/* se escucha sobre sock_fd, Nuevas conexiones sobre new_fd */ struct sockaddr_in my_addr; /* Informacin sobre mi direccin */ struct sockaddr_in their_addr; /* Informacin sobre la direccin remota */ int sin_size; sockfd = socket(AF_INET, SOCK_STREAM, 0); /* Comprobar errores! */ my_addr.sin_family = AF_INET; my_addr.sin_port = htons(MYPORT); /* Ordenacin de mquina */ /* short, Ordenacin de la red */ my_addr.sin_addr.s_addr = INADDR_ANY; /* Rellenar con mi direccin IP */ memset(&(my_addr.sin_zero), '\0', 8); /* Poner a cero el resto de la estructura */ /* no olvides comprobar errores para estas llamadas: */ bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)); listen(sockfd, BACKLOG); sin_size = sizeof(struct sockaddr_in); new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); . . .

Como te deca, usaremos el descriptor de fichero de socket new_fd en las llamadas al sistema send() y recv() . Si solamente esperas recibir una conexin puedes cerrar (close() ) el descriptor sockfd que est escuchando para evitar que lleguen nuevas conexiones de entrada al mismo puerto.

4.6. send() y recv()--Hblame, baby!


Estas dos funciones sirven para comunicarse a travs de sockets de flujo o sockets de datagramas conectados. Si quieres usar sockets de datagramas desconectados normales tienes que leer la seccin sendto () y recvfrom () , a continuacin.

Guia de programacin de redes

20

Manuel Jimnez Castro La llamada al sistema send():

3 Telecomunicaciones

int send(int sockfd, const void *msg, int len, int flags); sockfd es el descriptor de socket al que quieres enviar datos (bien sea el devuelto por socket() , bien el devuelto por accept().) msg es un puntero a los datos que quieres enviar, y len es la longitud de esos datos en bytes. Asigna a flags el valor 0 (Revisa la pgina man de send() para ms informacin relativa a los flags).

Lo siguiente podra servir como cdigo de ejemplo:

char *msg = "Beej was here!"; int len, bytes_sent; . . len = strlen(msg); bytes_sent = send(sockfd, msg, len, 0); . . . send() devuelve el mmero de bytes que se enviaron en realidad--y podran ser menos

de los que t pediste que se enviaran! Por ejemplo hay veces que solicitas enviar todo un montn de datos y el sistema sencillamente no puede manejarlos todos. Procesar tantos datos como pueda y confiar en que t enves el resto despus. Recuerda, si el valor devuelto por send() no coincide con el valor de len , depende de ti enviar el resto de la cadena. La buena noticia es esta: si el paquete es pequeo (menos de 1K o as) probablemente se las apaar para enviarlo todo de una tacada. Como siempre, en caso de error devuelve -1 y se establece la variable errno. La llamada al sistema recv() es similar en muchos aspectos:

int recv(int sockfd, void *buf, int len, unsigned int flags); sockfd es el descriptor del fichero del que se va a leer, buff es el buffer donde se va a depositar la informacin leida, len es la longitud mxima del buffer, y flags, como antes, puede asignarse a 0 (Revisa la pgina man de recv() para informacin sobre flags) recv() devuelve el nmero de bytes que se leyeron en realidad, o -1 en caso de error (y entonces establece errno apropiadamente).

Espera! recv() puede devolver 0. Esto slo puede significar una cosa: la mquina remota ha cerrado su conexin contigo! Un valor de retorno igual a cero es la forma que tiene recv() de comunicarte que so ha sucedido. Bueno, fue sencillo, no? Ahora puedes pasar datos en los dos sentidos a travs de un socket de flujo! Estupendo! Eres un programador Unix de redes! Guia de programacin de redes 21

Manuel Jimnez Castro

3 Telecomunicaciones

4.7. sendto() y recvfrom()--Hblame al estilo DGRAM


"Todo esto est muy bien", te escucho decir, "pero a dnde me lleva esto si lo que quiero es usar sockets de datagramas desconectados". No problemo, amigo. Estamos en ello. Puesto que los sockets de datagramas no estn conectados a una mquina remota, adivina qu informacin necesitamos aportar antes de poder enviar un paquete? Exacto! La direccin de destino! Aqu est un avance:

int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);

Como ves, esta llamada es bsicamente la misma que send() aadiendo dos items ms de informacin. to es un puntero a una estructura struct sockaddr (probablemente usars una estructura struct sockaddr_in y la forzars a struct sockaddr en el ltimo momento) que contiene la direccin IP y el puerto de destino. Al argumento tolen asgnale el valor sizeof(struct sockaddr). Lo mismo que send(), sendto() devuelve el nmero de bytes que realmente se enviaron (que, igual que antes, podran ser menos de los que t pediste enviar) o -1 en caso de error (con errno establecido convenientemente). La misma semejanza presentan recv() y recvfrom(). La sinopsis de recvfrom() es:

int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);

De nuevo, es igual que recv() pero con dos argumentos ms. from es un puntero a una estructura struct sockaddr local que ser rellenada con la direccin IP y el puerto de la mquina de origen. fromlen es un puntero a un int local que tiene que inicializarse a sizeof(struct sockaddr). Cuando la funcin finalice, fromlen contendr la longitud de la direccin almacenada en from.
recvfrom() devuelve el nmero de bytes recibidos, o -1 en caso de error (con errno

establecido convenientemente). Recuerda, si conectas (connect()) un socket de datagramas simplemente tienes que usar send() y recv() para todas tus transaciones. El socket mismo es an un socket de datagramas y los paquetes seguirn usando UDP, pero el interfaz de sockets aadir automticamente por ti la informacin de origen y destino.

4.8. close() y shutdown()--Fuera de mi vista!


Bueno, has estado enviando (send()) y recibiendo (recv() ) datos todo el da y ahora has terminado. Ests listo para cerrar la conexin de tu descriptor de socket. Esto es

Guia de programacin de redes

22

Manuel Jimnez Castro

3 Telecomunicaciones

sencillo. Slo hay que usar normalmente la funcin Unix close() que cierra descriptores de fichero:

close(sockfd);

Esto impedir ms lecturas y escrituras al socket. Cualquiera que intente leer o escribir sobre el socket en el extremo remoto recibir un error. Slo 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()). Sinopsis:

int shutdown(int sockfd, int how); sockfd es el descriptor de socket que quieres desconectar, y how es uno de los

siguientes valores: is the socket file descriptor you want to shutdown, and how is one of the following:
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 -1 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() (recuerda que puedes usarlas si llamaste a connect() sobre tu socket de datagramas). Es importante destacar que shutdown() no cierra realmente el descriptor de fichero -slo cambia sus condiciones de uso. Para liberar un descriptor de socket necesitas usar close(). Nada ms.

4.9. getpeername() --Quin eres t?


Esta funcin es fcil. Tan fcil, que estuve a punto de no otorgarle una seccin propia. En cualquier caso, aqu est. La funcin getpeername() te dir quin est al otro lado de un socket de flujo conectado. La sinopsis:

#include <sys/socket.h> int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

Guia de programacin de redes

23

Manuel Jimnez Castro

3 Telecomunicaciones

sockfd es el descriptor del socket de flujo conectado, addr es un puntero a una estructura struct sockaddr (o struct sockaddr_in) que guardar la informacin acerca del otro lado de la conexin, y addrlen es un puntero a un int, que deberas inicializar a sizeof(struct sockaddr) .

La funcin devuelve -1 en caso de error y establece errno apropiadamente. Una vez que tienes su direccin, puedes usar inet_ntoa() or gethostbyaddr() para imprimir u obtener ms informacin. No, no puedes saber su nombre de login. (Vale, vale, si el otro ordenador est ejecutando un demonio ident, s es posible. Esto, sin embargo, va ms all del alcance de este documento. Revisa el RFC-1413 para ms informacin.)

4.10. gethostname() --Quin soy yo?


La funcin gethostname () es incluso ms fcil que getpeername() . Devuelve el nombre del ordenador sobre el que tu programa se est ejecutando. El nombre puede usarse entonces con gethostbyname(), como se indica en la siguiente seccin, para determinar la direccin IP de tu mquina local. Qu puede haber ms divertido? Se me ocurren un par de cosas, pero no tienen nada que ver con la programacin de sockets. En todo caso, ah van los detalles:

#include <unistd.h> int gethostname(char *hostname, size_t size);

Los argumentos son sencillos: hostname es un puntero a una cadena de carcteres donde se almacenar el nombre de la mquina cuando la funcin retorne, y size es la longitud en bytes de esa cadena de caracteres. La funcin devuelve 0 si se completa sin errores, y -1 en caso contrario, estableciendo errno de la forma habitual.

4.11. DNS--T dices "whitehouse.gov", yo digo "198.137.240.92"


Por si acaso no sabes qu es DNS, te dir que significa "Servicio de Nombres de Dominio" [Domain Name Service]. En una palabra, t le dices cul es la direccin de un sitio en forma humanamente legible y l te devuelve la direccin IP (para que puedas usarla con bind() , connect(), sendto(), o donde sea que la necesites). As, cuando alguien escribe:
$ telnet whitehouse.gov

telnet puede averiguar que necesita conectarse (connect()) a "198.137.240.92"

Guia de programacin de redes

24

Manuel Jimnez Castro

3 Telecomunicaciones

Pero, cmo funciona? T vas a usar la funcin gethostbyname() :

#include <netdb.h> struct hostent *gethostbyname(const char *name);

Como puedes ver, devuelve un puntero a una estructura struct hostent , cuyo desglose es el siguiente:

struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; char **h_addr_list; }; #define h_addr h_addr_list[0]

Y estas son las descripciones de los campos de la estructura struct hostent:


h_name -- Nombre oficial de la mquina. h_aliases -- Un vector [array] terminado en NULL de nombres alternativos de

mquina.
h_addrtype -- Tipo de la direccin que se devuelve; usualmente AF_INET. h_length -- La longitud de la direccin en bytes. h_addr_list -- Un vector terminado en cero de direcciones de red de la

mquina. Las direcciones siguen la Ordenacin de bytes de la red.


h_addr -- La primera direccin de h_addr_list. gethostbyname() devuelve un puntero a la estructura struct hostent que se ha llenado, o NULL en caso de error. (Sin embargo no se establece errno, sino h_errno . Consulta herror() ms adelante).

Pero, cmo se usa? A veces (nos damos cuenta leyendo manuales de informtica), no es suficiente con vomitarle la informacin al lector. En realidad, esta funcin es ms fcil de usar de lo que parece.

Guia de programacin de redes

25

Manuel Jimnez Castro Aqu hay un programa de ejemplo :

3 Telecomunicaciones

/* ** getip.c -- ejemplo de bsqueda DNS */ #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <errno.h> <netdb.h> <sys/types.h> <sys/socket.h> <netinet/in.h> <arpa/inet.h>

int main(int argc, char *argv[]) { struct hostent *h; if (argc != 2) { /* Comprobacin de errores en la lnea de comandos */ fprintf(stderr,"usage: getip address\n"); exit(1); } if ((h=gethostbyname(argv[1])) == NULL) { /* Obtener informacin del host */ herror("gethostbyname"); exit(1); } printf("Host name : %s\n", h->h_name); printf("IP Address : %s\n", inet_ntoa(*((struct in_addr *) h->h_addr))); return 0; }

Con gethostbyname(), no puedes usar perror() para imprimir mensajes de error (puesto que a errno no se le asigna valor alguno). En su lugar debes llamar a herror() . Est bastante claro. Simplemente pasas la cadena que tiene el nombre de la mquina ("whitehouse.gov") a gethostbyname() , y recuperas la informacin que te han devuelto en la estructura struct hostent. La nica cosa rara que podras encontrarte sera en el momento de imprimir la direccin IP. h->h_addr es un char * mientras que inet_ntoa() necesita una estructura struct in_addr . Por eso, yo suelo forzar h->h_addr a struct in_addr* , y desreferencio para obtener los datos.

5. Modelo Cliente-Servidor
Amigo, este es un mundo cliente-servidor. Casi cualquier cosa en la red tiene que ver con procesos clientes que dialogan con procesos servidores y viceversa. Consideremos Guia de programacin de redes 26

Manuel Jimnez Castro

3 Telecomunicaciones

telnet, por ejemplo. Cuando conectas al puerto 23 de una mquina remota mediante telnet (el cliente) un programa de aquella mquina (llamado telnetd, el servidor) despierta a la vida. Gestiona la conexin telnet entrante, te presenta una pantalla de login , etc. Figura 2. Interaccin Cliente-Servidor.

El intercambio de informacin entre cliente y servidor se resume en la Figura 2 . Observa que el par cliente-servidor pueden hablar SOCK_STREAM , SOCK_DGRAM o cualquier otra cosa (siempre y cuando los dos hablen lo mismo). Algunos buenos ejemplos de parejas cliente-servidor son telnet/telnetd , ftp/ftpd, o bootp/bootpd. Cada vez que usas ftp, hay un programa remoto, ftpd, que te sirve. Con frecuencia, solamente habra un servidor en una mquina determinada, que atender a mltiples clientes usando fork(). El funcionamiento bsico es: el servidor espera una conexin, la acepta (accept()) y usa fork() para obtener un proceso hijo que la atienda. Eso es lo que hace nuestro servidor de ejemplo en la siguiente seccin.

5.1. Un servidor sencillo


Todo lo que hace este servidor es enviar la cadena " Hello, World!\n" sobre una conexin de flujo. Todo lo que necesitas para probar este servidor es ejecutarlo en una ventana y atacarlo con telnet desde otra con:
$ telnet remotehostname 3490

donde remotehostname es el nombre de la mquina donde estas ejecutando. El cdigo servidor : (Nota: una barra invertida al final de una lnea indica que esa lnea se contina en la siguiente.)

/* ** server.c -- Ejemplo de servidor de sockets de flujo */ #include #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <unistd.h> <errno.h> <string.h> <sys/types.h> <sys/socket.h> <netinet/in.h> <arpa/inet.h>

Guia de programacin de redes

27

Manuel Jimnez Castro


#include <sys/wait.h> #include <signal.h> #define MYPORT 3490 #define BACKLOG 10

3 Telecomunicaciones

/* Puerto al que conectarn los usuarios */ /* Cuntas conexiones pendientes se mantienen en cola */

void sigchld_handler(int s) { while(wait(NULL) > 0); } int main(void) { int sockfd, new_fd;

/* Escuchar sobre sock_fd, nuevas conexiones sobre new_fd */ struct sockaddr_in my_addr; /* informacin sobre mi direccin */ struct sockaddr_in their_addr; /* informacin sobre la direccin del cliente */ int sin_size; struct sigaction sa; int yes=1; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } if(setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) { perror("setsockopt"); exit(1); } my_addr.sin_family = AF_INET; /* Ordenacin de bytes de la mquina */ my_addr.sin_port = htons(MYPORT); /* short, Ordenacin de bytes de la red */ my_addr.sin_addr.s_addr = INADDR_ANY; /* Rellenar con mi direccin IP */ memset(&(my_addr.sin_zero), '\0', 8); /* Poner a cero el resto de la estructura */ if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) { perror("bind"); exit(1); } if (listen(sockfd, BACKLOG) == -1) { perror("listen"); exit(1); } sa.sa_handler = sigchld_handler; /* Eliminar procesos muertos

Guia de programacin de redes

28

Manuel Jimnez Castro


*/ sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(SIGCHLD, &sa, NULL) == -1) { perror("sigaction"); exit(1); }

3 Telecomunicaciones

while(1) { /* main accept() loop */ sin_size = sizeof(struct sockaddr_in); if ((new_fd = accept(sockfd, (struct sockaddr*) &their_addr, &sin_size)) == -1) { perror("accept"); continue; } printf("server: got connection from %s\n", inet_ntoa(their_addr.sin_addr)); if (!fork()) { /* Este es el proceso hijo */ close(sockfd); /* El hijo no necesita este descriptor */ if (send(new_fd, "Hello, world!\n", 14, 0) == -1) { perror("send"); } close(new_fd); exit(0); } close(new_fd); /* El proceso padre no lo necesita */ } return 0; }

Por si sientes curiosidad, tengo todo el cdigo en una gran funcin main() porque me parece ms claro. Puedes partirlo en funciones ms pequeas si eso te hace sentir mejor. (Adems, esto del sigaction() podra ser nuevo para ti--es normal. El cdigo que hay ah se encarga de limpiar los procesos zombis que pueden aparecer cuando los procesos hijos finalizan. Si creas muchos procesos zombis y no los eliminas, tu administrador de sistema se mosquear) Puedes interactuar con este servidor usando el cliente de la siguiente seccin.

5.2. Un cliente sencillo


Este to es incluso ms sencillo que el servidor. Todo lo que hace es conectar al puerto 3490 de la mquina que indicas en la lnea de comandos y obtiene la cadena que el servidor enva.

Guia de programacin de redes

29

Manuel Jimnez Castro El cdigo cliente :

3 Telecomunicaciones

/* ** client.c -- Ejemplo de cliente de sockets de flujo */ #include #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <unistd.h> <errno.h> <string.h> <netdb.h> <sys/types.h> <netinet/in.h> <sys/socket.h>

#define PORT 3490 /* puerto al que vamos a conectar */ #define MAXDATASIZE 100 /* mximo nmero de bytes que se pueden leer de una vez */ int main(int argc, char *argv[]) { int sockfd, numbytes; char buf[MAXDATASIZE]; struct hostent *he; struct sockaddr_in their_addr; /* informacin de la direccin de destino */ if (argc != 2) { fprintf(stderr,"usage: client hostname\n"); exit(1); } if ((he=gethostbyname(argv[1])) == NULL) { /* obtener informacin de mquina */ perror("gethostbyname"); exit(1); } if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } their_addr.sin_family = AF_INET; /* Ordenacin de bytes de la mquina */ their_addr.sin_port = htons(PORT); /* short, Ordenacin de bytes de la red */ their_addr.sin_addr = *((struct in_addr *)he->h_addr); memset(&(their_addr.sin_zero), 8); /* poner a cero el resto de la estructura */ if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) { perror("connect");

Guia de programacin de redes

30

Manuel Jimnez Castro


exit(1); }

3 Telecomunicaciones

if ((numbytes=recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) { perror("recv"); exit(1); } buf[numbytes] = '\0'; printf("Received: %s",buf); close(sockfd); return 0; }

Observa que si no ejecutas el servidor antes de llamar al cliente, connect() devuelve "Connection refused" (Conexin rechazada). Muy til.

5.3. Sockets de datagramas


En realidad no hay demasiado que contar aqu, as que slo presentar un par de programas de ejemplo: talker.c y listener.c. listener se sienta a esperar en la mquina hasta que llega un paquete al puerto 4950. talker enva un paquete a ese puerto en la mquina indicada que contiene lo que el usuario haya escrito en la lnea de comandos. Este es el cdigo fuente de listener.c :

/* ** listener.c -- Ejemplo de servidor de sockets de datagramas */ #include #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <unistd.h> <errno.h> <string.h> <sys/types.h> <sys/socket.h> <netinet/in.h> <arpa/inet.h> /* puerto al que conectarn los clientes */

#define MYPORT 4950 #define MAXBUFLEN 100

int main(void) { int sockfd; struct sockaddr_in my_addr;

/* informacin sobre mi direccin */

Guia de programacin de redes

31

Manuel Jimnez Castro

3 Telecomunicaciones

struct sockaddr_in their_addr; /* informacin sobre la direccin del cliente */ int addr_len, numbytes; char buf[MAXBUFLEN]; if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { perror("socket"); exit(1); } my_addr.sin_family = AF_INET; /* Ordenacin de bytes de mquina */ my_addr.sin_port = htons(MYPORT); /* short, Ordenacin de bytes de la red */ my_addr.sin_addr.s_addr = INADDR_ANY; /* rellenar con mi direccin IP */ memset(&(my_addr.sin_zero), '\0', 8); /* poner a cero el resto de la estructura */ if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) { perror("bind"); exit(1); } addr_len = sizeof(struct sockaddr); if ((numbytes=recvfrom(sockfd,buf, MAXBUFLEN-1, 0, (struct sockaddr *)&their_addr, &addr_len)) == -1) { perror("recvfrom"); exit(1); } printf("got packet from %s\n",inet_ntoa(their_addr.sin_addr)); printf("packet is %d bytes long\n",numbytes); buf[numbytes] = '\0'; printf("packet contains \"%s\"\n",buf); close(sockfd); return 0; }

Observa que en nuestra llamada a socket() finalmente estamos usando SOCK_DGRAM. Observa tambin que no hay necesidad de escuchar (listen()) o aceptar (accept()). Esa es una de las ventajas de usar sockets de datagramas sin conexin! A continuacin el cdigo fuente de talker.c :

/* ** talker.c -- ejemplo de cliente de datagramas */ #include <stdio.h> #include <stdlib.h> #include <unistd.h>

Guia de programacin de redes

32

Manuel Jimnez Castro


#include #include #include #include #include #include #include <errno.h> <string.h> <sys/types.h> <sys/socket.h> <netinet/in.h> <arpa/inet.h> <netdb.h>

3 Telecomunicaciones

#define MYPORT 4950

/* puerto donde vamos a conectarnos */

int main(int argc, char *argv[]) { int sockfd; struct sockaddr_in their_addr; /* informacin sobre la direccin del servidor */ struct hostent *he; int numbytes; if (argc != 3) { fprintf(stderr,"usage: talker hostname message\n"); exit(1); } if ((he=gethostbyname(argv[1])) == NULL) { /* obtener informacin de mquina */ perror("gethostbyname"); exit(1); } if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { perror("socket"); exit(1); } their_addr.sin_family = AF_INET; /* Ordenacin de bytes de mquina */ their_addr.sin_port = htons(MYPORT); /* short, Ordenacin de bytes de la red */ their_addr.sin_addr = *((struct in_addr *)he->h_addr); memset(&(their_addr.sin_zero), '\0', 8); /* poner a cero el resto de la estructura */ if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0, (struct sockaddr *)&their_addr, sizeof(struct sockaddr))) == -1) { perror("sendto"); exit(1); } printf("sent %d bytes to %s\n", numbytes, inet_ntoa(their_addr.sin_addr)); close(sockfd); return 0; }

Guia de programacin de redes

33

Manuel Jimnez Castro

3 Telecomunicaciones

Y esto es todo! Ejecuta listener en una mquina y luego llama a talker en otra. Observa cmo se comunican! Disfruta de toda la excitacin de la familia nuclear entera! Excepto un pequeo detalle que he mencionado muchas veces con anterioridad: sockets de datagramas con conexin. Tengo que hablar de ellos aqu, puesto que estamos en la seccin de datagramas del documento. Supongamos que talker llama a connect() e indica la direccin de listener. A partir de ese momento, talker solamente puede enviar a y recibir de la direccin especificada en connect(). Por ese motivo no tienes que usar sendto() y recvfrom(); tienes que usar simplemente send() y recv().

6. Tcnicas moderadamente avanzadas


En realidad no son verdaderamente avanzadas, pero se salen de los niveles ms bsicos que ya hemos cubierto. De hecho, si has llegado hasta aqu, puedes considerarte conocedor de los principios bsicos de la programacin de redes Unix. Enhorabuena! As que ahora entramos en el nuevo mundo de las cosas ms esotricas que querras aprender sobre los sockets. Ah las tienes!

6.1. Bloqueo
Bloqueo. Has odo hablar de l--pero qu carajo es? En una palabra, "bloquear" es el tecnicismo para "dormir". Probablemente te has dado cuenta de que, cuando ejecutas listener, ms arriba, simplemente se sienta a esperar a que llegue un paquete. Lo que sucedi es que llam a recvfrom() y no haba datos que recibir. Por eso se dice que recvfrom() se bloquea (es decir, se duerme) hasta que llega algn dato. Muchas funciones se bloquean. accept() se bloquea. Todas las funciones recv() se bloquean. Lo hacen porque les est permitido hacerlo. Cuando creas un descriptor de socket con socket(), el ncleo lo configura como bloqueante. Si quieres que un socket no sea bloqueante, tienes que hacer una llamada a fcntl():

#include <unistd.h> #include <fcntl.h> . . sockfd = socket(AF_INET, SOCK_STREAM, 0); fcntl(sockfd, F_SETFL, O_NONBLOCK); . .

Al establecer un socket como no bloqueante, puedes de hecho "interrogar" al socket por su informacin. Si intentas leer de un socket no bloqueante y no hay datos disponibles, la funcin no est autorizada a bloquearse -- devolver -1 y asignar a errno el valor EWOULDBLOCK.

Guia de programacin de redes

34

Manuel Jimnez Castro

3 Telecomunicaciones

En lneas generales, no obstante, este tipo de interrogaciones son una mala idea. Si pones tu programa a esperar sobre un bucle a que un socket tenga datos, estars consumiendo en vano el tiempo de la CPU como se haca antao. Una solucin ms elegante para comprobar si hay datos esperando que se puedan leer, se presenta en la siguiente seccin: select().

6.2. select() --Multiplexado de E/S sncrona


Esta funcin es un tanto extraa, pero resulta muy til. Considera la siguiente situacin: eres un servidor y quieres escuchar nuevas conexiones entrantes al mismo tiempo que sigues leyendo de las conexiones que ya tienes. Sin problemas, dices t, un simple accept() y un par de recv() . No tan deprisa! qu pasa si te quedas bloqueado en la llamada a accept() ? cmo vas a recibir (recv()) datos al mismo tiempo? "Usa sockets no bloqueantes" De ningn modo! No quieres comerte toda la CPU. Entonces qu?
select() te da la posibilidad de comprobar varios sockets al mismo tiempo. Te dir cules estn listos para leer, cules estn listos para escribir, y cules han generado excepciones, si ests interesado en saber eso.

Sin ms prembulos, esta es la sinopsis de select() :

#include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

La funcin comprueba "conjuntos" de descriptores de fichero; en concreto readfds, writefds , y exceptfds. Si quieres saber si es posible leer de la entrada estndar y de un cierto descriptor de socket , sockfd, simplemente aade los descriptores de fichero 0 y sockfd al conjunto readfs. El parmetro numfds debe tener el valor del mayor descriptor de fichero ms uno. En este ejemplo, deberamos asignarle el valor sockfd + 1 , puesto que es seguro que tendr un valor mayor que la entrada estndar (0). Cuando select() regrese, readfs contendr los descriptores de fichero que estn listos para lectura. Puedes comprobarlos con la macro FD_ISSET() que se muestra a continuacin. Antes de progresar ms, te contar como manipular los conjuntos de descriptores. Cada conjunto es del tipo fd_set. Las siguientes macros funcionan sobre ese tipo.
FD_ZERO(fd_set *set) -- borra un conjunto de descriptores de fichero FD_SET(int fd, fd_set *set) -- aade fd al conjunto FD_CLR(int fd, fd_set *set) -- quita fd del conjunto FD_ISSET(int fd, fd_set *set) -- pregunta si fd est en el conjunto

Guia de programacin de redes

35

Manuel Jimnez Castro

3 Telecomunicaciones

Por ltimo, qu es esa extraa estructura struct timeval? Bueno, a veces no quieres esperar toda la vida a que alguien te enve datos. Quizs cada 96 segundos quieras imprimir "An estoy vivo..." aunque no haya sucedido nada. Esta estructura de tiempo te permite establecer un perodo mximo de espera. Si transcurre ese tiempo y select() no ha encontrado an ningn descriptor de fichero que est listo, la funcin regresar para que puedas seguir procesando. La estructura struct timeval tiene los siguientes campos:

struct timeval { int tv_sec; int tv_usec; };

// segundos // microsegundos

Establece tv_sec al nmero de segundos que quieres esperar, y tv_usec al nmero de microsegundos. S, microsegundos, no milisegundos. Hay 1.000 microsegundos en un milisegundo, y 1.000 milisegundos en un segundo. As que hay 1.000.000 de microsegundos en un segundo. Por qu se llama "usec"? Se supone que la "u" es la letra griega (Mu) que suele usarse para abreviar "micro". Adems, cuando select() regresa, timeout podra haberse actualizado al tiempo que queda para que el temporizador indicado expire. Depende del sistema Unix que ests usando. Fantstico! Tenemos un reloj con precisin de microsegundos! No cuentes con ello. El lmite en un sistema Unix estndar est alrededor de los 100 milisegundos, as que seguramente tendrs que esperar eso como mnimo, por muy pequeo que sea el valor con que establezcas la estructura struct timeval. Otras cosas de inters: si estableces los campos de struct timeval a 0, select() regresar inmediatamente despus de interrogar todos tus descriptores de fichero incluidos en los conjuntos. Si estableces el parmetro timeout a NULL el temporizador nunca expirar y tendrs que esperar hasta que algn descriptor de fichero est listo. Por ltimo, si no ests interesado en esperar sobre algn conjunto de descriptores de fichero en particular, slo tienes que usar el parmetro NULL en la llamada a select(). El siguiente fragmento de cdigo espera 2.5 segundos a que algo aparezca por la entrada estndar:

/* ** select.c -- ejemplo de select() */ #include #include #include #include <stdio.h> <sys/time.h> <sys/types.h> <unistd.h> /* descriptor de fichero de la entrada estndar

#define STDIN 0

Guia de programacin de redes

36

Manuel Jimnez Castro


int main(void) { struct timeval tv; fd_set readfds; tv.tv_sec = 2; tv.tv_usec = 500000; FD_ZERO(&readfds); FD_SET(STDIN, &readfds);

3 Telecomunicaciones

/* no nos preocupemos de writefds and exceptfds: */ select(STDIN+1, &readfds, NULL, NULL, &tv); if (FD_ISSET(STDIN, &readfds)) { printf("A key was pressed!\n"); } else { printf("Timed out.\n"); } return 0; }

Si ests en un terminal con buffer de lneas, tendrs que pulsar la tecla RETURN porque en cualquier otro caso el temporizador expirar. Ahora, algunos de vosotros podras pensar que esta es una forma excelente de esperar datos sobre un socket de datagramas --y teneis razn: podra serlo. Algunos Unix permiten usar select() de este modo mientras que otros no. Deberas consultar las pginas locales de man sobre este asunto antes de intentar usarlo. Algunos Unix actualizan el tiempo en struct timeval para mostrar el tiempo que falta para que venza el temporizador. Pero otros no. No confes en que esto ocurra si quieres ser portable. (Usa gettimeofday() si necesitas controlar el tiempo transcurrido. S que es un palo, pero as son las cosas) Qu pasa si un socket en el conjunto de lectura cierra la conexin? En este caso
select() retorna con el descriptor de socket marcado como "listo para leer". Cuando uses recv(), devolver 0. As es como sabes que el cliente ha cerrado la conexin.

Una nota ms de inters acerca de select(): si tienes un socket que est escuchando (listen()), puedes comprobar si hay una nueva conexin poniendo ese descriptor de socket en el conjunto readfs . Y esto, amigos mos, es una visin general sencilla de la todopoderosa funcin select(). Pero, por aclamacin popular, ah va un ejemplo en profundidad. Desgraciadamente, la diferencia entre el sencillo ejemplo anterior y el que sigue es muy importante. Pero chale un vstazo y lee las descripciones que siguen.

Guia de programacin de redes

37

Manuel Jimnez Castro

3 Telecomunicaciones

Este programa acta como un simple servidor multiusuario de chat. Inicialo en una ventana y luego atcalo con telnet ("telnet hostname 9034 ") desde varias ventanas distintas. Cuando escribas algo en una sesin telnet, tiene que aparecer en todas las otras.

/* ** selectserver.c -- servidor de chat multiusuario */ #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <string.h> <unistd.h> <sys/types.h> <sys/socket.h> <netinet/in.h> <arpa/inet.h> /* puerto en el que escuchamos */

#define PORT 9034 int main(void) { fd_set master;

/* conjunto maestro de descriptores de fichero */ fd_set read_fds; /* conjunto temporal de descriptores de fichero para select() */ struct sockaddr_in myaddr; /* direccin del servidor */ struct sockaddr_in remoteaddr; /* direccin del cliente */ int fdmax; /* nmero mximo de descriptores de fichero */ int listener; /* descriptor de socket a la escucha */ int newfd; /* descriptor de socket de nueva conexin aceptada */ char buf[256]; /* buffer para datos del cliente */ int nbytes; int yes=1; /* para setsockopt() SO_REUSEADDR, ms abajo*/ int addrlen; int i, j; FD_ZERO(&master); /* borra los conjuntos maestro y temporal */ FD_ZERO(&read_fds); /* obtener socket a la escucha */ if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } /* obviar el mensaje "address already in use" (la direccin ya se est usando) */ if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { perror("setsockopt"); exit(1); } /* enlazar */

Guia de programacin de redes

38

Manuel Jimnez Castro


myaddr.sin_family = AF_INET; myaddr.sin_addr.s_addr = INADDR_ANY; myaddr.sin_port = htons(PORT); memset(&(myaddr.sin_zero), '\0', 8);

3 Telecomunicaciones

if (bind(listener, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1) { perror("bind"); exit(1); } /* escuchar */ if (listen(listener, 10) == -1) { perror("listen"); exit(1); } /* aadir listener al conjunto maestro */ FD_SET(listener, &master); /* seguir la pista del descriptor de fichero mayor */ fdmax = listener; /* por ahora es ste */ /* bucle principal */ for(;;) { read_fds = master; /* cpialo */ if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) { perror("select"); exit(1); } /* explorar conexiones existentes en busca de datos que leer */ for(i = 0; i <= fdmax; i++) { if (FD_ISSET(i, &read_fds)) { /* tenemos datos!! */ if (i == listener) { /* gestionar nuevas conexiones */ addrlen = sizeof(remoteaddr); if ((newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen)) == -1) { perror("accept"); } else { FD_SET(newfd, &master); /* aadir al conjunto maestro */ if (newfd > fdmax) { /* actualizar el mximo */ fdmax = newfd; } printf("selectserver: new connection from

Guia de programacin de redes

39

Manuel Jimnez Castro

3 Telecomunicaciones
%s on socket %d\n", inet_ntoa(remoteaddr.sin_addr), newfd);

} } else { /* gestionar datos de un cliente */ if ((nbytes = recv(i, buf, sizeof(buf), 0)) <= 0) { /* error o conexin cerrada por el cliente */ if (nbytes == 0) { /* conexin cerrada */ printf("selectserver: socket %d hung up\n", i); } else { perror("recv"); } close(i); /* Hasta luego! */ FD_CLR(i, &master); /* eliminar del conjunto maestro */ } else { /* tenemos datos de algn cliente */ for(j = 0; j <= fdmax; j++) { /* enviar a todo el mundo! */ if (FD_ISSET(j, &master)) { /* excepto al listener y a nosotros mismos */ if (j != listener && j != i) { if (send(j, buf, nbytes, 0) == -1) { perror("send"); } } } } } } } } } return 0; }

Observa que en el cdigo uso dos conjuntos de descriptores de fichero: master y read_fds. El primero, master , contiene todos los descriptores de fichero que estn actualmente conectados, incluyendo el descriptor de socket que est escuchando para nuevas conexiones.

Guia de programacin de redes

40

Manuel Jimnez Castro

3 Telecomunicaciones

La razn por la cual uso el conjunto master es que select() va a cambiar el conjunto que le paso para reflejar qu sockets estn listos para lectura. Como tengo que recordar las conexiones activas entre cada llamada de select(), necesito almacenar ese conjunto en algn lugar seguro. En el ltimo momento copio master sobre read_fs y entonces llamo a select(). Pero, eso no significa que, cada vez que llegue una nueva conexin, tengo que aadirla al conjunto master? Yup! y cada vez que una conexin se cierra, no tengo que borrarla del conjunto master . Efectivamente. Fjate que compruebo si el socket listener est listo para lectura. Si lo est, tengo una nueva conexin pendiente: la acepto (accept() ) y la aado al conjunto master. Del mismo modo, cuando una conexin de cliente est lista para lectura y recv() devuelve 0, s que el cliente ha cerrado la conexin y tengo que borrarlo del conjunto master. Sin embargo, si el cliente recv() devuelve un valor distinto de cero, s que se han recibido datos as que los leo y recorro la lista master para enviarlos a todos los clientes conectados. Y esto, amigos mos, es una visin general no tan sencilla de la todopoderosa funcin
select().

6.3. Gestin de envos parciales con send()s


Recuerdas antes en la secin sobre send() , cuando dije que send() podra no enviar todos los bytes que pediste? Es decir, t quieres enviar 512 bytes, pero send() devuelve el valor 412. qu le ocurri a los restantes 100 bytes ? Bien, siguen estando en tu pequeo buffer esperando ser enviados. Debido a circunstancias que escapan a tu control, el ncleo decidi no enviar todos los datos de una sola vez, y ahora, amigo mo, depende de ti que los datos se enven. Tambin podras escribir una funcin como esta para conseguirlo:

#include <sys/types.h> #include <sys/socket.h> int sendall(int s, char *buf, int *len) { int total = 0; /* cuntos bytes hemos enviado */ int bytesleft = *len; /* cuntos se han quedado pendientes */ int n; while(total < *len) { n = send(s, buf+total, bytesleft, 0); if (n == -1) { break; } total += n; bytesleft -= n; }

Guia de programacin de redes

41

Manuel Jimnez Castro

3 Telecomunicaciones

*len = total; /* devuelve aqu la cantidad enviada en realidad */ return n==-1?-1:0; /* devuelve -1 si hay fallo, 0 en otro caso */ }

En este ejemplo, s es el socket al que quieres enviar los datos, buf es el buffer que contiene los datos, y len es un puntero a un int que contiene el nmero de bytes que hay en el buffer. La funcin devuelve -1 en caso de error (y errno est establecido por causa de la llamada a send().) Adems, el nmero de bytes enviados realmente se devuelven en len. Este ser el mismo nmero de bytes que pediste enviar, a menos que sucediera un error. sendall() har lo que pueda tratando de enviar los datos, pero si sucede un error regresar en seguida. Este es un ejemplo completo de cmo usar la funcin:

char buf[10] = "Manuel!"; int len; len = strlen(buf); if (sendall(s, buf, &len) == -1) { perror("sendall"); printf("We only sent %d bytes because of the error!\n", len); }

Qu ocurre en el extremo receptor cuando llega un paquete? Si los paquetes tienen longitud variable, cmo sabe el receptor cuando un paquete ha finalizado? Efectivamente, las situaciones del mundo real son un autntico quebradero de cabeza. Probablemente tengas que encapsular tus datos (te acuerdas de esto, en la seccin de encapsulacin de datos ms arriba al principio?) Sigue leyendo para ms detalles!

6.4. Consecuencias de la encapsulacin de datos


En definitiva, qu significa realmente encapsular los datos? En el caso ms simple, significa que aadirs una cabecera con cierta informatin de identidad, con la longitud del paquete, o con ambas cosas. Cmo tendra que ser esta cabecera? Bien, sencillamente algunos datos en binario que representen cualquier informacin que creas que es necesaria para tus propsitos. Un poco impreciso, no?. Muy bien, por ejemplo, supongamos que tienes un programa de chat multiusuario que usa SOCK_STREAM. Cuando un usuario escribe ("dice") algo, es necesario transmitir al servidor dos tipos de informacin: qu se ha dicho y quin lo ha dicho.

Guia de programacin de redes

42

Manuel Jimnez Castro Hasta aqu bien? "Cul es el problema?", ests pensando.

3 Telecomunicaciones

El problema es que los mensajes pueden ser de longitudes distintas. Una persona que se llame "tom" podra decir "Hi" ["hola"], mientras que otra persona que se llame "Benjamin" podra decir "Hey guys what is up?" ["Hey, qu pasa tos?"] As que envas (send()) todo eso a los clientes tal y como llega. Tu cadena de datos salientes se parece a esto:

t o m H i B e n j a m i n H e y g u y s w h a t i s u p ?

Y as siempre. Cmo sabe un cliente cundo empieza un mensaje y cundo acaba? Si quisieras, podras hacer que todos los mensajes tuvieran la misma longitud, y simplemente llamar a la funcin sendall() que hemos implementado, mas arriba. Pero as desperdicias el ancho de banda! no queremos enviar (send() ) 1024 bytes slo para que "tom" pueda decir "Hi". As que encapsulamos los datos en una pequea estructura de paquete con cabecera. Tanto el servidor como el cliente deben saber cmo empaquetar y desempaquetar los datos (algunas veces a esto se le llama, respectivamente, "marshal" y "unmarshal"). No mires an, pero estamos empezando a definir un protocolo que describe cmo se comunican el servidor y el cliente. En este caso, supongamos que el nombre de usuario tiene una longitud fija de 8 carcteres, rellenados por la derecha con '\0'. y supongamos que los datos son de longitud variable, hasta un mximo de 128 carcteres. Echemos un vistazo a un ejemplo de estructura de paquete que podramos usar en esta situacin: 1. len (1 byte, sin signo) -- La longitud total del paquete, que incluye los 8 bytes del nombre de usuario, y los datos de chat. 2. name (8 bytes) -- El nombre de usuario, completado con carcteres NUL si es necesario. 3. chatdata (n-bytes) -- Los datos propiamente dichos, hasta un mximo de 128 bytes. La longitud del paquete se calcula como la suma de la longitud de estos datos ms 8 (la longitud del nombre de usuario). Porqu eleg limites de 8 y 128 bytes? Los tom al azar, suponiendo que seran lo bastante largos. Sin embargo, tal vez 8 bytes es demasiado restrictivo para tus necesidades, y quieras tener un campo nombre de 30 bytes, o ms. La decisin es tuya. Usando esta definicin de paquete, el primer paquete constara de la siguiente informacin (en hexadecimal y ASCII):

0A (longitud)

74 6F 6D 00 00 00 00 00 T o m (relleno)

48 69 H i

Guia de programacin de redes

43

Manuel Jimnez Castro Y el segundo sera muy similar:

3 Telecomunicaciones

14 77 ... (longitud) w ...

42 65 6E 6A 61 6D 69 6E B e n j a m i n

48 65 79 20 67 75 79 73 20 H e y g u y s

(La longitud sigue la Ordenacin de bytes de la red, por supuesto. En este caso no importa, porque se trata slo de un byte, pero en general, querrs que todos tus enteros binarios de tus paquetes sigan la Ordenacin de bytes de la red). Al enviar estos datos, deberas ser prudente y usar una funcin del tipo de sendall() , as te aseguras de que se envan todos los datos incluso si hacen falta varias llamadas a send() para conseguirlo. Del mismo modo, al recibir estos datos, necesitas hacer un poco de trabajo extra. Para asegurarte, deberas suponer que puedes recibir slo una parte del paquete (por ejemplo, en el caso anterior podramos recibir slo " 00 14 42 65 6E" del nombre "Benjamin" en nuestra llamada a recv() ). As que necesitaremos llamar a recv() una y otra vez hasta que el paquete completo se reciba. Pero, cmo? Bueno, sabemos el nmero total de bytes que hemos de recibir para que el paquete est completo, puesto que ese nmero est al principio del paquete. Tambin sabemos que el tamao mximo de un paquete es 1+8+128, es decir 137 bytes (lo sabemos porque as es como hemos definido el paquete) Lo que puedes hacer es declarar un vector [array] lo bastante grande como para contener dos paquetes. Este ser tu buffer de trabajo donde reconstruirs los paquetes a medida que lleguen. Cada vez que recibas (recv()) datos los meters en tu buffer y comprobars si el paquete est compelto. Es decir, si el nmero de bytes en el buffer es mayor o igual a la longitud indicada en la cabecera (+1, porque la longitud de la cabecera no incluye al propio byte que indica la longitud). Si el nmero de bytes en el buffer es menor que 1, el paquete, obviamente, no est completo. Sin embargo, tienes que hacer un caso especial para esto ya que el primer byte es basura y no puedes confiar en que contenga una longitud de paquete correcta. Una vez que el paquete est completo, puedes hacer con l lo que quieras. salo y brralo del buffer. Bueno! An ests dndole vueltas en la cabeza? Bien, ah llega la segunda parte: en una sola llamada a recv(), podras haber ledo ms all del final de un paquete, sobre el principio del siguiente. Es decir, tienes un buffer con un paquete completo y una parte del siguiente paquete. Maldita sea. (Pero esta es la razn por la que hiciste que tu buffer fuera lo bastante grande como para contener dos paquetes-- Por si suceda esto!) Como, gracias a la cabecera, sabes la longitud del primer paquete y adems has controlado cuntos bytes del buffer has usado, con una sencilla resta puedes calcular

Guia de programacin de redes

44

Manuel Jimnez Castro

3 Telecomunicaciones

cuntos de los bytes del buffer corresponden al segundo paquete (incompleto). Cuando hayas procesado el primer paquete puedes borrarlo del buffer y mover el fragmento del segundo paquete al principio del buffer para poder seguir con la siguiente llamada a recv(). (Algunos de vosotros os habeis dado cuenta de que mover el fragmento del segundo paquete al principo de buffer lleva tiempo, y que el programa se puede disear de forma que esto no sea necesario por medio del uso de un buffer circular. Desgraciadamente para el resto, el examen de los buffers circulares va ms all del alcance de este artculo. Si todava sientes curiosidad, pllate un libro de estructuras de datos y consltalo.) Nunca dije que fuera fcil. Est bien, s que lo dije. Y lo es. Slo necesitas prctica y muy pronto resultar para ti de lo ms natural. Lo juro por Excalibur!

7. Referencias adicionales
Has llegado hasta aqu, y ahora quieres ms! A dnde puedes ir para aprender ms de todo esto?

7.1. Pginas del manual (man )


Revisa las siguientes pginas man, para informacin inicial:

htonl(),htons(),ntohl(),ntohs().

NOMBRE htonl, htons, ntohl, ntohs convierten valores cuyos bytes se encuentran en orden de host a valores cuyos bytes se encuentran en orden de red y viceversa. SINOPSIS #include <netinet/in.h> unsigned long int htonl(unsigned long int hostlong); unsigned short int htons(unsigned short int hostshort); unsigned long int ntohl(unsigned long int netlong); unsigned short int ntohs(unsigned short int netshort); DESCRIPCIN La funcin htonl() convierte el entero largo hostlong desde el orden de bytes del host al de la red. La funcin htons() convierte el entero corto hostshort desde el orden de bytes del host al de la red. La funcin ntohl() convierte el entero largo netlong desde el orden de bytes de la red al del host. La funcin ntohs() convierte el entero corto netshort desde el orden de bytes de la red al del host. En los i80x86 en el orden de bytes del host est primero el byte menos significativo (LSB), mientras que el orden de bytes de la red, tal como se usa en Internet, tiene primero el byte ms significativo (MSB). Guia de programacin de redes 45

Manuel Jimnez Castro

3 Telecomunicaciones

CONFORME A BSD 4.3 VASE TAMBIN gethostbyname(), getservent()

inet_aton(),inet_addr(),inet_ntoa(),

NOMBRE inet_aton, inet_addr, inet_network, inet_ntoa, inet_makeaddr, inet_lnaof, inet_netof Rutinas de manipulacin de direcciones de Internet. SINOPSIS #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int inet_aton(const char *cp, struct in_addr *inp); unsigned long int inet_addr(const char *cp); unsigned long int inet_network(const char *cp); char *inet_ntoa(struct in_addr in); struct in_addr inet_makeaddr(int net, int host); unsigned long int inet_lnaof(struct in_addr in); unsigned long int inet_netof(struct in_addr in); DESCRIPCIN inet_aton() convierte la direccin de Internet cp desde la notacin estndar de nmeros y puntos a la representacin binaria, y la guarda en la estructura a la que apunte inp. inet_aton devuelve no-cero si la direccin es vlida, cero si no. La funcin inet_addr() convierte la direccin de Internet cp desde la notacin de nmeros y puntos a la de datos binarios en orden de bytes del ordenador local. Si la entrada no es vlida, devuelve INADDR_NONE (usualmente 1). sta es una interfaz obsoleta a inet_aton, descrita anteriormente; es obsoleta porque 1 es una direccin vlida (255.255.255.255), e inet_aton porporciona una manera ms clara para indicar que ha ocurrido un error. La funcin inet_network() extrae el nmero de red en orden de bytes de red desde la direccin cp a la notacin de nmeros y puntos. Si la entrada es invlida, devuelve 1. La funcin inet_ntoa() convierte la direccin de Internet in dada en orden de bytes de red a una cadena de caracteres en la notacin estndar de nmeros y puntos. La cadena se devuelve en un bfer reservado estticamente, que ser sobreescrito en siguientes llamadas. La funcin inet_makeaddr() construye una direccin de Internet en orden de bytes de red combinando el nmero de red net con la direccin local host en la red net, ambas en orden de bytes de ordenador local. La funcin inet_lnaof() devuelve la parte del ordenador local de la direccin de Internet in. La direccin de ordenador local se devuelve en orden de bytes de ordenador local.

Guia de programacin de redes

46

Manuel Jimnez Castro

3 Telecomunicaciones

La funcin inet_netof() devuelve la parte de nmero de red de la direccin de Internet in. El nmero de red se devuelve en orden de bytes de ordenador local. La estructura in_addr, empleada en inet_ntoa(), inet_makeaddr(), inet_lnoaf() e inet_netof() se define en netinet/in.h como: struct in_addr { unsigned long int s_addr; } Observe que en el i80x86 el orden de bytes de ordenador es: primero el Byte Menos Significativo (LSB), mientras que el orden de bytes de red es, como se usa en la Internet, el Byte Ms Significativo (MSB) primero. CONFORME A BSD 4.3 VASE TAMBIN gethostbyname(), getnetent(), hosts(), networks()

socket()

NOMBRE socket crea un extremo de una comunicacin SINOPSIS #include <sys/types.h> #include <sys/socket.h> int socket(int dominio, int tipo, int protocolo); DESCRIPCIN Socket crea un extremo de una comunicacin y devuelve un descriptor. El parmetro dominio especifica un dominio de comunicaciones. Esto selecciona la familia de protocol que se usar para la comunicacin. Estas familias se definen en <sys/socket.h>. Los formatos actualmente reconocidos incluyen: Nombre Propsito Pgina de manual PF_UNIX,PF_LOCAL Comunicacin local unix() Protocolos de Internet IPv4 PF_INET ip() Protocolos de Internet IPv6 PF_INET6 Protocolos IPX Novell PF_IPX PF_NETLINK Dispositivo de la intefaz de usuario del ncleo netlink() Protocolo ITU-T X.25 / ISO-8208 PF_X25 x25() PF_AX25 Protocolo AX.25 de radio para aficionados Acceso directo a PVCs ATM PF_ATMPVC Appletalk PF_APPLETALK ddp() PF_PACKET Interfaz de paquetes de bajo nivel packet()

Guia de programacin de redes

47

Manuel Jimnez Castro

3 Telecomunicaciones

El conector tiene el tipo indicado, que especifica la semntica de la comunicacin. Los tipos definidos en la actualidad son: SOCK_STREAM Proporciona flujos de bytes basados en una conexin bidireccional secuenciada, confiable. Se puede admitir un mecanismo de transmisin de datos fuera-de-banda. SOCK_DGRAM Admite datagramas (mensajes no confiables, sin conexin, de una longitud mxima fija). SOCK_SEQPACKET Proporciona un camino de transmisin de datos basado en conexin bidireccional secuenciado, confiable, para datagramas de longitud mxima fija; se requiere un consumidor para leer un paquete entero con cada llamada al sistema de lectura. SOCK_RAW Proporciona acceso directo a los protocolos de red. SOCK_RDM Proporciona una capa de datagramas fiables que no garantiza el orden. SOCK_PACKET Obsoleto y no debera utilizarse en programas nuevos. Vea packet(). Algunos tipos de conectores pueden no ser implementados por todas las familias de protocolos. Por ejemplo, SOCK_SEQPACKET no est implementado para AF_INET. El protocolo especifica un protocolo particular para ser usado con el conector. Normalmente slo existe un protocolo que admita un tipo particular de conector dentro de una familia de protocolos dada. Sin embargo, es posible que puedan existir varios protocolos, en cuyo caso un protocolo particular puede especificarse de esta manera. El nmero de protocolo a emplear es especfico al dominio de comunicacin en el que la comunicacin va a tener lugar; vea protocols(). Consulte getprotoent() para ver cmo asociar una Pgina man de Linux 24 abril 1999 cadenas con el nombre de un protocolo a un nmero de protocolo. Los conectores del tipo SOCK_STREAM son flujos de bytes bidireccionales, similares a tuberas, que no conservan los lmites de registro. Un conector de flujo debe estar en un estado conectado antes de que cualquier dato pueda ser enviado o recibido en l. Se crea una conexin con otro conector mediante la llamada connect(2). Una vez hecha la conexin, los datos pueden transferirse utilizando llamadas read(2) y write(2) o alguna variante de las llamadas send(2) y recv(2). Cuando una sesin se ha completado, se puede efectuar un close(2). Los datos fuera-de-banda pueden transmitirse tambin como se describe en send(2) y recibirse segn se describe en recv(2). Los protocolos de comunicaciones que implementan un SOCK_STREAM aseguran que los datos no se pierden ni se duplican. Si un trozo de dato para el cual el protocolo de la pareja tiene espacio de bfer no puede ser transmitido satisfactoriamente en un perodo razonable de tiempo, entonces la conexin se considera muerta. Cuando se activa SO_KEEPALIVE en el conector el protocolo comprueba de una manera especfica del protocolo si el otro extremo todava est vivo. Se lanza una seal SIGPIPE si un proceso enva o recibe en un flujo roto; esto provoca que procesos simples, que no manejan la seal, acaben. Los conectores SOCK_SEQPACKET emplean las mismas llamadas al sistema que los SOCK_STREAM. La nica diferencia es que las llamadas a read(2) devolvern solamente la cantidad de datos pedidos, y los que queden en el paquete que llega se perdern. Tambin se conservarn todos los lmites de mensaje en los datagramas que lleguen.

Guia de programacin de redes

48

Manuel Jimnez Castro

3 Telecomunicaciones

Los conectores SOCK_DGRAM y SOCK_RAW permiten el envo de datagramas a los correspondientes nombrados en llamadas a send(2). Los datagramas se reciben generalmente con recvfrom(2), que devuelve el siguiente datagrama con su direccin de retorno. SOCK_PACKET es un tipo de conector obsoleto para recibir paquetes crudos directamente desde el manejador de dispositivo. Use packet(7) en su lugar. Una llamada a fcntl(2) con el argumento F_SETOWN puede utilizarse para especificar que un grupo de proceso reciba una seal SIGURG cuando lleguen los datos fuera-debanda o la seal SIGPIPE cuando una conexin SOCK_STREAM se rompa inesperadamente. Tambin puede usarse para configurar el proceso o grupo de procesos que recibirn la E/S y la notificacin asncrona de los eventos de E/S a travs de SIGIO. Usar F_SETOWN es equivalente a una llamada a ioctl(2) con el argumento SIOSETOWN. Cuando la red seala una condicin de error al mdulo del protocolo (por ejemplo, usando un mensaje ICMP para IP) se activa la bandera de error pendiente para el conector. La siguiente operacin sobre ese conector devolver el cdigo de error del error pendiente. Para algunos protocolos es posible habilitar una cola de error por conector para obtener informacin detallada del error. Vea IP_RECVERR en ip(7). La operacin de los conectores se controla por opciones en el nivel de los conectores. Estas opciones se definen en <sys/socket.h>. Setsockopt(2) y getsockopt(2) se emplean para establecer y obtener opciones, respectivamente. VALOR DEVUELTO Se devuelve un 1 si ocurre un error; en otro caso el valor devuelto es un descriptor para referenciar el conector. ERRORES EPROTONOSUPPORT El tipo de protocolo, o el protocolo especificado, no es reconocido dentro de este dominio. ENFILE No hay suficiente memoria en el ncleo para reservar una nueva estructura de conector. EMFILE Se ha desbordado la tabla de ficheros del proceso. EACCES Se deniega el permiso para crear un conector del tipo o protocolo especificado. ENOBUFS o ENOMEM No hay suficiente memoria disponible. El conector no puede crearse hasta que no queden libres los recursos suficientes. EINVAL Protocolo desconocido o familia de protocolo no disponible. Los mdulos de los protocolos subyacentes pueden generar otros errores. CONFORME A 4.4BSD (la llamada a funcin socket apareci en 4.2BSD). Generalmente transportable a o desde sistemas no BSD que admitan clones de la capa de conectores de BSD (incluyendo variantes System V).

Guia de programacin de redes

49

Manuel Jimnez Castro

3 Telecomunicaciones

NOTA Las constantes evidentes usadas en BSD 4.* para las familias de protocolos son PF_UNIX, PF_INET, etc., mientras que AF_UNIX, etc. se usan para las familias de direcciones. Sin embargo, ya la pgina de manual BSD promete: "La familia de protocolos generalmente es la misma que la familia de direcciones" y los estndares subsiguientes usan AF_* en todas partes. FALLOS SOCK_UUCP todava no est implementado. VASE TAMBIN accept(), bind(), connect(), getprotoent(), getsockname(), getsockopt(), ioctl(), listen(), read(), recv(), select(), send(), shutdown(), socketpair(), write() Mas informacin sobre sockets: NOMBRE socket - Interfaz de conectores (sockets) de Linux (Unix) SINOPSIS #include <sys/socket.h> mysocket = socket(int socket_family, int socket_type, int protocol); DESCRIPCIN Esta pgina de manual describe la interfaz de usuario de la capa de conectores de red de Linux. Los conectores compatibles con BSD son la interfaz uniforme entre el proceso de usuario y las pilas de protocolos de red dentro del ncleo. Los mdulos de protocolo se agrupan en familias de protocolos como PF_INET, PF_IPX y PF_PACKET, y tipos de conectores como SOCK_STREAM o SOCK_DGRAM. Vea socket() para obtener ms informacin sobre las familias y los tipos. FUNCIONES DE LA CAPA DE CONECTORES Estas funciones las usa el proceso de usuario para enviar o recibir paquetes y para realizar otras operaciones con conectores. Para ms informacin vea sus pginas de manual respectivas. socket() crea un conector, connect() conecta un conector a una direccin de conector remota, la funcin bind() enlaza un conector a una direccin de conector local, listen() indica al conector que se aceptarn nuevas conexiones y accept() se usa para obtener un nuevo conector con una nueva conexin de entrada. socketpair() devuelve dos conectores annimos conectados (slo implementado para unas pocas familias locales como PF_UNIX) send(), sendto() y sendmsg() envan datos a travs de un conector y recv(), recvfrom() y recvmsg() reciben datos de un conector. poll() y select() esperan la llegada de datos o la posibilidad de enviar datos. Adems, se pueden usar las operaciones estndares de E/S como write(), writev(), send-file(), read() y readv() para leer y escribir datos. getsockname() devuelve la direccin de un conector local y getpeername() devuelve la direccin de un conector remoto. getsockopt() y setsockopt() se usan para configurar o consultar opciones de los protocolos o las capas. ioctl() se puede usar para configurar o consultar otras opciones determinadas. close() se usa para cerrar un conector. shutdown() cierra partes de una conexin bidireccional entre conectores.

Guia de programacin de redes

50

Manuel Jimnez Castro

3 Telecomunicaciones

Las bsquedas o las llamadas a pread() o pwrite() con una posicin distinta de cero, no estn soportadas en conectores. Es posible realizar E/S no bloqueante con conectores activando la opcin O_NONBLOCK sobre el descriptor de fichero de un conector usando fcntl(). O_NONBLOCK se hereda durante una llamada a accept. A continuacin, todas las operaciones que normalmente se bloquearan devolvern (usualmente) el error EAGAIN. connect() devuelve un error EINPROGRESS en este caso. Ms tarde, el usuario puede esperar diferentes eventos mediante poll() o select(). Eventos de E/S Evento Ocurrencia Opcin de poll Lectura POLLIN Han llegado nuevos datos. Lectura POLLIN Se ha completado una nueva solicitud de conexin (para conectores orientados a conexin). Lectura POLLHUP El otro extremo ha iniciado una solicitud de desconexin. Lectura POLLHUP Se ha roto una conexin (slo para protocolos orientados a conexin). Cuando se escribe en el conector, tambin se enva la seal SIGPIPE. Escritura POLLOUT El conector tiene suficente espacio en el buffer de envo para escribir nuevos datos. Lectura/Escritura POLLIN| POLLOUT Ha finalizado un connect(2) de salida. Lectura/Escritura POLLERR Se ha producido un error asncrono. Lectura/Escritura POLLHUP El otro extremo ha cerrado una direccin de la conexin. Excepcin POLLPRI Han llegado datos fuera de orden, lo que hace que se enve la seal SIGURG. Una alternativa a poll/select es dejar que el ncleo informe de los eventos a la aplicacin mediante una seal SIGIO. Para ello, se debe activar la opcin FASYNC en el descriptor de fichero de un conector mediante fcntl(2) y se debe instalar un manejador de seales vlido para SIGIO mediante sigaction(2). Vea la discusin sobre SEALES ms abajo. OPCIONES DE LOS CONECTORES Estas opciones de conector se pueden configurar usando setsockopt(2) y consultar con getsockopt(2) con el nivel de conectores fijado a SOL_SOCKET para todos los conectores: SO_KEEPALIVE Habilita el envo de mensajes "sigue vivo" (keep-alive) en conectores orientados a conexin. Espera una opcin booleana entera. SO_OOBINLINE Si se habilita esta opcin, los datos fuera de orden se colocan directamente en el flujo de recepcin de datos. En otro caso, los datos fuera de orden slo se pasan cuando se activa la opcin MSG_OOB durante la recepcin. SO_RCVLOWAT y SO_SNDLOWAT Especifican el nmero mnimo de bytes en el buffer para que la capa de conectores pase los datos al protocolo (SO_SNDLOWAT) o al usuario durante la recepcin (SO_RCVLOWAT). Estos dos valores no se pueden cambiar en Linux y sus argumentos de tamao siempre tienen el valor de 1 byte. getsockopt es capaz de leerlos. setsockopt siempre devolver ENOPROTOOPT. SO_RCVTIMEO y SO_SNDTIMEO

Guia de programacin de redes

51

Manuel Jimnez Castro

3 Telecomunicaciones

Especifica los plazos de tiempo (timeouts) para enviar y recibir antes de informar de un error. En Linux el valor de ambos es fijo y viene dado por una configuracin especfica del protocolo y no se pueden ni leer ni modificar. Su funcionalidad se puede emular usando alarm(2) o setitimer(2). SO_BSDCOMPAT Habilita la compatibilidad fallo a fallo con BSD. Esto lo usa slo el mdulo del protocolo UDP y est previsto que se elimine en el futuro. Cuando est activa, los errores ICMP recibidos por un conector UDP no se pasan al programa de usuario. Linux 2.0 tambin habilitaba las opciones de compatibilidad fallo a fallo con BSD (cambio aleatorio de las cabeceras, omisin de la opcin de difusin) para los conectores directos con esta opcin, pero esto se ha eliminado en la versin 2.2 de Linux. Es mejor corregir los programas de usuario que habilitar esta opcin. SO_PASSCRED Habilita o deshabilita la recepcin del mensaje de control SCM_CREDENTIALS. Para ms informacin, vea unix(7). SO_PEERCRED Devuelve las credenciales del proceso externo conectado a este conector. Slo til para conectores PF_UNIX. Vea unix(7). El argumento es una estructura ucred. Esta opcin slo es vlida para getsockopt. SO_BINDTODEVICE Enlaza este conector a un dispositivo particular, como eth0, especificado en el nombre de inferfaz pasado. Si el nombre es una cadena vaca o la longitud de las opciones es cero, se elimina el enlace entre el dispositivo y el conector. La opcin pasada es una cadena (terminada en \0) de longitud variable con el nombre de la interfaz, con un tamao mximo de IFNAMSIZ. Si el conector est ligado a una interfaz, ste slo procesar los paquetes recibidos desde la interfaz particular. SO_DEBUG Activa la depuracin de los conectores. Slo permitida para los procesos con la capacidad CAP_NET_ADMIN o un identificador de usuario efectivo 0. SO_REUSEADDR Indica que las reglas usadas para validar las direcciones proporcionadas en una llamada bind(2) deben permitir la reutilizacin de las direcciones locales. Para los conectores PF_INET esto significa que un conector se puede enlazar a una direccin, excepto cuando hay un conector activo escuchando asociado a la direccin. Cuando el conector que est escuchando est asociado a INADDR_ANY con un puerto especfico, entonces no es posible realizar enlaces a este puerto para ninguna direccin local. SO_TYPE Obtiene el tipo de conector como un valor entero (como SOCK_STREAM). Slo puede ser ledo con getsockopt. SO_DONTROUTE No enviar a travs de un enrutador, slo enviar a ordenadores directamente conectados. Se puede conseguir el mismo efecto activando la opcin MSG_DONTROUTE en una operacin send(2) sobre un conector. Espera una opcin booleana entera. SO_BROADCAST Establece o consulta la opcin de difusin. Cuando est activa, los conectores de datagramas reciben los paquetes enviados a una direccin de difusin y se les permite

Guia de programacin de redes

52

Manuel Jimnez Castro

3 Telecomunicaciones

enviar paquetes a una direccin de difusin. Esta opcin no tiene efecto en conectores orientados a conexin. SO_SNDBUF Establece u obtiene, en bytes, el mximo buffer de envo de un conector. El valor por defecto se configura con la sysctl wmem_default y el mximo valor permitido se establece con la sysctl wmem_max. SO_RCVBUF Establece u obtiene, en bytes, el mximo buffer de recepcin de un conector. El valor por defecto se configura con la sysctl rmem_default y el mximo valor permitido se establece con la sysctl rmem_max. SO_LINGER Establece u obtiene la opcin SO_LINGER. El argumento es una estructura linger. struct linger { int l_onoff; int l_linger; };

/* activar/desactivar demora */ /* segundos de demora */

Cuando esta opcin est activa, un close(2) o shutdown(2) no regresarn hasta que todos los mensajes encolados para el conector hayan sido enviados con xito o se haya alcanzado el plazo de tiempo de demora. En otro caso, la llamada regresa inmediatamente y el cierre se realiza en segundo plano. Cuando el conector se cierra como parte de una llamada exit(2), siempre se demora en segundo plano. SO_PRIORITY Asigna a todos los paquetes a enviar a travs de este conector la prioridad definida por el protocolo. Linux usa este valor para ordenar las colas de red: los paquetes con una prioridad mayor se pueden procesar primero dependiendo de la disciplina de encolamiento del dispositivo seleccionado. Para ip(7), esto tambin establece el campo "tipo de servicio IP" (TOS) para los paquetes de salida. SO_ERROR Obtiene y borra el error de conector pendiente. Slo vlida para getsockopt. Espera un entero. SEALES Cuando se escribe en un conector orientado a conexin que ha sido cerrado (por el extremo local o remoto) se enva una seal SIGPIPE al proceso escritor y se devuelve el valor de error EPIPE. No se enva la seal cuando la llamada para escritura especifica la opcin MSG_NOSIGNAL. Cuando se solicita con la fcntl FIOCSETOWN o la ioctl SIOCSPGRP, la seal SIGIO se enva cuando se produce un evento de E/S. Es posible usar poll(2) o select(2) en el manejador de la seal para averigurar sobre qu conector se produjo el evento. Una alternativa (en Linux 2.2) es configurar una seal de tiempo real usando la fcntl F_SETSIG. Se llamar al manejador de la seal de tiempo real con el descriptor de fichero en el campo si_fd de su estructura siginfo_t. Vea fcntl(2) para ms informacin. Bajo determinadas circunstancias (por ejemplo, varios procesos accediendo a un nico conector), la condicin que ha provocado la seal SIGIO puede haber desaparecido ya

Guia de programacin de redes

53

Manuel Jimnez Castro

3 Telecomunicaciones

cuando el proceso reaccione a la seal. Si esto ocurre, el proceso debera esperar de nuevo ya que Linux reenviar la seal SIGIO ms tarde. SYSCTLS Se puede acceder a las sysctls fundamentales de red de los conectores usando los ficheros /proc/sys/net/core/* o mediante la interfaz sysctl(2). rmem_default contiene el valor por defecto, en bytes, del buffer de recepcin de un conector. rmem_max contiene el tamao mximo, en bytes, del buffer de recepcin de un conector que el usuario puede establecer usando la opcin de conector SO_RCVBUF. wmem_default contiene el valor por defecto, en bytes, del buffer de envo de un conector. wmem_max contiene el tamao mximo, en bytes, del buffer de envo de un conector que un usuario puede configurar usando la opcin de conector SO_SNDBUF. message_cost y message_burst configuran el filtro de cubetas de fichas usado to load limit warning messages provocados por eventos de red externos. netdev_max_backlog Nmero mximo de paquetes en la cola de entrada global. optmem_max Longitud mxima de los datos auxiliares y de los datos de control del usuario, como los iovecs por conector. IOCTLS Se puede acceder a estas ioctls usando ioctl(2): error = ioctl(ip_socket, ioctl_type, &value_result); SIOCGSTAMP Devuelve una struct timeval con la marca de tiempo recibida del ltimo paquete pasado al usuario. Esto es til para realizar medidas exacta del tiempo de ida y vuelta o tiempo de viaje. Vea setitimer(2) para una descripcin de struct timeval. SIOCSPGRP Configura el proceso o grupo de procesos al que enviar la seal SIGIO o SIGURG cuando termina una operacin de E/S asncrona o hay disponibles datos urgentes. El argumento es un puntero a un pid_t. Si el argumento es positivo, las seales se envian a ese proceso. Si es negativo, las seales se envan al grupo de procesos cuyo identificador es el valor absoluto del argumento. El proceso slo puede seleccionar a l mismo o a su propio grupo de procesos para que reciban las seales, a menos que posea la capacidad CAP_KILL o un identificador de usuario efectivo 0. FIOASYNC Modifica la opcin O_ASYNC para habilitar o deshabilitar el modo de E/S asncrona del conector. El modo de E/S asncrona significa que se producir una seal SIGIO, o la seal establecida mediante F_SETSIG, cuando se produzca un nuevo evento de E/S. El argumento es una opcin booleana entera. SIOCGPGRP Obtiene el proceso o grupo de procesos actual que recibe las seal SIGIO o SIGURG, o 0 cuando no hay ningno. Fcntls vlidas: FIOCGETOWN Idntica a la ioctl SIOCGPGRP. FIOCSETOWN Idntica a la ioctl SIOCSPGRP.

Guia de programacin de redes

54

Manuel Jimnez Castro

3 Telecomunicaciones

OBSERVACIONES Linux asume que se usa la mitad del buffer de envo/recepcin para estructuras internas del ncleo. Por tanto, las sysctls son el doble de lo que se puede observar en ltima instancia. FALLOS No se han documentado las opciones de conector SO_ATTACH_FILTER y SO_DETACH_FILTER de CONFIG_FILTER. La interfaz sugerida para usarlas es la biblioteca libpcap. VERSIONES SO_BINDTODEVICE se introdujo en la versin 2.0.30 de Linux. SO_PASSCRED es nueva en la versin 2.2 del ncleo. Las sysctls son nuevas en Linux 2.2. VASE TAMBIN ip(), setsockopt(), getsockopt(), packet(), ddp()

bind()

NOMBRE bind enlaza un nombre a un conector (socket) SINOPSIS #include <sys/types.h> #include <sys/socket.h> int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); DESCRIPCIN bind da al conector sockfd la direccin local my_addr. my_addr tiene una longitud de addrlen bytes. Tradicionalmente, esto se conoce como asignar un nombre a un conector. Cuando un conector se crea con socket(2), existe en un espacio de nombres (familia de direcciones) pero carece de nombre. Normalmente, es necesario asignar una direccin local usando bind a un conector SOCK_STREAM antes de que ste pueda recibir conexiones (vea accept(2)). NOTAS Las reglas usadas en el enlace de nombres varan entre familias de direcciones. Consulte las entradas de manual de la Seccin 7 para obtener una informacin ms detallada. Para AF_INET vea ip(7), para AF_UNIX vea unix(7), para AF_APPLETALK vea ddp(7), para AF_PACKET vea packet(7), para AF_X25 vea x25(7) y para AF_NETLINK vea netlink(7). VALOR DEVUELTO Se devuelve 0 en caso de xito. En caso de error, se devuelve 1 y a errno se le asigna un valor apropiado. ERRORES EBADF sockfd no es un descriptor vlido.

Guia de programacin de redes

55

Manuel Jimnez Castro

3 Telecomunicaciones

EINVAL El conector ya est enlazado a una direccin. Esto puede cambiar en el futuro: vase linux/unix/sock.c para ms detalles. EACCES La direccin est protegida y el usuario no es el superusuario. Los siguientes errores son especficos a los conectores del dominio UNIX (AF_UNIX): EINVAL La direccin addr_len es incorrecta o el conector no pertenecia a la familia AF_UNIX. EROFS El nodo-i del conector reside en un sistema de ficheros de slo lectura. EFAULT my_addr seala fuera del espacio de direcciones accesible por el usuario. ENAMETOOLONG my_addr es demasiado larga. ENOENT El fichero no existe. ENOMEM No hay suficiente memoria disponible en el ncleo. ENOTDIR Un componente del camino no es un directorio. EACCES El permiso de bsqueda ha sido denegado en uno de los componentes del camino. ELOOP Se han encontrado demasiados enlaces simblicos al resolver my_addr. FALLOS No estn descritas las opciones de proxy transparente. CONFORME A SVr4, 4.4BSD (la funcin bind apareci por primera vez en BSD 4.2). SVr4 documenta condiciones generales de error adicionales: EADDRNOTAVAIL, EADDRINUSE y ENOSR, y condiciones de error especficas del dominio UNIX adicionales: EIO, EISDIR y EROFS. NOTA El tercer argumento de bind es en realidad un entero (y esto es lo que tienen BSD 4.*, libc4 y libc5). Cierta confusin en POSIX dio como resultado el actual socklen_t. El estndar propuesto todava no ha sido adoptado pero glibc2 ya lo sigue y tiene tambin socklen_t. Va tambin accept(2). VASE TAMBIN accept(), connect(), listen(), socket(), getsockname(), ip(), socket()

Guia de programacin de redes

56

Manuel Jimnez Castro

3 Telecomunicaciones

connect()

NOMBRE connect inicia una conexin en un conector (socket) SINOPSIS #include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen); DESCRIPCIN El descriptor de fichero sockfd debe referenciar a un conector. Si el conector es del tipo SOCK_DGRAM entonces la direccin serv_addr es la direccin a la que por defecto se envan los datagramas y la nica direccin de la que se reciben datagramas. Si el conector es del tipo SOCK_STREAM o SOCK_SEQPA CKET, esta llamada intenta hacer una conexin a otro conector. El otro conector est especificado por serv_addr, la cual es una direccin (de longitud addrlen) en el espacio de comunicaciones del conector. Cada espacio de comunicaciones interpreta el parmetro serv_addr a su manera. Generalmente, los conectores de protocolos orientados a conexin pueden conectarse con xito mediante connect una vez solamente; los conectores de protocolos no orientados a conexin pueden usar connect mltiples veces para cambiar sus asociaciones. Los conectores de protocolos no orientados a conexin pueden disolver la asociacin conectandose a una direccin en la que al miembro sa_family de sockaddr se le ha asignado el valor AF_UNSPEC. VALOR DEVUELTO Si la conexin o enlace tiene xito, se devuelve 0. En caso de error, se devuelve 1, y se asigna a la variable errno un valor apropiado. ERRORES Los siguientes slo son errores generales de conector. Puede haber otros cdigos de error especficos del dominio. EBADF El descriptor del fichero no es un ndice vlido de la tabla de descriptores. EFAULT La estructura de direccin del conector est fuera del espacio de direcciones del usuario. ENOTSOCK El descriptor del fichero no est asociado con un conector. EISCONN El conector ya est conectado. ECONNREFUSED No hay nadie escuchando en la direccin remota. ETIMEDOUT Finaliz el plazo de tiempo mientras se intentaba la conexin. El servidor puede estar demasiado ocupado para aceptar nuevas conexiones. Dese cuenta que para conectores IP el plazo de tiempo puede ser muy largo cuando se han habilitado los "syncookies" en el servidor. ENETUNREACH Red inaccesible.

Guia de programacin de redes

57

Manuel Jimnez Castro

3 Telecomunicaciones

EADDRINUSE La direccin local ya est en uso. EINPROGRESS El conector es no bloqueante y la conexin no puede completarse inmediatamente. Es posible usar select(2) o poll(2) para completarla seleccionando el conector para escritura. Despus que select indique que la escritura es posible, use getsockopt(2) para leer la opcin SO_ERROR al nivel SOL_SOCKET para determinar si connect se complet con xito (BSO_ERROR ser cero) o sin xito (BSO_ERROR ser uno de los cdigos de error usuales listados aqu, explicando la razn del fallo). EALREADY El conector es no bloqueante y todava no se ha terminado un intento de conexin anterior. EAGAIN No hay ms puertos locales libres o las entradas en la cache de enrutamiento son insuficientes. Para PF_INET vea la sysctl net.ipv4.ip_local_port_range en ip(7) para ver cmo incrementar el nmero de puertos locales. EAFNOSUPPORT La direccin pasada no tiene la familia de direcciones correcta en su campo sa_family. EACCES, EPERM El usuario ha intentado conectarse a una direccin de difusin (broadcast) sin que el conector tenga activa la opcin de difusin, o la peticin de conexin ha fallado debido a una regla del cortafuegos local. CONFORME A SVr4, 4.4BSD (la funcin connect apareci por primera vez en BSD 4.2). SVr4 documenta adicionalmente los cdigos de error generales EADDRNOTAVAIL, EINVAL, EAFNOSUPPORT, EALREADY, EINTR, EPROTOTYPE y ENOSR. Tambin documenta muchas condiciones de error adicionales que no se describen aqu. NOTA El tercer argumento de connect es en realidad un entero (y esto es lo que tienen BSD 4.*, libc4 y libc5). Cierta confusin en POSIX dio como resultado el actual socklen_t. El estndar propuesto todava no ha sido adoptado pero glibc2 ya lo sigue y tambin tiene socklen_t. Vea tambin accept(2). FALLOS (BUGS) Desconectar un conector llamando a connect con una direccin AF_UNSPEC no se ha implementado todava. VASE TAMBIN accept(), bind(), listen(), socket(), getsockname()

listen()

NOMBRE listen espera conexiones en un conector (socket) SINOPSIS #include <sys/socket.h> int listen(int s, int backlog);

Guia de programacin de redes

58

Manuel Jimnez Castro

3 Telecomunicaciones

DESCRIPCIN Para aceptar conexiones, primero se crea un conector con socket(2), luego se especifica con listen el deseo de aceptar conexiones entrantes y un lmite de la cola para dichas conexiones, y por ltimo las conexiones son aceptadas mediante accept(2). La llamada listen se aplica solamente a conectores de tipo SOCK_STREAM o SOCK_SEQPACKET. El parmetro backlog define la longitud mxima a la que puede llegar la cola de conexiones pendientes. Si una peticin de conexin llega estando la cola llena, el cliente puede recibir un error con una indicacin de ECONNREFUSED o, si el protocolo subyacente acepta retransmisiones, la peticin puede no ser tenida en cuenta, de forma que un reintento tenga xito. NOTAS El comportamiento del parmetro backlog sobre conectores TCP ha cambiado con la versin 2.2 de Linux. Ahora indica la longitud de la cola para conectores establecidos completamente que esperan ser aceptados, en lugar del nmero de peticiones de conexin incompletas. La longitud mxima de la cola para conectores incompletos se puede configurar con la sysctl tcp_max_syn_backlog. Cuando los "syncookies" estn activos, no existe una longitud mxima lgica y la configuracin de esta sysctl se ignora. Vea tcp(7) para ms informacin. VALOR DEVUELTO En caso de xito, se devuelve cero. En caso de error, se devuelve 1 y se pone en errno un valor apropiado. ERRORS EBADF El argumento s no es un descriptor vlido. ENOTSOCK El argumento s no es un conector. EOPNOTSUPP El conector no es de un tipo que admita la operacin listen. CONFORME A Single Unix, 4.4BSD, borrador POSIX 1003.1g. La llamada a funcin listen apareci por 1 vez en 4.2BSD. FALLOS Si el conector es de tipo AF_INET y el argumento backlog es mayor que la constante SOMAXCONN (128 en 2.0 y 2.2), se trunca silenciosamente a SOMAXCONN. Para aplicaciones transportables, no confe en este valor puesto que BSD (y algunos sistemas derivados de BSD) limitan backlog a 5. VASE TAMBIN accept(2), connect(2), socket(2)

Guia de programacin de redes

59

Manuel Jimnez Castro

3 Telecomunicaciones

accept()

NOMBRE accept acepta una conexin sobre un conector (socket). SINOPSIS #include <sys/types.h> #include <sys/socket.h> int accept(int s, struct sockaddr *addr, socklen_t *addrlen); DESCRIPCIN La funcin accept se usa con conectores orientados a conexin (SOCK_STREAM, SOCK_SEQPA CKET y SOCK_RDM). Extrae la primera peticin de conexin de la cola de conexiones pendientes, le asocia un nuevo conector con, practicamente, las misma propiedades que s y reserva un nuevo descriptor de fichero para el conector, el cul es el valor devuelto por la llamada. El conector original s no se ve afectado por esta llamada. Dese cuenta que cualquier opcin por descriptor de fichero (cualquiera que se pueda establecer con F_SETFL de fcntl, como no bloqueante o estado asncrono) no se hereda en un accept. El argumento s es un conector que ha sido creado con socket(2), ligado a una direccin local con bind(2) y que se encuentra a la escucha tras un listen(2). El argumento addr es un puntero a una estructura sockaddr. Esta estructura se rellena con la direccin de la entidad con la que se conecta, tal y como la conoce la capa de comunicaciones. El formato exacto de la direccin pasada en el parmetro addr viene determinado por la familia del conector (vea socket(2) y las pginas de manual del protocolo correspondiente). El argumento addrlen es un parmetro de entrada/salida: al efectuar la llamada debe contener el tamao de la estructura apuntada por addr; a la salida, contendr la longitud real (en bytes) de la direccin devuelta. Cuando addr es NULL nada se rellena. Si no hay conexiones pendientes en la cola y el conector no est marcado como "no bloqueante", accept bloquear al invocador hasta que se presente una conexin. Si el conector est marcado como no bloqueante y no hay conexiones pendientes en la cola, accept devolver EAGAIN. Para ser informado de las conexiones entrantes que se produzca en un conector, puede usar select(2) o poll(2). Se producir un evento de lectura en el intento de una nueva conexin y entonces puede llamar a accept para obtener un conector para esa conexin. Alternativamente, puede configurar el conector para que provoque una seal SIGIO cuando se produzca actividad en el conector; vea socket(7) para ms detalles. Para determinados protocolos que necesitan una confirmacin explcita, tales como DECNet, accept se puede interpretar como una funcin que, simplemente, desencola la siguiente peticin de conexin sin que ello implique la confirmacin. Se sobreentiende la confirmacin cuando se produce una lectura o escritura normal sobre el nuevo descriptor de fichero, y el rechazo puede ser de igual manera implcito cerrando el nuevo conector. Actualmente, slo DECNet tiene esta semntica en Linux. NOTAS Puede que no siempre haya una conexin esperando despus de que se produzca una seal SIGIO, o despus de que select(2) o poll(2) devuelvan un evento de lectura, debido a que la conexin podra haber sido eliminada por un error asncrono de red u

Guia de programacin de redes

60

Manuel Jimnez Castro

3 Telecomunicaciones

otro hilo antes de que se llame a accept. Si esto ocurre entonces la llamada se bloquear esperando a que llegue la siguiente conexin. Para asegurarse de que accept nunca se bloquea, es necesario que el conector s pasado tenga la opcin O_NONBLOCK activa (vea socket(7)). VALOR DEVUELTO La llamada devuelve 1 ante un error. Si tiene xito, devuelve un entero no negativo que es el descriptor del conector aceptado. MANEJO DE ERRORES La llamada accept de Linux pasa los errores de red ya pendienes sobre el nuevo conector como un cdigo de error de accept. Este comportamiento difiere de otras construcciones de conectores BSD. Para un funcionamiento fiable, la aplicacin debe detectar los errores de red definidos por el protocolo tras una llamada a accept y tratarlos como EAGAIN reintentado la operacin. En el caso de TCP/IP estos son ACCEPT(2) Manual del programador de Linux ACCEPT(2) ENETDOWN, EPROTO, ENOPROTOOPT, EHOSTDOWN, ENONET, EHOSTUNREACH, EOPNOTSUPP y ENETUNREACH. ERRORES EAGAIN o EWOULDBLOCK El conector est marcado como no-bloqueante y no hay conexiones que aceptar. EBADF El descriptor es invlido. ENOTSOCK El descriptor referencia a un fichero, no a un conector. EOPNOTSUPP El conector referenciado no es del tipo SOCK_STREAM. EFAULT El parmetro addr no se encuentra en una zona accesible para escritura por el usuario. EPERM Las reglas del cortafuegos prohiben la conexin. ENOBUFS, ENOMEM No hay suficiente memoria disponible. Esto normalmente significa que la asignacin de memoria est limitada por los lmites del buffer de conectores, no por la memoria del sistema, aunque esto no es 100% coherente. Adems, se pueden devolver otros errores de red para el nuevo conector y que se encuentren definidos en el protocolo. Diferentes ncleos de Linux pueden devolver otros errores diferentes como EMFILE, EINVAL, ENOSR, ENOBUFS, EPERM, ECONNABORTED, ESOCKTNOSUPPORT, EPROTONOSUPPORT, ETIMEDOUT, ERESTARTSYS. CONFORME A SVr4, 4.4BSD (la funcin accept apareci por primera vez en BSD 4.2). La pgina de manual de BSD documenta cinco posibles respuestas de error (EBADF, ENOTSOCK, EOPNOTSUPP, EWOULDBLOCK, EFAULT). SUSv2 documenta los errores EAGAIN, EBADF, ECONNABORTED, EFAULT, EINTR, EINVAL, EMFILE, ENFILE, ENOBUFS, ENOMEM, ENOSR, ENOTSOCK, EOPNOTSUPP, EPROTO, EWOULDBLOCK.

Guia de programacin de redes

61

Manuel Jimnez Castro

3 Telecomunicaciones

NOTA El tercer argumento de accept se declar originalmente como un int * (y as est en libc4 y libc5 y en otros muchos sistemas como BSD 4.*, SunOS 4, SGI); el estndar propuesto POSIX 1003.1g quiso cambiarlo a size_t * y as est en SunOS 5. Ms tarde, los borradores POSIX tenan socklen_t * y as lo tomaron the Single Unix Specification y glibc2. Citando a Linus Torvalds: _Cualquier_ biblioteca razonable _debe_ hacer que "socklen_t" sea del mismo tamao que int. Cualquier otra cosa destroza todo lo de la capa de conectores BSD. POSIX inicialmente estableci el tipo a size_t y, de hecho, yo (y es de suponer que otros aunque, obviamente, no demasiados) nos quejamos a gritos. El ser de tipo size_t es completamente desastroso, precisamente porque, por ejemplo, size_t muy rara vez es del mismo tamao que "int" en arquitecturas de 64 bit. Y _tiene_ que ser del mismo tamao que "int" porque as est en la interfaz de conectores BSD. De cualquier modo, los de POSIX finalmente tuvieron una idea y crearon "socklen_t". Para empezar, no deberan haberlo tocado pero, una vez que lo hicieron, pensaron que deban tener un tipo con nombre propio por alguna insondable razn (probablemente alguien no quera desprestigiarse por haber cometido la estupidez original por lo que, simplemente, renombraron su metedura de pata de forma silenciosa). VASE TAMBIN bind(2), connect(2), listen(2), select(2), socket(2)

send(),sendto().

NOMBRE send, sendto, sendmsg enva un mensaje de un conector (socket) SINTAXIS #include <sys/types.h> #include <sys/socket.h> int send(int s, const void *msg, size_t len, int flags); int sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen); int sendmsg(int s, const struct msghdr *msg, int flags); DESCRIPCIN Send, sendto y sendmsg son utilizados para transmitir un mensaje a otro conector. Send solo puede ser usado cuando un conector est en un estado connected mientras sendto y sendmsg pueden ser utilizados en cualquier momento. La direccin de destino viene dada por to con tolen especificando su tamao. La longitud del mensaje viene dada por len. Si el mensaje es demasiado largo para pasar automticamente a travs del protocolo inferior, se devuelve el error EMSGSIZE y el mensaje no es transmitido. La llamada send lleva implcita el que no se indiquen los posibles errores en la entrega. Los errores detectados localmente se indican devolviendo un valor 1. Cuando el mensaje no cabe en el buffer de envo del conector, send se bloquea, a no ser que el conector se haya colocado en el modo de E/S no bloqueante. En el modo no

Guia de programacin de redes

62

Manuel Jimnez Castro

3 Telecomunicaciones

bloqueante devolvera EAGAIN en este caso. Se puede utilizar la llamada select(2) para determinar cuando es posible enviar ms informacin. El parmetro flags es una palabra de opciones y puede contener las siguientes opciones: MSG_OOB Enviar datos fuera de orden(out-of-band) en conectores que soportan esta nocin (p.ej. SOCK_STREAM); el protocolo subyacente tambin debe soportar datos fuera de orden. MSG_DONTROUTE No usar un gateway para enviar el paquete, enviar slo a los ordenadores que se encuentren en redes conectadas directamente. Normalmente, esto slo lo utilizan los programas de diagnstico y enrutamiento. Esta opcin slo est definida para familias de protocolos que enrutan. Los conectores de paquetes no enrutan. MSG_DONTWAIT Habilitar el funcionamiento no bloqueante. Si la operacin se bloqueara, se devolvera EAGAIN (esto tambin se puede habilitar usando la bandera O_NONBLOCK con la operacin F_SETFL de fcntl(2)). MSG_NOSIGNAL Solicitar el no enviar SIGPIPE en caso de error en conectores orientados a conexin cuando el otro extremo rompa la conexin. Todava se devuelve el error EPIPE. Vea recv(2) para una descripcin de la estructura msghdr. Puede enviar informacin de control usando los miembros msg_control y msg_controllen. La longitud mxima del buffer de control que el ncleo puede procesar est limitada por conector por la sysctl net.core.optmem_max. Vea socket(7). VALOR DEVUELTO Las llamadas devuelven el numero de caracteres enviados, o 1 si ha ocurrido un error. ERRORES Estos son algunos errores estndares generados por la capa de conectores. Los mdulos de los protocolos subyacentes pueden generar y devolver errores adicionales. Vea sus pginas de manual respectivas. EBADF Se ha especificado un descriptor no vlido. ENOTSOCK El argumento s no es un conector. EFAULT Se ha especificado como parmetro una direccin incorrecta del espacio de usuario. tro. EMSGSIZE El conector requiere que este mensaje sea enviado automticamente, y el tamao del mensaje a ser enviado lo hace imposible. EAGAIN o EWOULDBLOCK El conector est marcado como no bloqueante y la operacin solicitada lo bloqueara. ENOBUFS La cola de salida del interface de red est llena. Esto generalmente indica que el interfaz ha parado de enviar, pero puede ser causado por una congestin temporal. (Esto no puede ocurrir en Linux, los paquetes simplemente se suprimen silenciosamente cuando la cola de un dispositivo se desborda.) EINTR Se ha producido una seal.

Guia de programacin de redes

63

Manuel Jimnez Castro

3 Telecomunicaciones

ENOMEM No hay memoria disponible. EINVAL Se ha pasado un argumento invlido. EPIPE Se ha desconectado el extremo local en un conector orientado a conexin. En este caso el proceso tamben recibir una seal SIGPIPE a menos que se active la opcin MSG_NOSIGNAL. CONFORME A 4.4BSD, SVr4, borrador POSIX 1003.1g (estas llamadas a funcin aparecieron en 4.2BSD). NOTA Los prototipos indicados ms arriba siguen the Single Unix Specification, ya que glibc2 tambin lo hace; el argumento flags era int en BSD 4.* pero unsigned int en libc4 y libc5; el argumento len era int en BSD 4.* y libc4 pero size_t en libc5; el argumento tolen era int en BSD 4.*, libc4 y libc5. Vea tambin accept(2). VASE TAMBIN fcntl(), recv(), select(), getsockopt(), sendfile(), socket(), write(), ip(), tcp(), udp()

recv(), recvfrom().

NOMBRE recv, recvfrom, recvmsg reciben un mensaje desde un conector SINOPSIS #include <sys/types.h> #include <sys/socket.h> int recv(int s, void *buf , size_t lon, int flags); int recvfrom(int s, void *buf , size_t lon, int flags, struct sockaddr *desde, socklen_t *londesde); int recvmsg(int s, struct msghdr *msg, int flags); DESCRIPCIN Las llamadas recvfrom y recvmsg se emplean para recibir mensajes desde un conector (socket), y pueden utilizarse para recibir datos de un conector sea orientado a conexin o no. Si desde no es NULL y el conector no es orientado a conexin, la direccin fuente del mensaje se llena. El argumento londesde es un parmetro por referencia, inicializado al tamao del bfer asociado con desde, y modificado cuando la funcin regresa para indicar el tamao real de la direccin guardada ah. La llamada a recv se utiliza normalmente slo en un conector conectado (vea connect(2)) y es idntica a recvfrom con un parmetro desde con valor NULL. Las tres rutinas devuelven la longitud del mensaje cuando terminan bien. Si un mensaje es demasiado largo como para caber en el bfer suministrado, los bytes que sobran pueden descartarse dependiendo del tipo de conector del que se reciba el mensaje (vea socket(2)).

Guia de programacin de redes

64

Manuel Jimnez Castro

3 Telecomunicaciones

Si no hay mensajes disponibles en el conector, las llamadas de recepcin esperan que llegue un mensaje, a menos que el conector sea no bloqueante (vea fcntl(2)) en cuyo caso se devuelve el valor 1 y la variable externa errno toma el valor EAGAIN. Las llamadas de recepcin devuelven normalmente cualquier dato disponible, hasta la cantidad pedida, en vez de esperar la recepcin de la cantidad pedida completa. Las llamadas select(2) o poll(2) pueden emplearse para determinar cundo llegan ms datos. El argumento flags de una llamada a recv se forma aplicando el operador de bits Olgico a uno ms de los valores siguientes: MSG_OOB Esta opcin pide la recepcin de datos fuera-de-banda que no se recibiran en el flujo de datos normal. Algunos protocolos ponen datos despachados con prontitud en la cabeza de la cola de datos normales, y as, esta opcin no puede emplearse con tales protocolos. MSG_PEEK Esta opcin hace que la operacin de recepcin devuelva datos del principio de la cola de recepcin sin quitarlos de all. As, una prxima llamada de recepcin devolver los mismos datos. MSG_WAITALL Esta opcin hace que la operacin se bloquee hasta que se satisfaga la peticin completamente. Sin embargo, la llamada puede an devolver menos datos de los pedidos si se captura una seal, si ocurre un error o una desconexin, o si los prximos datos que se van a recibir son de un tipo diferente del que se ha devuelto. MSG_NOSIGNAL Esta opcin desactiva el que se produzca una seal SIGPIPE sobre los conectores orientados a conexin cuando el otro extremo desaparece. MSG_ERRQUEUE Esta opcin indica que los errores encolados deben recibirse desde la cola de errores de conectores. El error se pasa en un mensaje auxiliar con un tipo dependiente del protocolo (para IPv4 ste es IP_RECVERR). El usuario debe proporciona un buffer de tamao suficiente. Vea cmsg(3) para obtener ms informacin sobre mensajes auxiliares. El error se suministra en una estructura sock_extended_err: #define SO_EE_ORIGIN_NONE 0 #define SO_EE_ORIGIN_LOCAL 1 #define SO_EE_ORIGIN_ICMP 2 #define SO_EE_ORIGIN_ICMP6 3 struct sock_extended_err { u_int32_t ee_errno; u_int8_t ee_origin; u_int8_t ee_type; u_int8_t ee_code; u_int8_t ee_pad; u_int32_t ee_info; u_int32_t ee_data; };

/* nmero de error */ /* origen del error */ /* tipo */ /* cdigo */ /* informacin adicional */ /* otros datos */ /* Pueden ir ms datos a continuacin .*/

Guia de programacin de redes

65

Manuel Jimnez Castro

3 Telecomunicaciones

struct sockaddr *SOCK_EE_OFFENDER(struct sock_extended_err *); ee_errno contiene el nmero errno del error encolado. ee_origin es el cdigo del origen en donde se ha originado el error. Los otros campos son especficos del protocolo. La macro SOCK_EE_OFFENDER devuelve un puntero a la direccin del objeto de red desde donde se ha originado el error dando un puntero al mensaje auxiliar. Si esta direccin se desconoce, el miembro sa_family de sockaddr contiene AF_UNSPEC y los otros campos de sockaddr quedan indefinidos. El contenido til del paquete que ha producido el error se pasa como datos normales. Para los errores locales no se pasa ninguna direccin (esto se puede comprobar con el miembro cmsg_len de cmsghdr). Para los errores recibidos, se asigna MSG_ERRQUEUE a msghdr. Despus de que se haya pasado un error, el error de conector pendiente se regenera basndose en el siguiente error encolado y se pasar en la siguiente operacin de conectores. La llamada recvmsg utiliza una estructura msghdr para minimizar el nmero de parmetros suministrados directamente. Esta estructura tiene la forma siguiente, segn se define en <sys/socket.h>: struct msghdr { void * msg_name; socklen_t msg_namelen; struct iovec * msg_iov; size_t msg_iovlen; void * msg_control; socklen_t msg_controllen; int msg_flags; };

/* direccin opcional */ /* tamao de la direccin */ /* vector dispersar/agrupar */ /* n de elementos en msg_iov */ /* datos auxiliares, ver ms abajo */ /* long buffer datos auxiliares */ /* opciones en mensaje recibido */

Aqu msg_name y msg_namelen especifican la direccin de destino si el conector est desconectado; msg_name puede darse como un puntero nulo si no se desean o requieren nombres. Los campos msg_iov y msg_iovlen describen localizaciones dispersar/agrupar, como se discute en readv(2). El campo msg_control, que tiene de longitud msg_controllen, apunta a un bfer para otros mensajes relacionados con control de protocolo o para datos auxiliares diversos. Cuando se llama a recvmsg, msg_controllen debe contener la longitud del buffer disponible en msg_control; a la vuelta de una llamada con xito contendr la longitud de la secuencia de mensajes de control. Los mensajes son de la forma: struct cmsghdr { socklen_t cmsg_len; int cmsg_level; int cmsg_type; };

/* N de byte de datos, incluye cab. */ /* protocolo originante */ /* tipo especfico del protocolo */ /* seguido poru_char cmsg_data[]; */

Guia de programacin de redes

66

Manuel Jimnez Castro

3 Telecomunicaciones

Los datos auxiliares slo deberan ser accedidos mediante las macros definidas en cmsg(3). Como ejemplo, Linux usa este mecanismo de datos auxiliares para pasar errores ampliados, opciones IP o descriptores de fichero mediante conectores Unix. El campo msg_flags toma un valor al regresar dependiendo del mensaje recibido. MSG_EOR indica finde-registro; los datos devueltos completaron un registro (generalmente empleado con conectores del tipo SOCK_SEQPACKET). MSG_TRUNC indica que la porcin trasera de un datagrama ha sido descartada porque el datagrama era ms grande que el bfer suministrado. MSG_CTRUNC indica que algn dato de control ha sido descartado debido a la falta de espacio en el bfer para datos auxiliares. MSG_OOB se devuelve para indicar que se han recibido datos despachados prontamente o fuera-de-banda. MSG_ERRQUEUE indica que no se ha recibido ningn dato sino un error ampliado de la cola de errores de conectores. VALOR DEVUELTO Estas llamadas devuelven el nmero de bytes recibidos, o bien 1 en caso de que ocurriera un error. ERRORES Estos son algunos errores estndares generados por la capa de conectores. Los modulos de los protocolos subyacentes pueden generar y devolver errores adicionales. Consulte sus pginas de manual. EBADF El argumento s es un descriptor invlido. ENOTCONN El conector est asociado con un protocolo orientado a la conexin y no ha sido conectado (vea connect(2) y accept(2)). ENOTSOCK El argumento s no se refiere a un conector. EAGAIN El conector est marcado como no-bloqueante, y la operacin de recepcin producira un bloqueo, o se ha puesto un lmite de tiempo en la recepcin, que ha expirado antes de que se recibieran datos. EINTR La recepcin ha sido interrumpida por la llegada de una seal antes de que hubiera algn dato disponible. EFAULT El puntero a bfer de recepcin (o punteros) apunta afuera del espacio de direcciones del proceso. EINVAL Se ha pasado un argumento invlido. CONFORME A 4.4BSD (estas funciones aparecieron por primera vez en 4.2BSD). NOTA Los prototipos datos anteriormente siguen a glibc2. The Single Unix Specification coincide en todo excepto en que el tipo de los valores devueltos es ssize_t (mientras

Guia de programacin de redes

67

Manuel Jimnez Castro

3 Telecomunicaciones

que BSD 4.*, libc4 y libc5 tienen int). El argumento flags es un int en BSD 4.* pero es un unsigned int en libc4 y libc5. El argumento lon es un int en BSD 4.* pero es un size_t en libc4 y libc5. El argumento londesde es un int en BSD 4.*, libc4 y libc5. El actual socklen_t * fue inventado por POSIX. Vea tambin accept(2). VASE TAMBIN fcntl(), read(), select(), getsockopt(), socket(), cmsg()

close()

NOMBRE close cierra un descriptor de fichero SINOPSIS #include <unistd.h> int close(int fd); DESCRIPCIN close cierra un descriptor de fichero de forma que ya no se refiera a fichero alguno y pueda ser reutilizado. Cualesquiera bloqueos mantenidos sobre el fichero con el que estaba asociado, y propiedad del proceso, son eliminados (sin importar qu descriptor de fichero fue utilizado para obtener el bloqueo). Si fd es la ltima copia de cierto descriptor de fichero, los recursos asociados con dicho descriptor son liberados; si el descriptor fuera la ltima referencia a un fichero que haya sido eliminada mediante unlink(2) entonces el fichero es borrado. VALOR DEVUELTO close devuelve 0 en caso de xito y 1 si ocurre algn error. ERRORES EBADF fd no es un descriptor de fichero abierto vlido. CONFORME A SVID, AT&T, POSIX, X/OPEN, BSD 4.3. SVr4 documenta una condicin de error ENOLINK adicional. NOTA El no comprobar el valor devuelto por close es un error de programacin comn y no obstante serio. Aquellas implementaciones de sistemas de ficheros que usan tcnicas tales como la conocida por write-behind (escribe por detrs) a fin de incrementar el rendimiento pueden resultar en que write(2) tenga xito aunque an no se hayan escrito los datos. El estado de error puede ser informado durante una posterior operacin de escritura, pero est garantizado que ser informado al cerrar el fichero. No comprobar el valor devuelto cuando se cierra un fichero puede dar lugar a una prdida silenciosa de datos. Esto se observa especialmente en NFS y con las cuotas de discos.

Guia de programacin de redes

68

Manuel Jimnez Castro VASE TAMBIN open(), fcntl(), shutdown(), unlink(), fclose().

3 Telecomunicaciones

shutdown()

NOMBRE shutdown cierra parte de una conexin bidireccional SINOPSIS #include <sys/socket.h> int shutdown(int s, int como); DESCRIPCIN La llamada a shutdown causa que se cierre completamente o en parte una conexin bidireccional en el conector asociado con s. Si como es 0, no se permitirn ms recepciones. Si como es 1, no se permitirn ms envos. Si como es 2, no se permitirn ms envos ni recepciones. VALOR DEVUELTO En caso de xito, se devuelve cero. En caso de error, se devuelve 1 y se pone un valor apropiado en errno. ERRORES EBADF s no es un desccriptor vlido. ENOTSOCK s es un fichero, no un conector. ENOTCONN El conector especificado no est conectado. CONFORME A 4.4BSD (la funcin shutdown apareci por primera vez en 4.2BSD). FALLOS Las opciones numricas how deben ser reemplazadas por las opciones SHUT_RD, SHUT_WR y SHUT_RDWR, segn ordena "the Single Unix Specification". VASE TAMBIN connect(), socket()

getpeername()

NOMBRE getpeername obtiene el nombre de la pareja conectada SINOPSIS #include <sys/socket.h> int getpeername(int s, struct sockaddr *nombre, socklen_t *longinom);

Guia de programacin de redes

69

Manuel Jimnez Castro

3 Telecomunicaciones

DESCRIPCIN Getpeername devuelve el nombre de la pareja conectada al zcalo s. El parmetro longinom debera inicializarse de forma que indicara la cantidad de espacio a la que apuntara nombre. Al regresar la funcin, contendr el tamao real del nombre devuelto (en bytes). El nombre se trunca si el bfer provisto es demasiado pequeo. VALOR DEVUELTO En caso de xito, se devuelve cero. En caso de error, se devuelve 1 y se pone en errno un valor apropiado. ERRORES EBADF El argumento s no es un descriptor vlido. ENOTSOCK El argumento s es un fichero, no un zcalo. ENOTCONN El zcalo no est conectado. ENOBUFS No haba en el sistema suficientes recursos como para efectuarse la operacin. EFAULT El parmetro nombre apunta a memoria que no est en una zona vlida del espacio de direcciones del proceso. CONFORME A SVr4, 4.4BSD (la llamada al sistema getpeername apareci por vez 1 en 4.2BSD). NOTA El tercer argumento de getpeername es en realidad un entero (y esto es lo que tienen BSD 4.*, libc4 y libc5). Cierta confusin en POSIX dio como resultado el actual socklen_t. El estndar propuesto todava no ha sido adoptado pero glibc2 ya lo sigue y tambin tiene socklen_t. Vea tambin accept(2). VASE TAMBIN accept(), bind(), getsockname()

getsockname()

NOMBRE getsockname obtener nombre de conexin SINOPSIS #include <sys/socket.h> int getsockname(int s , struct sockaddr * name , socklen_t * namelen ) DESCRIPCIN Getsockname devuelve el nombre actual para la conexin indicada. El parmetro namelen debe ser inicializado para indicar la cantidad de espacio apuntador por name. La devolucin contiene el tamao actual del nombre devuelto (en bytes).

Guia de programacin de redes

70

Manuel Jimnez Castro

3 Telecomunicaciones

VALOR DEVUELTO Si es correcto, devuelve un cero. Si hay error, devuelve 1, y se asigna a errno un valor apropiado. ERRORES EBADF El argumento s no es un descriptor vlido. ENOTSOCK El argumento s es un fichero, no una conexin. ENOBUFS No haba suficientes recursos disponibles en el sistema para llevar a cabo la operaicn. EFAULT El parametro name apunta a una memoria que no est dentro de una zona vlida del espacio de direcciones del proceso. CONFORME A SVr4, 4.4BSD (la funcion getsockname apareci en 4.2BSD). SVr4 documenta dos cdigos de error adicionales, ENOMEM y ENOSR. NOTA El tercer argumento de getsockname es en realidad un entero (y esto es lo que tienen BSD 4.*, libc4 y libc5). Cierta confusin en POSIX dio como resultado el actual socklen_t. El estndar propuesto todava no ha sido adoptado pero glibc2 ya lo sigue y tambin tiene socklen_t. Vea tambin accept(2). VASE TAMBIN bind(), socket()

gethostbyname(), gethostbyaddr(), herror().

NOMBRE gethostbyname, gethostbyaddr, sethostent, endhostent, herror, hstrerror obtienen una entrada de anfitrin de red SINOPSIS #include <netdb.h> extern int h_errno; struct hostent *gethostbyname(const char *name); #include <sys/socket.h> /* para AF_INET */ struct hostent *gethostbyaddr(const char *addr, int len, int type); void sethostent(int stayopen); void endhostent(void); void herror(const char *s); const char * hstrerror(int err);

Guia de programacin de redes

71

Manuel Jimnez Castro

3 Telecomunicaciones

DESCRIPCIN La funcin gethostbyname() devuelve una estructura del tipo hostent para el anfitrin (host) dado name. Aqu, name es ora un nombre de anfitrin, ora una direccin IPv4 en la notacin normal de puntos, ora una direccin IPv6 en la notacin de dos puntos (y posiblemente de puntos). (Vea la RFC 1884 para una descripcin de las direcciones en IPv6). Si name es una direccin IPv4 o IPv6, no se realiza ninguna bsqueda y gethostbyname() simplemente copia name en el campo h_name y su equivalente struct in_addr en el campo h_addr_list[0] de la estructura hostent devuelta. Si name no termina con un punto y la variable de ambiente HOSTALIASES est asignada, se buscar primero name en el fichero de alias sealado por HOSTALIASES. (Vea hostname(7) para saber cmo es el formato del fichero.) Se buscan el dominio actual y sus ancestros a menos que name termine en punto. La funcin gethostbyaddr() devuelve una estructura del tipo hostent para la direccin de anfitrin dada addr de longitud len y de tipo type. El nico tipo de direccin vlido actualmente es AF_INET. La funcin sethostent() especifica, si stayopen es true (1), que se debera emplear un conector (socket) TCP para las interrogaciones al servidor de nombres y que la conexin debera permanecer abierta durante sucesivas preguntas. De otro modo, las peticiones al servidor de nombres utilizarn datagramas UDP. La funcin endhostent() termina el uso de una conexin TCP para las peticiones al servidor de nombres. La (obsoleta) funcin herror() muestra en stderr un mensaje de error asociado con el valor actual de h_errno. La (obsoleta) funcin hstrerror() toma un nmero de error (habitualmente h_errno) y devuelve la cadena del mensaje correspondiente. Las preguntas al servidor de nombres llevadas a cabo por gethostbyname() y gethostbyaddr() usan una combinacin de uno o todos los servidores de nombres named(8), una declaracin en /etc/hosts, y el Servicio de Informacin de Red (NIS, antes Pginas Amarillas, YP), dependiendo de los contenidos de la lnea order en /etc/host.conf. (Vea resolv+(8)). La accin predeterminada es preguntar a named(8), seguido por /etc/hosts. La estructura hostent se define en <netdb.h> como sigue: struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; char **h_addr_list; } #define h_addr h_addr_list[0]

/* nombre oficial del anfitrin */ /* lista de alias */ /* tipo direccin anfitrin */ /* longitud de la direccin */ /* lista de direcciones */ /* por compatibilidad atrs */

Los miembros de la estructura hostent son: h_name El nombre oficial de un anfitrin. h_aliases Una cadena terminada en el carcter nulo de los nombres alternativos para el anfitrin. h_addrtype

Guia de programacin de redes

72

Manuel Jimnez Castro

3 Telecomunicaciones

El tipo de direccin; siempre AF_INET de momento. h_length La longitud de la direccin en bytes. h_addr_list Una cadena terminada en nulo de direcciones de red para el anfitrin en orden de bytes de red. h_addr La primera direccin en h_addr_list por compatibilidad hacia atrs. VALOR DEVUELTO Las funciones gethostbyname() y gethostbyaddr() devuelven la estructura hostent, o un puntero NULL si ha ocurrido un error. En caso de error, la variable h_errno contiene un nmero de error. ERRORES La variable h_errno puede tener los siguientes valores: HOST_NOT_FOUND El anfitrin especificado es desconocido. NO_ADDRESS o NO_DATA El nombre pedido es vlido pero no tiene una dirreccin IP. NO_RECOVERY Ha ocurrido un error no recuperable del servidor de nombres. TRY_AGAIN Ha ocurrido un error temporal sobre un servidor de nombres con autoridad. Intente luego ms tarde. FICHEROS /etc/host.conf fichero de configuracin del revolvedor /etc/hosts fichero de base de datos de anfitriones CONFORME A BSD 4.3. SUS-v2 declara el parmetro len como del tipo size_t. RUMORES La futura glibc2.2 seguir SUS-v2. VASE TAMBIN resolver(), hosts(), hostname(), resolv+(), named()

getprotobyname()

NOMBRE getprotoent, getprotobyname, getprotobynumber, setprotoent, endprotoent obtienen una entrada del fichero de protocolos SINOPSIS #include <netdb.h> struct protoent *getprotoent(void);

Guia de programacin de redes

73

Manuel Jimnez Castro struct protoent *getprotobyname(const char *nombre); struct protoent *getprotobynumber(int proto); void setprotoent(int dejaloabierto); void endprotoent(void);

3 Telecomunicaciones

DESCRIPCIN La funcin getprotoent() lee la siguiente lnea del fichero /etc/protocols y devuelve una estructura protoent que contiene los campos de que consta la lnea. El fichero /etc/protocols se abre si es necesario. La funcin getprotobyname() devuelve una estructura protoent para la lnea de /etc/protocols que concuerde con el nombre de protocolo nombre. La funcin getprotobynumber() devuelve una estructura protoent para la lnea que concuerde con el nmero de protocolo proto. La funcin setprotoent() abre y rebobina el fichero /etc/protocols. Si dejaloabierto es verdad (1), entonces el fichero no se cerrar entre llamadas a getprotobyname() o a getprotobynumber(). La funcin endprotoent() cierra /etc/protocols. La estructura protoent se define en <netdb.h> as: struct protoent { char *p_name; char **p_aliases; int p_proto; }

/* nombre oficial de protocolo */ /* lista de sinnimos */ /* nmero de protocolo */

Los miembros de la estructura protoent son: p_name El nombre oficial del protocolo. p_aliases Una lista terminada en cero de nombres alternos para el protocolo. p_proto El nmero del protocolo. VALOR DEVUELTO Las funciones getprotoent(), getprotobyname() y getprotobynumber() devuelven la estructura protoent, o un puntero NULL si ocurre un error o si se llega al final del fichero. FICHEROS /etc/protocols fichero con los datos de protocolos CONFORME A BSD 4.3 VASE TAMBIN getservent(), getnetent(), protocols()

Guia de programacin de redes

74

Manuel Jimnez Castro getservbyname()

3 Telecomunicaciones

NOMBRE getservent, getservbyname, getservbyport, setservent, endservent obtener valores de servicios SINOPSIS #include <netdb.h> struct servent *getservent(void); struct servent *getservbyname(const char *name, const char *proto); struct servent *getservbyport(int port, const char *proto); void setservent(int stayopen); void endservent(void); DESCRIPCIN La funcin getservent() lee la siguiente lnea del fichero /etc/services y devuelve una estructura servent que contiene en sus campos los campos de la lnea. Si es necesario, se abre el fichero /etc/services. La funcin getservbyname() devuelve una estructura servent conteniendo los campos de la lnea de /etc/services que contiene el servicio name y usa el protocolo proto. La funcin getservbyport() devuelve una estructura de tipo servent con los datos de la lnea que contiene el puerto port (con los bytes en el orden de red) y usa el protocolo proto. La funcin setservent() abre y reinicia el fichero /etc/services. Si stayopen es verdadero (1), entonces no se cerrar el fichero entre llamadas a las funciones getservbyname() y getservbyport(). La funcin endservent() cierra el fichero /etc/services. La estructura servent est definida en <netdb.h> como sigue: struct servent { char *s_name; char **s_aliases; int s_port; char *s_proto; }

/* nombre oficial del servicio */ /* lista de alias */ /* nmero de puerto */ /* protocolo a usar */

Los miembros de la estructura servent son: s_name El nombre oficial del servicio. s_aliases Una lista terminada en cero de nombres alternativos para el servicio. s_port El numero de puerto para el servicio, con sus bytes en el orden de red. s_proto El nombre del protoclo a usar con este servicio. VALOR DEVUELTO Las funciones getservent(), getservbyname() y getservbyport() devuelve una estructura de tipo servent, o un puntero NULL si ha ocurrido un error o se ha alcanzado el final del fichero.

Guia de programacin de redes

75

Manuel Jimnez Castro FICHEROS /etc/services Fichero de base de datos de servicios CONFORME A BSD 4.3 VASE TAMBIN getprotoent(), getnetent(), services()

3 Telecomunicaciones

fcntl()

NOMBRE fcntl manipula el descriptor de fichero SINOPSIS #include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); int fcntl(int fd, int cmd, struct flock * lock); DESCRIPCIN fcntl realiza una de las diversas y variadas operaciones sobre fd. La operacin en cuestin se determina mediante cmd: F_DUPFD Busca el descriptor de fichero disponible de menor nmero mayor o igual que arg y lo convierte en una copia de fd. Esto es diferente en dup2(2) que usa exactamente el descriptor especificado. Los descriptores antiguo y nuevo pueden usarse indistintamente. Ambos comparten candados (locks), indicadores de posicin de ficheros y banderas (flags); por ejemplo, si la posicin del fichero se modifica usando lseek en uno de los descriptores, la posicin del otro resulta modificada simultneamente. Sin embargo, los dos descriptores no comparten la bandera close-on-exec "cerrar-alejecutar". La bandera close-on-exec de la copia est desactivada, significando que no se cerrar en ejecucin. En caso de xito, se devuelve el nuevo descriptor. F_GETFD Lee la bandera close-on-exec. Si el bit FD_CLOEXEC es 0, el fichero permanecer abierto durante exec, en caso contrario se cerrar el fichero. F_SETFD Asigna el valor de la bandera close-on-exec al valor especificado por el bit FD_CLOEXEC de arg. F_GETFL Lee las banderas del descriptor (todas las banderas, segn hayan sido asignadas por open(2), sern devueltas). F_SETFL Asigna las banderas del descriptor al valor asignado por arg. Slo O_APPEND, O_NONBLOCK y O_ASYNC pueden asignarse; las otras banderas no se ven afectadas. Las banderas se comparten entre copias (hechas con dup(2), fork(2), etc.) del mismo descriptor de fichero.

Guia de programacin de redes

76

Manuel Jimnez Castro

3 Telecomunicaciones

Las banderas y su semntica estn descritas en open(2). F_GETLK, F_SETLK y F_SETLKW se utilizan para gestionar candados de ficheros discrecionales (discretionary file locks). El tercer argumento lock es un puntero a una struct flock (que puede ser sobrescrita por esta llamada). F_GETLK Devuelve la estructura flock que nos impide obtener el candado, o establece el campo l_type del candado a F_UNLCK si no hay obstruccin. F_SETLK El candado est cerrado (cuando l_type es F_RDLCK o F_WRLCK) o abierto (cuando es F_UNLCK). Si el candado est cogido por alguien ms, esta llamada devuelve 1 y pone en errno el cdigo de error EACCES o EAGAIN. F_SETLKW Como F_SETLK, pero en vez de devolver un error esperamos que el candado se abra. Si se recibe una seal a capturar mientras fcntl est esperando, se interrumpe y (despus de que el manejador de la seal haya terminado) regresa inmediatamente (devolviendo 1 y asignado a errno el valor EINTR). F_GETOWN, F_SETOWN, F_GETSIG y F_SETSIG se utilizan para gestionar las seales de disponibilidad de E/S: F_GETOWN Obtiene el ID de proceso o el grupo de procesos que actualmente recibe las seales SIGIO y SIGURG para los eventos sobre el descriptor de fichero fd. Los grupos de procesos se devuelven como valores negativos. F_SETOWN Establece el ID de proceso o el grupo de procesos que recibir las seales SIGIO y SIGURG para los eventos sobre el descriptor de fichero fd. Los grupos de procesos se especifican mediante valores negativos. (Se puede usar F_SETSIG para especificar una seal diferente a SIGIO). Si activa la bandera de estado O_ASYNC sobre un descriptor de fichero (tanto si proporciona esta bandera con la llamada open(2) como si usa la orden F_SETFL de fcntl), se enviar una seal SIGIO cuando sea posible la entrada o la salida sobre ese descriptor de fichero. El proceso o el grupo de procesos que recibir la seal se puede seleccionar usando la orden F_SETOWN de la funcin fcntl. Si el descriptor de fichero es un enchufe (socket), esto tambin seleccionar al recipiente de las seales SIGURG que se entregan cuando llegan datos fuera de orden (out-of-band, OOB) sobre el enchufe. (SIGURG se enva en cualquier situacin en la que select(2) informara que el enchufe tiene una "condicin excepcional"). Si el descriptor de fichero corresponde a un dispositivo de terminal, entonces las seales SIGIO se envan al grupo de procesos en primer plano de la terminal. F_GETSIG Obtiene la seal enviada cuando la entrada o la salida son posibles. Un valor cero significa que se enva SIGIO. Cualquier otro valor (incluyendo SIGIO) es la seal enviada en su lugar y en este caso se dispone de informacin adicional para el manejador de seal si se instala con SA_SIGINFO. F_SETSIG Establece la seal enviada cuando la entrada o la salida son posibles. Un valor cero significa enviar la seal por defecto SIGIO. Cualquier otro valor (incluyendo SIGIO) es

Guia de programacin de redes

77

Manuel Jimnez Castro

3 Telecomunicaciones

la seal a enviar en su lugar y en este caso se dispone de informacin adiciona para el manejador de seal si se instala con SA_SIGINFO. Usando F_SETSIF con un valor distinto de cero y asignando SA_SIGINFO para el manejador de seal (vea sigaction(2)), se pasa informacin extra sobre los eventos de E/S al manejador en la estructura siginfo_t. Si el campo si_code indica que la fuente is SI_SIGIO, el campo si_fd proporciona el descriptor de fichero asociado con el evento. En caso contrario, no se indican qu descriptores de ficheros hay pendientes y, para determinar qu descriptores de fichero estn disponibles para E/S, debera usar los mecanismos usuales (select(2), poll(2), read(2) con O_NONBLOCK activo, etc.). Seleccionando una seal de tiempo real POSIX.1b (valor >= SIGRTMIN), se pueden encolar varios eventos de E/S usando los mismos nmeros de seal. (El encolamiento depende de la memoria disponible). Se dispone de informacin extra si se asigna SA_SIGINFO al manejador de seal, como antes. Usando estos mecanismos, un programa puede implementar E/S totalmente asncrona, sin usar select(2) ni poll(2) la mayor parte del tiempo. El uso de O_ASYNC, F_GETOWN y F_SETOWN es especfico de Linux y BSD. F_GETSIG y F_SETSIG son especficos de Linux. POSIX posee E/S asncrona y la estructura aio_sigevent para conseguir cosas similares; estas tambin estn disponibles en Linux como parte de la biblioteca de C de GNU (GNU C Library, Glibc). VALOR DEVUELTO Para una llamada con xito, el valor devuelto depende de la operacin: F_DUPFD El nuevo descriptor. F_GETFD Valor de la bandera. F_GETFL Valor de las banderas. F_GETOWN Valor del propietario del descriptor. F_GETSIG Valor de la seal enviada cuando la lectura o la escritura son posibles o cero para el comportamiento tradicional con SIGIO. Para cualquier otra orden Cero. En caso de error el valor devuelto es 1, y se pone un valor apropiado en errno. ERRORES EACCES La operacin est prohibida por candados mantenidos por otros procesos. EAGAIN La operacin est prohibida porque el fichero ha sido asociado a memoria por otro proceso. EDEADLK Se ha detectado que el comando F_SETLKW especificado provocara un interbloqueo. EFAULT lock est fuera de su espacio de direcciones accesible. EBADF fd no es un descriptor de fichero abierto. EINTR El comando F_SETLKW ha sido interrumpido por una seal. Para F_GETLK y F_SETLK, la orden fue interrumpida por una seal antes de que el candado fuera comprobado o adquirido. Es ms probable al poner un candado a un fichero remoto (por ejemplo, un candado sobre NFS) pero algunas veces puede ocurrir localmente. EINVAL Para F_DUPFD, arg es negativo o mayor que el valor mximo permitido. Para F_SETSIG, arg no es un nmero de seal permitido. EMFILE Para F_DUPFD, el proceso ya ha llegado al nmero mximo de descriptores de ficheros abiertos. ENOLCK Demasiados candados de segmento abiertos, la tabla de candados est llena o ha fallado un protocolo de candados remoto (por ejemplo, un candado sobre NFS).

Guia de programacin de redes

78

Manuel Jimnez Castro

3 Telecomunicaciones

EPERM Se ha intentado limpiar la bandera O_APPEND sobre un fichero que tiene activo el atributo de slo aadir (append-only). NOTAS Los errores devueltos por dup2 son distintos de aqullos dados por F_DUPFD. CONFORME A SVID, AT&T, POSIX, X/OPEN, BSD 4.3. Slo las operaciones F_DUPFD, F_GETFD, F_SETFD, F_GETFL, F_SETFL, F_GETLK, F_SETLK y F_SETLKW se especifican en POSIX.1. F_GETOWN y F_SETOWN son BSD-ismos no aceptados en SVr4; F_GETSIG y F_SETSIG son especficos de Linux. Las banderas legales para F_GETFL/F_SETFL son aqullas que acepta open(2) y varan entre estos sistemas; O_APPEND, O_NONBLOCK, O_RDONLY y O_RDWR son las que se mencionan en POSIX.1. SVr4 admite algunas otras opciones y banderas no documentadas aqu. SVr4 documenta las condiciones de error adicionales EIO, ENOLINK y EOVERFLOW. VASE TAMBIN open(), socket(), dup2(), flock().

select()

NOMBRE select, FD_CLR, FD_ISSET, FD_SET, FD_ZERO multiplexacin de E/S sncrona SINOPSIS #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); FD_CLR(int fd, fd_set *set); FD_ISSET(int fd, fd_set *set); FD_SET(int fd, fd_set *set); FD_ZERO(fd_set *set); DESCRIPCIN select espera a que una serie de descriptores de ficheros cambien su estado. Se miran tres conjuntos independientes de descriptores. Aqullos listados en readfds sern observados para ver si hay caracteres que llegan a estar disponibles para lectura, aqullos en writefds sern observados para ver si es correcto escribir inmediatamente en ellos, y aqullos en exceptfds sern observados para ver si ocurren excepciones. En caso de xito, los conjuntos se modifican en marcha para indicar qu descriptores cambiaron realmente su estado. Se proporcionan cuatro macros para manipular los conjuntos. FD_ZERO limpiar un conjunto. FD_SET y FD_CLR aaden o borran un descriptor dado a o de un conjunto. FD_ISSET mira a ver si un descriptor es parte del conjunto; esto es til despus de que select regrese. n es el descriptor con el nmero ms alto en cualquiera de los tres conjuntos, ms 1.

Guia de programacin de redes

79

Manuel Jimnez Castro

3 Telecomunicaciones

timeout es un lmite superior de la cantidad de tiempo transcurrida antes de que select regrese. Puede ser cero, causando que select regrese inmediatamente. Si timeout es NULL (no hay tiempo de espera), select puede bloquear indefinidamente. VALOR DEVUELTO En caso de xito, select devuelve el nmero de descriptores contenidos en los conjuntos de descriptores, que puede ser cero si el tiempo de espera expira antes de que ocurra algo interesante. En caso de error, se devuelve 1, y se pone un valor apropiado en errno; los conjuntos y timeout estarn indefinidos, as que no confe en sus contenidos tras un error. ERRORES EBADF Se ha dado un descriptor de fichero invlido en uno de los conjuntos. EINTR Se ha capturado una seal no bloqueante. EINVAL n es negativo. ENOMEM select no ha sido capaz de reservar memoria para las tablas internas. OBSERVACIONES Hay algn cdigo por ah que llama a select con los tres conjuntos vacos, n cero, y un timeout distinto de cero como una forma transportable y curiosa de dormir con una precisin por debajo del segundo. En Linux, timeout se modifica para reflejar la cantidad de tiempo no dormido; la mayora de otras implementaciones no hacen esto. Esto produce problemas cuando el cdigo de Linux que lee timeout se transporta a otros sistemas operativos, y cuando se transporta a Linux cdigo que reutiliza una struct timeval para varias selects en un bucle sin reinicializarla. Considere que timeout est indefinido despus de que select regrese. EJEMPLO #include <stdio.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int main(void) { fd_set rfds; struct timeval tv; int valret; /* Mirar stdin (df 0) para ver si tiene entrada */ FD_ZERO(&rfds); FD_SET(0, &rfds); /* Esperar hasta 5 s */ tv.tv_sec = 5; tv.tv_usec = 0; valret = select(1, &rfds, NULL, NULL, &tv); /* No confiar ahora en el valor de tv! */

Guia de programacin de redes

80

Manuel Jimnez Castro

3 Telecomunicaciones

if (valret) { printf("Los datos ya estn disponibles.\n"); /* FD_ISSET(0, &rfds) ser verdadero */ } else { printf("Ningn dato en 5 segundos.\n"); return(0); } } CONFORME A 4.4BSD (la funcin select apareci por primera vez en 4.2BSD). Generalmente es transportable a o desde sistemas no-BSD que admitan clones de la capa de zcalos de BSD (incluyendo variantes System V). Sin embargo, observe que la variante System V normalmente pone la variable de espera antes de salir, pero la variante BSD no. VASE TAMBIN accept(), connect(), poll(), read(), recv(), send(), write()

perror()

NOMBRE perror muestra un mensaje correspondiente a un error del sistema SINOPSIS #include <stdio.h> void perror(const char *s); #include <errno.h> const char *sys_errlist[]; int sys_nerr; DESCRIPCIN La rutina perror() produce un mensaje que va a la salida estndar de errores, describiendo el ltimo error encontrado durante una llamada al sistema o a ciertas funciones de biblioteca. La cadena de caracteres s que se pasa como argumento, se muestra primero, luego un signo de dos puntos y un espacio en blanco; por ltimo, el mensaje y un salto de lnea. Para ser de ms utilidad, la cadena de caracteres pasada como argumento debera incluir el nombre de la funcin que incurri en el error. El cdigo del error se toma de la variable externa errno, que toma un valor cuando ocurre un error pero no es puesta a cero en una llamada no errnea. Puede utilizarse la lista global de errores sys_errlist[], indexada por errno, para obtener el mensaje del error sin el salto de lnea. El nmero ms alto de mensaje proporcionado en la tabla es sys_nerr 1. Tenga cuidado cuando acceda directamente a esta lista porque puede que no se hayan aadido an nuevos valores de error a sys_errlist[].

Guia de programacin de redes

81

Manuel Jimnez Castro

3 Telecomunicaciones

Cuando una llamada al sistema falla, normalmente devuelve 1 y pone en la variable errno un valor que describe qu fue mal. (Estos valores pueden encontrarse en <errno.h>.) Muchas funciones de biblioteca tambin hacen esto. La funcin perror() sirve para traducir este cdigo de error a una forma que los humanos puedan entender. Observe que errno tiene un valor sin definir tras una llamada exitosa: esta llamada podra muy bien cambiar incluso el valor de esta variable, a pesar de haber acabado bien, por ejemplo porque internamente empleara alguna otra funcin de biblioteca que hubiera fallado. As, si una llamada fallida no es seguida inmediatamente por una llamada a perror, el valor de errno debe guardarse. CONFORME A C ANSI, BSD 4.3, POSIX, X/OPEN VASE TAMBIN strerror()

gettimeofday()

NOMBRE gettimeofday, settimeofday pone u obtiene la hora SINOPSIS #include <sys/time.h> #include <unistd.h> int gettimeofday(struct timeval *tv, struct timezone *tz); int settimeofday(const struct timeval *tv , const struct timezone *tz); DESCRIPCIN gettimeofday y settimeofday pueden poner tanto la hora como una zona horaria. tv es una estructura timeval, tal como se especifica en /usr/include/sys/time.h: struct timeval { long tv_sec; long tv_usec; }; y tz es una estructura timezone : struct timezone { int tz_minuteswest; int tz_dsttime; };

/* segundos */ /* microsegundos */

/* minutos al O de Greenwich */ /* tipo de correcin horaria invierno/verano */

El uso de la estructura timezone es obsoleto; el campo tz_dsttime nunca ha sido utilizado en Linux - no lo ha sido y nunca ser soportado por libc o glibc. Todas y

Guia de programacin de redes

82

Manuel Jimnez Castro

3 Telecomunicaciones

cada una de las ocurrencias de este campo en el fuente del ncleo (distintas de la declaracin) son un fallo. Por tanto, lo siguiente es puramente de inters histrico. El campo tz_dsttime contiene una constante simblica (los valores se dan ms abajo) que indica en qu parte del ao la correcin horaria invierno/verano (Daylight Saving Time, DST, tambin conocida como horario de ahorro energtico) est en vigor. (Nota: su valor es constante a lo largo del ao - no indica si DST est o no en vigor, slo selecciona un algoritmo). Los algoritmos de correccin horaria invierno/verano se definen como sigue: DST_NONE /* no hay */ DST_USA /* estilo EE.UU. */ DST_AUST /* estilo australiano */ DST_WET /* como en Europa Occidental */ DST_MET /* como en Europa Central */ DST_EET /* como en Europa del Este */ DST_CAN /* Canad */ DST_GB /* Gran Bretaa e Irlanda */ DST_RUM /* Rumana */ DST_TUR /* Turqua */ DST_AUSTALT /* est. australiano con cambio en 1986 */ Naturalmente, result ser que el periodo en el que la correccin horaria invierno/verano est en vigor no se poda dar mediante un simple algoritmo, uno por pas: de hecho, este periodo se determina por decisiones polticas impredecibles. Por tanto, este mtodo de representacin de las zonas horarias se ha abandonado. Bajo Linux, en una llamada a settimeofday el campo tz_dsttime debe ser cero. Bajo Linux hay algunas semnticas de Warp clock peculariares asociadas a la llamada al sistema settimeofday si en la primera llamada de todas (despus del arranque) que tenga un argumento tz no NULL, el argumento tv es NULL y el campo tz_minuteswest es no cero. En tal caso, se asume que el reloj CMOS est en el horario local y que tiene que ser incrementado en esta cantidad para obtener el horario del sistema UTC. No cabe duda que usar esta caracterstica es una mala idea. Para operar sobre la estructura timeval se definen las siguientes macros: #define timerisset(tvp)\((tvp)->tv_sec || (tvp)->tv_usec) #define timercmp(tvp, uvp, cmp)\((tvp)->tv_sec cmp (uvp)->tv_sec ||\(tvp)->tv_sec == (uvp)->tv_sec &&\(tvp)->tv_usec cmp (uvp)->tv_usec) #define timerclear(tvp)\((tvp)->tv_sec = (tvp)->tv_usec = 0) Si tv o tz es nulo, la estructura correspondiente no se ajusta ni se devuelve. Solamente el super-usuario puede emplear settimeofday. VALOR DEVUELTO gettimeofday y settimeofday devuelven 0 en caso de xito -1 si ocurre un fallo (en cuyo caso errno toma un valor apropiado). ERRORES EPERM Alguien que no es el super-usuario ha llamado a settimeofday EINVAL La zona horaria (o algo ms) es invlida. EFAULT

Guia de programacin de redes

83

Manuel Jimnez Castro

3 Telecomunicaciones

Uno de tv o tz apuntaba afuera de su espacio de direcciones accesible. CONFORME A SVr4, BSD 4.3 VASE TAMBIN date(), adjtimex(), time(), ctime(), ftime()

7.2. Libros
Si buscas libros autnticos que se pueden tocar revisa alguna de las excelentes guas que hay a continuacin. Unix Network Programming, volumes 1-2 de W. Richard Stevens. Editado por Prentice Hall. Internetworking with TCP/IP, volumes I-III de Douglas E. Comer y David L. Stevens. Editado por Prentice Hall. TCP/IP Illustrated, volumes 1-3 de W. Richard Stevens y Gary R. Wright. Editado por Addison Wesley. TCP/IP Network Administration de Craig Hunt. Editado por O'Reilly & Associates, Inc. . Advanced Programming in the UNIX Environment de W. Richard Stevens. Editado por Addison Wesley. Using C on the UNIX System de David A. Curry. Editado por O'Reilly & Associates, Inc.

7.3. Referencias en la web


En la web: BSD Sockets: A Quick And Dirty Primer (Contiene adems informacin de otros aspectos de la programacin en UNIX!) http://www.cs.umn.edu/~bentlema/unix/ The Unix Socket FAQ http://www.ibrado.com/sock-faq/ Client-Server Computing http://pandonia.canberra.edu.au/ClientServer/

Guia de programacin de redes

84

Manuel Jimnez Castro Intro to TCP/IP (gopher)

3 Telecomunicaciones

gopher://gopher-chem.ucdavis.edu/11/Index/Internet_aw/Intro_the_Internet/intro.to.ip/ Internet Protocol Frequently Asked Questions http://www-iso8859-5.stack.net/pages/faqs/tcpip/tcpipfaq.html The Winsock FAQ http://tangentsoft.net/wskfaq/

7.4. RFCs
RFCs --La verdadera materia: http://www.rfc-editor.org/ RFC-768 --El Protocolo de Datagramas de Usuario (UDP) http://www.rfc-editor.org/rfc/rfc768.txt RFC-791 --El protocolo de Internet (IP) http://www.rfc-editor.org/rfc/rfc791.txt RFC-793 --El Protocolo de Control de Transmisin (TCP) http://www.rfc-editor.org/rfc/rfc793.txt RFC-854 --El Protocolo Telnet http://www.rfc-editor.org/rfc/rfc854.txt RFC-951 --El Protocolo de Arranque [Bootstrap] (BOOTP) http://www.rfc-editor.org/rfc/rfc951.txt RFC-1350 --El Protocolo Trivial de Transferencia de Archivos. (TFTP) http://www.rfc-editor.org/rfc/rfc1350.txt

8. Preguntas ms comunes
Q: De dnde saco esos archivos de cabecera? Q: Qu hago cuando bind() responde "Address already in use" [La direccin ya se est usando]? Q: Cmo puedo obtener una lista de los sockets abiertos en el sistema?

Guia de programacin de redes

85

Manuel Jimnez Castro

3 Telecomunicaciones

Q: Cmo se ve la tabla de encaminamiento? Q: Cmo puedo ejecutar el servidor y el cliente si solamente tengo un ordenador? No necesito una red para escribir programas de redes? Q: Cmo puedo saber si el sistema remoto ha cerrado la conexin? Q: Cmo se implementa la utilidad "ping"? Qu es ICMP? Dnde puedo averiguar ms cosas sobre los sockets puros [raw sockets]? Q: Cmo compilo sobre Windows ? Q: Cmo compilo sobre Solaris/SunOS? Todava tengo errores al enlazar! Q: Porque termina select() al recibir una seal? Q: Cmo puedo implementar un temporizador [timeout] en una llamada a recv() ? Q: Cmo puedo comprimir o encriptar los datos antes the enviarlos al socket? Q: Qu es eso de " PF_INET" tiene algo que ver con AF_INET ? Q: Cmo puedo escribir un servidor que acepte comandos de shell de un cliente y los ejecute? Q: Estoy enviando un montn de datos, pero cuando hago recv(), slo recibo 536 bytes 1460 bytes a la vez. Sin embargo, si ejecuto en mi mquina local recibo todos los datos al mismo tiempo. Qu pasa? Q: Yo uso Windows y no tengo la llamada al sistema fork() ni ningn tipo de estructura struct sigaction. Qu hago? Q: Cmo puedo enviar datos seguros con TCP/IP usando encriptacin? Q: Estoy detrs de un cortafuegos [firewall]--Cmo informo a la gente al otro del cortafuegos de cual es mi direccin IP para que puedan conectarse a mi mquina? Q: De dnde saco esos archivos de cabecera? A: Si no estn ya en tu sistema, probablemente no los necesitas. Consulta el manual de tu plataforma. Si ests compilando en Windows slo necesitas incluir #include <winsock.h>. Windows es tan genial que solo necesitas incluir este fichero!!! Q: Qu hago cuando bind() responde "Address already in use" [La direccin ya se est usando]? A: Tienes que usar setsockopt() con la opcin SO_REUSEADDR sobre el socket en el que ests escuchando ( listen() ). Consulta la seccin sobre bind() y la seccin sobre select() para ver un ejemplo. Q: Cmo puedo obtener una lista de los sockets abiertos en el sistema? A: Usa netstat . Revisa la pgina man para conocer a fondo los detalles, pero deberas tener un resultado aceptable con slo teclear:
$ netstat

El nico truco consiste en averiguar qu socket est asociado con qu programa. :-)

Guia de programacin de redes

86

Manuel Jimnez Castro Q: Cmo se ve la tabla de encaminamiento?

3 Telecomunicaciones

A: Usa el comando route (En la mayora de Linuxes lo encontrars en /sbin) o el comando netstat -r. Q: Cmo puedo ejecutar el servidor y el cliente si solamente tengo un ordenador? No necesito una red para escribir programas de redes? A: Afortunadamente para ti, no. Virtualmente todas las mquinas implementan un "dispositivo" de red de cierre de circuito [loopback] que reside en el ncleo y simula ser una tarjeta de red. (Este es el interfaz que se lista como "lo" en la tabla de encaminamiento.) Supn que has iniciado sesin en una mquina que se llama "goat". Ejecuta el cliente en una ventana y el servidor en otra. O inicia el servidor en segundo plano [background] (escribe "servidor &") y ejecuta el cliente en la misma ventana. Como consecuencia del dispositivo de cierre de circuito puedes sencillamente ejecutar cliente goat o cliente localhost (puesto que "localhost", muy probablemente, est definido en tu fichero /etc/hosts ) y tu cliente estar hablando de inmediato con el servidor. En resumen, no es necesario cambiar nada para que el cdigo funcione en una sola mquina sin conexin a la red! Magnfico! Q: Cmo puedo saber si el sistema remoto ha cerrado la conexin? A: Lo sabes porque recv() devuelve 0. Q: Cmo se implementa la utilidad "ping"? Qu es ICMP? Dnde puedo averiguar ms cosas sobre los sockets puros [raw sockets]? A: Todas tus dudas referidas a sockets puros tienen respuesta en los libros de programacin UNIX en redes de W. Richard Stevens. Consulta la seccin de libros de esta gua. Q: Cmo compilo sobre Windows ? A: Como Windows es el mejor sistema operativo que existe slo has de seguir las recomendaciones de la seccin sobre compilacin en Windows de la introduccin. Q: Cmo compilo sobre Solaris/SunOS ? Todava tengo errores al enlazar! A: Los errores de enlace suceden porque las mquinas Sun no enlazan automticamente las bibliotecas de sockets. Consulta la seccin sobre compilacin en Solaris/SunOS de la introduccin, donde hallars un ejemplo de cmo hacerlo. Q: Por qu termina select() al recibir una seal? A: Las seales suelen causar que las llamadas al sistema que estn bloqueadas devuelvan el valor -1 con errno establecifo a EINTR. Cuando preparas un manejador de

Guia de programacin de redes

87

Manuel Jimnez Castro

3 Telecomunicaciones

seales con sigaction() , puedes establecer el indicador SA_RESTART, que se supone que reiniciar la llamada al sistema que fue interrumpida. Naturalmente, esto no siempre funciona. Mi solucin favorita a esto, requiere el uso de una sentencia goto. Sabes que esto irrita a tus profesores sobremanera, as que al ataque!

select_restart: if ((err = select(fdmax+1, &readfds, NULL, NULL, NULL)) == -1) { if (errno == EINTR) { /* Alguna seal nos ha interrumpido, as que regresemos a select() */ goto select_restart; /* NO uses goto */ } /* Los errores reales de select() se manejan aqu: */ perror("select"); }

Por supuesto que, en este caso, no necesitas una sentencia goto; puedes usar otras estructuras de control. Pero creo que goto es una solucin ms limpia. Q: Cmo puedo implementar un temporizador [timeout] en una llamada a recv()? A: Usa select() ! Te permite indicar un parmetro de control de tiempo sobre los descriptores de socket en los que esperas leer. Tambin puedes implementar toda esa funcionalidad en una nica funcin como esta:

#include #include #include #include

<unistd.h> <sys/time.h> <sys/types.h> <sys/socket.h>

int recvtimeout(int s, char *buf, int len, int timeout) { fd_set fds; int n; struct timeval tv; /* Construir el conjunto de descriptores de fichero */ FD_ZERO(&fds); FD_SET(s, &fds); /* Construir la estructura timeval del temporizador */ tv.tv_sec = timeout; tv.tv_usec = 0; /* Esperar hasta que se reciban datos o venza el temporizador */ n = select(s+1, &fds, NULL, NULL, &tv); if (n == 0) return -2; /* El temporizador ha vencido! */ if (n == -1) return -1; /* error */

Guia de programacin de redes

88

Manuel Jimnez Castro

3 Telecomunicaciones

/*Los datos deben estar ah, as que llamo normalmente a recv() */ return recv(s, buf, len, 0); } /* Ejemplo de llamada a recvtimeout(): */ . . n = recvtimeout(s, buf, sizeof(buf), 10); /* 10 segundos de espera */ if (n == -1) { /* ocurri un error */ perror("recvtimeout"); } else if (n == -2) { /* El temporizador venci */ } else { /* Hay datos en el buffer */ } . .

Nota que recvtimeout() devuelve -2 en caso de que venza el temporizador. Por qu no devolver 0? Bueno, si recuerdas un valor de retorno de 0 para una llamada a recv() significa que la mquina remota ha cerrado la conexin. As que ese valor de retorno ya tiene un significado. El valor -1 significa "error", as que escog -2 como mi indicador de temporizador vencido. Q: Cmo puedo comprimir o encriptar los datos antes de enviarlos al socket? A: Una forma sencilla de encriptar es usar SSL (secure sockets layer), pero eso va ms all del alcance de esta gua. Pero suponiendo que quieres implementar tu propio sistema de compresin o encriptado, slo es cuestin de pensar que tus datos siguen una secuencia de etapas entre ambos extremos. Cada paso altera los datos en cierta manera. 1. El servidor lee datos de un fichero (o de donde sea) 2. El servidor encripta los datos (t aades esta parte) 3. El servidor enva ( send() ) los datos encriptados Y ahora el camino inverso: 4. El cliente recibe ( recv() ) los datos encriptados 5. El cliente desencripta los datos (t aades esta parte) 6. El cliente escribe los datos en un archivo (o donde sea) Podras comprimir los datos en el punto en que arriba se encritan/desencriptan los datos. O podras hacer las dos cosas!. Sencillamente recuerda comprimir antes de encriptar.

Guia de programacin de redes

89

Manuel Jimnez Castro

3 Telecomunicaciones

En la medida en que el cliente deshaga correctamente lo que el servidor haga, los datos llegarn correctamente al final, sin importar cuntos pasos intermedios se aadan. As que todo lo que necesitas para usar mi cdigo es encontrar el lugar situado entre la lectura y el envo ( send() ) de los datos, y poner all el cdigo que se encarga de realizar el encriptado. Q: Qu es eso de " PF_INET" ? Tiene algo que ver con AF_INET ? A: Por supuesto que s. Revisa la seccin socket() para los detalles. Q: Cmo puedo escribir un servidor que acepte comandos de shell de un cliente y los ejecute? A: Para simplificar las cosas, digamos que el cliente conecta ( connect() ), enva ( send() ), y cierra ( close() ) la conexin (es decir no hay llamadas al sistema posteriores sin que el cliente vuelva a conectar.) El proceso que sigue el cliente es este: 1. connect() con el servidor 2. send("/sbin/ls > /tmp/client.out") 3. close() la conexin Mientras tanto el servidor procesa los datos de esta manera: 1. 2. 3. 4.
accept() la conexin del cliente recv(str) el comando a ejecutar close() la conexin system(str) ejecuta el comando

Cuidado! Dejar que el servidor ejecute lo que el cliente dice es como dar acceso remoto al shell y la gente podra hacer cosas no deseadas con tu cuenta cuando se conectan al servidor. Por ejemplo, en el ejemplo anterior, qu pasa si el cliente enva "rm -rf * "? Borra todo lo que tengas en tu cuenta, eso es lo que pasa! As que aprendes la leccin e impides que el cliente use cualquier comando, a excepcin de un par de utilidades que sabes que son seguras, como la utilidad foobar :

if (!strcmp(str, "foobar")) { sprintf(sysstr, "%s > /tmp/server.out", str); system(sysstr); }

Desgraciadamente, todava no ests seguro: qu pasa si el cliente enva "foobar; rm -rf ~ "? Lo mejor que se puede hacer es escribir una pequea rutina que ponga un carcter de escape("\") delante de cualquier carcter no alfanumrico (incluyendo espacios, si es necesario) en los argumentos para el comando.

Guia de programacin de redes

90

Manuel Jimnez Castro

3 Telecomunicaciones

Como puedes ver, la seguridad es un problema bastante importante cuando el servidor comienza a ejecutar cosas que el cliente enva. Q: Estoy enviando un montn de datos, pero cuando hago recv(), slo recibo 536 bytes 1460 bytes a la vez. Sin embargo, si ejecuto en mi mquina local recibo todos los datos al mismo tiempo. Qu pasa? A: Te ests tropezando con la MTU (Maximum Transfer Unit)--El tamao mximo de paquete que el medio fsico puede manejar. En la mquina local ests usando el dispositivo de cierre de circuito [loopback] que puede manejar sin problemas 8K o ms. Pero en una Ethernet, que slo puede manejar 1500 bytes con una cabecera, tienes que plegarte a ese lmite. Sobre un mdem, con una MTU de 576 (con cabecera), has de plegarte a un lmite an menor. En primer lugar tienes que asegurarte de que todos los datos se estn enviando. (Revisa la implementacin de la funcin sendall() para ver los detalles). Una vez que ests seguro de eso, tienes que llamar a recv() en un bucle hasta que todos los datos se hayan ledo. Revisa la seccin Consecuencias de la encapsulacin de datos para los detalles acerca de como recibir paquetes completos de datos usando mltiples llamadas a recv(). Q: Yo uso Windows y no tengo la llamada al sistema fork() ni nign tipo de estructura struct sigaction . Qu hago? A: De estar en algn sitio estarn en las bibliotecas POSIX que quizs hayan venido con tu compilador. En realidad no puedo darte la respuesta, pero creo recordar que Microsoft tiene una capa de compatibilidad POSIX , y ah es donde fork() tendra que estar (y a lo mejor tambin sigaction.) Busca "fork" o "POSIX" en la ayuda de VC++, por si te da alguna pista. Si no hay forma de que funcione, olvdate de fork()/sigaction y usa en su lugar la funcin equivalente de Win32: CreateProcess(). No s cmo se usa CreateProcess() --Tiene muchos argumentos, pero seguramente est explicada en la ayuda de VC++ Q: Cmo puedo enviar datos seguros con TCP/IP usando encriptacin? A: Visita el Proyecto OpenSSL. Q: Estoy detrs de un cortafuegos [firewall]--Cmo informo a la gente del otro lado del cortafuegos de cual es mi direccin IP para que puedan conectarse a mi mquina? A: Desgraciadamente, la finalidad de un cortafuegos es evitar que la gente del otro lado del cortafuegos pueda conectarse a las mquinas de este lado del cortafuegos, as que permitir que lo hagan se considera en principio una brecha de seguridad. No es que diga que todo est perdido. Por una parte, todava puedes conectar ( connect() ) a travs del cortafuegos si ste est realizando algn tipo de enmascaramiento [masquerading] o NAT [Network Address Translation - Traduccin Guia de programacin de redes 91

Manuel Jimnez Castro

3 Telecomunicaciones

de direcciones de red] o algo por el estilo. Tan slo tienes que disear tus programas de modo que seas t siempre quien inicia la conexin, y todo ir bien. Si esta solucin no es satisfactoria, puedes pedir al administrador de tu sistema que practique un agujero en el cortafuegos de modo que la gente pueda conectarse contigo. El cortafuegos puede reenviarte sus datos, bien mediante el software NAT, bien mediante un proxy o algo similar. Ten en cuenta que un agujero en el cortafuegos no es algo que pueda uno tomarse a la ligera. Tienes que estar muy seguro de que no das acceso a la red interior a personas con malas intenciones; si eres un principiante, hacer software seguro es mucho ms difcil de lo que puedas imaginar. No hagas que el administrador de tu sistema se enfade conmigo.

9. Declinacin de responsabilidad y solicitud de ayuda


Bien, esto es todo. Con suerte, por lo menos parte de la informacin contenida en este documento habr sido remotamente exacta, y sinceramente espero que no haya errores muy descarados. Bueno, seguro, siempre los hay. As que esto te sirva de advertencia! Lamento mucho que alguna de las imprecisiones que has encontrado te haya causado problemas, pero sencillamente no puedes culparme. Legalmente hablando, no me responsabilizo de una sola de las palabras de este documento. Todo l podra ser completamente errneo. Aunque probablemente no es ese el caso. Al fin y al cabo, he invertido un montn de horas marendome con todo esto, y en el trabajo he implementado varias aplicaciones de red TCP/IP. Adems, he escrito los ncleos de algunos juegos multiusuario, y algunas cosas ms. Pero no soy el dios de los sockets. Solamente soy un to. Por cierto, si alguien tiene alguna crtica constructiva (o destructiva) sobre este documento, por favor que la enve a <putocisneros@hotmail.com > y tratar de hacer un esfuerzo para corregirla. Por si acaso te preguntas por qu hice esto, bueno, lo hice por dinero. Ha! en realidad no. Lo hice porque un montn de gente me preguntaba cosas acerca de los sockets, y cuando les deca que haba estado pensando en ponerlo todo junto en una pgina de sockets, me decan, "estupendo!" Adems, me pareca que todo este conocimiento que tanto me ha costado adquirir iba a desperdiciarse si no poda compartirlo con otros. La web solamente es el vehculo perfecto. Animo a los dems a suministrar informacin parecida en la medida en que sea posible. Basta de charlas--volved al trabajo!

Guia de programacin de redes

92

También podría gustarte