Guia Programacion Sockets

92
Manuel Jiménez Castro 3ª Telecomunicaciones Guía de Programación en Redes Uso de sockets de Internet Tabla de Contenidos 1. Introducción 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. Política de respuesta a correos electrónicos 1.7. Replicación 1.8. Nota para traductores 1.9. Copyright y distribución 2. ¿Qué es un socket? 2.1. Dos tipos de Sockets de Internet 2.2. Tonterías de bajo nivel y teoría de redes 3. struct s y manipulación 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() --¡Háblame, baby! 4.7. sendto() y recvfrom() --Háblame al estilo DGRAM 4.8. close() y shutdown() --¡Fuera de mi vista! 4.9. getpeername() --¿Quién eres tú? 4.10. gethostname() --¿Quién 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. Técnicas moderadamente avanzadas 6.1. Bloqueo 6.2. select() --Multiplexado de E/S síncrona 6.3. Gestión de envíos parciales con send() 6.4.Consecuencias de la encapsulación de datos 7. Referencias adicionales 7.1. Páginas del manual (man) 7.2. Libros 7.3. Referencias en la web 7.4. RFCs 8. Preguntas más comunes Guia de programación de redes 1

Transcript of Guia Programacion Sockets

Page 1: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Guía de Programación en Redes Uso de sockets de Internet Tabla de Contenidos 1. Introducción

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. Política de respuesta a correos electrónicos 1.7. Replicación 1.8. Nota para traductores 1.9. Copyright y distribución

2. ¿Qué es un socket? 2.1. Dos tipos de Sockets de Internet 2.2. Tonterías de bajo nivel y teoría de redes

3. structs y manipulación 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()--¡Háblame, baby! 4.7. sendto() y recvfrom()--Háblame al estilo DGRAM 4.8. close() y shutdown()--¡Fuera de mi vista! 4.9. getpeername()--¿Quién eres tú? 4.10. gethostname()--¿Quién 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. Técnicas moderadamente avanzadas 6.1. Bloqueo 6.2. select()--Multiplexado de E/S síncrona 6.3. Gestión de envíos parciales con send() 6.4.Consecuencias de la encapsulación de datos

7. Referencias adicionales 7.1. Páginas del manual (man) 7.2. Libros 7.3. Referencias en la web 7.4. RFCs

8. Preguntas más comunes

Guia de programación de redes 1

Page 2: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

9. Declinación de responsabilidad y solicitud de ayuda

1. Introducción ¡Eh!, ¿la programación de sockets te quita el sueño? ¿Todo esto es demasiado complicado para desentrañarlo sólo con las páginas 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 información con todo el mundo. Has venido al lugar adecuado. Este documento debería 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 guía de referencia. Probablemente, es de máxima utilidad para personas que están empezando a programar con sockets y buscan donde apoyarse. No es, en absoluto, la guía completa de programación de sockets.

Sin embargo, con suerte será suficiente para que esas páginas de man empiecen a cobrar sentido... :-)

1.2. Plataforma y compilador El código que se muestra en este documento se compiló en un PC con Linux usando el compilador gcc de Gnu. Sin embargo, debería poder compilarse en prácticamente cualquier otra plataforma que use gcc. Evidentemente, esto no te sirve si programas para Windows--consulta más abajo la sección sobre programación en Windows .

1.3. Sitio web Oficial Todavía no tengo página 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 línea de comandos para enlacer las bibliotecas adecuadas. Para conseguirlo simplemente añade " -lnsl -lsocket -lresolv" al final del comando de compilación, como sigue:

$ cc -o server server.c -lnsl -lsocket -lresolv

Guia de programación de redes 2

Page 3: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Si todavía tienes errores puedes probar a añadir " -lxnet" al final de la línea de comandos. No sé qué es lo que hace exactamente, pero algunas personas parecen necesitarlo para compilar correctamente.

Otro lugar donde podrías 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 máquina Sun no he probado ninguna de la información anterior--sólamente es lo que la gente me ha comentado por correo electrónico.

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 más de la superioridad de Windows frenta a Linux/Unix. También tienes que ejecutar WSAStartup() antes de hacer nada con la biblioteca de sockets. El código 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); } }

También 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 pestaña Link y

Guia de programación de redes 3

Page 4: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

busca el cuadro titulado "Object/library modules". Añade "wsock32.lib " a esa lista.

Al menos eso es lo que he oído.

Por último, necesitas ejecutar WSACleanup() cuando hayas terminado con la biblioteca de sockets. Consulta la ayuda en línea para los detalles.

Una vez hecho esto, el resto de ejemplos de este tutorial deberían 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() sólo funciona con descriptores de sockets pero no con descriptores de ficheros (como 0 para stdin).

También hay una clase socket que puedes utilizar, Csocket. Consulta la ayuda de tu compilador para más información

Para más información acerca de Winsock lee el FAQ de Winsock .

Finalmente, he oído que Windows no tiene la llamada al sistema fork() que, desgraciadamente, uso en algunos de mis ejemplos. Quizás debas enlazar también 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 estás dispuesto a lidiar con todos ellos, CreateThread() es un poco más fácil de digerir... Desgraciadamente, una explicación en profundidad de las técnicas multihebra (multithreading) va mucho más allá del alcance de este documento. ¡No puedo hablar de todo, sabes!

1.6. Política de respuesta a correos electrónicos En general estoy abierto a ayudar con tus preguntas por correo electrónico, 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; sólo es que nunca tendré tiempo para darte la respuesta tan detallada que necesitas.

Como norma general, cuanto más complicada sea tu pregunta, menos probable es que yo responda. Si puedes acotar tu pregunta antes de enviarla y asegurarte de incluir cualquier información que pueda ser de cierta relevancia (plataforma, compilador, mensajes de error que tienes, y cualquier otra cosa que pienses que puede ayudar) es mucho más probable que te responda. Si quieres más pistas, lee el documento de ESR: Cómo hacer preguntas inteligentes

Si no, trabájalo un poco más, trata de encontrar la respuesta, y si el problema se te sigue resistiendo escríbeme otra vez con la información 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 gustaría que supieras que de verdad aprecio todos los cumplidos que esta guía ha

Guia de programación de redes 4

Page 5: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

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

1.7. Replicación Es una excelente idea si quieres replicar este sitio, bien sea en público o en privado. Si replicas este sitio en público y quieres que añada un enlace desde la página principal escríbeme a: <[email protected] >.

1.8. Nota para traductores Si quieres traducir esta guía a otra idioma, escríbeme a <[email protected]> y añadiré un enlace a tu traducción desde la página principal.

No dudes en añadir tu nombre y tu dirección de correo a la traducción.

1.9. Copyright y distribución Esta guía 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 guía a sus estudiantes.

Esta guía puede traducirse con total libertad a cualquier idioma, siempre y cuando la traducción sea precisa y la guía se reproduzca íntegramente. La traducción podrá incluir también el nombre y la dirección de contacto del traductor.

El código fuente C que se presenta en el documento se otorga al dominio público.

Contacta con <[email protected] > para más información.

2. ¿Qué es un socket? Continuamente oyes hablar de los "sockets", y tal vez te estás preguntando qué es lo que son exactamente. Bien, esto es lo que son: una forma de comunicarse con otros programas usando descriptores de fichero estándar de Unix.

¿Qué?

Vale--puede que hayas oído a algún 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 más que un entero asociado a un archivo abierto. Pero (y aquí está el quid de la cuestión) ese fichero puede ser una conexión 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 programación de redes 5

Page 6: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

otro programa a través de Internet vas a tener que hacerlo con un descriptor de fichero, es mejor que lo creas.

"¿De dónde saco yo este descriptor de fichero para comunicar a través de la red, señor 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 través de un socket ." La respuesta corta es, "¡Sí que puedes!" La respuesta larga es, "Puedes usarlas, pero send() y recv() te ofrecen mucho más control sobre la transmisión 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 más en función de la modalidad de Unix que estés 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 más pero no quería asustarte. Aquí sólo voy a hablar de dos tipos. A excepción de esta frase, en la que voy a mencionar que los sockets puros [Raw Sockets] son también muy potentes y quizás quieras buscarlos más adelante.

Muy bien. ¿Cuáles 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 también "sockets sin conexión". (Aunque se puede usar connect() con ellos, si se quiere. Consulta connect () , más abajo.)

Los sockets de flujo definen flujos de comunicación en dos direcciones, fiables y con conexión. Si envías dos ítems a través del socket en el orden "1, 2" llegarán al otro extremo en el orden "1, 2", y llegarán sin errores. Cualquier error que encuentres es producto de tu extraviada mente, y no lo vamos a discutir aquí.

¿Qué aplicación tienen los sockets de flujo? Bueno, quizás has oído hablar del programa telnet. ¿Sí? telnet usa sockets de flujo. Todos los carácteres que tecleas tienen que llegar en el mismo orden en que tú los tecleas, ¿no? También los navegadores, que usan el protocolo HTTP , usan sockets de flujo para obtener las páginas. De hecho, si haces telnet a un sitio de la web sobre el puerto 80, y escribes " GET / ", recibirás como respuesta el código HTML.

¿Cómo consiguen los sockets de flujo este alto nivel de calidad en la transmisión de datos? Usan un protocolo llamado "Protocolo de Control de Transmisión", más

Guia de programación de redes 6

Page 7: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

conocido como "TCP" (consulta el RFC-793 para información extremadamente detallada acerca de TCP). TCP asegura que tu información llega secuencialmente y sin errores. Puede que hayas oído antes "TCP" como parte del acrónimo " TCP/IP ", donde "IP" significa "Protocolo de Internet" (consulta el RFC-791 .) IP se encarga básicamente del encaminamiento a través 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 conexión? ¿De qué va esto, en definitiva, y por qué no son fiables? Bueno, estos son los hechos: si envías un datagrama, puede que llegue. Puede que llegue fuera de secuencia. Si llega, los datos que contiene el paquete no tendrán errores.

Los sockets de datagramas también 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 conexión? Bueno, básicamente porque no tienes que mantener una conexión abierta como harías con los sockets de flujo. Simplemente montas un paquete, le metes una cabecera IP con la información de destino y lo envías. No se necesita conexión. Generalmente se usan para transferencias de información por paquetes. Aplicaciones que usan este tipo de sockets son, por ejemplo, tftp y bootp.

"¡Basta!" puede que grites. "¿Cómo pueden siquiera funcionar estos programas si los datagramas podrían 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. Tonterías de bajo nivel y teoría de redes Puesto que acabo de mencionar la disposición 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 práctica puedes saltarte esta sección. Sin embargo no está mal como culturilla.

Figura 1. Encapsulación de datos.

¡Eh tíos, es hora de aprender algo sobre Encapsulación de datos ! Esto es muy, muy importante. Tan importante que podrías aprenderlo si te matricularas en el curso de redes aquí, en el estado de Chico ;-). Básicamente 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 programación de redes 7

Page 8: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

otra vez por el siguiente (IP) y otra vez por el protocolo final o nivel (físico) del hardware (por ejemplo, Ethernet)

Cuando otro ordenador recibe el paquete, el hardware retira la cabecera Ethernet, el núcleo [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 cómo los datos se transmiten físicamente (serie, thin ethernet, AUI, lo que sea) porque los programas en los niveles más bajos se ocupan de eso por ti. El hardware y la topología real de la red son transparentes para el programador de sockets.

Sin más preámbulos, te presento los niveles del modelo completo. Recuerda esto para tus exámenes de redes:

• Aplicación • Presentación • Sesión • Transporte • Red • Enlace de datos • Físico

El nivel físico es el hardware (serie, Ethernet, etc.) El nivel de aplicación está tan lejos del nivel físico como puedas imaginar--es el lugar donde los usarios interactúan con la red.

Sin embargo, este modelo es tan general que si quisieras podrías usarlo como una guía para reparar coches. Un modelo de niveles más consistente con Unix podría ser:

• Nivel de aplicación (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 encapsulación de los datos originales.

¿Ves cuánto trabajo se necesita para construir un solo paquete? ¡Dios! ¡Y tienes que teclear la cabecera de los paquetes tú mismo, usando "cat"! Sólo 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 según el método de tu elección y enviarlo usando sendto(). El núcleo implementa los niveles de Internet y de Transporte por ti, y el hardware el nivel de acceso a la red. Ah, tecnología moderna.

Así finaliza nuestra breve incursión en la teoría de redes. Ah, olvidaba contarte todo lo que quería decir acerca del encaminamiento: ¡nada! Exacto, no voy a hablar de él en

Guia de programación de redes 8

Page 9: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

absoluto. el encaminador retira la información 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 sobrevivirás.

3. structs y manipulación de datos Bueno, aquí estamos por fin. Es hora de hablar de programación. En esta sección 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 difíciles de digerir.

Empecemos por el fácil: un descriptor de socket. Un descriptor de socket es del tipo siguiente:

int

Nada más que un int.

A partir de aquí las cosas se complican, así que leételo todo y ten paciencia conmigo. Debes saber, para empezar, que existen dos formas distintas de ordenación de bytes (a veces se les llama "octetos"): primero el byte más significativo, o primero el byte menos significativo. A la primera forma se la llama "Ordenación de bytes de la red" [Network Byte Order]. Algunos ordenadores almacenan internamente los números según la Ordenación de bytes de la red, mientras que otros no. Cuando diga que algo tiene que seguir la Ordenación de bytes de la red, tendrás que llamar a alguna función (como por ejemplo htons() ) para realizar la conversión desde la "Ordenación de bytes de máquina" [Host Byte Order]. Si no digo "Ordenación de bytes de la red", entonces puedes dejarlo en la Ordenación de bytes de máquina.

(Para los que sientan curiosidad, la "Ordenación de bytes de la red" también se conoce como ordenación Big-Endian.)

Mi Primer StructTM--struct sockaddr. Esta estructura mantiene información de direcciones de socket para diversos tipos de sockets:

struct sockaddr { unsigned short sa_family; /* familia de direcciones, AF_xxx */ char sa_data[14]; /* 14 bytes de la dirección del protocolo */ };

sa_family admite varios valores, pero será AF_INET para todo lo que hagamos en este documento. sa_data contiene una dirección y número de puerto de destino para el socket. Esto resulta bastante farrogoso, porque no resulta nada agradable tener que empaquetar a mano una dirección y un número de puerto dentro de sa_data .

Guia de programación de redes 9

Page 10: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Por eso, para manejar struct sockaddr más cómodamente, los programadores han creado una estructura paralela: struct sockaddr_in ("in" por "Internet".)

struct sockaddr_in { short int sin_family; /* familia de direcciones, AF_INET */ unsigned short int sin_port; /* Número de puerto */ struct in_addr sin_addr; /* Dirección de Internet */ unsigned char sin_zero[8]; /* Relleno para preservar el tamaño original de struct sockaddr */ };

Esta estructura hace más sencillo referirse a los elementos de la dirección de socket. Observa que sin_zero (que se incluye para que la nueva estructura tenga el mismo tamaño que un struct sockaddr ) debe rellenarse todo a ceros usando la función memset(). Además, 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 también que el sin_family se corresponde con el sa_family de struct sockaddr y debe asignársele siempre el valor " AF_INET". Por último, sin_port y sin_addr tienen que seguir la Ordenación de bytes de la red.

"Pero", podrías preguntar ahora, "¿cómo puede toda la estructura struct in_addr sin_addr, seguir la Ordenación de bytes de la red?" Esta pregunta requiere un cuidadoso examen de la estructura struct in_addr , una de las peores uniones que existen:

/* Dirección de Internet (una estructura por herencia histórica)*/ 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 asignándole el tipo struct sockaddr_in, entonces ina.sin_addr.s_addr se refiere a la dirección IP de 4 bytes (según la Ordenación de bytes de la red). Observa que, aunque tu sistema use todavía esa aberrante union para el struct in_addr, sigue siendo posible referirse a la dirección IP de 4 bytes de la misma manera en que yo lo hice antes (debido a algunos #defineS).

Guia de programación de redes 10

Page 11: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

3.1. ¡Convierte a valores nativos! Todo lo anterior nos lleva a lo que se trata en esta sección. Hemos hablado mucho acerca de la conversión entre la Ordenación de máquina y la Ordenación de la red: ¡Ahora es el momento para la acción!

Muy bien. Existen dos tipos sobre los cuales podemos aplicar la conversión: short (dos bytes) y long (cuatro bytes ). Las funciones de conversión también funcionan con las respectivas versiones unsigned de short y long. Imagina que quieres convertir un short desde la Ordenación de máquina [Host Byte Order] a la Ordenación de la red [Network byte order]. Empieza con una "h" de "host", síguela 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 máquina a short de la red")

Casi resulta demasiado sencillo...

Puedes usar cualquier combinación de "n", "h", "s" y "l" (de long ), sin contar las absurdas. Por ejemplo, NO hay ninguna función stolh() ("Short to Long Host" -- short de máquina a long de máquina). Por lo menos no la hay en lo que a nosotros nos concierne. Sin embargo sí que existen:

• htons() -- "Host to Network Short " (short de máquina a short de la red) • htonl() -- "Host to Network Long" (long de la máquina a long de la red) • ntohs() -- "Network to Host Short " (short de la red a short de máquina) • ntohl() -- "Network to Host Long" (long de la red a long de máquina)

Ahora, puedes creer que le estás cogiendo el truco a esto. Podrías pensar, "¿Qué pasa si tengo que cambiar la Ordenación de bytes de un char ?", entonces podrías pensar, "Bueno, en realidad no importa". También podrías pensar que, puesto que tu máquina 68000 ya sigue la Ordenación de bytes de la red, no necesitas llamar a htonl() sobre tus direcciones IP. Tendrías razón, PERO si intentas portar tu código a una máquina que siga la ordenación contraria tu programa fallará. ¡Sé portable! ¡Este es un mundo Unix! (Tanto como a Bill Gates le gustaría que no lo fuera). Recuerda: dispón tus bytes según la Ordenación de bytes de la red antes de ponerlos en la red.

Una cuestión final: ¿por qué sin_addr y sin_port necesitan seguir la Ordenación de bytes de la red, pero sin_family no, estando todos en la misma estructura struct sockaddr_in? La razón es que sin_addr y sin_port se encapsulan en un paquete en los niveles IP y UDP, respectivamente. Por eso, deben seguir la Ordenación de bytes de la red. Por contra, el núcleo solamente utiliza el campo sin_family para determinar qué tipo de dirección contiene la estructura, así que debe seguir la Ordenación de bytes de máquina. Además, como sin_family no se envía a través de la red, puede preservar la Ordenación de máquina.

Guia de programación de redes 11

Page 12: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

3.2. Direcciones IP y como tratarlas Afortunadamente para ti, hay un montón de funciones que te permiten manejar direcciones IP. No hay necesidad de complicarse usando el operador << sobre un long, ni cosas así.

Para empezar, supón que tienes una estructura struct sockaddr_in ina , y que quieres guardar en ella la dirección IP " 10.12.110.57 ". La función que necesitas usar, inet_addr(), convierte una dirección IP dada en la notación de cifras y puntos en un unsigned long . La asignación se puede hacer así:

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

Fíjate en que inet_addr() ya devuelve la dirección según la Ordenación de bytes de la red--no necesitas llamar a htonl(). ¡Magnífico!

Sin embargo, el fragmento de código de arriba no es demasiado robusto porque no se hace ninguna comprobación de errores. inet_addr() devuelve el valor -1 en caso de error. ¿Recuerdas los números binarios? ¡Resulta que (unsigned) -1 se corresponde con la dirección IP 255.255.255.255! La dirección de difusión. Malo. Recuerda comprobar adecuadamente las condiciones de error.

La verdad es que hay una interfaz aún más 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 continuación un ejemplo de cómo se usa al construir una estructura struct sockaddr_in (entenderás mejor el ejemplo cuando llegues a las secciones sobre bind () y connect() .)

struct sockaddr_in my_addr; my_addr.sin_family = AF_INET; /* Ordenación de máquina */ my_addr.sin_port = htons(MYPORT); /* short, Ordenación 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 prácticamente todas las otras funciones de sockets, devuelve un valor distinto de cero si tiene éxito, y cero cuando falla. Y la dirección se devuelve en inp .

Guia de programación de redes 12

Page 13: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Desgraciadamente, no todas las plataformas implementan inet_aton() así que, aunque su uso se recomienda, en esta guía usaré inet_addr() que, aunque más antigua, está más extendida.

Muy bien, ahora ya puedes convertir direcciones IP en formato carácter a su correspondiente representación binaria. ¿Qué hay del camino inverso? Qué pasa si tienes una estructura struct in_addr y quieres imprimirla en la notación de cifras y puntos. En ese caso necesitarás usar la función inet_ntoa() ("ntoa" significa "network to ascii ") según se muestra a continuación:

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

Eso imprimirá la dirección IP. Fíjate en que inet_ntoa() toma un struct in_addr como argumento, y no un long. Date cuenta también de que devuelve un puntero a char. Éste apunta a una zona estática de memoria dentro de inet_ntoa(), así que cada vez que llames a inet_nota() se perderá la última dirección IP que pediste. Por ejemplo:

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

imprimirá:

address 1: 10.12.110.57 address 2: 10.12.110.57

Si necesitas conservar la dirección, usa strcpy() para copiarla a tu propia variable.

Eso es todo sobre este asunto por ahora. Más adelante, aprenderás a convertir una cadena como "whitehouse.gov" en su correspondiente dirección IP (Consulta DNS , más abajo.)

4. Llamadas al sistema Esta sección está dedicada a las llamadas al sistema que te permiten acceder a la funcionalidad de red de una máquina Unix. Cuando llamas a una de estas funciones, el núcleo toma el control y realiza todo el trabajo por ti automágicamente.

Lo que más confunde a la gente es el orden en que deben realizarse las llamadas. En esto las páginas man son completamente inútiles, como probablemente ya has descubierto. Como ayuda en una situación tan desagradable, he tratado de disponer las

Guia de programación de redes 13

Page 14: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

llamadas al sistema en las siguientes secciones en el orden (más o menos) exacto en que debes llamarlas en tus programas.

Esto, unido con unos pocos fragmentos de código por aquí y por allí, un poco de leche con galletas (que me temo que tendrás que aportar tú) y un poco de convencimiento y valor, ¡y estarás enviando datos a la red como un poseso!

4.1. socket() --¡Consigue el descriptor de fichero! Supongo que ya no puedo postponerlo más--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. Además, el argumento type le dice al núcleo 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 función del tipo ( type). (Notas: Hay muchos más dominios (domain ) de los que yo he listado. También hay muchos más tipos (type ) de los que yo he listado. Consulta la página man de select() . Además, hay una manera mejor de obtener el protocolo (protocol ). Consulta la página man de getprotobyname().)

socket() tan sólo 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 página man de perror() .)

En alguna documentación se menciona un valor místico: "PF_INET ". Se trata de una extraña y etérea 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 serían 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 práctica 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 más llamadas al sistema para que esto tenga algún sentido.

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

Guia de programación de redes 14

Page 15: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Una vez que tienes tu socket, tendrías que asociarlo con algún puerto de tú máquina local. (Esto es lo que comúnmente se hace si vas a escuchar [listen()] a la espera de conexiones entrantes sobre un puerto específico--Esto es lo que hacen cuando te dicen que hagas a telnet a x.y.z puerto 6969). El núcleo usa el número 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 léelo, sólo 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 información acerca de tu propia dirección, a saber, puerto y dirección IP. addrlen se puede asignar a sizeof(struct sockaddr) .

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

#include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <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; /* Ordenación de máquina */ my_addr.sin_port = htons(MYPORT); /* short, Ordenación 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 Ordenación de bytes de la red. También la sigue my_addr.sin_addr.s_addr . Otra cosa para estar atentos es que los ficheros de cabecera podrían ser distintos en sistemas distintos. Para asegurarte, revisa tus páginas locales de man.

Guia de programación de redes 15

Page 16: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Para finalizar con bind(), tendría que mencionar que parte del proceso de obtención de tu propia dirección IP y/o número 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 dirección IP */

Observa que, al poner my_addr.sin_port a cero, le estás 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 estás diciendo que escoja automáticamente la dirección IP de la máquina sobre la que está ejecutando el proceso.

Si te estás percatando de los detalles tal vez hayas visto que no puse INADDR_ANY en la Ordenación de bytes de la red. Qué travieso soy. Sin embargo tengo información privilegiada: ¡en realidad INADDR_ANY es cero! Cero es siempre cero, aunque reordenes los bytes. Sin embargo, los puristas pueden aducir que podría existir una dimensión paralela donde INADD_ANY fuera, por ejemplo, 12, y que mi código no funcionaría 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 dirección IP */

Ahora somos tan portables que probablemente no lo creerías. Solamente quería señalarlo, porque en la mayoría del código que te encuentres no habrán pasado INADDR_ANY a través de htonl().

bind() también 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 números de puerto demasiado pequeños. Todos los puertos por debajo del 1024 están RESERVADOS (a menos que seas el superusuario). Puedes usar cualquier número 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 dirección ya se está usando). ¿Qué significa? Bueno, una parte de un socket que estuvo conectado, está todavía colgando en el núcleo y está bloqueando el puerto. Puedes esperar a que se libere (alrededor de un minuto) o añadirle código a tu programa permitiendo reutilizar el puerto, de este modo:

int yes=1; /* char yes='1'; // La gente de Solaris usa esto */ /* Olvidémonos del error "Address already in use" [La dirección

Guia de programación de redes 16

Page 17: Guia Programacion Sockets

Manuel Jiménez 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 más acerca de bind(): en ocasiones no tendrás que usar esta función en absoluto. Si vas a conectar ( connect() ) a una máquina remota y no te importa cuál sea tu puerto local (como es el caso de telnet, donde sólo te importa el puerto remoto), puedes llamar sólo 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 aplicación telnet. Tu usuario te ordena (como en la película 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 estándar de telnet). ¿Qué haces ahora?

Afortunadamente para ti, programa, estás leyendo ahora la sección connect() -- o cómo conectar con una máquina remota. ¡Así que devóralo! ¡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 dirección IP de destino, y a addrlen le podemos asignar el valor sizeof(struct sockaddr).

¿No está esto empezando a tener más sentido? Veamos un ejemplo:

#include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define DEST_IP "10.12.110.57" #define DEST_PORT 23 main() { int sockfd; struct sockaddr_in dest_addr; /* Guardará la dirección de destino */

Guia de programación de redes 17

Page 18: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

sockfd = socket(AF_INET, SOCK_STREAM, 0); /* ¡Comprueba errores! */ dest_addr.sin_family = AF_INET; /* Ordenación de máquina */ dest_addr.sin_port = htons(DEST_PORT); /* short, Ordenación 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, asegúrate de comprobar el valor de retorno de connect() --devolverá -1 en caso de error y establecerá la variable errno .

Además fíjate en que no hemos llamado a bind(). Básicamente nos da igual nuestro número local de puerto; Sólo nos importa a dónde nos dirigimos (el puerto remoto). El núcleo elegirá un puerto local por nosotros, y el sitio al que nos conectamos será informado automáticamente. 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 máquina remota? Supongamos, sólo 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 después aceptas ( accept() ) (mira más abajo)

La llamada listen() es bastante sencilla, pero requiere una breve explicación:

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 número 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 más abajo) y éste es el límite de conexiones que puede haber en cola. La mayoría de los sistemas limitan automáticamente esta cifra a 20; probablemente puedes apañarte 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 núcleo nos tendrá esperando en un puerto aleatorio.

Guia de programación de redes 18

Page 19: Guia Programacion Sockets

Manuel Jiménez 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 válido como código de ejemplo porque es bastante claro. (El código de la sección accept(), a continuación, es más completo). El auténtico truco de todo esto está en la llamada a accept().

4.5. accept() --"Gracias por llamar al puerto 3490." Prepárate--la llamada al sistema accept() es un tanto extraña. Lo que va a suceder es lo siguiente: alguien muy, muy lejano intentará conectar (connect() ) con tu máquina en un puerto en el que tú estás escuchando (listen()). Su conexión pasará a cola, esperando a ser aceptada ( accept() ). Cuando llamas a accept() le estás diciendo que quieres obtener una conexión 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 conexión. Exacto. De repente, y por el precio de uno, tienes dos descriptores de fichero de socket. El original está todavía escuchando en tu puerto, y el de nueva creación 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 estás escuchando (listen()). Es muy fácil. addr es normalmente un puntero a una estructura struct sockaddr_in local. Ahí es donde se guardará la información de la conexión entrante (y con ella puedes averiguar que máquina te está llamando, y desde qué puerto). addrlen es un puntero a una variable local int a la que deberías asignar el valor de sizeof(struct sockaddr_in) . accept() pondrá dentro de addr un máximo 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 código de ejemplo para que te lo estudies:

Guia de programación de redes 19

Page 20: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

#include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define MYPORT 3490 /* Puerto al que conectarán los usuarios */ #define BACKLOG 10 /* Cuántas conexiones vamos a mantener en cola */ main() { int sockfd, new_fd; /* se escucha sobre sock_fd, Nuevas conexiones sobre new_fd */ struct sockaddr_in my_addr; /* Información sobre mi dirección */ struct sockaddr_in their_addr; /* Información sobre la dirección remota */ int sin_size; sockfd = socket(AF_INET, SOCK_STREAM, 0); /* ¡Comprobar errores! */ my_addr.sin_family = AF_INET; /* Ordenación de máquina */ my_addr.sin_port = htons(MYPORT); /* short, Ordenación de la red */ my_addr.sin_addr.s_addr = INADDR_ANY; /* Rellenar con mi dirección 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 decía, usaremos el descriptor de fichero de socket new_fd en las llamadas al sistema send() y recv() . Si solamente esperas recibir una conexión 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()--¡Háblame, baby! Estas dos funciones sirven para comunicarse a través de sockets de flujo o sockets de datagramas conectados. Si quieres usar sockets de datagramas desconectados normales tienes que leer la sección sendto () y recvfrom () , a continuación.

Guia de programación de redes 20

Page 21: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

La llamada al sistema send():

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 página man de send() para más información relativa a los flags).

Lo siguiente podría servir como código de ejemplo:

char *msg = "Beej was here!"; int len, bytes_sent; . . len = strlen(msg); bytes_sent = send(sockfd, msg, len, 0); . . .

send() devuelve el múmero de bytes que se enviaron en realidad--¡y podrían ser menos de los que tú pediste que se enviaran! Por ejemplo hay veces que solicitas enviar todo un montón de datos y el sistema sencillamente no puede manejarlos todos. Procesará tantos datos como pueda y confiará en que tú envíes el resto después. 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 pequeño (menos de 1K o así) probablemente se las apañará 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 información leida, len es la longitud máxima del buffer, y flags, como antes, puede asignarse a 0 (Revisa la página man de recv() para información sobre flags)

recv() devuelve el número de bytes que se leyeron en realidad, o -1 en caso de error (y entonces establece errno apropiadamente).

¡Espera! recv() puede devolver 0. Esto sólo puede significar una cosa: ¡la máquina remota ha cerrado su conexión 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 través de un socket de flujo! ¡Estupendo! ¡Eres un programador Unix de redes!

Guia de programación de redes 21

Page 22: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

4.7. sendto() y recvfrom()--Háblame al estilo DGRAM "Todo esto está muy bien", te escucho decir, "pero a dónde 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 están conectados a una máquina remota, ¿adivina qué información necesitamos aportar antes de poder enviar un paquete? ¡Exacto! ¡La dirección 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 básicamente la misma que send() añadiendo dos items más de información. to es un puntero a una estructura struct sockaddr (probablemente usarás una estructura struct sockaddr_in y la forzarás a struct sockaddr en el último momento) que contiene la dirección IP y el puerto de destino. Al argumento tolen asígnale el valor sizeof(struct sockaddr).

Lo mismo que send(), sendto() devuelve el número de bytes que realmente se enviaron (que, igual que antes, podrían 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 más. from es un puntero a una estructura struct sockaddr local que será rellenada con la dirección IP y el puerto de la máquina de origen. fromlen es un puntero a un int local que tiene que inicializarse a sizeof(struct sockaddr). Cuando la función finalice, fromlen contendrá la longitud de la dirección almacenada en from.

recvfrom() devuelve el número 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 aún un socket de datagramas y los paquetes seguirán usando UDP, pero el interfaz de sockets añadirá automáticamente por ti la información de origen y destino.

4.8. close() y shutdown()--¡Fuera de mi vista! Bueno, has estado enviando (send()) y recibiendo (recv() ) datos todo el día y ahora has terminado. Estás listo para cerrar la conexión de tu descriptor de socket. Esto es

Guia de programación de redes 22

Page 23: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

sencillo. Sólo hay que usar normalmente la función Unix close() que cierra descriptores de fichero:

close(sockfd);

Esto impedirá más lecturas y escrituras al socket. Cualquiera que intente leer o escribir sobre el socket en el extremo remoto recibirá un error.

Sólo en el caso que quieras un poco más de control sobre cómo se cierra el socket puedes usar la función shutdown(). Te permite cortar la comunicación 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 más datos • 1 -- No se permite enviar más datos • 2 -- No se permite enviar ni recibir más 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 conexión, 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 --sólo cambia sus condiciones de uso. Para liberar un descriptor de socket necesitas usar close().

Nada más.

4.9. getpeername() --¿Quién eres tú? Esta función es fácil. Tan fácil, que estuve a punto de no otorgarle una sección propia. En cualquier caso, aquí está.

La función getpeername() te dirá quién 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 programación de redes 23

Page 24: Guia Programacion Sockets

Manuel Jiménez 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 información acerca del otro lado de la conexión, y addrlen es un puntero a un int, que deberías inicializar a sizeof(struct sockaddr) .

La función devuelve -1 en caso de error y establece errno apropiadamente.

Una vez que tienes su dirección, puedes usar inet_ntoa() or gethostbyaddr() para imprimir u obtener más información. 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 más allá del alcance de este documento. Revisa el RFC-1413 para más información.)

4.10. gethostname() --¿Quién soy yo? La función gethostname () es incluso más fácil 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 sección, para determinar la dirección IP de tu máquina local.

¿Qué puede haber más divertido? Se me ocurren un par de cosas, pero no tienen nada que ver con la programación 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 carácteres donde se almacenará el nombre de la máquina cuando la función retorne, y size es la longitud en bytes de esa cadena de caracteres.

La función 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 cuál es la dirección de un sitio en forma humanamente legible y él te devuelve la dirección 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 programación de redes 24

Page 25: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Pero, ¿cómo funciona? Tú vas a usar la función 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 máquina. • h_aliases -- Un vector [array] terminado en NULL de nombres alternativos de

máquina. • h_addrtype -- Tipo de la dirección que se devuelve; usualmente AF_INET. • h_length -- La longitud de la dirección en bytes. • h_addr_list -- Un vector terminado en cero de direcciones de red de la

máquina. Las direcciones siguen la Ordenación de bytes de la red. • h_addr -- La primera dirección 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() más adelante).

Pero, ¿cómo se usa? A veces (nos damos cuenta leyendo manuales de informática), no es suficiente con vomitarle la información al lector. En realidad, esta función es más fácil de usar de lo que parece.

Guia de programación de redes 25

Page 26: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Aquí hay un programa de ejemplo :

/* ** getip.c -- ejemplo de búsqueda DNS */ #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { struct hostent *h; if (argc != 2) { /* Comprobación de errores en la línea de comandos */ fprintf(stderr,"usage: getip address\n"); exit(1); } if ((h=gethostbyname(argv[1])) == NULL) { /* Obtener información 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 máquina ("whitehouse.gov") a gethostbyname() , y recuperas la información que te han devuelto en la estructura struct hostent.

La única cosa rara que podrías encontrarte sería en el momento de imprimir la dirección 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 programación de redes 26

Page 27: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

telnet, por ejemplo. Cuando conectas al puerto 23 de una máquina remota mediante telnet (el cliente) un programa de aquella máquina (llamado telnetd, el servidor) despierta a la vida. Gestiona la conexión telnet entrante, te presenta una pantalla de login , etc.

Figura 2. Interacción Cliente-Servidor.

El intercambio de información 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 máquina determinada, que atenderá a múltiples clientes usando fork(). El funcionamiento básico es: el servidor espera una conexión, 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 sección.

5.1. Un servidor sencillo Todo lo que hace este servidor es enviar la cadena " Hello, World!\n" sobre una conexión 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 máquina donde estas ejecutando.

El código servidor : (Nota: una barra invertida al final de una línea indica que esa línea se continúa en la siguiente.)

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

Guia de programación de redes 27

Page 28: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

#include <sys/wait.h> #include <signal.h> #define MYPORT 3490 /* Puerto al que conectarán los usuarios */ #define BACKLOG 10 /* Cuántas 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; /* información sobre mi dirección */ struct sockaddr_in their_addr; /* información sobre la dirección 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; /* Ordenación de bytes de la máquina */ my_addr.sin_port = htons(MYPORT); /* short, Ordenación de bytes de la red */ my_addr.sin_addr.s_addr = INADDR_ANY; /* Rellenar con mi dirección 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 programación de redes 28

Page 29: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

*/ sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(SIGCHLD, &sa, NULL) == -1) { perror("sigaction"); exit(1); } 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 código en una gran función main() porque me parece más claro. Puedes partirlo en funciones más pequeñas si eso te hace sentir mejor.

(Además, esto del sigaction() podría ser nuevo para ti--es normal. El código 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 sección.

5.2. Un cliente sencillo Este tío es incluso más sencillo que el servidor. Todo lo que hace es conectar al puerto 3490 de la máquina que indicas en la línea de comandos y obtiene la cadena que el servidor envía.

Guia de programación de redes 29

Page 30: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

El código cliente :

/* ** client.c -- Ejemplo de cliente de sockets de flujo */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #define PORT 3490 /* puerto al que vamos a conectar */ #define MAXDATASIZE 100 /* máximo número 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; /* información de la dirección de destino */ if (argc != 2) { fprintf(stderr,"usage: client hostname\n"); exit(1); } if ((he=gethostbyname(argv[1])) == NULL) { /* obtener información de máquina */ perror("gethostbyname"); exit(1); } if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } their_addr.sin_family = AF_INET; /* Ordenación de bytes de la máquina */ their_addr.sin_port = htons(PORT); /* short, Ordenación 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 programación de redes 30

Page 31: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

exit(1); } 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" (Conexión rechazada). Muy útil.

5.3. Sockets de datagramas En realidad no hay demasiado que contar aquí, así que sólo presentaré un par de programas de ejemplo: talker.c y listener.c.

listener se sienta a esperar en la máquina hasta que llega un paquete al puerto 4950. talker envía un paquete a ese puerto en la máquina indicada que contiene lo que el usuario haya escrito en la línea de comandos.

Este es el código fuente de listener.c :

/* ** listener.c -- Ejemplo de servidor de sockets de datagramas */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define MYPORT 4950 /* puerto al que conectarán los clientes */ #define MAXBUFLEN 100 int main(void) { int sockfd; struct sockaddr_in my_addr; /* información sobre mi dirección */

Guia de programación de redes 31

Page 32: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

struct sockaddr_in their_addr; /* información sobre la dirección 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; /* Ordenación de bytes de máquina */ my_addr.sin_port = htons(MYPORT); /* short, Ordenación de bytes de la red */ my_addr.sin_addr.s_addr = INADDR_ANY; /* rellenar con mi dirección 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 también que no hay necesidad de escuchar (listen()) o aceptar (accept()). ¡Esa es una de las ventajas de usar sockets de datagramas sin conexión!

A continuación el código fuente de talker.c :

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

Guia de programación de redes 32

Page 33: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

#include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #define MYPORT 4950 /* puerto donde vamos a conectarnos */ int main(int argc, char *argv[]) { int sockfd; struct sockaddr_in their_addr; /* información sobre la dirección 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 información de máquina */ perror("gethostbyname"); exit(1); } if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { perror("socket"); exit(1); } their_addr.sin_family = AF_INET; /* Ordenación de bytes de máquina */ their_addr.sin_port = htons(MYPORT); /* short, Ordenación 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 programación de redes 33

Page 34: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

¡Y esto es todo! Ejecuta listener en una máquina y luego llama a talker en otra. ¡Observa cómo se comunican! ¡Disfruta de toda la excitación de la familia nuclear entera!

Excepto un pequeño detalle que he mencionado muchas veces con anterioridad: sockets de datagramas con conexión. Tengo que hablar de ellos aquí, puesto que estamos en la sección de datagramas del documento. Supongamos que talker llama a connect() e indica la dirección de listener. A partir de ese momento, talker solamente puede enviar a y recibir de la dirección especificada en connect(). Por ese motivo no tienes que usar sendto() y recvfrom(); tienes que usar simplemente send() y recv().

6. Técnicas moderadamente avanzadas En realidad no son verdaderamente avanzadas, pero se salen de los niveles más básicos que ya hemos cubierto. De hecho, si has llegado hasta aquí, puedes considerarte conocedor de los principios básicos de la programación de redes Unix. ¡Enhorabuena!

Así que ahora entramos en el nuevo mundo de las cosas más esotéricas que querrías aprender sobre los sockets. ¡Ahí las tienes!

6.1. Bloqueo Bloqueo. Has oído 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, más arriba, simplemente se sienta a esperar a que llegue un paquete. Lo que sucedió es que llamó a recvfrom() y no había datos que recibir. Por eso se dice que recvfrom() se bloquea (es decir, se duerme) hasta que llega algún 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 núcleo 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 información. Si intentas leer de un socket no bloqueante y no hay datos disponibles, la función no está autorizada a bloquearse -- devolverá -1 y asignará a errno el valor EWOULDBLOCK.

Guia de programación de redes 34

Page 35: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

En líneas 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, estarás consumiendo en vano el tiempo de la CPU como se hacía antaño. Una solución más elegante para comprobar si hay datos esperando que se puedan leer, se presenta en la siguiente sección: select().

6.2. select() --Multiplexado de E/S síncrona Esta función es un tanto extraña, pero resulta muy útil. Considera la siguiente situación: 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() ? ¿cómo vas a recibir (recv()) datos al mismo tiempo? "Usa sockets no bloqueantes" ¡De ningún modo! No quieres comerte toda la CPU. Entonces ¿qué?

select() te da la posibilidad de comprobar varios sockets al mismo tiempo. Te dirá cuáles están listos para leer, cuáles están listos para escribir, y cuáles han generado excepciones, si estás interesado en saber eso.

Sin más preámbulos, 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 función comprueba "conjuntos" de descriptores de fichero; en concreto readfds, writefds , y exceptfds. Si quieres saber si es posible leer de la entrada estándar y de un cierto descriptor de socket , sockfd, simplemente añade los descriptores de fichero 0 y sockfd al conjunto readfs. El parámetro numfds debe tener el valor del mayor descriptor de fichero más uno. En este ejemplo, deberíamos asignarle el valor sockfd + 1 , puesto que es seguro que tendrá un valor mayor que la entrada estándar (0).

Cuando select() regrese, readfs contendrá los descriptores de fichero que están listos para lectura. Puedes comprobarlos con la macro FD_ISSET() que se muestra a continuación.

Antes de progresar más, 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) -- añade 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 programación de redes 35

Page 36: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Por último, ¿qué es esa extraña estructura struct timeval? Bueno, a veces no quieres esperar toda la vida a que alguien te envíe datos. Quizás cada 96 segundos quieras imprimir "Aún estoy vivo..." aunque no haya sucedido nada. Esta estructura de tiempo te permite establecer un período máximo de espera. Si transcurre ese tiempo y select() no ha encontrado aún ningún descriptor de fichero que esté listo, la función regresará para que puedas seguir procesando.

La estructura struct timeval tiene los siguientes campos:

struct timeval { int tv_sec; // segundos int tv_usec; // microsegundos };

Establece tv_sec al número de segundos que quieres esperar, y tv_usec al número 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". Además, cuando select() regresa, timeout podría haberse actualizado al tiempo que queda para que el temporizador indicado expire. Depende del sistema Unix que estés usando.

¡Fantástico! ¡Tenemos un reloj con precisión de microsegundos! No cuentes con ello. El límite en un sistema Unix estándar está alrededor de los 100 milisegundos, así que seguramente tendrás que esperar eso como mínimo, por muy pequeño que sea el valor con que establezcas la estructura struct timeval.

Otras cosas de interés: si estableces los campos de struct timeval a 0, select() regresará inmediatamente después de interrogar todos tus descriptores de fichero incluidos en los conjuntos. Si estableces el parámetro timeout a NULL el temporizador nunca expirará y tendrás que esperar hasta que algún descriptor de fichero esté listo. Por último, si no estás interesado en esperar sobre algún conjunto de descriptores de fichero en particular, sólo tienes que usar el parámetro NULL en la llamada a select().

El siguiente fragmento de código espera 2.5 segundos a que algo aparezca por la entrada estándar:

/* ** select.c -- ejemplo de select() */ #include <stdio.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #define STDIN 0 /* descriptor de fichero de la entrada estándar

Guia de programación de redes 36

Page 37: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

int main(void) { struct timeval tv; fd_set readfds; tv.tv_sec = 2; tv.tv_usec = 500000; FD_ZERO(&readfds); FD_SET(STDIN, &readfds); /* 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 estás en un terminal con buffer de líneas, tendrás que pulsar la tecla RETURN porque en cualquier otro caso el temporizador expirará.

Ahora, algunos de vosotros podrías pensar que esta es una forma excelente de esperar datos sobre un socket de datagramas --y teneis razón: podría serlo. Algunos Unix permiten usar select() de este modo mientras que otros no. Deberías consultar las páginas 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 confíes 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 conexión? 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 conexión.

Una nota más de interés acerca de select(): si tienes un socket que está escuchando (listen()), puedes comprobar si hay una nueva conexión poniendo ese descriptor de socket en el conjunto readfs .

Y esto, amigos míos, es una visión general sencilla de la todopoderosa función select().

Pero, por aclamación 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 programación de redes 37

Page 38: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Este programa actúa como un simple servidor multiusuario de chat. Inicialo en una ventana y luego atácalo con telnet ("telnet hostname 9034 ") desde varias ventanas distintas. Cuando escribas algo en una sesión telnet, tiene que aparecer en todas las otras.

/* ** selectserver.c -- servidor de chat multiusuario */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 9034 /* puerto en el que escuchamos */ 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; /* dirección del servidor */ struct sockaddr_in remoteaddr; /* dirección del cliente */ int fdmax; /* número máximo de descriptores de fichero */ int listener; /* descriptor de socket a la escucha */ int newfd; /* descriptor de socket de nueva conexión aceptada */ char buf[256]; /* buffer para datos del cliente */ int nbytes; int yes=1; /* para setsockopt() SO_REUSEADDR, más 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 dirección ya se está usando) */ if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { perror("setsockopt"); exit(1); } /* enlazar */

Guia de programación de redes 38

Page 39: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

myaddr.sin_family = AF_INET; myaddr.sin_addr.s_addr = INADDR_ANY; myaddr.sin_port = htons(PORT); memset(&(myaddr.sin_zero), '\0', 8); if (bind(listener, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1) { perror("bind"); exit(1); } /* escuchar */ if (listen(listener, 10) == -1) { perror("listen"); exit(1); } /* añadir 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; /* cópialo */ 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); /* añadir al conjunto maestro */ if (newfd > fdmax) { /* actualizar el máximo */ fdmax = newfd; } printf("selectserver: new connection from

Guia de programación de redes 39

Page 40: Guia Programacion Sockets

Manuel Jiménez 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 conexión cerrada por el cliente */ if (nbytes == 0) { /* conexión 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 algún 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 código uso dos conjuntos de descriptores de fichero: master y read_fds. El primero, master , contiene todos los descriptores de fichero que están actualmente conectados, incluyendo el descriptor de socket que está escuchando para nuevas conexiones.

Guia de programación de redes 40

Page 41: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

La razón por la cual uso el conjunto master es que select() va a cambiar el conjunto que le paso para reflejar qué sockets están listos para lectura. Como tengo que recordar las conexiones activas entre cada llamada de select(), necesito almacenar ese conjunto en algún 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 conexión, tengo que añadirla al conjunto master? ¡Yup! y cada vez que una conexión se cierra, ¿no tengo que borrarla del conjunto master . Efectivamente.

Fíjate que compruebo si el socket listener está listo para lectura. Si lo está, tengo una nueva conexión pendiente: la acepto (accept() ) y la añado al conjunto master. Del mismo modo, cuando una conexión de cliente está lista para lectura y recv() devuelve 0, sé que el cliente ha cerrado la conexión 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 míos, es una visión general no tan sencilla de la todopoderosa función select().

6.3. Gestión de envíos parciales con send()s ¿Recuerdas antes en la seción sobre send() , cuando dije que send() podría 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 pequeño buffer esperando ser enviados. Debido a circunstancias que escapan a tu control, el núcleo decidió no enviar todos los datos de una sola vez, y ahora, amigo mío, depende de ti que los datos se envíen.

También podrías escribir una función como esta para conseguirlo:

#include <sys/types.h> #include <sys/socket.h> int sendall(int s, char *buf, int *len) { int total = 0; /* cuántos bytes hemos enviado */ int bytesleft = *len; /* cuántos 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 programación de redes 41

Page 42: Guia Programacion Sockets

Manuel Jiménez 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 número de bytes que hay en el buffer.

La función devuelve -1 en caso de error (y errno está establecido por causa de la llamada a send().) Además, el número de bytes enviados realmente se devuelven en len. Este será el mismo número 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 cómo usar la función:

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, ¿cómo sabe el receptor cuando un paquete ha finalizado? Efectivamente, las situaciones del mundo real son un auténtico quebradero de cabeza. Probablemente tengas que encapsular tus datos (¿te acuerdas de esto, en la sección de encapsulación de datos más arriba al principio?) ¡Sigue leyendo para más detalles!

6.4. Consecuencias de la encapsulación de datos En definitiva, ¿qué significa realmente encapsular los datos? En el caso más simple, significa que añadirás una cabecera con cierta informatión de identidad, con la longitud del paquete, o con ambas cosas.

¿Cómo tendría que ser esta cabecera? Bien, sencillamente algunos datos en binario que representen cualquier información que creas que es necesaria para tus propósitos.

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 información: qué se ha dicho y quién lo ha dicho.

Guia de programación de redes 42

Page 43: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

¿Hasta aquí bien? "¿Cuál es el problema?", estás pensando.

El problema es que los mensajes pueden ser de longitudes distintas. Una persona que se llame "tom" podría decir "Hi" ["hola"], mientras que otra persona que se llame "Benjamin" podría decir "Hey guys what is up?" ["Hey, ¿qué pasa tíos?"]

Así que envías (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. ¿Cómo sabe un cliente cuándo empieza un mensaje y cuándo acaba? Si quisieras, podrías hacer que todos los mensajes tuvieran la misma longitud, y simplemente llamar a la función sendall() que hemos implementado, mas arriba. ¡Pero así desperdicias el ancho de banda! no queremos enviar (send() ) 1024 bytes sólo para que "tom" pueda decir "Hi".

Así que encapsulamos los datos en una pequeña estructura de paquete con cabecera. Tanto el servidor como el cliente deben saber cómo empaquetar y desempaquetar los datos (algunas veces a esto se le llama, respectivamente, "marshal" y "unmarshal"). No mires aún, pero estamos empezando a definir un protocolo que describe cómo se comunican el servidor y el cliente.

En este caso, supongamos que el nombre de usuario tiene una longitud fija de 8 carácteres, rellenados por la derecha con '\0'. y supongamos que los datos son de longitud variable, hasta un máximo de 128 carácteres. Echemos un vistazo a un ejemplo de estructura de paquete que podríamos usar en esta situación:

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 carácteres NUL si es necesario.

3. chatdata (n-bytes) -- Los datos propiamente dichos, hasta un máximo de 128 bytes. La longitud del paquete se calcula como la suma de la longitud de estos datos más 8 (la longitud del nombre de usuario).

¿Porqué elegí limites de 8 y 128 bytes? Los tomé al azar, suponiendo que serían 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 más. La decisión es tuya.

Usando esta definición de paquete, el primer paquete constaría de la siguiente información (en hexadecimal y ASCII):

0A 74 6F 6D 00 00 00 00 00 48 69 (longitud) T o m (relleno) H i

Guia de programación de redes 43

Page 44: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Y el segundo sería muy similar:

14 42 65 6E 6A 61 6D 69 6E 48 65 79 20 67 75 79 73 20 77 ... (longitud) B e n j a m i n H e y g u y s w ...

(La longitud sigue la Ordenación de bytes de la red, por supuesto. En este caso no importa, porque se trata sólo de un byte, pero en general, querrás que todos tus enteros binarios de tus paquetes sigan la Ordenación de bytes de la red).

Al enviar estos datos, deberías ser prudente y usar una función del tipo de sendall() , así te aseguras de que se envían 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, deberías suponer que puedes recibir sólo una parte del paquete (por ejemplo, en el caso anterior podríamos recibir sólo " 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, ¿cómo? Bueno, sabemos el número total de bytes que hemos de recibir para que el paquete esté completo, puesto que ese número está al principio del paquete. También sabemos que el tamaño máximo 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 reconstruirás los paquetes a medida que lleguen.

Cada vez que recibas (recv()) datos los meterás en tu buffer y comprobarás si el paquete está compelto. Es decir, si el número 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 número 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 bórralo del buffer.

¡Bueno! ¿Aún estás dándole vueltas en la cabeza? Bien, ahí llega la segunda parte: en una sola llamada a recv(), podrías haber leído más 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 razón por la que hiciste que tu buffer fuera lo bastante grande como para contener dos paquetes-- ¡Por si sucedía esto!)

Como, gracias a la cabecera, sabes la longitud del primer paquete y además has controlado cuántos bytes del buffer has usado, con una sencilla resta puedes calcular

Guia de programación de redes 44

Page 45: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

cuántos 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 diseñar 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 más allá del alcance de este artículo. Si todavía sientes curiosidad, píllate un libro de estructuras de datos y consúltalo.)

Nunca dije que fuera fácil. Está bien, sí que lo dije. Y lo es. Sólo necesitas práctica y muy pronto resultará para ti de lo más natural. ¡Lo juro por Excalibur!

7. Referencias adicionales ¡Has llegado hasta aquí, y ahora quieres más! ¿A dónde puedes ir para aprender más de todo esto?

7.1. Páginas del manual (man ) Revisa las siguientes páginas man, para información 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); DESCRIPCIÓN La función htonl() convierte el entero largo hostlong desde el orden de bytes del host al de la red. La función htons() convierte el entero corto hostshort desde el orden de bytes del host al de la red. La función ntohl() convierte el entero largo netlong desde el orden de bytes de la red al del host. La función 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 más significativo (MSB).

Guia de programación de redes 45

Page 46: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

CONFORME A BSD 4.3 VÉASE TAMBIÉN 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 manipulación 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); DESCRIPCIÓN inet_aton() convierte la dirección de Internet cp desde la notación estándar de números y puntos a la representación binaria, y la guarda en la estructura a la que apunte inp. inet_aton devuelve no-cero si la dirección es válida, cero si no. La función inet_addr() convierte la dirección de Internet cp desde la notación de números y puntos a la de datos binarios en orden de bytes del ordenador local. Si la entrada no es válida, devuelve INADDR_NONE (usualmente −1). Ésta es una interfaz obsoleta a inet_aton, descrita anteriormente; es obsoleta porque −1 es una dirección válida (255.255.255.255), e inet_aton porporciona una manera más clara para indicar que ha ocurrido un error. La función inet_network() extrae el número de red en orden de bytes de red desde la dirección cp a la notación de números y puntos. Si la entrada es inválida, devuelve −1. La función inet_ntoa() convierte la dirección de Internet in dada en orden de bytes de red a una cadena de caracteres en la notación estándar de números y puntos. La cadena se devuelve en un búfer reservado estáticamente, que será sobreescrito en siguientes llamadas. La función inet_makeaddr() construye una dirección de Internet en orden de bytes de red combinando el número de red net con la dirección local host en la red net, ambas en orden de bytes de ordenador local. La función inet_lnaof() devuelve la parte del ordenador local de la dirección de Internet in. La dirección de ordenador local se devuelve en orden de bytes de ordenador local.

Guia de programación de redes 46

Page 47: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

La función inet_netof() devuelve la parte de número de red de la dirección de Internet in. El número 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 Más Significativo (MSB) primero. CONFORME A BSD 4.3 VÉASE TAMBIÉN gethostbyname(), getnetent(), hosts(), networks()

• socket()

NOMBRE socket − crea un extremo de una comunicación SINOPSIS #include <sys/types.h> #include <sys/socket.h> int socket(int dominio, int tipo, int protocolo); DESCRIPCIÓN Socket crea un extremo de una comunicación y devuelve un descriptor. El parámetro dominio especifica un dominio de comunicaciones. Esto selecciona la familia de protocol que se usará para la comunicación. Estas familias se definen en <sys/socket.h>. Los formatos actualmente reconocidos incluyen: Nombre Propósito Página de manual PF_UNIX,PF_LOCAL Comunicación 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 núcleo 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 programación de redes 47

Page 48: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

El conector tiene el tipo indicado, que especifica la semántica de la comunicación. Los tipos definidos en la actualidad son: SOCK_STREAM Proporciona flujos de bytes basados en una conexión bidireccional secuenciada, confiable. Se puede admitir un mecanismo de transmisión de datos fuera-de-banda. SOCK_DGRAM Admite datagramas (mensajes no confiables, sin conexión, de una longitud máxima fija). SOCK_SEQPACKET Proporciona un camino de transmisión de datos basado en conexión bidireccional secuenciado, confiable, para datagramas de longitud máxima 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 debería 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 sólo 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 número de protocolo a emplear es específico al “dominio de comunicación” en el que la comunicación va a tener lugar; vea protocols(). Consulte getprotoent() para ver cómo asociar una Página man de Linux 24 abril 1999 cadenas con el nombre de un protocolo a un número de protocolo. Los conectores del tipo SOCK_STREAM son flujos de bytes bidireccionales, similares a tuberías, que no conservan los límites 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 conexión con otro conector mediante la llamada connect(2). Una vez hecha la conexión, los datos pueden transferirse utilizando llamadas read(2) y write(2) o alguna variante de las llamadas send(2) y recv(2). Cuando una sesión se ha completado, se puede efectuar un close(2). Los datos fuera-de-banda pueden transmitirse también como se describe en send(2) y recibirse según 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 búfer no puede ser transmitido satisfactoriamente en un período razonable de tiempo, entonces la conexión se considera muerta. Cuando se activa SO_KEEPALIVE en el conector el protocolo comprueba de una manera específica del protocolo si el otro extremo todavía está vivo. Se lanza una señal SIGPIPE si un proceso envía o recibe en un flujo roto; esto provoca que procesos simples, que no manejan la señal, 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) devolverán solamente la cantidad de datos pedidos, y los que queden en el paquete que llega se perderán. También se conservarán todos los límites de mensaje en los datagramas que lleguen.

Guia de programación de redes 48

Page 49: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Los conectores SOCK_DGRAM y SOCK_RAW permiten el envío 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 dirección 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 señal SIGURG cuando lleguen los datos fuera-de-banda o la señal SIGPIPE cuando una conexión SOCK_STREAM se rompa inesperadamente. También puede usarse para configurar el proceso o grupo de procesos que recibirán la E/S y la notificación asíncrona de los eventos de E/S a través de SIGIO. Usar F_SETOWN es equivalente a una llamada a ioctl(2) con el argumento SIOSETOWN. Cuando la red señala una condición de error al módulo del protocolo (por ejemplo, usando un mensaje ICMP para IP) se activa la bandera de error pendiente para el conector. La siguiente operación sobre ese conector devolverá el código de error del error pendiente. Para algunos protocolos es posible habilitar una cola de error por conector para obtener información detallada del error. Vea IP_RECVERR en ip(7). La operación 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 núcleo 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 módulos de los protocolos subyacentes pueden generar otros errores. CONFORME A 4.4BSD (la llamada a función 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 programación de redes 49

Page 50: Guia Programacion Sockets

Manuel Jiménez 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 página de manual BSD promete: "La familia de protocolos generalmente es la misma que la familia de direcciones" y los estándares subsiguientes usan AF_* en todas partes. FALLOS SOCK_UUCP todavía no está implementado. VÉASE TAMBIÉN accept(), bind(), connect(), getprotoent(), getsockname(), getsockopt(), ioctl(), listen(), read(), recv(), select(), send(), shutdown(), socketpair(), write() Mas información 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); DESCRIPCIÓN Esta página 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 núcleo. Los módulos 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 más información 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 más información vea sus páginas de manual respectivas. socket() crea un conector, connect() conecta un conector a una dirección de conector remota, la función bind() enlaza un conector a una dirección de conector local, listen() indica al conector que se aceptarán nuevas conexiones y accept() se usa para obtener un nuevo conector con una nueva conexión de entrada. socketpair() devuelve dos conectores anónimos conectados (sólo implementado para unas pocas familias locales como PF_UNIX) send(), sendto() y sendmsg() envían datos a través 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. Además, se pueden usar las operaciones estándares de E/S como write(), writev(), send-file(), read() y readv() para leer y escribir datos. getsockname() devuelve la dirección de un conector local y getpeername() devuelve la dirección 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 conexión bidireccional entre conectores.

Guia de programación de redes 50

Page 51: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Las búsquedas o las llamadas a pread() o pwrite() con una posición distinta de cero, no están soportadas en conectores. Es posible realizar E/S no bloqueante con conectores activando la opción O_NONBLOCK sobre el descriptor de fichero de un conector usando fcntl(). O_NONBLOCK se hereda durante una llamada a accept. A continuación, todas las operaciones que normalmente se bloquearían devolverán (usualmente) el error EAGAIN. connect() devuelve un error EINPROGRESS en este caso. Más tarde, el usuario puede esperar diferentes eventos mediante poll() o select(). Eventos de E/S Evento Ocurrencia Opción de poll Lectura POLLIN Han llegado nuevos datos. Lectura POLLIN Se ha completado una nueva solicitud de conexión (para conectores orientados a conexión). Lectura POLLHUP El otro extremo ha iniciado una solicitud de desconexión. Lectura POLLHUP Se ha roto una conexión (sólo para protocolos orientados a conexión). Cuando se escribe en el conector, también se envía la señal SIGPIPE. Escritura POLLOUT El conector tiene suficente espacio en el buffer de envío para escribir nuevos datos. Lectura/Escritura POLLIN| POLLOUT Ha finalizado un connect(2) de salida. Lectura/Escritura POLLERR Se ha producido un error asíncrono. Lectura/Escritura POLLHUP El otro extremo ha cerrado una dirección de la conexión. Excepción POLLPRI Han llegado datos fuera de orden, lo que hace que se envíe la señal SIGURG. Una alternativa a poll/select es dejar que el núcleo informe de los eventos a la aplicación mediante una señal SIGIO. Para ello, se debe activar la opción FASYNC en el descriptor de fichero de un conector mediante fcntl(2) y se debe instalar un manejador de señales válido para SIGIO mediante sigaction(2). Vea la discusión sobre SEÑALES más 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 envío de mensajes "sigue vivo" (keep-alive) en conectores orientados a conexión. Espera una opción booleana entera. SO_OOBINLINE Si se habilita esta opción, los datos fuera de orden se colocan directamente en el flujo de recepción de datos. En otro caso, los datos fuera de orden sólo se pasan cuando se activa la opción MSG_OOB durante la recepción. SO_RCVLOWAT y SO_SNDLOWAT Especifican el número mínimo de bytes en el buffer para que la capa de conectores pase los datos al protocolo (SO_SNDLOWAT) o al usuario durante la recepción (SO_RCVLOWAT). Estos dos valores no se pueden cambiar en Linux y sus argumentos de tamaño siempre tienen el valor de 1 byte. getsockopt es capaz de leerlos. setsockopt siempre devolverá ENOPROTOOPT. SO_RCVTIMEO y SO_SNDTIMEO

Guia de programación de redes 51

Page 52: Guia Programacion Sockets

Manuel Jiménez 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 configuración específica 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 sólo el módulo 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 también habilitaba las opciones de compatibilidad fallo a fallo con BSD (cambio aleatorio de las cabeceras, omisión de la opción de difusión) para los conectores directos con esta opción, pero esto se ha eliminado en la versión 2.2 de Linux. Es mejor corregir los programas de usuario que habilitar esta opción. SO_PASSCRED Habilita o deshabilita la recepción del mensaje de control SCM_CREDENTIALS. Para más información, vea unix(7). SO_PEERCRED Devuelve las credenciales del proceso externo conectado a este conector. Sólo útil para conectores PF_UNIX. Vea unix(7). El argumento es una estructura ucred. Esta opción sólo es válida 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 vacía o la longitud de las opciones es cero, se elimina el enlace entre el dispositivo y el conector. La opción pasada es una cadena (terminada en \0) de longitud variable con el nombre de la interfaz, con un tamaño máximo de IFNAMSIZ. Si el conector está ligado a una interfaz, éste sólo procesará los paquetes recibidos desde la interfaz particular. SO_DEBUG Activa la depuración de los conectores. Sólo 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 reutilización de las direcciones locales. Para los conectores PF_INET esto significa que un conector se puede enlazar a una dirección, excepto cuando hay un conector activo escuchando asociado a la dirección. Cuando el conector que está escuchando está asociado a INADDR_ANY con un puerto específico, entonces no es posible realizar enlaces a este puerto para ninguna dirección local. SO_TYPE Obtiene el tipo de conector como un valor entero (como SOCK_STREAM). Sólo puede ser leído con getsockopt. SO_DONTROUTE No enviar a través de un enrutador, sólo enviar a ordenadores directamente conectados. Se puede conseguir el mismo efecto activando la opción MSG_DONTROUTE en una operación send(2) sobre un conector. Espera una opción booleana entera. SO_BROADCAST Establece o consulta la opción de difusión. Cuando está activa, los conectores de datagramas reciben los paquetes enviados a una dirección de difusión y se les permite

Guia de programación de redes 52

Page 53: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

enviar paquetes a una dirección de difusión. Esta opción no tiene efecto en conectores orientados a conexión. SO_SNDBUF Establece u obtiene, en bytes, el máximo buffer de envío de un conector. El valor por defecto se configura con la sysctl wmem_default y el máximo valor permitido se establece con la sysctl wmem_max. SO_RCVBUF Establece u obtiene, en bytes, el máximo buffer de recepción de un conector. El valor por defecto se configura con la sysctl rmem_default y el máximo valor permitido se establece con la sysctl rmem_max. SO_LINGER Establece u obtiene la opción SO_LINGER. El argumento es una estructura linger. struct linger {

int l_onoff; /* activar/desactivar demora */ int l_linger; /* segundos de demora */

}; Cuando esta opción está activa, un close(2) o shutdown(2) no regresarán 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 través 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 también establece el campo "tipo de servicio IP" (TOS) para los paquetes de salida. SO_ERROR Obtiene y borra el error de conector pendiente. Sólo válida para getsockopt. Espera un entero. SEÑALES Cuando se escribe en un conector orientado a conexión que ha sido cerrado (por el extremo local o remoto) se envía una señal SIGPIPE al proceso escritor y se devuelve el valor de error EPIPE. No se envía la señal cuando la llamada para escritura especifica la opción MSG_NOSIGNAL. Cuando se solicita con la fcntl FIOCSETOWN o la ioctl SIOCSPGRP, la señal SIGIO se envía cuando se produce un evento de E/S. Es posible usar poll(2) o select(2) en el manejador de la señal para averigurar sobre qué conector se produjo el evento. Una alternativa (en Linux 2.2) es configurar una señal de tiempo real usando la fcntl F_SETSIG. Se llamará al manejador de la señal de tiempo real con el descriptor de fichero en el campo si_fd de su estructura siginfo_t. Vea fcntl(2) para más información. Bajo determinadas circunstancias (por ejemplo, varios procesos accediendo a un único conector), la condición que ha provocado la señal SIGIO puede haber desaparecido ya

Guia de programación de redes 53

Page 54: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

cuando el proceso reaccione a la señal. Si esto ocurre, el proceso debería esperar de nuevo ya que Linux reenviará la señal SIGIO más 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 recepción de un conector. rmem_max contiene el tamaño máximo, en bytes, del buffer de recepción de un conector que el usuario puede establecer usando la opción de conector SO_RCVBUF. wmem_default contiene el valor por defecto, en bytes, del buffer de envío de un conector. wmem_max contiene el tamaño máximo, en bytes, del buffer de envío de un conector que un usuario puede configurar usando la opción 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 Número máximo de paquetes en la cola de entrada global. optmem_max Longitud máxima 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 descripción de struct timeval. SIOCSPGRP Configura el proceso o grupo de procesos al que enviar la señal SIGIO o SIGURG cuando termina una operación de E/S asíncrona o hay disponibles datos urgentes. El argumento es un puntero a un pid_t. Si el argumento es positivo, las señales se envian a ese proceso. Si es negativo, las señales se envían al grupo de procesos cuyo identificador es el valor absoluto del argumento. El proceso sólo puede seleccionar a él mismo o a su propio grupo de procesos para que reciban las señales, a menos que posea la capacidad CAP_KILL o un identificador de usuario efectivo 0. FIOASYNC Modifica la opción O_ASYNC para habilitar o deshabilitar el modo de E/S asíncrona del conector. El modo de E/S asíncrona significa que se producirá una señal SIGIO, o la señal establecida mediante F_SETSIG, cuando se produzca un nuevo evento de E/S. El argumento es una opción booleana entera. SIOCGPGRP Obtiene el proceso o grupo de procesos actual que recibe las señal SIGIO o SIGURG, o 0 cuando no hay ningúno. Fcntls válidas: FIOCGETOWN Idéntica a la ioctl SIOCGPGRP. FIOCSETOWN Idéntica a la ioctl SIOCSPGRP.

Guia de programación de redes 54

Page 55: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

OBSERVACIONES Linux asume que se usa la mitad del buffer de envío/recepción para estructuras internas del núcleo. 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 versión 2.0.30 de Linux. SO_PASSCRED es nueva en la versión 2.2 del núcleo. Las sysctls son nuevas en Linux 2.2. VÉASE TAMBIÉN 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); DESCRIPCIÓN bind da al conector sockfd la dirección 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 dirección 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 varían entre familias de direcciones. Consulte las entradas de manual de la Sección 7 para obtener una información más 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 válido.

Guia de programación de redes 55

Page 56: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

EINVAL El conector ya está enlazado a una dirección. Esto puede cambiar en el futuro: véase linux/unix/sock.c para más detalles. EACCES La dirección está protegida y el usuario no es el superusuario. Los siguientes errores son específicos a los conectores del dominio UNIX (AF_UNIX): EINVAL La dirección 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 ‘sólo lectura’. EFAULT my_addr señala 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 núcleo. ENOTDIR Un componente del camino no es un directorio. EACCES El permiso de búsqueda ha sido denegado en uno de los componentes del camino. ELOOP Se han encontrado demasiados enlaces simbólicos al resolver my_addr. FALLOS No están descritas las opciones de proxy transparente. CONFORME A SVr4, 4.4BSD (la función bind apareció por primera vez en BSD 4.2). SVr4 documenta condiciones generales de error adicionales: EADDRNOTAVAIL, EADDRINUSE y ENOSR, y condiciones de error específicas 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 confusión en POSIX dio como resultado el actual socklen_t. El estándar propuesto todavía no ha sido adoptado pero glibc2 ya lo sigue y tiene también socklen_t. Véa también accept(2). VÉASE TAMBIÉN accept(), connect(), listen(), socket(), getsockname(), ip(), socket()

Guia de programación de redes 56

Page 57: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

• connect()

NOMBRE connect − inicia una conexión 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); DESCRIPCIÓN El descriptor de fichero sockfd debe referenciar a un conector. Si el conector es del tipo SOCK_DGRAM entonces la dirección serv_addr es la dirección a la que por defecto se envían los datagramas y la única dirección de la que se reciben datagramas. Si el conector es del tipo SOCK_STREAM o SOCK_SEQPA CKET, esta llamada intenta hacer una conexión a otro conector. El otro conector está especificado por serv_addr, la cual es una dirección (de longitud addrlen) en el espacio de comunicaciones del conector. Cada espacio de comunicaciones interpreta el parámetro serv_addr a su manera. Generalmente, los conectores de protocolos orientados a conexión pueden conectarse con éxito mediante connect una vez solamente; los conectores de protocolos no orientados a conexión pueden usar connect múltiples veces para cambiar sus asociaciones. Los conectores de protocolos no orientados a conexión pueden disolver la asociación conectandose a una dirección en la que al miembro sa_family de sockaddr se le ha asignado el valor AF_UNSPEC. VALOR DEVUELTO Si la conexión 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 sólo son errores generales de conector. Puede haber otros códigos de error específicos del dominio. EBADF El descriptor del fichero no es un índice válido de la tabla de descriptores. EFAULT La estructura de dirección 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 dirección remota. ETIMEDOUT Finalizó el plazo de tiempo mientras se intentaba la conexión. 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 programación de redes 57

Page 58: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

EADDRINUSE La dirección local ya está en uso. EINPROGRESS El conector es no bloqueante y la conexión no puede completarse inmediatamente. Es posible usar select(2) o poll(2) para completarla seleccionando el conector para escritura. Después que select indique que la escritura es posible, use getsockopt(2) para leer la opción 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 códigos de error usuales listados aquí, explicando la razón del fallo). EALREADY El conector es no bloqueante y todavía no se ha terminado un intento de conexión anterior. EAGAIN No hay más 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 cómo incrementar el número de puertos locales. EAFNOSUPPORT La dirección pasada no tiene la familia de direcciones correcta en su campo sa_family. EACCES, EPERM El usuario ha intentado conectarse a una dirección de difusión (broadcast) sin que el conector tenga activa la opción de difusión, o la petición de conexión ha fallado debido a una regla del cortafuegos local. CONFORME A SVr4, 4.4BSD (la función connect apareció por primera vez en BSD 4.2). SVr4 documenta adicionalmente los códigos de error generales EADDRNOTAVAIL, EINVAL, EAFNOSUPPORT, EALREADY, EINTR, EPROTOTYPE y ENOSR. También 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 confusión en POSIX dio como resultado el actual socklen_t. El estándar propuesto todavía no ha sido adoptado pero glibc2 ya lo sigue y también tiene socklen_t. Vea también accept(2). FALLOS (BUGS) Desconectar un conector llamando a connect con una dirección AF_UNSPEC no se ha implementado todavía. VÉASE TAMBIÉN 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 programación de redes 58

Page 59: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

DESCRIPCIÓN Para aceptar conexiones, primero se crea un conector con socket(2), luego se especifica con listen el deseo de aceptar conexiones entrantes y un límite 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 parámetro backlog define la longitud máxima a la que puede llegar la cola de conexiones pendientes. Si una petición de conexión llega estando la cola llena, el cliente puede recibir un error con una indicación de ECONNREFUSED o, si el protocolo subyacente acepta retransmisiones, la petición puede no ser tenida en cuenta, de forma que un reintento tenga éxito. NOTAS El comportamiento del parámetro backlog sobre conectores TCP ha cambiado con la versión 2.2 de Linux. Ahora indica la longitud de la cola para conectores establecidos completamente que esperan ser aceptados, en lugar del número de peticiones de conexión incompletas. La longitud máxima de la cola para conectores incompletos se puede configurar con la sysctl tcp_max_syn_backlog. Cuando los "syncookies" están activos, no existe una longitud máxima lógica y la configuración de esta sysctl se ignora. Vea tcp(7) para más información. 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 válido. ENOTSOCK El argumento s no es un conector. EOPNOTSUPP El conector no es de un tipo que admita la operación listen. CONFORME A Single Unix, 4.4BSD, borrador POSIX 1003.1g. La llamada a función 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 confíe en este valor puesto que BSD (y algunos sistemas derivados de BSD) limitan backlog a 5. VÉASE TAMBIÉN accept(2), connect(2), socket(2)

Guia de programación de redes 59

Page 60: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

• accept()

NOMBRE accept − acepta una conexión sobre un conector (socket). SINOPSIS #include <sys/types.h> #include <sys/socket.h> int accept(int s, struct sockaddr *addr, socklen_t *addrlen); DESCRIPCIÓN La función accept se usa con conectores orientados a conexión (SOCK_STREAM, SOCK_SEQPA CKET y SOCK_RDM). Extrae la primera petición de conexión 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 cuál es el valor devuelto por la llamada. El conector original s no se ve afectado por esta llamada. Dese cuenta que cualquier opción por descriptor de fichero (cualquiera que se pueda establecer con F_SETFL de fcntl, como no bloqueante o estado asíncrono) no se hereda en un accept. El argumento s es un conector que ha sido creado con socket(2), ligado a una dirección 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 dirección de la entidad con la que se conecta, tal y como la conoce la capa de comunicaciones. El formato exacto de la dirección pasada en el parámetro addr viene determinado por la familia del conector (vea socket(2) y las páginas de manual del protocolo correspondiente). El argumento addrlen es un parámetro de entrada/salida: al efectuar la llamada debe contener el tamaño de la estructura apuntada por addr; a la salida, contendrá la longitud real (en bytes) de la dirección 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 conexión. 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 conexión y entonces puede llamar a accept para obtener un conector para esa conexión. Alternativamente, puede configurar el conector para que provoque una señal SIGIO cuando se produzca actividad en el conector; vea socket(7) para más detalles. Para determinados protocolos que necesitan una confirmación explícita, tales como DECNet, accept se puede interpretar como una función que, simplemente, desencola la siguiente petición de conexión sin que ello implique la confirmación. Se sobreentiende la confirmación cuando se produce una lectura o escritura normal sobre el nuevo descriptor de fichero, y el rechazo puede ser de igual manera implícito cerrando el nuevo conector. Actualmente, sólo DECNet tiene esta semántica en Linux. NOTAS Puede que no siempre haya una conexión esperando después de que se produzca una señal SIGIO, o después de que select(2) o poll(2) devuelvan un evento de lectura, debido a que la conexión podría haber sido eliminada por un error asíncrono de red u

Guia de programación de redes 60

Page 61: Guia Programacion Sockets

Manuel Jiménez 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 conexión. Para asegurarse de que accept nunca se bloquea, es necesario que el conector s pasado tenga la opción 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 código de error de accept. Este comportamiento difiere de otras construcciones de conectores BSD. Para un funcionamiento fiable, la aplicación debe detectar los errores de red definidos por el protocolo tras una llamada a accept y tratarlos como EAGAIN reintentado la operación. 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 inválido. ENOTSOCK El descriptor referencia a un fichero, no a un conector. EOPNOTSUPP El conector referenciado no es del tipo SOCK_STREAM. EFAULT El parámetro addr no se encuentra en una zona accesible para escritura por el usuario. EPERM Las reglas del cortafuegos prohiben la conexión. ENOBUFS, ENOMEM No hay suficiente memoria disponible. Esto normalmente significa que la asignación de memoria está limitada por los límites del buffer de conectores, no por la memoria del sistema, aunque esto no es 100% coherente. Además, se pueden devolver otros errores de red para el nuevo conector y que se encuentren definidos en el protocolo. Diferentes núcleos de Linux pueden devolver otros errores diferentes como EMFILE, EINVAL, ENOSR, ENOBUFS, EPERM, ECONNABORTED, ESOCKTNOSUPPORT, EPROTONOSUPPORT, ETIMEDOUT, ERESTARTSYS. CONFORME A SVr4, 4.4BSD (la función accept apareció por primera vez en BSD 4.2). La página 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 programación de redes 61

Page 62: Guia Programacion Sockets

Manuel Jiménez 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 estándar propuesto POSIX 1003.1g quiso cambiarlo a ‘size_t *’ y así está en SunOS 5. Más tarde, los borradores POSIX tenían ‘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 tamaño 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 tamaño que "int" en arquitecturas de 64 bit. Y _tiene_ que ser del mismo tamaño 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 deberían haberlo tocado pero, una vez que lo hicieron, pensaron que debían tener un tipo con nombre propio por alguna insondable razón (probablemente alguien no quería desprestigiarse por haber cometido la estupidez original por lo que, simplemente, renombraron su metedura de pata de forma silenciosa). VÉASE TAMBIÉN bind(2), connect(2), listen(2), select(2), socket(2)

• send(),sendto().

NOMBRE send, sendto, sendmsg − envía 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); DESCRIPCIÓN 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 dirección de destino viene dada por to con tolen especificando su tamaño. La longitud del mensaje viene dada por len. Si el mensaje es demasiado largo para pasar automáticamente a través del protocolo inferior, se devuelve el error EMSGSIZE y el mensaje no es transmitido. La llamada send lleva implícita 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 envío 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 programación de redes 62

Page 63: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

bloqueante devolvería EAGAIN en este caso. Se puede utilizar la llamada select(2) para determinar cuando es posible enviar más información. El parámetro 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 noción (p.ej. SOCK_STREAM); el protocolo subyacente también debe soportar datos fuera de orden. MSG_DONTROUTE No usar un ‘‘gateway’’ para enviar el paquete, enviar sólo a los ordenadores que se encuentren en redes conectadas directamente. Normalmente, esto sólo lo utilizan los programas de diagnóstico y enrutamiento. Esta opción sólo está definida para familias de protocolos que enrutan. Los conectores de paquetes no enrutan. MSG_DONTWAIT Habilitar el funcionamiento no bloqueante. Si la operación se bloqueara, se devolvería EAGAIN (esto también se puede habilitar usando la bandera O_NONBLOCK con la operación F_SETFL de fcntl(2)). MSG_NOSIGNAL Solicitar el no enviar SIGPIPE en caso de error en conectores orientados a conexión cuando el otro extremo rompa la conexión. Todavía se devuelve el error EPIPE. Vea recv(2) para una descripción de la estructura msghdr. Puede enviar información de control usando los miembros msg_control y msg_controllen. La longitud máxima del buffer de control que el núcleo 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 estándares generados por la capa de conectores. Los módulos de los protocolos subyacentes pueden generar y devolver errores adicionales. Vea sus páginas de manual respectivas. EBADF Se ha especificado un descriptor no válido. ENOTSOCK El argumento s no es un conector. EFAULT Se ha especificado como parámetro una dirección incorrecta del espacio de usuario. tro. EMSGSIZE El conector requiere que este mensaje sea enviado automáticamente, y el tamaño del mensaje a ser enviado lo hace imposible. EAGAIN o EWOULDBLOCK El conector está marcado como no bloqueante y la operación solicitada lo bloquearía. 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 congestión 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 señal.

Guia de programación de redes 63

Page 64: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

ENOMEM No hay memoria disponible. EINVAL Se ha pasado un argumento inválido. EPIPE Se ha desconectado el extremo local en un conector orientado a conexión. En este caso el proceso tambíen recibirá una señal SIGPIPE a menos que se active la opción MSG_NOSIGNAL. CONFORME A 4.4BSD, SVr4, borrador POSIX 1003.1g (estas llamadas a función aparecieron en 4.2BSD). NOTA Los prototipos indicados más arriba siguen ‘the Single Unix Specification’, ya que glibc2 también 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 también accept(2). VÉASE TAMBIÉN 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); DESCRIPCIÓN 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 conexión o no. Si desde no es NULL y el conector no es orientado a conexión, la dirección fuente del mensaje se llena. El argumento londesde es un parámetro por referencia, inicializado al tamaño del búfer asociado con desde, y modificado cuando la función regresa para indicar el tamaño real de la dirección guardada ahí. La llamada a recv se utiliza normalmente sólo en un conector conectado (vea connect(2)) y es idéntica a recvfrom con un parámetro 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 búfer suministrado, los bytes que sobran pueden descartarse dependiendo del tipo de conector del que se reciba el mensaje (vea socket(2)).

Guia de programación de redes 64

Page 65: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Si no hay mensajes disponibles en el conector, las llamadas de recepción 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 recepción devuelven normalmente cualquier dato disponible, hasta la cantidad pedida, en vez de esperar la recepción de la cantidad pedida completa. Las llamadas select(2) o poll(2) pueden emplearse para determinar cuándo llegan más datos. El argumento flags de una llamada a recv se forma aplicando el operador de bits O-lógico a uno más de los valores siguientes: MSG_OOB Esta opción pide la recepción de datos fuera-de-banda que no se recibirían 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 opción no puede emplearse con tales protocolos. MSG_PEEK Esta opción hace que la operación de recepción devuelva datos del principio de la cola de recepción sin quitarlos de allí. Así, una próxima llamada de recepción devolverá los mismos datos. MSG_WAITALL Esta opción hace que la operación se bloquee hasta que se satisfaga la petición completamente. Sin embargo, la llamada puede aún devolver menos datos de los pedidos si se captura una señal, si ocurre un error o una desconexión, o si los próximos datos que se van a recibir son de un tipo diferente del que se ha devuelto. MSG_NOSIGNAL Esta opción desactiva el que se produzca una señal SIGPIPE sobre los conectores orientados a conexión cuando el otro extremo desaparece. MSG_ERRQUEUE Esta opción 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 tamaño suficiente. Vea cmsg(3) para obtener más información 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; /* número de error */ u_int8_t ee_origin; /* origen del error */ u_int8_t ee_type; /* tipo */ u_int8_t ee_code; /* código */ u_int8_t ee_pad; u_int32_t ee_info; /* información adicional */ u_int32_t ee_data; /* otros datos */

/* Pueden ir más datos a continuación .*/ };

Guia de programación de redes 65

Page 66: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

struct sockaddr *SOCK_EE_OFFENDER(struct sock_extended_err *); ee_errno contiene el número errno del error encolado. ee_origin es el código del origen en donde se ha originado el error. Los otros campos son específicos del protocolo. La macro SOCK_EE_OFFENDER devuelve un puntero a la dirección del objeto de red desde donde se ha originado el error dando un puntero al mensaje auxiliar. Si esta dirección 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 dirección (esto se puede comprobar con el miembro cmsg_len de cmsghdr). Para los errores recibidos, se asigna MSG_ERRQUEUE a msghdr. Después de que se haya pasado un error, el error de conector pendiente se regenera basándose en el siguiente error encolado y se pasará en la siguiente operación de conectores. La llamada recvmsg utiliza una estructura msghdr para minimizar el número de parámetros suministrados directamente. Esta estructura tiene la forma siguiente, según se define en <sys/socket.h>: struct msghdr {

void * msg_name; /* dirección opcional */ socklen_t msg_namelen; /* tamaño de la dirección */ struct iovec * msg_iov; /* vector dispersar/agrupar */ size_t msg_iovlen; /* nº de elementos en msg_iov */ void * msg_control; /* datos auxiliares, ver más abajo */ socklen_t msg_controllen; /* long buffer datos auxiliares */ int msg_flags; /* opciones en mensaje recibido */

}; Aquí msg_name y msg_namelen especifican la dirección 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 búfer 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; /* Nº de byte de datos, incluye cab. */ int cmsg_level; /* protocolo originante */ int cmsg_type; /* tipo específico del protocolo */

/* seguido poru_char cmsg_data[]; */ };

Guia de programación de redes 66

Page 67: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Los datos auxiliares sólo deberían 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 porción trasera de un datagrama ha sido descartada porque el datagrama era más grande que el búfer suministrado. MSG_CTRUNC indica que algún dato de control ha sido descartado debido a la falta de espacio en el búfer 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 ningún dato sino un error ampliado de la cola de errores de conectores. VALOR DEVUELTO Estas llamadas devuelven el número de bytes recibidos, o bien −1 en caso de que ocurriera un error. ERRORES Estos son algunos errores estándares generados por la capa de conectores. Los modulos de los protocolos subyacentes pueden generar y devolver errores adicionales. Consulte sus páginas de manual. EBADF El argumento s es un descriptor inválido. ENOTCONN El conector está asociado con un protocolo orientado a la conexión 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 operación de recepción produciría un bloqueo, o se ha puesto un límite de tiempo en la recepción, que ha expirado antes de que se recibieran datos. EINTR La recepción ha sido interrumpida por la llegada de una señal antes de que hubiera algún dato disponible. EFAULT El puntero a búfer de recepción (o punteros) apunta afuera del espacio de direcciones del proceso. EINVAL Se ha pasado un argumento inválido. 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 programación de redes 67

Page 68: Guia Programacion Sockets

Manuel Jiménez 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 también accept(2). VÉASE TAMBIÉN fcntl(), read(), select(), getsockopt(), socket(), cmsg()

• close()

NOMBRE close − cierra un descriptor de fichero SINOPSIS #include <unistd.h> int close(int fd); DESCRIPCIÓN 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 algún error. ERRORES EBADF fd no es un descriptor de fichero abierto válido. CONFORME A SVID, AT&T, POSIX, X/OPEN, BSD 4.3. SVr4 documenta una condición de error ENOLINK adicional. NOTA El no comprobar el valor devuelto por close es un error de programación común y no obstante serio. Aquellas implementaciones de sistemas de ficheros que usan técnicas tales como la conocida por ‘‘write-behind’’ (‘‘escribe por detrás’’) a fin de incrementar el rendimiento pueden resultar en que write(2) tenga éxito aunque aún no se hayan escrito los datos. El estado de error puede ser informado durante una posterior operación 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 pérdida silenciosa de datos. Esto se observa especialmente en NFS y con las cuotas de discos.

Guia de programación de redes 68

Page 69: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

VÉASE TAMBIÉN open(), fcntl(), shutdown(), unlink(), fclose().

• shutdown()

NOMBRE shutdown − cierra parte de una conexión bidireccional SINOPSIS #include <sys/socket.h> int shutdown(int s, int como); DESCRIPCIÓN La llamada a shutdown causa que se cierre completamente o en parte una conexión bidireccional en el conector asociado con s. Si como es 0, no se permitirán más recepciones. Si como es 1, no se permitirán más envíos. Si como es 2, no se permitirán más envíos 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 válido. ENOTSOCK s es un fichero, no un conector. ENOTCONN El conector especificado no está conectado. CONFORME A 4.4BSD (la función shutdown apareció por primera vez en 4.2BSD). FALLOS Las opciones numéricas how deben ser reemplazadas por las opciones SHUT_RD, SHUT_WR y SHUT_RDWR, según ordena "the Single Unix Specification". VÉASE TAMBIÉN 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 programación de redes 69

Page 70: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

DESCRIPCIÓN Getpeername devuelve el nombre de la pareja conectada al zócalo s. El parámetro longinom debería inicializarse de forma que indicara la cantidad de espacio a la que apuntara nombre. Al regresar la función, contendrá el tamaño real del nombre devuelto (en bytes). El nombre se trunca si el búfer provisto es demasiado pequeño. 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 válido. ENOTSOCK El argumento s es un fichero, no un zócalo. ENOTCONN El zócalo no está conectado. ENOBUFS No había en el sistema suficientes recursos como para efectuarse la operación. EFAULT El parámetro nombre apunta a memoria que no está en una zona válida 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 confusión en POSIX dio como resultado el actual socklen_t. El estándar propuesto todavía no ha sido adoptado pero glibc2 ya lo sigue y también tiene socklen_t. Vea también accept(2). VÉASE TAMBIÉN accept(), bind(), getsockname()

• getsockname()

NOMBRE getsockname − obtener nombre de conexión SINOPSIS #include <sys/socket.h> int getsockname(int s , struct sockaddr * name , socklen_t * namelen ) DESCRIPCIÓN Getsockname devuelve el nombre actual para la conexión indicada. El parámetro namelen debe ser inicializado para indicar la cantidad de espacio apuntador por name. La devolución contiene el tamaño actual del nombre devuelto (en bytes).

Guia de programación de redes 70

Page 71: Guia Programacion Sockets

Manuel Jiménez 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 válido. ENOTSOCK El argumento s es un fichero, no una conexión. ENOBUFS No había suficientes recursos disponibles en el sistema para llevar a cabo la operaicón. EFAULT El parametro name apunta a una memoria que no está dentro de una zona válida del espacio de direcciones del proceso. CONFORME A SVr4, 4.4BSD (la funcion getsockname apareció en 4.2BSD). SVr4 documenta dos códigos 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 confusión en POSIX dio como resultado el actual socklen_t. El estándar propuesto todavía no ha sido adoptado pero glibc2 ya lo sigue y también tiene socklen_t. Vea también accept(2). VÉASE TAMBIÉN bind(), socket()

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

NOMBRE gethostbyname, gethostbyaddr, sethostent, endhostent, herror, hstrerror − obtienen una entrada de anfitrión 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 programación de redes 71

Page 72: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

DESCRIPCIÓN La función gethostbyname() devuelve una estructura del tipo hostent para el anfitrión (host) dado name. Aquí, name es ora un nombre de anfitrión, ora una dirección IPv4 en la notación normal de puntos, ora una dirección IPv6 en la notación de dos puntos (y posiblemente de puntos). (Vea la RFC 1884 para una descripción de las direcciones en IPv6). Si name es una dirección IPv4 o IPv6, no se realiza ninguna búsqueda 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 señalado por HOSTALIASES. (Vea hostname(7) para saber cómo es el formato del fichero.) Se buscan el dominio actual y sus ancestros a menos que name termine en punto. La función gethostbyaddr() devuelve una estructura del tipo hostent para la dirección de anfitrión dada addr de longitud len y de tipo type. El único tipo de dirección válido actualmente es AF_INET. La función sethostent() especifica, si stayopen es true (1), que se debería emplear un conector (socket) TCP para las interrogaciones al servidor de nombres y que la conexión debería permanecer abierta durante sucesivas preguntas. De otro modo, las peticiones al servidor de nombres utilizarán datagramas UDP. La función endhostent() termina el uso de una conexión TCP para las peticiones al servidor de nombres. La (obsoleta) función herror() muestra en stderr un mensaje de error asociado con el valor actual de h_errno. La (obsoleta) función hstrerror() toma un número 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 combinación de uno o todos los servidores de nombres named(8), una declaración en /etc/hosts, y el Servicio de Información de Red (NIS, antes Páginas Amarillas, YP), dependiendo de los contenidos de la línea order en /etc/host.conf. (Vea resolv+(8)). La acción 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; /* nombre oficial del anfitrión */ char **h_aliases; /* lista de alias */ int h_addrtype; /* tipo dirección anfitrión */ int h_length; /* longitud de la dirección */ char **h_addr_list; /* lista de direcciones */

} #define h_addr h_addr_list[0] /* por compatibilidad atrás */ Los miembros de la estructura hostent son: h_name El nombre oficial de un anfitrión. h_aliases Una cadena terminada en el carácter nulo de los nombres alternativos para el anfitrión. h_addrtype

Guia de programación de redes 72

Page 73: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

El tipo de dirección; siempre AF_INET de momento. h_length La longitud de la dirección en bytes. h_addr_list Una cadena terminada en nulo de direcciones de red para el anfitrión en orden de bytes de red. h_addr La primera dirección en h_addr_list por compatibilidad hacia atrás. 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 número de error. ERRORES La variable h_errno puede tener los siguientes valores: HOST_NOT_FOUND El anfitrión especificado es desconocido. NO_ADDRESS o NO_DATA El nombre pedido es válido pero no tiene una dirrección 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 más tarde. FICHEROS /etc/host.conf fichero de configuración del revolvedor /etc/hosts fichero de base de datos de anfitriones CONFORME A BSD 4.3. SUS-v2 declara el parámetro len como del tipo size_t. RUMORES La futura glibc2.2 seguirá SUS-v2. VÉASE TAMBIÉN 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 programación de redes 73

Page 74: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

struct protoent *getprotobyname(const char *nombre); struct protoent *getprotobynumber(int proto); void setprotoent(int dejaloabierto); void endprotoent(void); DESCRIPCIÓN La función getprotoent() lee la siguiente línea del fichero /etc/protocols y devuelve una estructura protoent que contiene los campos de que consta la línea. El fichero /etc/protocols se abre si es necesario. La función getprotobyname() devuelve una estructura protoent para la línea de /etc/protocols que concuerde con el nombre de protocolo nombre. La función getprotobynumber() devuelve una estructura protoent para la línea que concuerde con el número de protocolo proto. La función 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 función endprotoent() cierra /etc/protocols. La estructura protoent se define en <netdb.h> así: struct protoent {

char *p_name; /* nombre oficial de protocolo */ char **p_aliases; /* lista de sinónimos */ int p_proto; /* número 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 número 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 VÉASE TAMBIÉN getservent(), getnetent(), protocols()

Guia de programación de redes 74

Page 75: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

• getservbyname() 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); DESCRIPCIÓN La función getservent() lee la siguiente línea del fichero /etc/services y devuelve una estructura servent que contiene en sus campos los campos de la línea. Si es necesario, se abre el fichero /etc/services. La función getservbyname() devuelve una estructura servent conteniendo los campos de la línea de /etc/services que contiene el servicio name y usa el protocolo proto. La función getservbyport() devuelve una estructura de tipo servent con los datos de la línea que contiene el puerto port (con los bytes en el orden de red) y usa el protocolo proto. La función 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 función endservent() cierra el fichero /etc/services. La estructura servent está definida en <netdb.h> como sigue: struct servent {

char *s_name; /* nombre oficial del servicio */ char **s_aliases; /* lista de alias */ int s_port; /* número de puerto */ char *s_proto; /* 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 programación de redes 75

Page 76: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

FICHEROS /etc/services Fichero de base de datos de servicios CONFORME A BSD 4.3 VÉASE TAMBIÉN getprotoent(), getnetent(), services()

• 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); DESCRIPCIÓN fcntl realiza una de las diversas y variadas operaciones sobre fd. La operación en cuestión se determina mediante cmd: F_DUPFD Busca el descriptor de fichero disponible de menor número 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 posición de ficheros y banderas (flags); por ejemplo, si la posición del fichero se modifica usando lseek en uno de los descriptores, la posición del otro resulta modificada simultáneamente. Sin embargo, los dos descriptores no comparten la bandera close-on-exec "cerrar-al-ejecutar". La bandera close-on-exec de la copia está desactivada, significando que no se cerrará en ejecución. 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, según hayan sido asignadas por open(2), serán devueltas). F_SETFL Asigna las banderas del descriptor al valor asignado por arg. Sólo 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 programación de redes 76

Page 77: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Las banderas y su semántica están 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 obstrucción. 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 más, esta llamada devuelve −1 y pone en errno el código 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 señal a capturar mientras fcntl está esperando, se interrumpe y (después de que el manejador de la señal 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 señales de disponibilidad de E/S: F_GETOWN Obtiene el ID de proceso o el grupo de procesos que actualmente recibe las señales 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 señales 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 señal 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 señal SIGIO cuando sea posible la entrada o la salida sobre ese descriptor de fichero. El proceso o el grupo de procesos que recibirá la señal se puede seleccionar usando la orden F_SETOWN de la función fcntl. Si el descriptor de fichero es un enchufe (socket), esto también seleccionará al recipiente de las señales SIGURG que se entregan cuando llegan datos fuera de orden (out-of-band, OOB) sobre el enchufe. (SIGURG se envía en cualquier situación en la que select(2) informaría que el enchufe tiene una "condición excepcional"). Si el descriptor de fichero corresponde a un dispositivo de terminal, entonces las señales SIGIO se envían al grupo de procesos en primer plano de la terminal. F_GETSIG Obtiene la señal enviada cuando la entrada o la salida son posibles. Un valor cero significa que se envía SIGIO. Cualquier otro valor (incluyendo SIGIO) es la señal enviada en su lugar y en este caso se dispone de información adicional para el manejador de señal si se instala con SA_SIGINFO. F_SETSIG Establece la señal enviada cuando la entrada o la salida son posibles. Un valor cero significa enviar la señal por defecto SIGIO. Cualquier otro valor (incluyendo SIGIO) es

Guia de programación de redes 77

Page 78: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

la señal a enviar en su lugar y en este caso se dispone de información adiciona para el manejador de señal si se instala con SA_SIGINFO. Usando F_SETSIF con un valor distinto de cero y asignando SA_SIGINFO para el manejador de señal (vea sigaction(2)), se pasa información 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 están disponibles para E/S, debería usar los mecanismos usuales (select(2), poll(2), read(2) con O_NONBLOCK activo, etc.). Seleccionando una señal de tiempo real POSIX.1b (valor >= SIGRTMIN), se pueden encolar varios eventos de E/S usando los mismos números de señal. (El encolamiento depende de la memoria disponible). Se dispone de información extra si se asigna SA_SIGINFO al manejador de señal, como antes. Usando estos mecanismos, un programa puede implementar E/S totalmente asíncrona, sin usar select(2) ni poll(2) la mayor parte del tiempo. El uso de O_ASYNC, F_GETOWN y F_SETOWN es específico de Linux y BSD. F_GETSIG y F_SETSIG son específicos de Linux. POSIX posee E/S asíncrona y la estructura aio_sigevent para conseguir cosas similares; estas también están 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 operación: 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 señal 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 operación está prohibida por candados mantenidos por otros procesos. EAGAIN La operación está prohibida porque el fichero ha sido asociado a memoria por otro proceso. EDEADLK Se ha detectado que el comando F_SETLKW especificado provocaría 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 señal. Para F_GETLK y F_SETLK, la orden fue interrumpida por una señal antes de que el candado fuera comprobado o adquirido. Es más 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 máximo permitido. Para F_SETSIG, arg no es un número de señal permitido. EMFILE Para F_DUPFD, el proceso ya ha llegado al número máximo 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 programación de redes 78

Page 79: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

EPERM Se ha intentado limpiar la bandera O_APPEND sobre un fichero que tiene activo el atributo de ‘sólo añadir’ (append-only). NOTAS Los errores devueltos por dup2 son distintos de aquéllos dados por F_DUPFD. CONFORME A SVID, AT&T, POSIX, X/OPEN, BSD 4.3. Sólo 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 específicos de Linux. Las banderas legales para F_GETFL/F_SETFL son aquéllas que acepta open(2) y varían 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. VÉASE TAMBIÉN open(), socket(), dup2(), flock().

• select()

NOMBRE select, FD_CLR, FD_ISSET, FD_SET, FD_ZERO − multiplexación de E/S síncrona 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); DESCRIPCIÓN select espera a que una serie de descriptores de ficheros cambien su estado. Se miran tres conjuntos independientes de descriptores. Aquéllos listados en readfds serán observados para ver si hay caracteres que llegan a estar disponibles para lectura, aquéllos en writefds serán observados para ver si es correcto escribir inmediatamente en ellos, y aquéllos en exceptfds serán 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 añaden 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 después de que select regrese. n es el descriptor con el número más alto en cualquiera de los tres conjuntos, más 1.

Guia de programación de redes 79

Page 80: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

timeout es un límite 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 número 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 estarán indefinidos, así que no confíe en sus contenidos tras un error. ERRORES EBADF Se ha dado un descriptor de fichero inválido en uno de los conjuntos. EINTR Se ha capturado una señal no bloqueante. EINVAL n es negativo. ENOMEM select no ha sido capaz de reservar memoria para las tablas internas. OBSERVACIONES Hay algún código por ahí que llama a select con los tres conjuntos vacíos, n cero, y un timeout distinto de cero como una forma transportable y curiosa de dormir con una precisión por debajo del segundo. En Linux, timeout se modifica para reflejar la cantidad de tiempo no dormido; la mayoría de otras implementaciones no hacen esto. Esto produce problemas cuando el código de Linux que lee timeout se transporta a otros sistemas operativos, y cuando se transporta a Linux código que reutiliza una struct timeval para varias selects en un bucle sin reinicializarla. Considere que timeout está indefinido después 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 programación de redes 80

Page 81: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

if (valret) {

printf("Los datos ya están disponibles.\n"); /* FD_ISSET(0, &rfds) será verdadero */

} else {

printf("Ningún dato en 5 segundos.\n"); return(0);

} } CONFORME A 4.4BSD (la función select apareció por primera vez en 4.2BSD). Generalmente es transportable a o desde sistemas no-BSD que admitan clones de la capa de zócalos 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. VÉASE TAMBIÉN 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; DESCRIPCIÓN La rutina perror() produce un mensaje que va a la salida estándar 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 línea. Para ser de más utilidad, la cadena de caracteres pasada como argumento debería incluir el nombre de la función que incurrió en el error. El código 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 errónea. Puede utilizarse la lista global de errores sys_errlist[], indexada por errno, para obtener el mensaje del error sin el salto de línea. El número más 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 añadido aún nuevos valores de error a sys_errlist[].

Guia de programación de redes 81

Page 82: Guia Programacion Sockets

Manuel Jiménez 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 también hacen esto. La función perror() sirve para traducir este código de error a una forma que los humanos puedan entender. Observe que errno tiene un valor sin definir tras una llamada exitosa: esta llamada podría muy bien cambiar incluso el valor de esta variable, a pesar de haber acabado bien, por ejemplo porque internamente empleara alguna otra función 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 VÉASE TAMBIÉN 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); DESCRIPCIÓN 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; /* segundos */ long tv_usec; /* microsegundos */

}; y tz es una estructura timezone : struct timezone {

int tz_minuteswest; /* minutos al O de Greenwich */ int tz_dsttime; /* tipo de correción 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 programación de redes 82

Page 83: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

cada una de las ocurrencias de este campo en el fuente del núcleo (distintas de la declaración) son un fallo. Por tanto, lo siguiente es puramente de interés histórico. El campo tz_dsttime contiene una constante simbólica (los valores se dan más abajo) que indica en qué parte del año la ‘correción horaria invierno/verano’ (Daylight Saving Time, DST, también conocida como ‘horario de ahorro energético’) está en vigor. (Nota: su valor es constante a lo largo del año - no indica si DST está o no en vigor, sólo selecciona un algoritmo). Los algoritmos de corrección 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 Bretaña e Irlanda */ DST_RUM /* Rumanía */ DST_TUR /* Turquía */ DST_AUSTALT /* est. australiano con cambio en 1986 */ Naturalmente, resultó ser que el periodo en el que la corrección horaria invierno/verano está en vigor no se podía dar mediante un simple algoritmo, uno por país: de hecho, este periodo se determina por decisiones políticas impredecibles. Por tanto, este método de representación 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 semánticas de ‘Warp clock’ peculariares asociadas a la llamada al sistema settimeofday si en la primera llamada de todas (después 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 característica 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 más) es inválida. EFAULT

Guia de programación de redes 83

Page 84: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Uno de tv o tz apuntaba afuera de su espacio de direcciones accesible. CONFORME A SVr4, BSD 4.3 VÉASE TAMBIÉN date(), adjtimex(), time(), ctime(), ftime()

7.2. Libros Si buscas libros auténticos que se pueden tocar revisa alguna de las excelentes guías que hay a continuación.

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 además información de otros aspectos de la programación 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 programación de redes 84

Page 85: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Intro to TCP/IP (gopher)

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 Transmisión (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 más comunes Q: ¿De dónde saco esos archivos de cabecera? Q: ¿Qué hago cuando bind() responde "Address already in use" [La dirección ya se está usando]? Q: ¿Cómo puedo obtener una lista de los sockets abiertos en el sistema?

Guia de programación de redes 85

Page 86: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Q: ¿Cómo se ve la tabla de encaminamiento? Q: ¿Cómo puedo ejecutar el servidor y el cliente si solamente tengo un ordenador? ¿No necesito una red para escribir programas de redes? Q: ¿Cómo puedo saber si el sistema remoto ha cerrado la conexión? Q: ¿Cómo se implementa la utilidad "ping"? ¿Qué es ICMP? ¿Dónde puedo averiguar más cosas sobre los sockets puros [raw sockets]? Q: ¿Cómo compilo sobre Windows ? Q: ¿Cómo compilo sobre Solaris/SunOS? ¡Todavía tengo errores al enlazar! Q: ¿Porque termina select() al recibir una señal? Q: ¿Cómo puedo implementar un temporizador [timeout] en una llamada a recv() ? Q: ¿Cómo 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: ¿Cómo puedo escribir un servidor que acepte comandos de shell de un cliente y los ejecute? Q: ¿Estoy enviando un montón de datos, pero cuando hago recv(), sólo recibo 536 bytes ó 1460 bytes a la vez. Sin embargo, si ejecuto en mi máquina local recibo todos los datos al mismo tiempo. ¿Qué pasa? Q: Yo uso Windows y no tengo la llamada al sistema fork() ni ningún tipo de estructura struct sigaction. ¿Qué hago? Q: ¿Cómo puedo enviar datos seguros con TCP/IP usando encriptación? Q: Estoy detrás de un cortafuegos [firewall]--¿Cómo informo a la gente al otro del cortafuegos de cual es mi dirección IP para que puedan conectarse a mi máquina?

Q: ¿De dónde saco esos archivos de cabecera?

A: Si no están ya en tu sistema, probablemente no los necesitas. Consulta el manual de tu plataforma. Si estás compilando en Windows sólo 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 dirección ya se está usando]?

A: Tienes que usar setsockopt() con la opción SO_REUSEADDR sobre el socket en el que estás escuchando ( listen() ). Consulta la sección sobre bind() y la sección sobre select() para ver un ejemplo.

Q: ¿Cómo puedo obtener una lista de los sockets abiertos en el sistema?

A: Usa netstat . Revisa la página man para conocer a fondo los detalles, pero deberías tener un resultado aceptable con sólo teclear:

$ netstat

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

Guia de programación de redes 86

Page 87: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Q: ¿Cómo se ve la tabla de encaminamiento?

A: Usa el comando route (En la mayoría de Linuxes lo encontrarás en /sbin) o el comando netstat -r.

Q: ¿Cómo 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 máquinas implementan un "dispositivo" de red de cierre de circuito [loopback] que reside en el núcleo y simula ser una tarjeta de red. (Este es el interfaz que se lista como "lo" en la tabla de encaminamiento.)

Supón que has iniciado sesión en una máquina 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 código funcione en una sola máquina sin conexión a la red! ¡Magnífico!

Q: ¿Cómo puedo saber si el sistema remoto ha cerrado la conexión?

A: Lo sabes porque recv() devuelve 0.

Q: ¿Cómo se implementa la utilidad "ping"? ¿Qué es ICMP? ¿Dónde puedo averiguar más cosas sobre los sockets puros [raw sockets]?

A: Todas tus dudas referidas a sockets puros tienen respuesta en los libros de programación UNIX en redes de W. Richard Stevens. Consulta la sección de libros de esta guía.

Q: ¿Cómo compilo sobre Windows ?

A: Como Windows es el mejor sistema operativo que existe sólo has de seguir las recomendaciones de la sección sobre compilación en Windows de la introducción.

Q: ¿Cómo compilo sobre Solaris/SunOS ? ¡Todavía tengo errores al enlazar!

A: Los errores de enlace suceden porque las máquinas Sun no enlazan automáticamente las bibliotecas de sockets. Consulta la sección sobre compilación en Solaris/SunOS de la introducción, donde hallarás un ejemplo de cómo hacerlo.

Q: ¿Por qué termina select() al recibir una señal?

A: Las señales suelen causar que las llamadas al sistema que están bloqueadas devuelvan el valor -1 con errno establecifo a EINTR. Cuando preparas un manejador de

Guia de programación de redes 87

Page 88: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

señales 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 solución 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 señal 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 solución más limpia.

Q: ¿Cómo puedo implementar un temporizador [timeout] en una llamada a recv()?

A: ¡Usa select() ! Te permite indicar un parámetro de control de tiempo sobre los descriptores de socket en los que esperas leer. También puedes implementar toda esa funcionalidad en una única función como esta:

#include <unistd.h> #include <sys/time.h> #include <sys/types.h> #include <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 programación de redes 88

Page 89: Guia Programacion Sockets

Manuel Jiménez 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 máquina remota ha cerrado la conexión. 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: ¿Cómo 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 más allá del alcance de esta guía.

Pero suponiendo que quieres implementar tu propio sistema de compresión o encriptado, sólo es cuestión 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ú añades esta parte) 3. El servidor envía ( send() ) los datos encriptados

Y ahora el camino inverso:

4. El cliente recibe ( recv() ) los datos encriptados 5. El cliente desencripta los datos (tú añades esta parte) 6. El cliente escribe los datos en un archivo (o donde sea)

Podrías comprimir los datos en el punto en que arriba se encritan/desencriptan los datos. ¡O podrías hacer las dos cosas!. Sencillamente recuerda comprimir antes de encriptar.

Guia de programación de redes 89

Page 90: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

En la medida en que el cliente deshaga correctamente lo que el servidor haga, los datos llegarán correctamente al final, sin importar cuántos pasos intermedios se añadan.

Así que todo lo que necesitas para usar mi código es encontrar el lugar situado entre la lectura y el envío ( send() ) de los datos, y poner allí el código 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 sección socket() para los detalles.

Q: ¿Cómo 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() ), envía ( send() ), y cierra ( close() ) la conexión (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 conexión

Mientras tanto el servidor procesa los datos de esta manera:

1. accept() la conexión del cliente 2. recv(str) el comando a ejecutar 3. close() la conexión 4. 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 podría hacer cosas no deseadas con tu cuenta cuando se conectan al servidor. Por ejemplo, en el ejemplo anterior, ¿qué pasa si el cliente envía "rm -rf * "? Borra todo lo que tengas en tu cuenta, ¡eso es lo que pasa!

Así que aprendes la lección e impides que el cliente use cualquier comando, a excepción 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, todavía no estás seguro: ¿qué pasa si el cliente envía "foobar; rm -rf ~ "? Lo mejor que se puede hacer es escribir una pequeña rutina que ponga un carácter de escape("\") delante de cualquier carácter no alfanumérico (incluyendo espacios, si es necesario) en los argumentos para el comando.

Guia de programación de redes 90

Page 91: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

Como puedes ver, la seguridad es un problema bastante importante cuando el servidor comienza a ejecutar cosas que el cliente envía.

Q: ¿Estoy enviando un montón de datos, pero cuando hago recv(), sólo recibo 536 bytes ó 1460 bytes a la vez. Sin embargo, si ejecuto en mi máquina local recibo todos los datos al mismo tiempo. ¿Qué pasa?

A: Te estás tropezando con la MTU (Maximum Transfer Unit)--El tamaño máximo de paquete que el medio físico puede manejar. En la máquina local estás usando el dispositivo de cierre de circuito [loopback] que puede manejar sin problemas 8K o más. Pero en una Ethernet, que sólo puede manejar 1500 bytes con una cabecera, tienes que plegarte a ese límite. Sobre un módem, con una MTU de 576 (con cabecera), has de plegarte a un límite aún menor.

En primer lugar tienes que asegurarte de que todos los datos se están enviando. (Revisa la implementación de la función sendall() para ver los detalles). Una vez que estás seguro de eso, tienes que llamar a recv() en un bucle hasta que todos los datos se hayan leído.

Revisa la sección Consecuencias de la encapsulación de datos para los detalles acerca de como recibir paquetes completos de datos usando múltiples llamadas a recv().

Q: Yo uso Windows y no tengo la llamada al sistema fork() ni nigún tipo de estructura struct sigaction . ¿Qué hago?

A: De estar en algún sitio estarán en las bibliotecas POSIX que quizás 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() tendría que estar (y a lo mejor también sigaction.)

Busca "fork" o "POSIX" en la ayuda de VC++, por si te da alguna pista.

Si no hay forma de que funcione, olvídate de fork()/sigaction y usa en su lugar la función equivalente de Win32: CreateProcess(). No sé cómo se usa CreateProcess() --Tiene muchos argumentos, pero seguramente está explicada en la ayuda de VC++

Q: ¿Cómo puedo enviar datos seguros con TCP/IP usando encriptación?

A: Visita el Proyecto OpenSSL.

Q: Estoy detrás de un cortafuegos [firewall]--¿Cómo informo a la gente del otro lado del cortafuegos de cual es mi dirección IP para que puedan conectarse a mi máquina?

A: Desgraciadamente, la finalidad de un cortafuegos es evitar que la gente del otro lado del cortafuegos pueda conectarse a las máquinas 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, todavía puedes conectar ( connect() ) a través del cortafuegos si éste está realizando algún tipo de enmascaramiento [masquerading] o NAT [Network Address Translation - Traducción

Guia de programación de redes 91

Page 92: Guia Programacion Sockets

Manuel Jiménez Castro 3ª Telecomunicaciones

de direcciones de red] o algo por el estilo. Tan sólo tienes que diseñar tus programas de modo que seas tú siempre quien inicia la conexión, y todo irá bien.

Si esta solución 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 más difícil de lo que puedas imaginar.

No hagas que el administrador de tu sistema se enfade conmigo.

9. Declinación de responsabilidad y solicitud de ayuda Bien, esto es todo. Con suerte, por lo menos parte de la información 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 podría ser completamente erróneo.

Aunque probablemente no es ese el caso. Al fin y al cabo, he invertido un montón de horas mareándome con todo esto, y en el trabajo he implementado varias aplicaciones de red TCP/IP. Además, he escrito los núcleos de algunos juegos multiusuario, y algunas cosas más. Pero no soy el dios de los sockets. Solamente soy un tío.

Por cierto, si alguien tiene alguna crítica constructiva (o destructiva) sobre este documento, por favor que la envíe a <[email protected] > 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 montón de gente me preguntaba cosas acerca de los sockets, y cuando les decía que había estado pensando en ponerlo todo junto en una página de sockets, me decían, "¡estupendo!" Además, me parecía que todo este conocimiento que tanto me ha costado adquirir iba a desperdiciarse si no podía compartirlo con otros. La web solamente es el vehículo perfecto. Animo a los demás a suministrar información parecida en la medida en que sea posible.

¡Basta de charlas--volved al trabajo!

Guia de programación de redes 92