Guía Beej de programación en redes

Anterior


Siguiente


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 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?

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.

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 direeció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) a 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 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 no está 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
        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. 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á lista 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:



    #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.

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).
is the socket descriptor you want to send data to (whether it's the one returned by socket() or the one you got with accept() .) msg is a pointer to the data you want to send, and len is the length of that data in bytes. Just set flags to 0. (See the send() man page for more information concerning 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 puede y confía en que tu 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!

4.7. sendto() y recvfrom()--Háblame al estilo DGRAM

"Todo esto está muy bien", te escucho, "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 real 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 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 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:

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); 

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 el 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.2.240.92"

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:

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.

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_addrstruct in_addr* , y desreferencio para obtener los datos.


Anterior

Inicio

Siguiente

struct s y manipulación de datos


Modelo Cliente-Servidor