U prethodnoj laboratorijskoj vježbi zadatak je bio načiniti jednostavan par klijent/poslužitelj temeljen na bespojnoj usluzi TCP/IP porodice protokola, tj. korištenjem UDP transportnog sloja. Iako se bespojna usluga često koristi, za većinu aplikacija daleko je pogodnija spojna usluga. U sljeidećem tekstu ukratko je opisana ta usluga, dane su upute za korištenje funkcija socket API-ja kako bi se ta usluga mogla koristiti u aplikacijskim programima, te je na kraju dan opis klijenta i poslužitelja koje je potrebno isprogramirati.
Za razliku od UDP-a, TCP zahtijeva uspostavljanje veze između dva entiteta koja komuniciraju prije no što otpočne sama komunikacija. Nadalje, TCP obavlja slijedeće funkcije o kojima je u slučaju UDP-a bilo potrebno da se brine sama aplikacija:
Na slijedećoj slici prikazan je redoslijed poziva funkcija Socket API-ja dok je u nastavku teksta dan opis novih funkcija u odnosu na UDP (listen, connect, read, write) te razlika u upotrebi već poznatih funkcija (socket). Također je u idućem poglavlju dan opis funkcija uz pomoć kojih se pretvara simboličko ime računala u odgovarajući IP broj.
Slika 1. Redoslijed poziva funkcija socket API-ja za korištenje spojne usluge
Za razliku od prethodne vježbe kada je funkcija socket pozivana u slijedećem obliku:
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
u ovom slučaju se za kreiranje pristupne točke za spojnu vezu koristi sljedeći oblik:
sockfd = socket(AF_INET, SOCK_STREAM, 0);
Razlika je dakle samo u drugom argumentu. Treći argument opet označava da se protokol automatski odabere što će u ovom slučaju biti TCP.
Ovu funkciju koristi klijent kako bi se povezao sa serverom. Prototip funkcije je sljedeći:
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
Prvi parametar predstavlja pristupnu točku kreiranu sa funkcijom socket, drugi parametar je struktura sockaddr_in sa popunjenom adresom poslužitelja (IP adresa i port), dok je zadnji parametar duljina strukture sockaddr_in. Primjetite da iako u prototipu stoji sockaddr u stvarnosti se radi o nekoj od sockaddr_* struktura, a u našem slučaju o sockaddr_in. U slučaju uspješnog uspostavljanja veze sa poslužiteljem ova funkcija vraća nulu, dok u slučaju greške vraća -1 i postavlja vrijednost varijable errno na odgovarajuću vrijednost.
Ovo su standardne funkcije za rad sa datotekama.
Uz pomoć ove funkcije poslužitelj definira maksimalnu veličinu reda koji sadrži zahtijeve za vezom. Svi zahtijevi koji pristignu kada je taj red pun će biti odbijeni. U određenim će slučajevima IP stog jednostavno ignorirati zahtijev, tj. neće obavijestiti klijenta o odbijanju veze te će u tom slučaju nakon isteka vremenskog ograničenja zahtijev biti ponovljen. Prototip funkcije je:
int listen(int s, int backlog);
s predstavlja deskriptor pristupne točke kreirane funkcijom socket, dok drugi parametar definira maksimalnu veličinu reda. Povratna vrijednost funkcije je 0 u slučaju uspjeha ili -1 u slučaju greške kada je errno postavljen na odgovarajući kod greške.
Ova funkcija blokira poslužitelj do trenutka kada se uspostavi veza sa nekim klijentom. U tom slučaju vraća se novi deskriptor kako bi originalna pristupna točka mogla nastaviti dalje prihvaćati zahtijeve za vezom. Nakon toga potrebno je pokrenuti novi proces uz pomoć funkcije fork() koji će obrađivati tog klijenta, a glavni program će i dalje čekati na zahtijeve. Drugi način da se to postigne je upotrebom thread-ova. Prototip funkcije je:
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
U drugom argumentu, tipa sockaddr_in, po izlasku iz funkcije pohranjena je adresa klijenta dok se u trećem argumentu nalazi duljina strukture sockaddr, tj. u ovom slučaju sockaddr_in.
U prethodnoj vježbi je kao argument klijentu davana IP adresa računala na kojemu je bio poslužitelj. To je dosta nezgodno za korištenje jer se IP adrese dosta teško pamte, a sa sve većom rasprostranjenošću IPv6 to postaje još veći problem. Kako bi se olakšalo korištenje mrežnih aplikacija za računala se koriste simbolička imena. U pripremnoj vježbi spomenuto je postojanje posebnih funkcija koje obavljaju pretvaranje imena u adrese (i obratno). U ovom poglavlju će malo detaljnije biti opisane te funkcije. Prije opisa samih funkcija ukratko će biti opisan DNS, a potom će biti opisane funkcije za korištenje DNS-a.
DNS serveri se izvršavaju na točno određenim računalima i obično se nalazi jedan primarni DNS poslužitelj po domeni, te jedan ili više sekundarnih odnosno pomoćnih poslužitelja. Prikaz računala je u obliku stabla pri čemu se korijen tog stabla crta na vrhu. Upit za određenim računalom uglavnom označava šetnju tim stablom od vrha prema zadanoj domeni, odn. računalu, no taj kompleksan postupak obavljaju sami DNS poslužitelji. Konkretno, kada postavimo upit za nekim računalom DNS poslužitelj će sam proputovati stablo i pronaći traženo računalo (pod uvijetom da ono postoji) te nam vratiti odgovor. Najčešći program koji implementira DNS protokole te se dominantno koristi na Internetu za pretvaranje imena računala u IP adrese i obratno je BIND, trenutno u verziji 9.2.0. BIND je dosta kompleksan program sa mnoštvom mogućnosti koje ovdje nećemo spominjati, dok zainteresirani mogu skinuti taj program sa Interneta te pogledati dosta detaljnu dokumentaciju koja se nalazi u samoj arhivi.
DNS poslužitelji čuvaju podatke o računalima u zapisima (eng. Resource Records, RR). Postoji više različitih tipova zapisa, dok ćemo ovdje spomenuti samo neke češće korištene, a za ilustraciju zapisa koje ćemo opisati koristiti ćemo sljedeći isječak iz konfiguracijske datoteke BIND-a:
gandalf IN A 161.53.65.11 IN AAAA 5f1b:df00:ce3e:e200:0020:0800:2078:e3e3 IN MX 5 mailhost.zemris.fer.hr. IN MX 10 mailhost2.zemris.fer.hr. www CNAME gandalf.zemris.fer.hrU prvoj koloni nalazi se ime računala unutar domene (domena je definirana na drugom mjestu te u gornjem isječku nije navedena). Potom dolazi Zapisi su:
A zapis sadrži IP adresu računala.
AAAA zapis sadrži IPv6 adresu računala.
PTR zapis (koji se ne nalazi u prethodnom primjeru) služi za pretvaranje IP adrese u ime računala.
MX zapisi definiraju računalo zaduženo za primanje mail-a za određenu domenu ili računalo. U gornjem primjeru primanje mail-a upućeno računalu gandalf obavlja računalo sa imenom mailhost.zemris.fer.hr, ili računalo mailhost2.zemris.fer.hr. Brojka neposredno prije imena računala određuje prioritet, što je manji broj to je viši prioritet.
Ovaj zapis predstavlja alias i najčešće se upotrebljava za označavanje ftp i web servera. U gornjem primjeru www.zemris.fer.hr je alias na računalo gandalf.zemris.fer.hr.
Glavna struktura u koju se smještaju podaci o računalu i funkcije koje operiraju nad tom strukturom definirane su u zaglavnoj datoteci netdb.h. Struktura ima sljedeća polja:
struct hostent { char *h_name; /* Official name of host. */ char **h_aliases; /* Alias list. */ int h_addrtype; /* Host address type. */ int h_length; /* Length of address. */ char **h_addr_list; /* List of addresses from name server. */ };
Ta struktura je grafički prikazana na sljedećom slici za slučaj korištenja IPv4. Analogno je za slučaj IPv6 no on ovdje nije posebno prikazan budući da se ne koristi.
Slika 2. Grafički prikaz strukture hostent
Primarno ime računala zajedno sa domenom (npr. pinus.cc.fer.hr). Ime je terminirano sa karakterom \0.
Pokazivač na polje pokazivača koji pokazuju na aliase računala. Npr. u prethodnom slučaju kada je primarno ime računala gandalf.zemris.fer.hr u ovom polju bi se nalazilo ime www.zemris.fer.hr. Kao i u slučaju prethodnog polja i ovdje je svako ime terminirano znakom \0.
Tip vraćenih adresa. Moguće vrijednosti su AF_INET i AF_INET6. U našem slučaju koristi se samo porodica AF_INET.
Veličina pojedine adrese. Za slučaj IPv4 vrijednost ovog polja je 4 budući da su te adrese veličine četiri okteta, dok je za IPv6 vrijednost tog polja 16.
Pokazivač na polje pokazivača od kojih svaki pokazuje na jednu IP adresu računala. Ta vrijednost se može direktno preslikati u polje sin_addr.s_addr strukture sockaddr_in.
Strukturu hostent popunjavaju funkcije gethostbyname i gethostbyaddr. Prva funkcija, gethostbyname ima sljedeći prototip:
struct hostent *gethostbyname(const char *name);
Parametar te funkcije je ime računala čiju IP adresu tražimo. S druge strane funkcija gethostbyaddr uzima IP adresu računala te vraća popunjenu strukturu hostent. Prototip te funkcije je:
struct hostent *gethostbyaddr(const char *addr, int len, int type);
Drugi parametar te funkcije je duljina adrese, a treći je vrsta adrese, tj. da li se radi o IPv4 (AF_INET) ili o IPv6 adresama (AF_INET6). Primjetite da je za korištenje ove funkcije potrebno dodatno uključiti i sys/socket.h zbog tih konstanti.
Obje funkcije u slučaju greške vraćaju -1 no kod greške postavljaju u varijablu h_errno. Neki od bitnijih grešaka su:
Greška | Značenje |
---|---|
HOST_NOT_FOUND | Zadano računalo nije pronađeno |
NO_ADDRESS/NO_DATA | Zadano računalo postoji, no nema IP adresu |
NO_RECOVERY | Označava grešku bez mogućnosti oporavka. |
TRY_AGAIN | Pojavila se greška privremenog karaktera, može se pokušati ponovo nakon nekog vremena. |
Potrebno je napraviti jednostavni echo klijent i poslužitelj ovaj puta koristeći TCP protokol kako je to opisano u gornjem tekstu. Sve je isto kao i u prethodnoj vježbi osim što ovaj puta klijent kao parametar treba prihvatiti ime računala umjesto IP adrese.