Beej-ov vodič za mrežno programiranje

Prethodno

 

Dalje


3. struct-ure i rukovanje podacima

Konačno smo ovde. Vrijeme je da pričamo o programiranju. U ovom dijelu, pokriću razne tipove podataka korištene u obraćanju soketima, pošto su neke od njih prava muka za shvatiti.

Prvo nešto lako: soket-deskriptor. Soket-deskriptor je sledećeg tipa:

    int 

Najobičniji cio broj.

Stvari odavde pa nadalje postaju uvrnute, tako da samo čitaj i trpi me. Znaj ovo: postoje dva uređenja bajtova: najvažniji bajt (ponekad poznat kao "oktet") na prvom mjestu, i pod dva: najmanje bitan bajt na prvom mjestu. Ovaj prethodni je nazvan "Mrežno uređenje bajtova" (Network Byte Order). Neke mašine interno smještaju svoje brojeve u mrežnom uređenju bajtova, neke ne. Kad kažem da nešto mora biti u mrežnom uređenju bajtova, moraš da pozoveš neku funkciju (recimo htons()) da ga prevedeš iz "serverskog uređenja bajtova" (Host Byte Order). Ako ne spomenem "mrežno uređenje bajtova", onda možeš da ga ostaviš u serverskom uređenju bajtova.

(Za radoznale, "mrežno uređenje bajtova" je takođe poznato kao "Big-Endian Byte Order".)

Moja Prva StrukturaTM  - struct sockaddr. Ova struktura čuva informacije o adresi soketa za mnoge tipove soketa:

    struct sockaddr {
        unsigned short    sa_family;    // familija adrese, AF_xxx
        char              sa_data[14];  // 14 bajtova adrese protokola
    }; 

sa_family može biti mnoštvo stvari, ali biće AF_INET za sve što mi radimo u ovom dokumentu. sa_data sadrži odredišnu adresu i broj porta za soket. Ovo je prilično nezgrapno pošto niko neće da ručno, mukotrpno pakuje adresu u sa_data.

Da bi radili sa struct sockaddr, programeri su razvili paralelnu strukturu: struct sockaddr_in ("in" kao "internet".)

    struct sockaddr_in {
        short int          sin_family;  // Familija adrese
        unsigned short int sin_port;    // Broj porta
        struct in_addr     sin_addr;    // Internet adresa
        unsigned char      sin_zero[8]; // Da bude iste veličine kao struct sockaddr
    };

Ova struktura čini lakim da se obraćamo elementima adrese soketa. Primijeti da bi sin_zero (koji je uključen da produži strukturu do dužine strukture struct sockaddr) trebao biti podešen na sve same nule funkcijom memset(). Takođe, a ovo je JAKO bitno, pokazivač na strukturu struct sockaddr_in može biti kastovan u pokazivač na struct sockaddr i obrnuto. Tako da, iako socket() očekuje struct sockaddr *, možeš koristiti struct sockaddr_in i kastovati kad zatreba! Takođe, primijeti da sin_family odgovara sa_family u strukturi struct sockaddr i treba da bude podešen na AF_INET. Konačno, sin_port i sin_addr moraju biti u mrežnom uređenju bajtova!

"Ali," protiviš se ti, "kako može cijela struktura, struct in_addr sin_addr, biti u mrežnom uređenju bajtova?" Ovo pitanje zahtijeva pažljiv prilaz strukturi struct in_addr, jednoj od najgorih živih unija:

    // Internet adresa (struktura zbog istorijskih razloga)
    struct in_addr {
        unsigned long s_addr; // to je 32 bita dužine, ili 4 bajta
    }; 

Pa, nekad je to bila unija, ali sad su, izgleda, ti dani prošli. Dobro izbavljenje. Tako ako si predstavio ina da bude tipa struct sockaddr_in, onda ina.sin_addr.s_addr predstavlja četvorobajtnu IP adresu (u mrežnom uređenju bajtova). Primijeti da čak iako tvoj sistem možda koristi od Boga prognanu uniju umjesto strukture za struct in_addr, ipak možeš da se obratiš četvorobajtnoj adresi baš kao što sam i ja iznad (Ovo zahvaljujući #define-ovima)

3.1. Prevedi primitive!

Eto nas dovedenih pravac u sledeće poglavlje. Dosta je bilo priče o "mrežno u serversko uređenje" prevođenjima – vrijeme je za akciju.

E, fino. Postoje dva tipa koja možeš prevesti: short (dva bajta) i long (četiri bajta). Ove funkcije rade i sa unsigned verzijama isto tako dobro. Recimo da hoćeš da prevedeš neki short iz serverskog uređenja bajtova u mrežno uređenje bajtova. Počni sa "h" za "host" (server), nastavi sa "to" (u – prevedi u), i onda "n" za "network" (mreža), i "s" za "short": h-to-n-s, or htons() (čita se: "Host to Network Short").

Skoro da je prejednostavno....

Možeš koristiti bilo koju kombinaciju "n", "h", "s", i "l", isključujući one stvarno glupe. Npr., nema stolh() ("Short to Long Host") funkcije – ne na ovoj žurci, zapravo. Ali postoje:

·         htons() - "Host to Network Short"

·         htonl() - "Host to Network Long"

·         ntohs() - "Network to Host Short"

·         ntohl() - "Network to Host Long"

Sad, možda ti se učini da ulaziš polako u ovo. Možeš pomisliti "Šta ako treba da promijenim raspored bajtova u promjenjivoj tipa char?" Onda ćeš možda pomisliti, "Uh, nema veze." Možeš takođe pomisliti da, budući da tvoja mašina "68000" već koristi mrežno uređenje bajtova, pa da ti ne moraš pozivati htonl() na svojim IP adresama. Bio bi u pravu, ALI ako pokušaš da preneseš program na mašinu koja ima obrnut raspored, program će pasti. Neka ti programi budu prenosivi! Ovo je svijet UNIX-a! (Koliko god Bil Gejts htio misliti drugačije.) Zapamti: stavi bajtove u mrežno uređenje prije nego što odu na mrežu.

Jedna stavka za kraj: Zašto sin_addr i sin_port moraju biti u mrežnom uređenju bajtova u strukturi struct sockaddr_in, a sin_family ne mora? Odgovor je: sin_addr i sin_port se enkapsuliraju u paket na IP i UDP slojevima, redom. Stoga, moraju biti u tom uređenju. S druge strane, sin_family polje je korišteno samo od strane jezgra da sazna kakav tip adrese struktura sadrži, pa mora biti u serverskom uređenju bajtova. Takođe, pošto sin_family ne biva poslata napolje na mrežu, ona u svakom slušaju može biti u serverskom uređenju bajtova.

3.2. IP Adrese i kako rukovati njima

Na sreću po tebe, postoji mnoštvo funkcija koje rukuju IP adresama. Nema potrebe da ih prevodiš ručno i stavljaš u long pomoću << operatora.

Prvo, recimo da imaš struct sockaddr_in ina, i IP adresu "10.12.110.57" koju hoćeš da smjestiš u datu strukturu. Funkcija koja ti treba, inet_addr(), prevodi brojeve iz zapisa "numbers-and-dots" (brojke i tačke) u oblik neoznačenog cijelog broja (unsigned long). Pridruživanje se može evo ovako obaviti:

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

Primijeti da inet_addr() vraća adresu u mrežnom uređenju bajtova, automatski – ne moraš da pozivaš htonl(). Divno!

Sad, ovaj isječak kôda nije baš jak jer nema provjere za greškama. Vidiš, inet_addr() vraća -1 ako je došlo do greške. Sjećaš se binarnih brojeva? (unsigned)-1 će odgovarati IP adresi 255.255.255.255! To je validna adresa! Promašaj. Sjeti se da odradiš provjeravanje greške kako treba.

Ustvari, postoji drugačiji sistem koji stoji umjesto inet_addr(): nazvan je inet_aton() ("aton" znači "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); 

A evo i primjera upotrebe, dok se pakuje struct sockaddr_in (ovaj će primjer imati više smisla kad dođeš do sekcija o bind() i connect().)

    struct sockaddr_in my_addr;
    my_addr.sin_family = AF_INET;         // serversko uređenje bajtova
    my_addr.sin_port = htons(MYPORT);     // kratko, mrežno uređenje bajtova
    inet_aton("10.12.110.57", &(my_addr.sin_addr));
    memset(&(my_addr.sin_zero), '\0', 8); // ostatak strukture popuni nulama 

inet_aton()ne liči ni na jednu drugu funkciju vezanu za sokete, jer vraća različito od nule pri uspijehu, a nulu kao grešku. A adresa se prosleđuje u inp (pogledaj deklaraciju iznad ovog primjera.)

Nažalost, ne implementiraju sve platforme inet_aton() pa, iako se preporučuje njegova upotreba prije no inet_addr, inet_addr() se koristi u ovom vodiču.

Dobro, sad znamo prevesti IP adresu u njegovu binarnu reprezentaciju. Kako u suprotnom slučaju? Šta ako imamo struct in_addr a želimo da vidimo oblik te adrese u brojkama i tačkama? U tom slučaju, trebaće nam funkcija inet_ntoa() ("ntoa" znači "network to ascii"):

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

To će odštampati IP adresu. Primijeti da inet_ntoa() uzima struct in_addr kao argument, ne long. Takođe primijeti da vraća pokazivač na char. Ovaj pokazivač pokazuje na stalno smješteni niz karaktera unutar inet_ntoa() (static char *) tako da svaki put kad pozoveš inet_ntoa(), ovaj će prepisati novu preko stare IP adrese. Na primjer:

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

će odštampati:

    adresa 1: 10.12.110.57
    adresa 2: 10.12.110.57 

Ako ti treba da čuvaš adrese, koristi strcpy() da ih iskopiraš u sopstvene nizove.

To je sve o ovoj temi zasad. Kasnije, naučićeš da prevedeš tekst poput "whitehouse.gov" u njegovu odgovarajuću IP adresu (vidi DNS, ispod.)


Prethodno

Glavna strana

Dalje

Šta je soket?

 

Sistemski pozivi