struct E Gestione dei Dati

Bene, finalmente ci siamo. E' ora di parlare di programmazione. In questa sezione, affronterò vari tipi di dato usati dalle interfacce dei socket, perchè alcune di esse sono brutte bestie da capire.

Prima quello facile: un descrittore di socket. Un descrittore di socket è il seguente tipo:

    int 

Proprio un normale int.

Le cose divengono strane da qui in poi, quindi continuate a leggere e abbiate pazienza con me. Sappiate questo: ci sono due ordinamenti di byte: il byte più significativo (talvolta detto "ottetto") per primo, o il meno significativo per primo. Il primo viene detto "Network Byte Order" (ordine dei byte della Rete). Alcune mecchine immagazzinano i loro numeri internamente in Network Byte Order, alcune no. Quando dico che qualcosa deve essere in Network Byte Order, dovete chiamare una funzione (come htons()) per convertirlo dal suo "Host Byte Order" (ordine dei byte della macchina) . Se non dico "Network Byte Order", allora dovreste lasciare il valore in Host Byte Order.

(Per i curiosi, il "Network Byte Order" è spesso noto anche come "Big-Endian Byte Order".)

La Mia Prima StructTM--struct sockaddr. Questa struttura contiene le informazioni sull'indirizzo del socket per molti tipi di socket differenti:

    struct sockaddr {
        unsigned short    sa_family;    // famiglia dell'indirizzo, AF_xxx
        char              sa_data[14];  // 14 byte di indirizzo del protocollo
    }; 

sa_family può essere una varietà di cose, ma sarà sempre AF_INET per tutto quello che faremo in questo documento. sa_data contiene un indirizzo di destinazione ed un numero di porta per il socket. Ciò è abbastanza ingestibile poichè non vorrete mai annoiarvi a infilare l'indirizzo in sa_data a mano.

Per gestire struct sockaddr, i programmatori hanno creato una struttura parallela: struct sockaddr_in ("in" sta per "Internet".)

    struct sockaddr_in {
        short int          sin_family;  // famiglia dell'indirizzo
        unsigned short int sin_port;    // numero di Porta
        struct in_addr     sin_addr;    // indirizzo Internet 
        unsigned char      sin_zero[8]; // stessa dimensione di struct sockaddr
    }; 

questa struttura rende semplice fare riferimento all'indirizzo del socket. Notate che sin_zero (che viene incluso per fararrivare la struttura alla lunghezza di una struct sockaddr) dovrebbe essere riempita di zeri con la funzione memset(). Inoltre, e questa è la parte importante, un puntatore a una struct sockaddr_in può essere sottoposto a cast e convertito in una struct sockaddr e viceversa. Così anche se socket() vuole una struct sockaddr*, potet comunque usare una struct sockaddr_in e poi convertirla con un cast all'ultimo minuto! Inoltre, notate che sin_family corrisponde al parametro sa_family in una struct sockaddr e dovrebbe essere impostato a "AF_INET". Infine, sin_port e sin_addr devono essere in Network Byte Order!

"Ma," obietterete, "come può l'intera struttura, struct in_addr sin_addr, essere in Network Byte Order?" Questa domanda richiede un'esame accurato della struttura struct in_addr, una delle peggiori unioni viventi:

    // indirizzo Internet  (una struttura per ragioni storiche)
    struct in_addr {
        unsigned long s_addr; // questo è lungo 32-bit, o 4 byte
    }; 

bene, questa era una union, ma ora quesi giorni sembrano passati. Che sollievo. Dunque avete dichiarato ina come tipo struct sockaddr_in, e ina.sin_addr.s_addr si riferisce all'indirizzo IP di 4-byte (in Network Byte Order). Notate che anche se il vostro sistema usa ancora un'Immonda union per struct in_addr, potete comunque fare riferimento all'indirizzo IP di 4-byte esattamanete nel modo in cui io l'ho fatto qui sopra (ciò grazie ai #define.)

Converti i Nativi!

Ora siamo arrivati nel bel mezzo della prossima sezione. C'è stato un bel parlare di questa conversione da ordine di Rete a ordine dell'Host--ora è il momento dell'azione!

Perfetto. Ci sono due tipi che potete convertire: short (due byte) e long (quattro byte). Queste funzioni funzionano anche per le varianti unsigned. Supponiamo che vogliate convertire uno short da ordine dell'Host a ordine di Rete. Cominciate con una "h" per "host", seguita da "to" , poi "n" per "rete", e "s" per "short": h-to-n-s, o htons() (leggete: "Host to Network Short" ,short dall'ordine di macchina all'ordine di rete).

E' quasi troppo semplice...

Potete usare ogni combinazione volete con "n", "h", "s", ed "l", senza contare quelle veramente stupide. Ad esempio, NON c'è una funzione stolh() ("Short to Long Host") --not at this party, almeno(!). Ma ci sono:

Ora, potreste pensare di saperne abbastanza su questo punto. Potreste pensare, "cosa dovrei fare se dovessi cambiare l'ordine dei byte per un tipo char?" Allora potreste pensare, "Oh, chi se ne frega." ootreste anche pensare che visto che la vostra macchina con CPU 68000 usa già l'ordinamento di Rete dei byte, non dovreste chiamare htonl() sul vostro indirizzo IP. Avreste ragione, MA se provaste a portare il vostro programma a una macchina che avesse un ordinamento differente, il vostro programma fallirebbe. Siate portabili! Questo è un mondo Unix ! (Per quanto a Bill Gates piaccia pensare diversamente.) ricordate: mettete i vostri byte in Network Byte Order prima di mandarli sulla rete.

Un appunto finale: perchè sin_addr e sin_port devono essere in Network Byte Order in una struct sockaddr_in, m sin_familyno? La risposta: sin_addr e sin_port vengono incapsulati nel pacchetto rispettivamente al livello IP ed al livello UDP. Dunque, devono essere in Network Byte Order. Invece, il campo sin_family viene usato solo dal kernel per determinare quale tipo di indirizzo sia contenuto nella struttura, dunque deve essere in Host Byte Order. Ancora, poichè sin_family non viene inviato sulla rete, può essere in in Host Byte Order.

Gli Indirizzi IP e come Gestirli

Fortunatamente per voi, ci sono un sacco di funzioni che vi permettono di manipolarethat gli indirizzi IP. Non c'è bisogno di fabbricarseli a mano e di sbatterli in un long con l'operatore << .

Anzitutto, supponiamo che abbiate una struct sockaddr_in ina, ed abbiate l'indirizzo IP "10.12.110.57" , da immagazzinare al suo interno. La funzione che volete usare, inet_addr(), converte un indirizzo IP in notazione decimale puntata in una unsigned long. L'assegnazione può essere effettuata come segue:

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

Notate che la funzione inet_addr() restituisce l'indirizzo già in Network Byte Order --non dovrete chiamare htonl(). Eccellente!

Ora, il ritaglio di codice non è molto robusto poichè non c'è controllo d'errore. Guardate, inet_addr() restituisce un errore -1 . Ricordate i numeri binari? pare proprio che (unsigned)-1 corrisponda all'indirizzo IP 255.255.255.255! Questo è l'indirizzo di broadcast ! Sbagliato. Ricordate di effettuare il controllo d'errore in maniera appropriata.

Effettivamente, c'è un'interfaccia più pulita che potete usare al posto di inet_addr(): si chiama inet_aton() ("aton" sta per "ascii to network", da stringa a byte ordinati per la rete):

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>

    int inet_aton(const char *cp, struct in_addr *inp); 

E qui c'è un esempio d'uso, nella creazione di una struct sockaddr_in (questo esempio avrà più senso per voi quando arriverete alla sezione su bind() e connect().)

    struct sockaddr_in my_addr;

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

inet_aton(), diversamente da praticamente tutte le altr funzioni collegate ai socket, restituisce non-zero in caso di sucesso, e zero nel caso di fallimento. e l'indirizzo viene restituito come inp(!).

Sfortunatamente, non tutte le piattaforme implementano inet_aton() dunque, anche se non è la preferita, in questa guida viene usata la più vecchia e comune inet_addr() .

Bene, ora potete convertire indirizzi IP da stringa alla loro rappresentazione binaria. Cosa dire dell'altra strada? Cosa dovreste fare se aveste una struct in_addr e voleste stamparla in notazione decimale puntata? In questo caso, vorrete usare la funzione inet_ntoa() ("ntoa" sta per "network to ascii") così:

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

Questo stamperà l'indirizzo IP . Notate che inet_ntoa() prende una struct in_addr come argomento, non un long. Notate anche che restituisce un puntatore a carattere. Questo punta ad un array di caratteri immagazzinato staticamente in inet_ntoa() cosicchè ogni volta che chiamate inet_ntoa() essa sovrascriverà l'ultimo indirizzo IP che avevate richiesto. Ad esempio:

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

stamperà :

    indirizzo 1: 10.12.110.57
    indirizzo 2: 10.12.110.57 

Se avete bisogno di salvare l'indirizzo, usate strcpy() per copiarlo in un vostro array di caratteri.

Questo è tutto su questo argomento per ora. Più tardi, imparerete a convertire una stringa come "whitehouse.gov" nel suo indirizzo IP corrispondente (guardate DNS, più avanti.)