Spojna usluga TCP/IP porodice protokola

1. Uvod

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.

2. Spojna usluga TCP/IP porodice protokola

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:

3. Socket API

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

socket

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.

connect

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.

read/write

Ovo su standardne funkcije za rad sa datotekama.

listen

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.

accept

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.

4. Korištenje DNS-a u programima

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.

4.1. Općenito o DNS-u

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.hr
U 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:

4.2. Funkcije i strukture podataka

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

Pojedina polja sadrže slijedeće vrijednosti:

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škaZnačenje
HOST_NOT_FOUNDZadano računalo nije pronađeno
NO_ADDRESS/NO_DATAZadano računalo postoji, no nema IP adresu
NO_RECOVERYOznačava grešku bez mogućnosti oporavka.
TRY_AGAINPojavila se greška privremenog karaktera, može se pokušati ponovo nakon nekog vremena.

4. Zadatak

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.