Beej-ov vodič za mrežno programiranje

Prethodno

 

Dalje


4. Sistemski pozivi

Tu smo gdje se saznaju sistemski pozivi koji omogućavaju pristup radu na mreži sa UNIX mašine. Kad pozoveš neku od ovih funkcija, jezgro preuzima kontrolu i obavlja sav ostatak posla za tebe automatski.

Ljudi se najčešće zbune oko toga koji sistemski poziv kad pozvati. Tu man stranice ništa ne pomažu, kao što si vjerovatno već primijetio. Zbog strahote te situacije, pokušao sam da ti predstavim sistemske pozive u tačno onom rasporedu u kom se oni i pozivaju u programima.

To, skupa sa nešto malo izvornog kôda tu i tamo, nešto mlijeka i kolača (koje ćeš, bojim se, morati sam nabaviti), i nešto sirove snage i hrabrosti, i bacaćeš podatke po internetu kao sin Džona Postela (ko god to bio :), prim. prev.).

4.1. socket() - Daj mi fajl-deskriptor!

Izgleda da ne mogu više da odlažem – moram da govorim o sistemskom pozivu socket() . Evo:

    #include <sys/types.h>
    #include <sys/socket.h>
    int socket(int domain, int type, int protocol); 

Ali šta predstavljaju ovi argumenti? Prvo, domain treba podesiti na "AF_INET", baš kao u strukturi struct sockaddr_in (iznad.) Dalje, type argument kaže jezgru koji tip soketa je ovo: SOCK_STREAM ili SOCK_DGRAM. Konačno, podesi protocol na "0" da bi socket() izabrao pravi protokol zavisno od type argumenta. (Pazi: postoji mnogo više stvari koje mogu da stoje na mjestu domain argumenta. Postoji mnogo više opcija za type argument. Pogledaj socket() man stranicu. Takođe, postoji "bolji" način da se podesi protocol. Pogledaj getprotobyname() man stranicu.)

socket() jednostavno vraća soket-deskriptor koji kasnije možeš koristiti u sistemskim pozivima, ili -1 ako je došlo do greške. Globalna promjenjiva errno se tada podesi na odgovarajuću vrijednost. (Vidi perror() man stranicu.)

U nekim knjigama ćeš vidjeti kako se pominje mistični "PF_INET". Ovo je jedna neobična zvjerka koja se uopšte rijetko viđa u prirodi, ali ipak ću pokušati da malo razjasnim o čemu se radi. Jednom davno, mislilo se da bi familija adrese (AF u AF_INET) mogla podržavati nekoliko protokola koji su bili predstavljeni svojom familijom protokola (PF u PF_INET). Ali nije bilo tako. Ali dobro. Prava stvar da se uradi je da se koristi AF_INET u strukturi struct sockaddr_in a PF_INET u pozivu funkcije socket(). Ali, AF_INET možeš praktično svuda da koristiš. I pošto tako radi Richard Stevens u svojoj knjizi, tako ću i ja raditi ovdje.

Dobro, dobro, dobro, ali šta ću ja sad sa ovim soketom? Odgovor je da on sam po sebi ništa ne predstavlja, ali moraš da čitaš dalje i naučiš još sistemskih poziva da bi učinio da stvari počnu da funkcionišu.

4.2. bind() - Na kom sam portu?

Jednom kad dobiješ soket, možda bi trebao da ga pridružiš nekom portu na svojoj lokalnoj mašini. (Ovo obično radiš ako ćeš očekivati (listen()) poziv za uspostavljanje veze na nekom portu. Jezgro koristi broj porta da poveže pristigli paket i odgovarajući proces i njegov soket-deskriptor. Ako ćeš samo koristiti connect(), ovo onda možda nije neophodno. Ipak pročitaj, za svaki slučaj.

Evo sintakse sistemskog poziva bind():

    #include <sys/types.h>
    #include <sys/socket.h>
    int bind(int sockfd, struct sockaddr *my_addr, int addrlen); 

sockfd je soket-deskriptor, broj koji je vracen od funkcije socket(). my_addr je pokazivač na strukturu struct sockaddr koja sadrži adresu tj. port i IP adresu. addrlen se obično podesi na sizeof(struct sockaddr).

E tako. To smo sve u jednom zalogaju. 'de da vidimo primjer:

    #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); // ovde je potrebno provjeriti greške!
        my_addr.sin_family = AF_INET;         // serversko uređenje bajtova
        my_addr.sin_port = htons(MYPORT);     // kratko, mrežno uređenje bajtova
        my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
        memset(&(my_addr.sin_zero), '\0', 8); // ostatak strukture popuniti nulama
        // Ti ćeš sam uraditi provjeru grešaka za bind():
        bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
 .
  .
  . 

Ovde postoji par stvari za primijetiti: my_addr.sin_port je u mrežnom uređenju bajtova. my_addr.sin_addr.s_addr je takođe. Druga stvar koje treba da se paziš je da se sistemska .h zaglavlja koja sam gore uključio razlikuju od sistema do sistema. Jedini način da se uvjeriš je da provjeriš man stranice na svom računaru.

I još nešto, na samom sam vrhu poglavlja trebao spomenuti da svoju IP adresu i/ili port možeš automatski da dobiješ:

        my_addr.sin_port = 0; // izaberi slučajnim izborom slobodan port
        my_addr.sin_addr.s_addr = INADDR_ANY;  // izaberi svoju IP adresu

Vidiš, postavljajući my_addr.sin_port na nulu, ti ustvari kažeš bind()-u da izabere slobodan port. Isto tako, postavljajući my_addr.sin_addr.s_addr na INADDR_ANY, kažeš mu da automatski popuni mjesto za IP adresu adresom računara na kom proces teče.

Ako primjećuješ male stvari, onda si primijetio da nisam stavio INADDR_ANY u mrežno uređenje bajtova! Baš sam nevaljao. Ali, uradio sam to jer sam znao jednu stvar: INADDR_ANY je ustvari uvijek nula! Ako mu okrenemo bajtove, dobićemo istu stvar. Ipak, čistunci bi rekli da može da postoji paralelna dimenzija u kojoj bi INADDR_ANY moglo biti, recimo, 12 i da bi tu moj program pukao. E pa kako hoćete:

        my_addr.sin_port = htons(0); // izaberi slobodan port slučajnim izborom
        my_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // izaberi svoju IP adresu

Nećeš vjerovati koliko je program sad prenosiv. Samo sam htio ovo naročito da istaknem da znaš, ali ubuduće se neću mučiti da provlačim INADDR_ANY kroz htonl().

bind() takođe vraća -1 ako dođe do greške i podešava errno na odgovarajuću vrijednost.

Još jedna stvar koje treba da se paziš sa funkcijom bind(): nemoj da ideš ispod donje granice brojeva porta. Svi portovi sa brojem ispod 1024 su REZERVISANI (osim ako si superkorisnik(root))! Može bilo koji port iznad 1024, i to sve do 65535 (osim ako nije već rezervisan od strane nekog drugog procesa.)

Ponekad se desi da pokušaš ponovo pokrenuti isti program i da bind() padne, ostavljajući poruku "Adresa već u upotrebi" ("Address already in use"). Šta to znači? Pa, recimo da tada "dio" soketa ostane poslije gašenja programa u memoriji. Možeš da sačekaš (jedno minut), ili da dodaš kôd programu tako da mu dozvoliš da ponovo koristi port, nešto ovakvo:

    int yes=1;
        //char yes='1'; // za Solaris korisnike
    // da sprečiš poruku "Address already in use"
    if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
        perror("setsockopt");
        exit(1);
    } 

Jedna mala potpuno sićušna ekstra-posljednja napomena za funkciju bind(): često je uopšte nećeš morati pozivati. Ako koristiš connect() da se spojiš sa udaljenim računarom i nije te briga s kog porta ideš (kao što je slučaj sa telnet-om kad te zanima samo port računara na koji se spajaš), onda jednostavno koristiš connect(), koji će provjeriti da li je soket slobodan, pa će ga povezati (bind()) na slobodan lokalni port, ako je neophodno.

4.3. connect() - Hej, ti!

Hajde da se na trenutak pravimo da si ti telnet. Korisnik ti naređuje (Baš kao u filmu TRON) da pribaviš soket-deskriptor. Ti se složiš i pozoveš socket(). Dalje, korisnik ti kaže da se spojiš na "10.12.110.57", port "23" (standardni telnet port.) Hah! Šta ćeš sad?

Imaš sreće, sad čitaš poglavlje o funkciji connect() – kako da se spojiš na udaljeni računar. Zato sad navali na čitanje! Nemaš puno vremena!

Poziv connect() glasi:

    #include <sys/types.h>
    #include <sys/socket.h>
    int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); 

sockfd je soket-deskriptor, kog je vratio poziv socket(), serv_addr je struktura struct sockaddr koja sadrži odedišni port i IP adresu, a addrlen se obično podesi na sizeof(struct sockaddr).

Počinje da ima smisla, zar ne? Evo primjera:

    #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;   // sadržaće odredišni port i IP adresu
        sockfd = socket(AF_INET, SOCK_STREAM, 0); // Ovde je potrebno provjeriti greške!
        dest_addr.sin_family = AF_INET;          // serversko uređenje bajtova
        dest_addr.sin_port = htons(DEST_PORT);   // kratko, mrežno uređenje bajtova
        dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
        memset(&(dest_addr.sin_zero), '\0', 8);  // ostatak strukture popuni nulama
        // Ne zaboravi da provjeriš greške poslije connect()!
        connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
        .
        .
        . 

Da ponovim, uvjeri se da si provjerio da li je dobro prošao poziv funkcije connect() – vratiće -1 ako je došlo do greške  i errno će sadržati odgovarajuću vrijednost.

Takođe, jesi li primijetio da uopšte nismo pozivali bind(). U osnovi, nije nas briga s kog idemo porta; briga nas je samo na koji port idemo (udaljeni port). Jezgro će samo izabrati naš lokalni port, a sajt na koji se spajamo će lako saznati koji je to port. Nema da brineš.

4.4. listen() - Molim vas, hoće li me neko nazvati?

OK, vrijeme je za promjenu tempa. Šta ako ti uopšte nećeš da se spajaš na bilo kakav udaljeni računar? Recimo da samo, dakle, hoćeš da sačekaš bilo kakav poziv sa udaljenog računara i obradiš svaki takav poziv na odgovarajući način. Proces je iz dva koraka: prvo koristiš funkciju listen()(slušaj()) a onda accept() (primi()) (vidi ispod.)

Poziv listen() je relativno jednostavan, ali zahtjeva malo objašnjenje:

    int listen(int sockfd, int backlog); 

sockfd je uobičajeni soket-deskriptor dobijen funkcijom socket(). backlog  je broj dozvoljenih poziva koji će moći da čekaju u redu za obradu. Šta to znači? Pa, svi nadolazeći pozivi će sačekati u redu za čekanje, dok ih ne primi tvoj program funkcijom accept() (vidi ispod) i ovaj broj određuje koliko je dozvoljeno da ih bude u redu za čekanje (queue). Većina sistema ovaj broj prećutno ograničava na 20; tebi vjerovatno neće trebati više od 5 ili 10.

Da, naravno, ako dođe do greške, listen() vraća -1 i podešava errno kako treba.

Kao što pretpostavljaš, zovemo bind() prije nego listen(); inače će jezgro samo izabrati port na kom će da čeka pozive. Znači ako ćeš da slušaš pridolazeće pozive, raspored sistemskih poziva će biti ovakav:

    socket();
    bind();
    listen();
    /* accept() ide ovde */ 

Neka to stoji umjesto nekog izvornog kôda, pošto je lako za razumjeti.  (Kôd u poglavlju accept(), dole niže, je potpuniji.) Dio o kom se dobro mora razmisliti, jedini takav u cijeloj priči, je dio o funkciji accept().

4.5. accept() – "Hvala vam što ste zvali port 3490."

Spremi se – poziv accept() je pomalo uvrnut! Evo šta će se desiti: Neko ko je jako daleko će pokušati da ti se spoji (connect()) na računar, na port na kom čekaš (listen()). Njegov poziv će se smjestiti u red za čekanje sve dok ga ne primiš (accept()). Pozivaš accept() kojim preuzimaš jedan poziv iz reda za čekanje. Ova funkcija će ti, kao izlaznu vrijednost, vratiti potpuno nov soket-deskriptor koji odgovara ovom jednom pozivu (vezi)! Tako je, odjednom imaš dva soket-deskriptora po cijenu jednog! Onaj početni i dalje očekuje pozive na datom portu, a ovaj novi je konačno spreman da se proslijedi funkcijama send() i recv(). Došli smo dotle!

Funkcija glasi:

     #include <sys/socket.h>
     int accept(int sockfd, void *addr, int *addrlen); 

sockfd je soket-deskriptor  soketa koji očekuje pozive na našem portu. Lako. addr je obično pokazivač na lokalnu strukturu struct sockaddr_in. Tu idu informacije o pozivu koji trenutno obrađujemo (i tu saznamo odakle nam dolazi poziv i s kog porta). addrlen je tipa int i treba biti podešen na  sizeof(struct sockaddr_in) prije nego se njegova adresa proslijedi funkciji accept(). accept() će najviše toliko bajtova smjestiti u addr. Ako stavi manje nego toliko, to će se odraziti na addrlen.

Znaš šta? accept() vraća -1 i podešava errno ako se pojavi greška. Kladim se da to nisi sam shvatio.

Kao i prije, ovo je komadina za jedan zalogaj, pa navodim primjer izvornog kôda za proučavanje:

    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #define MYPORT 3490    // port na kom čekamo poziv
    #define BACKLOG 10     // koliki je maksimum poziva u redu za čekanje
    main()
    {
        int sockfd, new_fd;  // čekaj na sock_fd, dolazeći pozivi u new_fd
        struct sockaddr_in my_addr;    // informacije o mojoj adresi
        struct sockaddr_in their_addr; // informacije o adresi onog koji nas zove
        int sin_size;
        sockfd = socket(AF_INET, SOCK_STREAM, 0); // provjera grešaka!
        my_addr.sin_family = AF_INET;         // serversko uređenje bajtova
        my_addr.sin_port = htons(MYPORT);     // kratko, mrežno uređenje bajtova
        my_addr.sin_addr.s_addr = INADDR_ANY; // automatski popuni mojom IP adresom
        memset(&(my_addr.sin_zero), '\0', 8); // ostatak strukture popuni nulama
        // ne zaboravi provjeriti eventualne greške u ovim pozivima:
        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);
        .
        .
        . 

Još jednom da napomenem, soket-deskriptor new_fd će biti korišten za sve send() i recv() pozive. Ako ćeš imati samo jednu vezu, možeš zatvoriti (close()) sockfd koji čeka poziv, da bi sprečio nove pridolazeće pozive na istom portu – ako ti tako treba.

4.6. send() i recv() – Pričaj sa mnom, lutko!

Ove dvije funkcije služe za komunikaciju preko stream-soketa ili povezanog datagram-soketa. Ako želiš koristiti uobičajeni, nepovezani datagram soket, pogledaj poglavlja sendto() i recvfrom(), niže.

send() poziv:

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

sockfd je soket-deskriptor preko kojeg hoćeš da šalješ podatke (bilo da je to onaj vraćen od funkcije socket() ili onaj što si ga dobio sa accept().)  msg je pokazivač na podatke koje hoćeš da šalješ, a len je dužina tog teksta u bajtovima. flags podesi na 0. (Vidi send() man strane za više informacija o mogućim opcijama za flags.)

Primjer:

    char *msg = "Beej je bi ovde!";
    int len, bytes_sent;
    .
    .
    len = strlen(msg);
    bytes_sent = send(sockfd, msg, len, 0);
    .
    .
    . 

send() vraća broj bajtova koje uspije da pošalje – ovo zna biti manje nego broj bajtova koje si mu rekao da pošalje! Vidiš, ponekad mu kažeš da pošalje veliku hrpu podataka i on jednostavno ne uspije. Poslaće koliko god više može, i povjeriće tebi da ostatak pošalješ kasnije. Zapamti, ako vrijednost vraćena funkcijom send() ne odgovara vrijednosti len, na tebi je da pošalješ ostatak teksta. Dobra vijest je da će send(), ako je paket mali (manji od 1K ili nešto slično), vjerovatno uspjeti poslati sve odjednom. Opet, -1 se vraća kao greška, i errno se postavlja na broj greške.

recv() je sličan u mnogim aspektima:

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

sockfd je soket-deskriptor sa kog čitaš, buf je pokazivač na mjesto gdje će se učitani podaci skladištiti, len je broj za koji se pretpostavlja da neće stići više bajtova od njega (maksimum primljenih bajtova tim pozivom), a flags se opet, obično, postavi na 0. (Pogledaj recv() man stranice za informacije.)

recv() vraća broj bajtova koji su se učitali, ili -1 ako dođe do greške (errno se naravno podešava kako treba.)

Čekaj! recv() može vratiti 0. Ovo znači samo jedno: Druga strana je zatvorila vezu!

Eto, lako, zar ne? Sad možeš slati i primati podatke kroz stream sokete! Jupi! Postao si mrežni programer za UNIX!

4.7. sendto() i recvfrom() – pričaj sa mnom, DGRAM-stil

"Sve je ovo fino, moj prijatelju", čujem već kako govoriš, "ali gdje se tu uklapaju nepovezani soketi?" Nema frke, čovječe. Imamo pravu stvar za tebe.

Pošto datagram-soketi nisu spojeni sa drugim računarom, već znaš šta treba da se stavi u paket prije nego što ga pošaljemo? Upravo tako! Odredišnu adresu! Da zagrebemo:

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

Kao što vidiš, ovaj poziv je u osnovi isti kao send() sa dodatkom dvije informacije. to je pokazivač na strukturu struct sockaddr (koju ćeš vjerovatno čuvati u obliku strukture struct sockaddr_in i prevesti je (cast) u zadnjem trenutku) koja sadrži informacije o odredišnoj IP adresi i portu. tolen se uglavnom jednostavno podesi na  sizeof(struct sockaddr).

Baš kao send(), i sendto() vraća kao izlaznu vrijednost broj bajtova koje je poslao (koji, opet, može biti manji od broja koji smo zahtijevali!), ili -1 ako doće do greške.

Analogno su slične recv() i recvfrom(). Sintaksa funkcije recvfrom() je:

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

Ponovo, baš kao i kod recv() sa dodatkom dva polja. from je pokazivač na strukturu struct sockaddr koja će biti popunjena IP adresom i portom mašine od koje primamo podatke. fromlen je pokazivač na lokalni int i trebao bi biti isprva podešen na sizeof(struct sockaddr). Kad se funkcija završi, fromlen će sadržati dužinu adrese smještene u from.

recvfrom() vraća broj primljenih bajtova, ili -1 pri grešci (a errno se postavlja na odgovarajuću vrijednost.)

Zapamti, ako se spojiš (connect()) preko datagram soketa, možeš koristiti obične send() i recv() funkcije. soket i dalje ostaje tipa datagram i paketi i dalje koriste UDP, ali soket-interfejs će automatski ubacivati u pakete informacije o odredišnoj adresi.

4.8. close() and shutdown() – Bježi mi s očiju!

Uf! Slao si i primao informacije čitav dan, i dosta ti je toga. Spreman si da zatvoriš vezu. To je lako. Možeš jednostavno iskoristiti standardnu UNIX-ovu funkciju close() za zatvaranje datoteke:

    close(sockfd); 

Ovo će sprečiti sva daljnja zapisivanja i čitanja preko tog soketa. Svako ko pokuša sa jedne ili druge strane da piše ili čita – dobijaće poruku o grešci.

Ako poželiš malo više kontrole nad zatvaranjem soketa, možeš koristiti funkciju shutdown(). Ona ti dozvoljava da vezu prekineš u određenom smijeru, ili oba smijera (kao što radi close()). Sintaksa:

    int shutdown(int sockfd, int how); 

sockfd je soket deskriptor koji hoćeš dsa ugasiš, a how je nešto od sledećeg:

·         0 – Sprečavaju se daljnja primanja podataka

·         1 -- Sprečavaju se daljnja slanja podataka

·         2 -- Sprečavaju se i daljnja slanja i daljnja primanja podataka (to takođe radi close())

shutdown() vraća 0 ako je uspio, i -1 ako je došlo do greške (errno – postavlja se na odgovarajuću vrijednost broja greške.)

Ako se usudiš da koristiš shutdown() na nepovezanom datagram soketu, onda na njemu više nećeš moći koristiti funkcije send() i recv() (sjeti se da ih možeš koristiti samo ako prvo koristiš connect() na tom datagram soketu.)

Važno je primijetiti da  shutdown() ustvari ne zatvara soket-deskriptor – samo mijenja njegovu funkciju. Da bi oslobodio soket-deskriptor, moraš koristiti funkciju close().

Ništa drugo.

4.9. getpeername() – Ko si sad pa ti?

Ova funkcija je jako laka.

Toliko je laka da umalo da joj ne dam posebno poglavlje. Ali ipak evo je.

Funkcija getpeername() će ti reći ko je na drugom kraju spojenog stream soketa. Sintaksa:

    #include <sys/socket.h>
    int getpeername(int sockfd, struct sockaddr *addr, int *addrlen); 

sockfd je soket-deskriptor spojenog stream soketa, addr je pokazivač na strukturu struct sockaddr (ili struct sockaddr_in) koja će sadržati informacije o adresi te druge strane, a addrlen je pokazivač na int, koji bi trebao biti na početku postavljen na sizeof(struct sockaddr).

Funkcija vraća -1 pri grešci i postavlja errno na odgovarajuću vrijednost.

Kad dobiješ tu adresu, možeš koristiti inet_ntoa() ili gethostbyaddr() radi više informacija. Ne, ne možeš saznati njegov nalog. (ok, ok, ako taj drugi računar ima "ident daemon", onda je moguće. Ipak, to je van domašaja ovog dokumenta. Pogledaj RFC-1413 ako te zanima više.)

4.10. gethostname() – Ko sam ja?

Ovo je još lakše nego getpeername(). Vraća ime računara na kom teče tvoj program. Ime može biti zatim korišteno u funkciji gethostbyname(), opisanoj ispod, da odrediš IP adresu svoje mašine.

Zar išta može biti zabavnije? Možda bih se mogao i sjetiti par stvari, ali ne bi bile vezane za soket-programiranje. U svakom slučaju, evo:

    #include <unistd.h>
    int gethostname(char *hostname, size_t size); 

Parametri su jednostavni: hostname je pokazivač na niz karaktera koji će sadržati ime, a size je veličina niza hostname u bajtovima.

Funkcija vraća 0 ako je bila uspješna, a -1 pri grešci, errno na odgovarajuću vrijednost, bla, bla....

4.11. DNS – Ti kažeš "bijelakuca.gov", ja kažem "198.137.240.92"

U slučaju da ne znaš šta je DNS, to je skraćenica za "Domain Name Service". Ukratko, kažeš mu neku običnu adresu sajta, a on tebi vrati IP adresu (pa je onda možeš koristiti sa funkcijama bind(), connect(), sendto(), ili za šta ti već treba.) Tako, ako neko unese:

    $ telnet bijelakuca.gov

telnet zna da treba da se spoji (connect()) sa "198.137.240.92".

Ali kako to radi? Koristićeš funkciju gethostbyname():

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

Kao što vidiš, vraća pokazivač na strukturu struct hostent, koja ima sledeću organizaciju:

    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] 

Evo objašnjenja polja u strukturi struct hostent:

·         h_name – Zvanično ime tog računara.

·         h_aliases – Niz alternativnih imena za taj računar.

·         h_addrtype – Tip adrese koja se vraća; obično AF_INET.

·         h_length – Dužina adrese u bajtovima.

·         h_addr_list – Niz mrežnih adresa za taj računar. Sve su u mrežnom uređenju bajtova.

·         h_addr – Prva adresa u listi h_addr_list.

gethostbyname() vraća pokazivač na popunjenu strukturu struct hostent, ili NULL ako je došlo do greške. (Ali errno se ne modifikuje; umjesto njega, h_errno se podešava na odgovarajuću vrijednost. Pogledaj herror(), ispod.)

Ali kako se ova funkcija koristi? Ova funkcija je lakša za korištenje nego što se na prvi pogled čini.

Evo programa za primjer

    /*
    ** getip.c – primjer programa za dosezanje imena računara
    */
    #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) {  // provjeri da li je ispravna komandna linija
            fprintf(stderr,"korištenje: getip adresa\n");
            exit(1);
        }
        if ((h=gethostbyname(argv[1])) == NULL) {  // daj informacije o računaru
            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;
    } 

Sa funkcijom gethostbyname(), ne možeš koristiti perror() da odštampaš poruku o grešci (pošto se ne koristi errno). Umjesto toga, pozovi herror().

Prilično je direktno. Jednostavno proslijediš string koji predstavlja adresu ("bijelakuca.gov") funkciji gethostbyname(), a onda vadiš potrebne informacije iz strukture struct hostent.


Prethodno

Glavna strana

Dalje

struct-ure i rukovanje podacima

 

Pozadina klijent-servera