Cilj ove laboratorijske vježbe je upoznavanje sa API-jem za pristup uslugama računalne mreže na Unix operacijskom sustavu, a također upoznavanje sa korištenjem i karakteristikama bespojne usluge TCP/IP porodice protokola. Budući da su Windowsi dobar dio mrežne tehnologije preuzeli od BSD varijante Unix-a to za tu grupu operacijskih sustava vrijedi većina idućeg teksta.
TCP/IP porodica protokola je dominantan skup protokola koji se danas koristi za povezivanje računala i srodnih uređaja i predstavlja osnovu Interneta.
Iako se u nazivu spominju samo TCP i IP ta porodica uključuje i mnoštvo drugih protokola od kojih je dio prikazan na slici 1. TCP/IP nikada nije nastao niti je modeliran na osnovu nekog formalnog modela - kao što je to slučaj sa OSI stogom, ali se ipak uslojavanje, tj. hijerarhija protokola, može primjetiti iščitavanjem slike odozdo prema gore (ili obrnuto). Na dnu imamo podatkovni sloj koji se nalazi neposredno iznad fizičkog sloja (koji nije prikazan) te sa njim direktno komunicira. Iznad njega je mrežni sloj sa IPv4, IPv6 i nekim pomoćnim protokolima. Nakon toga dolaze transportni slojevi TCP i UDP te na kraju imamo aplikacije koje su prikazane na samom vrhu slike. Svi protokoli iz TCP/IP porodice protokola, ali i mnoštvo drugih stvari, definirani su serijom RFC dokumenata koji se besplatno mogu skinuti sa Interneta. Mjesta gdje su ti dokumenti pohranjeni navedeno je u literaturi. Dio ispod isprekidane crte označene tekstom API nalazi se implementiran u jezgri operacijskog sustava te u pomoćnim bibliotekama. Dio iznad isprekidane crte su aplikacijski programi koji su isporučeni sa operacijskim sustavom ili ih je napravio korisnik.
Slika 1. Dio porodice TCP/IP protokola
Kako bi programer mogao koristiti usluge koje mu pruža mreža tj. operacijski sustav, potrebno je koristiti API koji nudi OS preko biblioteka isporučenih sa mrežnom komponentom OS-a. Postoji mnoštvo različitih API-ja kao što su to npr. XTI/TLI, STREAMS, itd. No dominantan API je tzv. socket API koji se je prvi puta pojavio 1983. godine prilikom izdavanja 4.2BSD Unix operacijskog sustava. U sljedećem poglavlju taj API je detaljnije opisan sa naglaskom na dio potreban za obavljanje ove laboratorijske vježbe. Ovdje ćemo još samo napomenuti da su mreže toliko integrirane u današnje računastvo da se dobar dio implementacije mrežnih protokola nalazi u samoj jezgri operacijskog sustava. Te implementacije su dosta komplicirane no ipak je zanimljivo pogledati kako je to izvedeno u stvarnosti, tj. u operacijskom susavu koji se koristi i u komercijalnim a ne samo hobističkim ili znanstvenim projektima. To je moguće zahvaljujući dostupnosti, tj. otvorenosti koda *BSD i Linux operacijskih sustava. Iako ti svi operacijski sustavi pripadaju Unix porodici, implementacije su potpuno različite zbog povijesnih razloga i različitih licenci.
Kao što je to već rečeno u drugom poglavlju, dominantan API za korištenje usluga mrežnog sloja je tzv. socket API. Taj API dizajniran je za programski jezik C iako postoje odgovarajuće ekstenzije i prilagodbe drugim programskim jezicima. Sastoji se od zaglavlja (.h) te biblioteka u kojima se nalazi implementacija dijela funkcionalnosti tog API-ja. Ostatak je implementiran unutar operacijskog sustava. Cijeli sustav je dosta kompleksan i nudi mnoštvo mogućnosti ali u ovoj vježbi ćemo se ograničiti na bespojnu uslugu i funkcionalnosti neophodne za implementaciju vrlo jednostavno poslužitelja i klijenta. Bespojnu uslugu u domeni TCP/IP protokola nudi UDP prijenosni protokol koji ima sljedeće karakteristike:
Kao i svaki drugi API tako se i ovaj sastoji od podatkovnih struktura i funkcija koje koriste te podatkovne strukture te obavljaju operacije nad njima. Redoslijed poziva nekih funkcija je za pojedine namjene unaprijed definiran. Na slici 2 prikazan je redoslijed pozivanja funkcija u slučaju kada se želi ostvariti bespojna veza.
Slika 2. Redoslijed poziva funkcija socket API-ja za korištenje bespojne usluge
Lijeva strana slike odnosi se na poziv funkcija unutar klijenta, dok se desna strana odnosi na poslužitelj. U slijedećem tekstu nešto detaljnije su opisane pojedine funkcije, a zatim i podatkovne strukture koje se koriste. Prije korištenja funkcija socket API-ja potrebno je uključiti zaglavlja sys/types.h, sys/socket.h i netinet/in.h.
Prije no što je moguća bilo kakva komunikacija potrebno je kreirati pristupnu točku ili socket. Pozivom ove funkcije samo se najavljuje kernelu korištenje mrežnih usluga određenog tipa na što kernel odnosno odgovarajuće funkcije u bibliotekama kreiraju potrebne strukture za rad sa mrežom. Funkcija kao rezultat vraća deskriptor koji se koristi u drugim funkcijama slično kao što se deskriptor otvorene datoteke koristi u funkcijama za čitanje i pisanje u datoteku.
Prototip ove funkcije ima sljedeći oblik:
int socket(int domain, int type, int protocol);dok argumenti imaju sljedeća značenja:
Ovaj argument određuje koju grupu komunikacijskih usluga želimo koristiti. Vrijednost koja nas zanima za ostvarenje bespojne usluge je AF_INET čime naznačujemo da želimo koristiti grupu protokola baziranih na IPv4 mrežnom protokolu. Možemo spomenuti još dva argumenta AF_UNIX i AF_INET6. Prvi označava Unix međuprocesnu komunikaciju (cjevovodi), dok drugi označava grupu protokola baziranih na IPv6, tj. na idućoj generaciji IP-a. Što se aplikacija tiče jedina razlika u tim protokolima je u načinu adresiranja. Prefix AF_ potiče od skraćenice Address Family no u nekim uputstvima se sreće i prefiks PF_ što dolazi od Protocol Family.
Uz pomoć ovog argumeta određuje se vrsta komunikacije koju želimo ostvariti. U ovom trenutku nas zanima SOCK_DGRAM koji označava bespojnu uslugu bez garancije isporuke.
Konačno, ovaj argument bira protokol koji se koristi za implementaciju željene vrste komunikacije u danoj grupi komunikacijskih usluga. Budući da najčešće samo jedan protokol implementira određenu vrstu komunikacije to se za ovaj parametar najčešće navodi 0 što znači da kernel i/ili funkcije u bibliotekama same odaberu odgovarajući protokol na osnovu prva dva parametra. U konkretnom slučaju TCP/IP porodice protokola jedino UDP implementira bespojnu uslugu bez garancije isporuke te će on zato biti automatski odabran.
Povratna vrijednost funkcije je -1 u slučaju greške, a u suprotnom pozitivni broj.
Nakon što je kreirana, pristupna točka se u slučaju klijenta može početi koristiti. Konkretno moguće je slati poruke na poslužitelj upotrebom funkcije sendto.
Prototip ove funkcije ima sljedeći oblik:
int sendto(int sockfd, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);dok argumenti imaju sljedeća značenja:
Ovo je deskriptor koji je vratila funcija socket prilikom kreiranja pristupne točke.
Pokazivač na spremnik u kojemu se nalazi poruka koju je potrebno poslati.
Broj okteta u spremniku.
Argument uz pomoć kojega se mogu definirati dodatni parametri slanja, no u ovom slučaju kao vrijednost ovog argumenta može se postaviti 0.
Struktura koja sadrži adresu primaoca, a opisana je u daljnjem tekstu. Tu strukturu je potrebno inicijalizirati odgovarajućim vrijednostima prije no što se koristi.
Veličina prethodne strukture, odnosno sizeof(to).
Povratna vrijednost funkcije je -1 u slučaju greške, a u suprotnom broj poslanih okteta.
Za razliku od klijenta koji koji može koristiti pristupnu točku odmah nakon njenog kreiranja, poslužitelj mora prvo vezati adresu uz nju. To se obavlja uz pomoć ove funkcije. Nakon njenog poziva pristupna točka ima adresu te klijent, u slučaju da poznaje tu adresu, može pristupiti poslužitelju.
Prototip ove funkcije ima sljedeći oblik:
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);dok argumenti imaju sljedeća značenja:
Pristupna točka kreirana funkcijom socket.
Unutar ove podatkovne strukture upisana je adresa koju želimo dodijeliti pristupnoj točki. Odgovarajuća polja te strukture i konstante i funkcije koje se koriste za inicijalizaciju opisana su u daljnjem tekstu.
Veličina prethodne strukture, tj. sizeof(addrlen)
Povratna vrijednost funkcije je -1 u slučaju greške, a u suprotnom 0.
Primanje paketa obavlja se funkcijom recvfrom. Ta funkcija ima sljedeći prototip:
int recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
Argumenti te funkcije analogni su argumentima funkcije sendto sa odgovarajućim razlikama. Tako je prvi argument deskriptor pristupne točke, drugi argument je pokazivač na spremnik u koji se pohranjuje pristigli paket, treći argument određuje maksimalnu veličinu spremnika, četvrti argument je u našem slučaju 0. Peti argument je pokazivač na strukturu u koju se upisuje adresa pristupne točke sa koje je pristigao paket, a u šesti argument se upisuje duljina petog argumenta. U slučaju da nas ne zanima adresa pošiljatelja paketa tada se kao peti i šesti argument navodi konstanta NULL.
Povratna vrijednost funkcije je broj primljenih okteta, odnosno -1 u slučaju greške.
Funkcija close je uobičajna funkcija za zatvaranje deskriptora.
Ovo je struktura u koju se upisuje odgovarajuća internet adresa. Adresa se sastoji od IP adrese računala i pristupa. U skladu s tim ova struktura ima sljedeća polja:
Ovaj član strukture sadrži konstantu koja određuje o kojoj vrsti adrese se radi. Taj član je neophodan budući da postoji generički oblik adrese koji također sadrži ovo polje. U našem slučaju vrijednost ovog polja se uvijek inicijalizira na vrijednost AF_INET.
Pristupna točka na kojoj čeka poslužitelj ili joj pristupa klijent - ovisno u kojem kontekstu se upotrebljava ova struktura. To je 16-bitni broj pri čemu se ne inicijalizira vrijednost direktno, tj. jednostavnim pridodjeljivanjem već je to potrebno raditi preko funkcije htons. Argument te funkcije je 16 bitni broj čiji okteti su u poretku računala na kojemu se izvršava program, dok je povratna vrijednost 16 bitni broj čiji okteti su u standardom poretku definiranom za mrežu.
Ovo je struktura sa samo jednim članom i to s_addr. U slučaju klijenta ovo polje sadrži adresu računala kojemu se pristupa i na kojemu se očekuje poslužitelj. U slučaju servera ovo polje sadrži adresu sučelja na koje se veže server. Recimo, postavljanjem ovog polja na vrijednost 127.0.0.1 server bi primao pakete od klijenta isključivo na loopback sučelju bez obzira na postojanje dodatnih sučelja. U slučaju da želimo da poslužitelj prima pakete na sva sučelja koristi se posebna konstanta INADDR_ANY.
Kao i u slučaju polja sin_port tako se i ovome 32-bitne vrijednosti moraju dodjeljivati koristeći funkciju htonl. Recimo:
... struct sockaddr_in recv_addr; ... ... recv_addr.sin_addr.s_addr = htonl(INADDR_ANY); ...
Ako umjesto 32-bitnog broja imamo ASCII zapis IP adrese (u obliku aaa.bbb.ccc.ddd), u tom slučaju koristimo funkciju inet_pton. Ta funkcija ima sljedeći prototip:
int inet_pton(int af, const char *src, void *dst);
Ova funkcija je u stanju prevesti više različitih oblika adresa te se kao prvi argument navodi porodica o kojoj se radi. U našem slučaju to je ponovo konstanta AF_INET koja označava internet protokole bazirane na IPv4. Drugi argument je ASCII zapis IP adrese i konačno, treći argument je pokazivač na polje sin_addr varijable tipa sockaddr_in.
Za korištenje ove funkcije potrebno je osim već navedenih uključiti i zaglavlje arpa/inet.h
Napomena: Na starijim implementacijama Socket API-ja umjesto funkcije inet_pton koristila se je funkcija inet_aton. Ta funkcija je zastarjela budući da se može koristiti isključivo sa IP adresama vezanim uz verziju 4 IP protokola. Njen opis ovdje nije dan ali se kao i za sve ostale funkcije može naći u on-line uputstvima.
Potrebno je napraviti jednostavni echo klijent i poslužitelj koristeći UDP protokol. Drugim riječima, server čeka na određenom pristupu i sve što mu pristigne na taj pristup vraća nazad klijentu. S druge strane, klijent čita sa tipkovnice liniju po liniju, te svaku liniju šalje poslužitelju, a potom očekuje odgovor poslužitelja. Klijent mora ispisati odgovor. U trenutku kada se upiše "kraj" kao jedina riječ u liniji klijent šalje poslužitelju tu riječ, a poslužitelj nakon odgovora prekida rad. U trenutku primitka odgovora od poslužitelja i klijent prekida rad. Argument klijentu je IP adresa na kojoj se nalazi poslužitelj te pristup na kojem poslužitelj čeka. Argument poslužitelju je pristup na kojemu će čekati pakete od klijenta.