Guía Beej de programación en redes

Anterior


Siguiente


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 .

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 (precisamente porque sigue siendo un struct ).

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:

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.

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. (Si alguien sabe porqué, por favor que me lo diga.) La dirección se almacena en inp .

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.

Esto 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.)


Anterior

Inicio

Siguiente

¿Qué es un socket ?


Llamadas al sistema