Chiamate il sistema

Questa è la sezione dove entriamo nelle chiamate di sistema che vi permettono di avere accesso alle funzionalità di rete di una macchina Unix. Quando chiamate una di queste funzioni, il kernel prende il controllo della situazione e magicamente fa tutto il lavoro per voi.

Il punto dove la maggior parte della gente si blocca qui intorno, è nel capire l'ordine in cui le cose devono essere chiamate. In quello, le pagine man non sono utili, come probabilmenteavrete scoperto. Bene, per aiutarvi in questa situazione fastidiosa, Ho cercato di mostrare le chiamate di sistema nella seguente sezione esattamente (approsimativamente) nello stesso ordine in cui avrete bisogno di chiamarle nei vostri programmi.

Ciò, accopiato con qualche pezzo di codice d'esempio qui e là, con un po di latte e biscotti (che temo dovrete procurarvi da soli), un po' di rude coraggio e spavalderia, e comincerete a sparare dati in giro per Internet like the Son of Jon Postel! (!)

socket()--Prendete quel Descrittore di File!

Credo di non poter più rimandare --Devo parlare della chiamata di sistema socket() . Qui c'è il tutto:

    #include <sys/types.h>
    #include <sys/socket.h>

    int socket(int domain, int type, int protocol); 

Ma cosa sono questi argomenti? Primo, domain deve essere impostato ad "AF_INET", proprio come nella struct sockaddr_in ( sopra). Poi, l'argomento type dice al di che tipo di socket si tratta: SOCK_STREAM o SOCK_DGRAM. Infine, impostate semplicemente protocol a "0" per far si che socket() scelga il protocollo corretto basato su type. (Notate: ci sono molti domain (domini) che non ho elencato. Ci sono molti più type (tipi) di quelli che ho elencato. Guardatevi la pagina man di socket(). Inoltre c'è un modo "migliore" per ottenere il valore di protocol. Guardate la pagina man della funzione getprotobyname() .)

socket() vi restituisce semplicemente un descrittore di socket che potrete usare nelle successive chiamate di sistema, o un -1 per un errore. La variabile globale errno viene settata al valore dell'errore (guardate la pagina man di perror() .)

In certe documentazioni, vedrete menzionata una mistica "PF_INET". Queta è una strana bestia eterea che si vede raramente in natura, ma potrei comunque chiarirvi un po le cose. Tanto tempo fa, si pensò che forse una famiglia di indirizzi (quello per cui c'è "AF" in "AF_INET" ) avrebbe potuto supportare parecchi protocolli a cui ci si sarebbe riferiti tramite la loro famiglia di protocolli (ecco perchè "PF" in "PF_INET" ). Ciò non è accaduto. Oh, bene. Dunque la cosa corretta sarebbe usare AF_INET nella vostra struct sockaddr_in e PF_INET nella vostra chiamata alla funzione socket(). Ma in senso pratico, potete usare AF_INET dappertutto. E, poichè questo è ciò che fa W. Richard Stevens nel suo libro, questo è quello che farò io qui.

Bene, bene, bene, ma a cosa serve questo socket? La risposta è che in effetti non serve a nulla da solo, ed avrete bisogno di leggere oltre ed usare altre chiamate di sistema affinchè abbia un qualche senso .

bind()--Su che porta mi trovo?

Una volta che avete ottenuto un socket, potreste dover associare quel socket con una port sulla vostra macchina locale. (Questo normalmente viene effettuato se dovete ascoltare con listen() connessioni in arrivo su una porta specifica-- i MUD fanno questo quando vi dicono di "aprire telnet verso x.y.z sulla porta 6969".) Il numero di porta viene usato dal kernel per associare un pacchetto in arrivo al descrittore di socket di un certo processo. Se dovete solo effettuare un collegamento, con connect(), questo potrebbe essere superfluo. Leggetelo ugualmente, tanto per (!)just for kicks.

Qui c'è la sinossi per la chiamata di sistema bind():

    #include <sys/types.h>
    #include <sys/socket.h>

    int bind(int sockfd, struct sockaddr *my_addr, int addrlen); 

sockfd è il descrittore di file del socket restituito da socket(). my_addr è un puntatore a una struct sockaddr che contiene le informazioni sul vostro indirizzo, vale a dire, porte e indirizzo IP. addrlen può essere impostato alla dimensione della struct con sizeof(struct sockaddr).

Fiiiù. Questo è un po troppo da assorbire in una volta. Facciamo un esempio:

    #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); // qualche controllo d'errore!

        my_addr.sin_family = AF_INET;         // host byte order
        my_addr.sin_port = htons(MYPORT);     // short, network byte order
        my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
        memset(&(my_addr.sin_zero), '\0', 8); // azzera il resto della struct

        // non dimenticate il vostro controllo d'errore per bind():
        bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
        .
        .
        . 

Ci sono alcune cose da notare qui: my_addr.sin_port è in Network Byte Order. Così come my_addr.sin_addr.s_addr. Un'altra da cui guardarsi è che i file header potrebbero essere differenti da sistema a sistema . Per essere sicuri, dovrete controllare le vostre pagine man locali.

In ultimo, parlando di bind(), avrei docuto dire che alcuni dei processi per ottenere il vostro indirizzo IP e/o la porta possono essere automatizzati:

        my_addr.sin_port = 0; // sceglie una porta inutilizzata casuale
        my_addr.sin_addr.s_addr = INADDR_ANY;  // usa il mio indirizzo IP  

Vedete, impostando a zero my_addr.sin_port, state dicendo a bind() di scegliere la porta per voi. In modo simile, impostando my_addr.sin_addr.s_addr a INADDR_ANY, gli state dicendo di metterci automaticamente l'indirizzo IP della macchina sulla quale sta girando il processo.

Se siete abituati a notare le piccole cose, potresteaver visto che non ho messo INADDR_ANY in Network Byte Order! Sono stato cattivo. Comunque, Io ho delle notizie dall'interno: INADDR_ANY è in realtà zero! Zero è ancora zero in bit anche se riordinate i byte. Comunque, i puristi faranno notare che potrebbe esserci una dimensione parallela dove INADDR_ANY è, diciamo, 12 e nella quale il mio codice non funzionerebbe. Questo mi sta bene:

        my_addr.sin_port = htons(0); // sceglie una porta inutilizzata casuale
        my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // usa il mio indirizzo IP 

Ora siamo così portabili che probabilmente non ci credereste. Volevo solo farlo notare, perchè la maggior parte del codice che incontreretet non si preoccuperà di far passare INADDR_ANY attraverso htonl().

anche

bind() restituisce -1 per gli errori e imposta errno al valore dell'errore.

un'altra cosa a cui stare attenti quando si chiama bind(): non andate troppo giù con i numeri di porta. tutte le porte sotto la 1024 sono RISERVATE (a meno che non siate il superutente)! Potete avere ogni numero di porta sopra quelli, fino a 65535 (posto che essi non siano già usati da altri programmi.)

Talvolta, potreste notare, cercherete di riavviare un server e bind() fallirà, lamentandosi con "Address already in use." (indirizzo già utilizzato) . Cosa significa? Bene, evidentemente un socket che era stato connesso è ancora da qualche parte nel kernel, e si tiene la porta per se. Potreste o aspettare che si liberi (un minuto o giù di lì), o aggiungere codice al vostro programma permettendogli di riutilizzare la porta , così:

    int yes=1;
	//char yes='1'; // gli utente di Solaris usino questo

    // elimina il fastidioso messaggio d'errore "Address already in use" 
    if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
        perror("setsockopt");
        exit(1);
    } 

Una piccola nota finale extra a proposito di bind(): Ci sono casi in cui non dovrete assolutamente chiamarla . Se state usando connect()per collegarvi a una macchina remota e non dovete curarvi di qual'è la vostra porta locale (come nel caso di telnet dove dovrete solo preoccuparvi della porta remota), potete semplicemente chiamare connect(), ed essa controllerà per sapere se il socket è libero, e la attaccherà con bind() ad una porta locale inutilizzata se necessario.

connect()--Hey, tu!

Supponiamo per pochi minuti che voi siate un'applicazione telnet. il vostro utente vi ordina (proprio come nel film TRON) di procurarvi un descrittore socket. Voi obbedite e chiamate socket(). Poi, l'utente vi chiede di connettervi a "10.12.110.57" sulla porta "23" (la porta telnet standard.) Yow! Cosa fate ora?

Fortunato te, mio caro programma, ora stai affrontando la sezione su connect()-- come connettersi a un host remoto. Dunque leggete oltre furiosamente! Non c'è tempo da perdere!

La chiamata connect() è la seguente:

    #include <sys/types.h>
    #include <sys/socket.h>

    int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); 

sockfd è il nostro amichevole vicino socket descrittore di file, come viene restituito dalla chiamata socket() , serv_addr è una struct sockaddr contenente la porta e l'indirizzo IP di destinazione, e addrlen può essere impostata a sizeof(struct sockaddr).

Il tutto non comincia ad avere senso ? Facciamo un esempio:

    #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;   // conterrà l'indirizzo di destinazione

        sockfd = socket(AF_INET, SOCK_STREAM, 0); // qualche controllo d'errore!

        dest_addr.sin_family = AF_INET;          // host byte order
        dest_addr.sin_port = htons(DEST_PORT);   // short, network byte order
        dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
        memset(&(dest_addr.sin_zero), '\0', 8);  // a zero il resto della struttura

        // non dimenticate di effettuare il controllo su connect()!
        connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
        .
        .
        . 

Ancora, assicuratevi di controllare il valore di ritorno di connect()--restituirà -1 in caso d'errore e imposterà la variabile errno.

Inoltre, notate che noi non abbiamo chiamiato bind(). Di base, non ci importa del nostro numero di porta locale; ci preoccupiamo solo di dove stiamo andando (la porta remota). Il kernel sceglierà una porta locale libera per noi, ed il sito al quale ci collegheremo otterrà automaticamente questa informazione da noi. Non preoccupatevi.

listen()--Qualcuno mi chiama per favore?

Ok, è ora di cambiare passo. Cosa fare se non volete collegarvi a un host remoto? Poniamo, tanto per parlare, che vogliate attendere le connessioni in arrivo per gestirle in qualche maniera. Il procedimento si compone di due passi: prima dovrete ascoltare, con listen(), poi potrete usare accept() (vedete oltre.)

La chiamata listen è abbastanza semplice, ma richiede un po di spiegazioni:

    int listen(int sockfd, int backlog); 

sockfd è il solito socket descrittore di file generato dalla chiamata socket(). backlog è il numero di connessioni ammesse nella coda di ricezione. Che significa questo? Bene, le connessioni in entrata dovranno aspettare in questa coda finchè non le accetterete usando accept() (vedere più in basso)e questo è il limite su quante possono mettersi in coda. La maggior parte dei sistemi limita silenziosamente questo numero a circa 20; potete stare tranquilli impostandolo a 5 o 10.

Ancora, come al solito, listen() restituisce -1 e imposta errno ad un certo errore.

Bene, come potrete immaginare,abbiamo bisogno di chiamare bind() prima di chiamare listen() o il kernel sarà in ascolto su una porta casuale. Bleah! Dunque se dovete ascoltare le connessioni in arrivo, la sequenza di chiamate di sistema che dovrete fare sarà

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

Lascerò questo al posto del codice d'esempio, perchè è abbastanza auto esplicativo. (Il codice nella sezione di accept(), qui sotto, è più completo.) La parte veramente veramente complessa di tutta questa roba è la chiamata di accept().

accept()--"Grazie per aver chiamato la Porta 3490."

Tenetevi pronti--la chiamata accept() è piuttosto strana! Quello che sta per accadere è questo: qualcuno da molto molto lontano cercherà di connettersi (con connect()) alla vostra macchina ad una porta su cui siete in ascolto (con listen()). La loro connessione verrà messa in coda nell'attesa che sia accettata (con accept()). Voi chiamate accept() e le dite di andrae a prendersi la connessione in attesa. Esa vi restituirà un descrittore di file nuovo di zecca da usare per questa sola connessione. Esattamente, all'improvviso vi ritrovate con due socket al prezzo di unoone! L'originale è ancora in ascolto sulla vostra porta ed il nuovo è finalmente pronto per inviare e ricevere! (con send() e recv()). Ci siamo!

La chiamata è la seguente:

     #include <sys/socket.h>

     int accept(int sockfd, void *addr, int *addrlen); 

sockfd è il descrittore socket che è in ascolto con listen(). Abbastanza semplice. addr generalmente sarà un puntatore a una struttura locale struct sockaddr_in. Qui è dove andranno le informazioni sulla connessione (e con esse potrete determinare quale host vi sta chiamando e da quale porta). addrlen è un variabile intera locale che dovrebbe essere impostata a sizeof(struct sockaddr_in) prima di essere passata ad accept(). Accept non metterà più di tanti byte in addr. Se ne mettesse di meno, cambierà il valore di addrlen per riflettere il cambiamento.

Indovinate ? accept() restituisce -1 ed imposta errno se c'è un errore. Scommetto che non ve l'avreste mai immaginato.

Come prima, questo è un bel pacco di roba da assimilare tutto in una volta, quindi qui c'è un codice d'esempio da studiarvi:

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

    #define MYPORT 3490    // la porta alla quale gli utenti si collegheranno

    #define BACKLOG 10     // quante connessioni verranno tenute in coda d'attesa

    main()
    {
        int sockfd, new_fd;  // ascolta su sock_fd, le nuove connessioni vanno su new_fd
        struct sockaddr_in my_addr;    // le informazioni del mio indirizzo
        struct sockaddr_in their_addr; // le informazioni dell'indirizzo di quello che si collega
        int sin_size;

        sockfd = socket(AF_INET, SOCK_STREAM, 0); // facciamo qualche controllo d'errore!

        my_addr.sin_family = AF_INET;         // host byte order
        my_addr.sin_port = htons(MYPORT);     // short, network byte order
        my_addr.sin_addr.s_addr = INADDR_ANY; // si auto riempe con il mio  IP
        memset(&(my_addr.sin_zero), '\0', 8); // azzera il resto della struttura

        // non dimenticate di fare controlli d'errore su queste chiamate:
        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);
        .
        .
        . 

Ancora una volta, notate che useremo il descrittore di socket new_fd per tutte le funzioni di ricezione e invio ( send() e recv() ). Se doveste avere solo una singola connessione per volta, potreste usare close() per chiudere sockfd in modo da evitare altre connessioni sulla stessa porta, se lo desideraste.

send() e recv()--Parlami, dolcezza!

Queste due funzioni servono a comunicare attraverso gli stream socket o attraverso i datagram socket connessi. Se volete usare i normali datagram socket non connessi, dovreste vedere la sezione su sendto() e recvfrom(), più avanti.

La chiamata send():

    int send(int sockfd, const void *msg, int len, int flags); 

sockfd è il descrittore di socket al quale volete inviare dei dati ( sia quello restituito da socket() che quello che avete ottenuto con accept().) msg è un puntatore ai dati che volete inviare, e len è la lunghezza dei dati in byte. Impostate semplicemente flags a 0. (vedere la pagina man di send() per maggiori informazioni riguardo le flag.)

Un codice d'esempio potrebbe essere:

    char *msg = "Beej è stato qui!"
    int len, bytes_sent;
    .
    .
    len = strlen(msg);
    bytes_sent = send(sockfd, msg, len, 0);
    .
    .
    . 

send() restituisce il numero di byte effettivamente inviati --potrebbe essere meno del numero che gli avete detto di inviare! Vedete, alle volte potreste dirgli di inviare un enorme pacco di dati e potrebbe non riuscire a gestirli. Saranno trasmessi tutti i dati possibili, e ci si aspetta che voi inviate i restanti in seguito. Ricordate, se il valore rstituito da send() non corrisponde al valore di len, sta a voi inviare il resto della stringa. La buona notizia e questa: se il pacchetto è piccolo (meno di 1K o poco più) probabilmente sarà possibile gestirlo tutto con un unico invio. Ancora, verrà restituito -1 in caso d'errore, e errno verrà impostato al numero dell'errore.

La chiamata recv() è simile in molti aspetti:

    int recv(int sockfd, void *buf, int len, unsigned int flags); 

sockfd è il descrittore di socket dal quale leggere, buf è il buffer nel quale leggere le informazioni, len è la massima lunghezza del buffer, e flags ancora una volta può essere impostato a 0. (Vedere la pagina man di recv() per informazioni sulle flag )

recv() restituisce il numero di byte effettivamente letti nel buffer, o -1 in caso d'errore (con errno impostata di conseguenza)

Aspettate! recv() può restituire 0. Questo può significare una sola cosa: il lato remoto della connessione ha chiuso la connessione verso di voi! Un valore di ritorno uguale a 0 è il modo che ha recv() per farvi capire che è successo questo.

Ci siamo, quwesto era facile, non è vero? Ora potete passare i dati su e giù per gli stream socket! Olè! Siete Programmatori Di Rete UNIX!

sendto() e recvfrom()--Parlami, modalità-DGRAM

"Tutto questo è piacevole e (!) dandy," vi sento dire, "ma come me la cavo con i datagram socket non connessi?" No problemo, amigo. Abbiamo la cosa che fa per te.

poichè i datagram socket non sono connessi ad un host remoto, indovinate quale parte di informazione dobbiamo fornire prima di poter inviare un pacchetto? Esatto! L'indirizzo di destinazione! Qui c'è tutta la storia:

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

Come potete vedere, questa chiamata è sostanzialmente uguale a send() con l'aggiunta di altri due pezzetti di informazione. to è un puntatore a una struct sockaddr (che probabilmente avrete come struct sockaddr_in e passerete attraverso un cast all'ultimo minuto) che contiene l'indirizzo IP di destinazione e la porta. tolen può essere semplicemente impostato a sizeof(struct sockaddr).

Proprio come send(), sendto() restituisce il numero di byte effettivamente inviati (i quali, ancora una volta, possono essere meno del numero di byte che gli avevate detto di trasmetter!) , o un -1 in caso di errore.

Alla stessa maniere, recv() e recvfrom() sono simili. La sinossi per recvfrom() è:

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

Ancora una volta, questa è esattamente come per recv() con l'aggiunta di un paio di campi. from è un puntatore a una struct sockaddr locale che verrà riempita con l'indirizzo e la porta della macchina d'origine. fromlen è un puntatore a un int locale che dovrebbe venire inizializzato a sizeof(struct sockaddr). Quando la funzione ritorna, fromlen contterrà la lunghezza dell'indirizzo effettivamente immagazzinato in from.

recvfrom() restituisce il numero di byte ricevuti, o -1 in caso d'errore (con errno impostato in maniera appropriata.)

Ricordate, se usate connect() con un datagram socket, potete semplicemente usare send() e recv() per tutte l transazioni. Il socket in se è ancora un datagram socket ed i pacchetti sono ancora UDP, ma l'interfaccia che avrete verso il socket aggiungerà automaticamente le informazioni sulla destinazione e sulla sorgente per voi.

close() e shutdown()--Via da davanti a me!

Fiiù! Siete stati a inviare e ricevere dati con send() e recv() tutto il giorno ed, alla fine l'avete compreso per bene. Siete pronti per chiudere la connessione su vostro socket. Questa è facile. Potete usare la normale funzione di Unix per i descrittori di file, close():

    close(sockfd); 

Questo eviterà qualsiasi altra lettura e scrittura verso il socket. Chiunque tenti di leggere o scrivere il socket da remoto riceverà un errore.

Solo nel caso voleste avere un più di controllo sulla chiusura del socket, potreste usare la funzione shutdown() . Essa vi permette di troncare le comunicazioni in una certa direzione, o in entrambe (proprio come fa close().) Sinossi:

    int shutdown(int sockfd, int how); 

sockfd è il descrittore di file che volete chiudere, e how ha uno dei seguenti valori:

shutdown() restituisce 0 in caso vada a buon termine, e -1 in caso di errore (con errno impostata di conseguenza.)

Se vi scomodaste a usare shutdown() su datagram socket non connessi, essa renderà semplicemente impossibili ulteriori chiamate send() e recv() (ricordate che potete usarle, se usate connect() su un socket datagramma.)

E' importante notare che shutdown() non chiude veramente il descrittore di file--semplicemente ne cambia l'usabilità. Per liberare un descrittore, dovete usare close().

Non c'è altro.

getpeername()--Chi sei tu?

Questa funzione è così facile.

E' così semplice, che quasi non le avevo dato una sua sezione. Ma è qui, comunque.

La funzione getpeername() vi dirà chi si trova dall'altra parte di uno stream socket connesso. La sinossi:

    #include <sys/socket.h>

    int getpeername(int sockfd, struct sockaddr *addr, int *addrlen); 

sockfd è il descrittore dello stream socket connesso, addr è un puntatore a una struct sockaddr (o a unastruct sockaddr_in) che conterrà le informazioni sull'altro lato della connessione, e addrlen è un puntatore a un int, che dovrebbe esere inizializzato al valore di sizeof(struct sockaddr).

La funzione restituisce -1 in caso d'errore e imposta errno di conseguenza.

Una volte che avete il loroindirizzo, potete usare inet_ntoa() o gethostbyaddr() per stampare o ottenere maggiori informazioni. No, non potete ottenere il loro nome di login. (Ok, ok. Se 'altro computer ha un demone ident attivo, ciò è possibile. Comunque, ciò è oltre lo scopo di questo documento. Date un'occhiata aRFC-1413 per maggiori informazioni)

gethostname()--Chi sono io ?

La funzione gethostname() è anche più facile di getpeername() . Essa restituisce il nome del computer sul quale sta girando il vostro programma. Il nome può poi essere usato da gethostbyname(), più avanti, per determinare l'indirizzo IP della vostra macchina locale.

Cosa potrebbe essere più divertente? Mi vengono in mente un paio di cose, ma non hanno a che fare con la programmazione dei socket. Ad ogni modo, qui c'è tutta la storia:

    #include <unistd.h>

    int gethostname(char *hostname, size_t size); 

gli argomenti sono semplici: hostname è un puntatore a una stringa di caratteri che conterrà l'hostname dopo che la fine della funzione, e size è la lunghezza in byte dell'array hostname.

La funzione restituisce 0 in caso di completamento con successo, e -1 in caso d'errore, impostando errno come al solito.

DNS--Tu dici "whitehouse.gov", Io dico "198.137.240.92"

In caso non sappiate cos'è il DNS, esso sta per "Domain Name Service" . In parole povere, voi gli dite queal'è l'indirizzo umanizzato per un sito, ed esso vi restituirà l'indirizzo IP (così potrete usarlo con bind(), connect(), sendto(), o qualunque altra cosa di cui abbiate bisogno.) in questo modo, quando qualcuno digita:

    $ telnet whitehouse.gov

telnet può capire che deve collegarsi con connect() a "198.137.240.92".

Ma come fa a funzionare? Dovrete usare la funzione gethostbyname():

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

Come potete vedere, esso restituisce un puntatore a una struct hostent, la quale è fatta così:

    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] 

E qui ci sono i descrittori dei campi in struct hostent:

gethostbyname() restituisce un puntatore alla struct hostent appena riempita, o NULL in caso d'errore. (Ma errno non viene modificato --al suo posto viene impostato h_errno. Vedere herror(), più avanti.)

Ma come si usa? Alcune volte (come si vede leggendo i manuali di computer), vomitare informazioni in faccia al lettore non è abbastanza. Questa funzione è nettamente più facile da usare di quello che sembra.

Qui c'è un esempio:

    /*
    ** getip.c -- una dimostrazione per ottenere gli ip
    */

    #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) {  // controlla la correttezza della riga di comando
            fprintf(stderr,"uso: getip indirizzo\n");
            exit(1);
        }

        if ((h=gethostbyname(argv[1])) == NULL) {  // ottiene le informazioni sull'host
            herror("gethostbyname");
            exit(1);
        }

        printf("Host name  : %s\n", h->h_name);
        printf("Indirizzo IP  : %s\n", inet_ntoa(*((struct in_addr *)h->h_addr)));
       
       return 0;
    } 

Con gethostbyname(), non potete usare perror() per stampare un messaggio d'errore (poichè errno non viene usato). Invece potete chiamare herror().

E' abbastanza lineare. Semplicemente passate la stringa che contiene il nome della macchina ("whitehouse.gov") a gethostbyname(), e poi prendetevi le informazioni dalla struct hostent che vi è stata restituita.

La sola stranezza possibile potrebbe apparire nella stampa dell'indirizzo IP qua sopra. h-h_addr è una char*, ma inet_ntoa() vuole che gli sia passata una struct in_addr . Quindi io sottopongo h-h_addr a cast in una struct in_addr*, che la dereferenzia per avereaccesso al dato.