Gyorstalpaló hálózatprogramozás C nyelven UNIX. környezetben

Hasonló dokumentumok
Dr. Varga Imre. Socket-programozás. C nyelven

Számítógépes hálózatok

Dr. Varga Imre Debreceni Egyetem, Informatikai Kar. Socket-programozás. C nyelven, Linux alatt

Hálózati architektúrák laborgyakorlat

Számítógépes hálózatok I.

Socket programozás Példák

FTP: Elkülönített kontroll- és adatkapcsolat. FTP: File Transfer Protocol

Számítógép-hálózatok: 4. Labor. TCP kliens. A gyakorlat célja:

Számítógépes hálózatok

Számítógépes Hálózatok. 9. gyakorlat

Számítógépes Hálózatok 2012

Számítógépes Hálózatok. 5. gyakorlat

Hálózati architektúrák laborgyakorlat

TCP szerver készítése

Számítógépes Hálózatok ősz 2006

Számítógépes Hálózatok GY 3-4.hét

URL-LEL ADOTT OBJEKTUM LETÖLTÉSE (1) URL-LEL ADOTT OBJEKTUM LETÖLTÉSE

Számítógépes Hálózatok GY 6.hét

Számítógép labor V. Egyszer Web szerver. Dokumentáció. Készítette: Ács Gergely (K4C03M)

Számítógépes Hálózatok GY 7.hét

SOCKET használata UDP kliens

TCP szerver. TCP szerver alkalmazás írásának az elsajátítása TCP protokoll tulajdonságainak a tanulmányozása kisérleti úton

Bevezetés a programozásba I.

UNIX: folyamatok kommunikációja

Számítógépes Hálózatok. 3. gyakorlat

UDP idő szerver. UDP protokollal kapcsolatos ismeretek elmélyítése. Egy UPP protokollt használó időszerver megvalósítása

Operációs rendszerek. 6. gyakorlat: Processzusok közti kommunikáció (osztott memória, üzenetsor)

Bevezetés a SOCKET programozásba UDP kliens

SZAKDOLGOZAT ÓBUDAI EGYETEM. Neumann János Informatikai kar Alba Regia Egyetemi Központ

Mutatók és címek (ism.) Programozás alapjai C nyelv 8. gyakorlat. Indirekció (ism) Néhány dolog érthetőbb (ism.) Változók a memóriában

Kommunikáció. Távoli eljáráshívás. RPC kommunikáció menete DCE RPC (1) RPC - paraméterátadás. 3. előadás Protokollok. 2. rész

Programozás C++ -ban 2007/7

A programozás alapjai 1 Rekurzió

C programozási nyelv Pointerek, tömbök, pointer aritmetika

Számítógépes Hálózatok GY 4.hét

Programozás alapjai C nyelv 8. gyakorlat. Mutatók és címek (ism.) Indirekció (ism)

Programozás 6. Dr. Iványi Péter

Fábián Zoltán Hálózatok elmélet

ő ő Ű ü ú ú Ú ü ű ő ő ő ő Á Á Í ü É ő ő ő ő ő É ő ú ú ú ő Á Ö ő

Ü ű ö Á Ü ü ö ö

Ö Ö ú

É ő ő ű ú Á ő Á ő ű ő ő ő ő ő ő ő ő ű ú ű ű ő ő ő ű

Í Ú É ő ő ú ö Ö ú ú ú ö ö ú ö ö ű ö ő ö ö ú ö ő ő ö ö ö ő ő ú ő ú ö ö ö ú ö ö ú ő ö ú ö ű ö ő Ó ő Á ö ő ö ö

ü ö ú ö ú ü ö ü Á Ó ö ö ö ö ú ü ú ü ü ú ú ö ö ü ü ú ü ü ö ö ű ö ü ü ü ü ö ö

ú ú ü ü Á ú ú ü ű ű ú ü ü ü ü

ú ű ű É ü ű ü ű ű í ü í ő í Ü ő ő ü ú Í ő ő í ú ü ü ő ü

ő ő ő ő ő ő ú ő ü Á ü ü ő ő ő ő ő ő ő ő ő ő Ö Ó ő ő ő Ö ő ő ő


Í ö ö ű ú ö ö Í ö ü ö ü

ú ű ú ú ű ú ű ű ú ű ú ű Á ű ű Á ű ű ú ú ú ú ú ú ű ú ú ú ú ú ú ú ú

ő ő ő ő ú É ü ú ú ű ú ű ő ő ő ő Á Á ü ő É É É É É É Á Ú Á Á ő ő ő ő ő É Á Á Á ő ő ő Á ü ő ő ü

ű Á ü ő ö í ö ö ő ő ő ő ö

Ö Ö ű ű ű Ú Ú ű ű ű Ú ű

É Í ü ú É ü ő ő ő ő ú ő ú ü ü ő ü ú ü ű ú ú ü ü Í ü ű ő ő É ő

í Ó ó ó í ó ó ó ő í ó ó ó ó

ü ő ő ü ü ő ő ű í í ű ő ő ő ü ő ő í í ő ő ő ő ő ő ü ü í ő Ö ő ü í ő ü í í ő ü ő í ő ő í í ő ü ü í ő ü í ő í ő í ő ü í ő í ü í í ő

ú ú ö ö ü ü ü ü ű ü ü

Í Í Ó ű Ü Ó Ó Ü ü Ö Í Ü Í Í ú Ö Ó Í ú ú Ö Ó É Í ű ú

ű í ú ü ü ü ü ü Ó í ü í í í É Á

ö ö ö ö ö ö ö ü ö ü ö ű ö ú ü ű ö ü Í ö ú ü ü ű ö ú ü Á ü

Í Í Í Ü Ó Ó Ö Á Ü Ü Ó Ü Ü Ó Ö Í É Ö

ő ö ő ú ő ö ö ő ó ő ö ü ú ö ö ó ő ö ü ó ó ó ó ő ő ő ó ó ú ő ü ő ö ö ó ü ö ö ő ű ö ö ő ú ú ó ö ő ű ö ó

é ö é Ö é é ő í ó í é ő ö ú é ó é ő ü ü é ó ö é é ó é é ö é ő í é é ő é é ö é ű ö é í ó é é í ö í ó í ó é é ö ó í ó ó í ó é é ö ő í ó ó í ó ü é í ü

ú ü ü ú Ö ú ü ü ü ü ü ú ü ú ü ű Í ü ü ű ü ű Ó ü Ü ű ú ú Á ü ű ű ü ü Ö ü ű ü Í ü ü

ö ö ó ú ö ö ú ü ó ö ö Í ö ö ö ü ó ö ö ú ú ö ü ó ü ó ü ö ú ü ó ü ö ó Á Á ö ü ú ó ö ü ü ö ó ü ü Á ü ö ü ö ü ö ö ö ü ö ú ö ö ö ü ú ö ú ö ű ú ú ü ö ó ö ö

Ö Ö Ú Ó Ö ű Ő Ő ű ű Ü Ő Ó Ő

í í ü í í í í í Ó ő ő í í í Ú ü Ú í í Ú ő ü Ú ü ő

é ú é é é é é é é é é é é é ú é ö é é é ö Ő é é é ú é é é é é é é é ö é é é ö é Ö é é ö é ö é é é ű é ö ö é ö é é ö ö é é ö ö é ö é Ö é ú é é é é é é

í ó ő í é ö ő é í ó é é ó é í é é í é í íí é é é í é ö é ő é ó ő ő é ö é Ö ü é ó ö ü ö ö é é é ő í ő í ő ö é ő ú é ö é é é í é é í é é ü é é ö é ó í é

ó ú ú ü ú ő ó ő ő ó ó ó ö ó ü ő ó ő ö ü ü ó ö ő É ó ö ö ö ó ó ö ü ü ö ü ó ó ő ó ü ó ü ü ö ö É ú ó ó ö ú ö ü ü ó ó ó ü Á ö ö ü ó ö ó ö ö ö ö ó ó ö ó ó

ö ö Ö ó ó ö ó ó ó ü ö í ü ú ó ó í ö ö ö ó ö ü ú ó ü ö ü ö ö Ö ü ö ö Ö ó

ü É ö É É ö ö ö ü ö ö Á ű ö ű ű ű Á Í ö ö Ó ö

ő ö ő ű ó ö ó ű Í Ö Ö Á Í Ó Ö Ü É Ö Ö Ö Á Á Ö É Á Ö

ű ú ú Ö ó Ö ó ó ó Ö ű ó ű ű ü Á ó ó ó ó ü ó ü Ö ó ó ó Ö ű ű ü Ö ű Á ú ú ú ó ű í í Ő ú Á É Ö í ó ü ű í ó ű ó Ö ú Ő ú ó í ú ó

ö Ó ű ö ó í ó ü ö Ó ó í ö ö ó Ö ó ö í ó í ó Á í ó Á Á Ő ú ü ó Í ü ú ü

í ü í ü ő ő ü Í ő ő ő ú í ő ő ö ö ö ű ü í ő ő í ú ö ö ú ő ő ú í ő í ő ö ö í ő ü ü í ő ö ü ü ú í í ü ő í ü Í í í í ö ő ö ü ő í ő ő ü ű ő ő í ő í í ő ő

ö ö ö Ö ö ú Ö í Ö ű ö í Ö í ö ü ö í ú Ö Ö ö í ű ö ö í ö ö Ő ö í ü ö ö í Ö ö ö í ö í Ő í ű ű í Ö Ó í ö ö ö ö Ö Ö ö í ü ö ö Ö í ü Ö ö í ö ö ö ö ö Ö ö í

Í Ó ü ü í ü ü ü í Í í É í í Í Í ü ü ü í Í ü

ö ü ü ú ó í ó ü ú ö ó ű ö ó ö í ó ö í ö ű ö ó Ú ú ö ü É ó í ö Ó Á í ó í í Ú ö ú ö ű ü ó

É Ö Á Í Á Ó Ö ü

Ü ü Ü Ö Ó ö ü ö Ó Ú Ó ü Ó ö ö Á ö ö ö ö ü

ú Ü Í ú ú ú ú ú ú

ű ú ü ü ü Í ü ö ü ö ü ö ü Ó ü ö ü ö ö ü ű ű ú ü ö ö ü Ó ö ű ü ö ú ö ö ü ü ű ü ü ö ö ü ü ú ö ö ü ü ú ü

ó É ó í ó ó í í ö í ó í ö ö ö ü ö ó ó ó ü ú ö ü ó ó ö ö ü ü ü ö ö ó ö í ó ű Ü ó í ú í ö í ö í Í ó ó í í ö ü ö ö í ö í ö ö ö ü ó í ö ö ó í ú ü ó ö

ű ú Í Ó Á ú Ű ű Ő Ö Á ú Ű Ü ú ú Á ú ű

Ü

í ó í ó ó ó í í ü ú í ú ó ó ü ü í ó ü ú ó ü í í ü ü ü ó í ü í ü ü í ü ü í ó ó ó í ó í ü ó í Á

É ö Ű ő ű ő ő ű ű

Ö Ö ö Ó Ó Ó Ó Ü ú ü Ű Ö Ö Ö ö Ü ö Í ü ű

í í É í ó ó É ö í ó í ó í ó ó í ó í í ó ó ó í ö ö ö ö í í í ó ó ö ó

Ö ő ü Ö Ö Ő ü ő Ö Ö ü ű Á Í Ö ű ü ő ő ő Ö ü ü ő ő ő Ü ü ő ő ő ü ő ő ü ü

ö ő ő ü ü Ó ü ö ű Á ő ő ö ő Á Ó ű ö ü ő ő ű

Ö Ö Ö Ö Ö Á Ű É Ö Ö Ö

É Í Á Á É Ü Ó É É É É Í Ó Ó Ő Á Á É Á É É É É Á É É Á Á É É Á É Í

ó ö í í ü Ű Ö ó ó ű ö ü Í í í ö Ö Ó ö Ű Ö ú ó ó í í ű ö ö ö ö í ó ö ö í ö ű ö ű ö ö ö ö ö í ó Ö Ö ü ú ö ó ü ö Ö ű ö Ö ü ó ö ö ó ö ö Ó í ű ö ű ö ö ű í

ű ö ú ö ö ö ö í ű ö ö ö ű ö ö ö í ü ú í ű í ö í ú ű í ü ö ö ú ö í ö ű ú ü ö ö í ö ü ö ú ű ö ö ö í Á í ü í ö ü ö í ü ö Ő ü ö í ű ü ö í í í í í

É ú É ö ö ű ö ö ö ú ú ú ű ű ú ö ű ö ű ű ü ö ö ü ű ö ü ö ö ö ö ú ü ö ö ö ú ö ö ú ö ö ú ü ú ú ú ű ü ö ö ű ú ű ű ü ö ű ö ö ö ű ú ö ö ü ú ü ö ö ö ü ú ö ű

ű ú ó ó ü í Á Á ú ó ó ó ó ó ó ó ó ó ó ó ó ó ó í ó ü É ű ü ó í ü í í í í í ó í ü í í ó ó Á

í í í í ó í ó ö ö í ű ü ó ó ü ú Á Á ó ó ó ó ó ó í ó ö ö ü Ó ö ü í ö ó ö í í ö í ó ó í ö í ú ó ú í ö ú ö ö ö í ó ó ó ú ó ü ó ö í ó ó í í í Á í ó ó ó

ü ö ö ő ü ó ó ú ó

Átírás:

Gyorstalpaló hálózatprogramozás C nyelven UNIX környezetben Hajba Gábor László 2007. február 20.

Tartalomjegyzék 1. A legfontosabb parancs 4 2. TCP alapú kliens m ködése 4 3. Kliens socket létrehozása 5 4. Kapcsolódás a szerverhez 6 5. TCP alapú szerver m ködése 9 6. Szerver socket porthoz kötése 10 7. Szerver socket felkészítése kliens fogadására 11 8. Kapcsolat felvétele a klienssel 11 9. Példa a szerver kapcsolatra kész állapotra hozására 12 10.Kommunikáció 14 11.Kapcsolat lezárása 15 12.Kapcsolat felépítése a kliens szemszögéb l 16 13.Kapcsolat felépítése a szerver szemszögéb l 16 14.Kapcsolat bontása az aktív oldalon 17 15.Kapcsolat bontása a passzív oldalon 17 16.Egy korszer kliens-szerver megoldás 18 17.A legegyszer bb megoldás 18 18.Kliensek párhuzamos kiszolgálása gyermek folyamatok segítségével 20 19.Folyamatok közötti kommunikáció pipe-ok segítségével 22 1

20.Kliensek kiszolgálása szálak segítségével 23 21.Signalok 23 22.Események, amelyek signalt válthatnak ki 23 23.A session fogalma 23 24.Daemon megvalósítása 23 25.Szolgáltatás nyújtása inetd segítségével 23 2

El szó Ez ahogy a címe is jelzi egy gyorstalpaló és összefoglaló C nyelven történ hálózatprogramozáshoz. Így feltételez egy meglév programozási hátteret. Nem tér ki a téma egészére, és nem fed le minden említett témát teljes egészében. Ellenben arra éppen elég, hogy ne okozzon nagy nehézséget a Számítógéplapbor 2. elnevezés tantárgy sikeres elvégzése (a program megvédésével együtt). Írhatnék még egy általános bevezet t is, de szeritnem azt már mindenki átlapozza, ezért ismertnek tételezem fel a hálózati kommunikáció alapjait. A két lehetséges kommunikációs csatorna közül a TCP-vel foglalkozunk. A legtöbb helyen vázlatpontokat írok fel, ez is b ven elég a megértéshez, illetve a lényeg is könnyebben olvasható mint folyószövegben. 3

1. A legfontosabb parancs A programok fordítása a legfontosabb lépés a programozásban, így a legfontosabb parancs az, amelyikkel rá tudjuk venni a fordítót, hogy forrásfájljainkból bájtkódot generáljon, és futtathassuk alkalmazásunk. Ha megvan a kódunk, a következ paranccsal fordíthatjuk le: gcc -o programneve forrás1.c forrás2.c... Ha C++ nyelven írjuk a programot: g++ -o programneve forrás1.cpp forrás2.cpp... Amennyiben a program már tartalmaz hálózat kezelést, úgy szükséges még némi kiegészítés a fordításban: gcc -o programneve forrás1.c forrás2.c... -lnsl -lsocket -lresolv g++ -o programneve forrás1.cpp forrás2.cpp... -lnsl -lsocket -lresolv Ezek után a./programneve parancs kiadása után el is indul a program ha mindent jól csináltunk. Amennyiben mégsem, úgy hibajelzéssel tér vissza a fordító, melyet ki kell javítanunk. Nem kell meglep dni, ha más fordítón lefordul a program, míg a célrendszeren nem: el fordulnak eltérések a rendszer kongurációjából adódóan (f leg az URAL2-n). 2. TCP alapú kliens m ködése A program TCP-re épül szolgáltatást próbál elérni. A végrehajtás lépései: 1. socket létrehozása, 2. porthoz kötés, 4

3. kapcsolódás, 4. kommunikáció a szerverrel, 5. kapcsolat bezárása. A fent említett lépések közül a porthoz kötéssel nem foglalkozunk, annak a kliensnél nincs sok szerepe. 3. Kliens socket létrehozása Független attól, hova akarunk csatlakozni. Nem kell tudnunk a szerverprocessz címét. Vigyázni kell, hogy a megadott tartomány és protokoll helyes legyen. Létrehozásához a socket függvényt használjuk. A socket metódus: #include <sys / types. h> #include <sys / s o c ket. h> int s o c ket ( int domain, int type, int p r o t o c o l ) ; A függvény visszatérsi értéke egy socket leíró, hiba esetén 1. A domain paraméter értéke a következ lehet: AF_UNIX ha helyi hálózatra kívánunk csatlakozni, AF_INET ha az internetre kívánunk csatlakozni. A type paraméter értéke a következ k egyike lehet: SOCK_STREAM TCP kapcsolat létrehozásához, SOCK_DGRAM UDP kapcsolat létrehozásához, 5

SOCK_RAW_IP bele lehet nyúlni az üzenet fejrészébe át lehet írni az IP címet, csak root jogokkal futtatható (ezt nem használjuk). A protocol paraméter meghatározza a protokolt, ha 0-t adunk meg értékeként, akkor az alapértelmezett protokollt fogja használni (a type paraméterben megadott kapcsolat típushoz igazodva). 4. Kapcsolódás a szerverhez A connect metódussal történik. TCP-nél kötelez a használata, UDP-nél nem. A connect metódus: #include <sys / types. h> #include <sys / s o c ket. h> int connect ( int sd, const struct sockaddr addr, int addrlen ) ; A függvény visszatérési értéke 0 siker esetén, 1, ha valamilyen hiba lépett fel. Az sd paraméterben kell átadni a socket függvény által visszaadott socket leírót. Az addr mutatóban kell megadni a szerver processz címét (IP-cím + port) tároló struktúra hivatkozását. Err l kés bb olvashatunk b vebben. Az addrlen paraméter pedig a szerver processz címét tároló struktúra mérete. A connect metódus (sikeres) visszatérésével felépül a kapcsolat. A szerver processz címét egy sockaddr típusú struktúrába kell elhelyezni: struct in_addr{ u_long s_addr ; } ; struct sockaddr_in { 6

u_short sin_family ; u_short sin_port ; struct in_addr sin_addr ; char sin_zero [ 8 ] ; // nem h a s z n á l t } ; A sin_family a socket tartománya (esetünkben AF_INET). A sin_port a szolgáltatás portja. A sin_addr a szerver processz IP címét tárolja struktúra, ennek a s_addr részébe kell az IP-t elhelyezni. Felmerül a kérdés: honnan tudjuk az IP-t? Van, amikor alapból így hívjuk meg a programot, mert ismert a szerver processz IP címe, másik lehet ség pedig, hogy a neve alapján lekérjük az IP címet egy DNS szervert l. Ebben nyújt segítséget a gethostbyname metódus. #include <netdb. h> struct h o s t e n t gethostbyname ( const char hostname ) ; A hostname paraméter a szerver processz címét jelenti, pl. ural2.bme.hu. A függvény egy hostent struktúrával tér vissza siker esetén, egyébként 0-t ad vissza. struct hostent { // host neve char h_name ; // 0 v a l l e z á r t l i s t a a host t ö b b i n e v é r l char h_aliases ; // a cím t í p u s a ( esetünkben AF_INET) int h_addrtype ; // a cím hossza b á j t b a n 7

int h_length ; // 0 v a l l e z á r t l i s t a az IP címekr l char h_addr_list ; //a l i s t a e l s eleme a k o m p a t i b i l i t á s miatt #d e f i n e h_addr h_addr_list [ 0 ] } ; Az IP címek listájának els elemét (h_addrlist[0]) bemásoljuk a struktúra sin_addr tagjába. A htons paranccsal a hálózati bájtsorrendet módsíthatjuk, ha esetleg rossz formában volna (azaz mindenképp használjuk). Kommunikációt a read és write metódusokkal valósítjuk meg. A kapcsolat végén a socketet a close függvénnyel zárhatjuk le; ha stream-eket használunk, akkor pedig az fclose metódust kell meghívni. 1 Példa kapcsolat felépítésére: #include <sys / types. h> #include <sys / s o c ket. h> #include <n e t i n e t / in. h> // htons hoz s z u k s e g e s #include <netdb. h> int s o c ket ; struct hostent he ; struct sockaddr_in their_addr ; int k a p c s o l a t L e t e s i t e s e ( void ) 1 Nálam a stream-ek használata nem m ködött az URAL2-n. 8

{ i f ( ( he = gethostbyname ( " u r a l 2. bme. hu" ) == NULL) { // megkapja a h o s t i n f o r m a c i o t e x i t ( 1 ) ; } i f ( ( s o c k e t = s o c k et (AF_INET, SOCK_STREAM, 0 ) ) == 1) { e x i t ( 1 ) ; } their_addr. sin_family = AF_INET; // host b a j t sorrend their_addr. sin_port = htons ( 2 3 4 5 6 ) ; // a htons parametere a port their_addr. sin_addr = (( struct in_addr ) he >h_addr ) ; // k i n u l l a z z a a s t r u k t u r a t o b b i r e s z e t memset(&( their_addr. sin_zero ), ' \0 ', 8 ) ; i f ( connect ( socket, ( struct sockaddr )&their_addr, s i z e o f ( struct sockaddr ) ) == 1) { // k a p c s o l o d a s e x i t ( 1 ) ; } return 0 ; } 5. TCP alapú szerver m ködése Egy szerver is hasonló m veleteket végez el, mint a kliens: 1. Socket létrehozása a socket függvénnyel (azonosan m ködik, mint a kliensnél), 9

2. socket hozzákötése egy helyi porthoz a bind függvény segítségével ezen a porton keresztül tud csatlakozni a kliensünk, 3. socket felkészítése a kliensek fogadására a listen függvény segítségével, 4. a klienst l érkez kapcsolatkérés elfogadása az accept függvénnyel, 5. kommunikáció a read és a write függvényekkel, 6. kapcsolat lezárása a close vagy shutdown függvények valamelyikével. Most nézzük meg egyesével a lépéseket. 6. Szerver socket porthoz kötése #include <sys / types. h> #include <sys / s o c ket. h> int bind ( int sd, const struct sockaddr addr, int addrlen ) ; A függvény visszatérési értéke 0, ha minden rendben van, hiba esetén pedig 1. Az sd paraméter egy socket leíró, melyet a socket metódussal hoztunk létre. Az addr paraméter egy pontos cím, ahova kötjük a socketet (lásd a sockaddr struktúra korábbi leírását). Az addrlen paraméter a struktúra méretét (hosszát) tárolja. Ebben az esetben nincs értelme a 0 portot megadni, hisz akkor mi magunk sem tudjuk, melyik porton gyel a szerverünk. Talán akkor volna esély a használatára, mikor mi magunk nem tudjuk, melyik port lehet szabad (például sok hálózati alkalmazást egyszerre futtató gépünk van), és a kliens is broadcast kéréssel keresi meg a szervert. A laborfeladatban ez nem szükséges (ha valaki olyan forgatja ezt a jegyzetet, aki ilyesmit ír, akkor biztos jobban ért hozzá, mint ahogy itt leírom a dolgokat). 10

A számítógép összes IP címét az INADDR_ANY paraméter jelenti, ezeket is sorrendbe kell fordítani a htons paraméter használatával. (Tehát nem korlátozzuk be a klienst, hogy épp melyik IP címet kell feltárcsáznia, hogy elérje szerverszolgáltatásunk.) Ha egy socket már hozzá van kötve egy porthoz, akkor a getsockname függvénnyel kérdezhetjük le (ami azt takarja, hogy megadunk a függvénynek paraméterként egy sockaddr struktúrát és egy socketet, pedig visszaadja a struktúrában a megadott socket adatait). 7. Szerver socket felkészítése kliens fogadására Ezt a részt nevezhetjük listening nek az alkalmazott módszer után. A listen metódust csak a socket porthoz kötése után lehet meghívni ez eléggé nyilvánvaló; illetve csak kapcsolat orientált kapcsolatnál m ködik (tehát nekünk pont megfelel, hisz mi TCP kapcsolatot alkalmazunk). #include <sys / s o c ket. h> int l i s t e n ( int sd, int qlen ) ; A függvény visszatérési értéke 0, ha minden rendben, ellenkez esetben 1. Az sd paraméter a socket leírója. A qlen paraméter a várósor hosszát reprezentálja. A várósor azt jelenti, mennyi klienst engedünk egyszerre várakozni, míg felépül a kapcsolat. Ha túl nagy ez a szám, sok kliens várakozhat; ha pedig kicsi ez a szám, akkor csak kevés kliens fér be, a többieknek pedig nem m köd nek látszódik a szolgáltatás. 8. Kapcsolat felvétele a klienssel A kapcsolatot az accept függvény meghívásával (pontosabban visszatérésével) építhetjük fel szerverünk és egy kliens között. 11

Ezt a függvényt csak a listen függvény kiadása után hívhatjuk meg (természetesen ez az általunk használt kapcsolatorientált esetben szükséges). A függvény addig várakozik (ez által blokkolja a program továbbfutását), míg egy kliens nem kezdeményez kapcsolódást. Ekkor a kapcsolat felépítésével egy új socket jön létre (visszatérési érték). Ebb l következik, hogy az eredeti (szerver socket) megmarad, így az accept metódus kívánalmainknak megfelel számban meghívható; másik következménye pedig, hogy több kapcsolat lehet egyszerre, ezt pedig kezelni kell erre több módszer van, melyeket a kés bbiekben ismertetek. #include <sys / s o c ket. h> #include <sys / types. h> int accept ( int fd, struct sockaddr addrp, int addrlen ) ; A függvény visszatérési értéke 1 hiba esetén, egyébként pedig egy socket leíró (a kliens socketé). Az fd paraméter egy fájl leíró, ami a mi esetünkben a listen-nel el készített (szerver) socket leíró. Az addrp struktúrába kerül bele a kliens IP címe és a port, amelyen keresztül kapcsolat fel van építve. Itt jegyzem meg, hogy az itt eltárolt port nem egyezik meg azzal a porttal, amelyre a kapcsolat-kérés érkezett, hiszen ezen továbbra is a szerver gyeli a beérkez kéréseket. Így a szerver egy másik (éppen szabad) portot rendel hozzá a kapcsolathoz. Az addrlen paraméter pedig szokás szerint a fent említett címstruktúra méretét jelenti. A függvény visszatérése után a socket leírón (visszaadott érték) keresztül tudunk kommunikálni a klienssel (a read és write függvények felhasználásával). 9. Példa a szerver kapcsolatra kész állapotra hozására A következ kben megnézünk egy egyszer példát, ahol a szerverünk az 12345-ös porton gyel, és a várósor mérete 5 (tehát egyszerre 5 klienst engedünk várakozni a kapcsolatra). 12

#include <sys / types. h> #include <sys / s o c ket. h> void k a p c s o l a t _ l e t e s i t e s e ( ) { int listen_sock, c l i e n t _ s o c k ; // a s o c k e t l e í r ó k struct sockaddr_in other_addr ; // k l i e n s címe l i s t e n _ s o c k = c r e a t e L i s t e n S o c k e t ( 1 2 3 4 5 ) ; / varakozosor meretenek b e a l l i t a s a / i f ( l i s t e n ( listen_sock, 5) < 0) { e x i t ( 1 ) ; } / Kapcsolatra várunk... / while ( 1 ) // FIGYELEM!!! v é g t e l e n c i k l u s { c l i e n t _ s o c k = accept ( listen_sock, ( struct sockaddr )&other_addr, &s i n _ s i z e ) ; i f ( c l i e n t _ s o c k == 1) { continue ; // emiatt nem k e l l m e g á l l n i } / Kapcsolat f e l é p í t v e, i n n e n t l jön a kommunikáció / } } 13

Látható, hogy a szerver kapcsolat felépítése a createlistensocket metódus meghívásával történik. A paramétere az a port, amelyen a kapcsolatokat várjuk. A visszatér értéke pedig egy leíró socket, melyet listen_sock névvel jelölünk a minta programban. 10. Kommunikáció Ebben a részben gyorsan megtekintjük, hogyan kommunikálhatunk mind a klienssel, mind pedig a szerverrel. A kommunikáció a socketeken keresztül történik, mivel a socketek egyfajta fájlleírók, melyeket használhatunk a read és write függvényekkel. Ezen kívül stream-eket is rá lehet húzni, mellyel valamennyire kultúráltabban tudjuk kezelni az üzeneteket. Igaz, ez nem m ködik az Ural2-n, így az egyszer read és write függvényeket kell alkalmaznunk. Az olvasó függvény meghívásával a program további futása blokkolódik, így amíg nem érkezik semmilyen válasz az olvasott socketr l, programunk áll. Természetesen ha valami hiba történik a távoli oldalon (például megszakad a kapcsolat), akkor err l kapunk visszajelzést, és a read metódus blokkolódása feloldódik. A hibás üzenet lekezelése már a mi feladatunk. Most nézzünk egy-egy (egyszer ) példát mind a stream-ekkel mind pedig a hagyományos módszerekkel történ kommunikációra. FILE socket_stream ; int s o c k e t _ l e i r o ; char b u f f e r [ 1 0 0 ] ;... socket_stream = fdopen ( s o c k e t _ l e i r o, " r+" ) ; f p r i n t f ( " h e l l o \n", socket_stream ) ; // e l k ü l d ü n k egy ü z e n e t e t 14

f s c a n f ( "%s ", &b u f f e r ) ; // ü z e n e t e t várunk int s o c k e t _ l e i r o ; char b u f f e r [ 1 0 0 ] ;... w r i t e ( s o c k e t _ l e i r o, " h e l l o \ r \n", s t r l e n ( " h e l l o \ r \n" )+1); read ( s o c k e t _ l e i r o, b u f f e r, 1 0 0 ) ; Stream-ek használatával küldött üzenetek a másik oldalon ugyanúgy jelennek meg, még ha ott hagyományos módszerrel is fogadják ket, tehát a stream-ek használata egyik oldalon nem kötelezi a másik oldal készít jét stream-ek alkalmazására. 11. Kapcsolat lezárása A kapcsolatot a close vagy shutdown metódusokkal lehet lezárni. A close függvény használata: c l o s e ( s o c k e t _ l e i r o ) ; Szerintem ez nem szorul b vebb magyarázatra. Ha stream-eket használunk, akkor a lezáráshoz az fclose metódust kell használnunk. A shutdown metódus hasonló a fent említett close metódushoz, azzal a különbséggel, hogy ezzel a socketet külön le tudjuk zárni írás, olvasás vagy mindkett szemszögéb l. Például: lezárjuk a socket-ünket írásra (ez azt takarja, hogy nem tudunk rajta keresztül üzeneteket küldeni a másik oldalra); amíg a másik oldal észreveszi ezt a lezárást, addig mi tudjuk olvasni a küldött üzenetet. Amikor a másik oldal érzékeli a lezárást, a kapcsolat megszakat, a mi oldalunk ún. fájlvége üzentetet kap, mi is lezárhatjuk a kapcsolatot. int shutdown ( int sd, int a c t i o n ) ; 15

A függvény visszatérési értéke 0, hiba esetén pedig 1. Az sd paraméter egy socket leíró. Az action paraméterrel azt adhatjuk meg, hogy mire zárjuk a socket-ünket (0: olvasásra, 1: írásra, 2: mindkett re). 12. Kapcsolat felépítése a kliens szemszögéb l Ez egy áttekint rész, melyben címszavakban áttekintjük a kapcsolat felépítését a kliens szemszögéb l: socket elkészítése, kapcsolat kezdeményezése connect függvénnyel (ezt nevezzük active open-nek: mindig a kliens oldalán történik a kapcsolat kezdeményezése, mivel tudja a szerver címét; SYN jelet küld a szervernek, majd SYN_SENT állapotba kerül, a connect metódus nem tér vissza, a szerver válaszára vár, a szerver válasza tartalmazza a kérés nyugtáját, és jó esetben engedélyt ad a csatlakozásra, ez esetben pedig ESTABLISHED állapotba kerül a socket. 13. Kapcsolat felépítése a szerver szemszögéb l Ebben az áttekint részben megnézzük, hogyan épül fel a kapcsolat a szerver szemszögéb l: socket létrehozása, socket hozzákötése egy lokális (és természetesen szabad) porthoz, felkészül a kapcsolat fogadására a listen metódussal, és LISTEN állapotba kerül (ez a passzív nyitás), SYN üzenet fogadása után SYN_RCVD állapotba kerül a socket, 16

nyugta küldése a kliensnek, kapcsolat engedélyezése, nyugta fogadása, majd a socket ESTABLISHED állapotba kerül. 14. Kapcsolat bontása az aktív oldalon Ebben az áttekint részben megnézzük, mi is történik az aktív (a kapcsolat bontását kezdeményez ) oldalon a kapcsolat bontásakor: close metódus meghívása, FIN üzenet küldése a kliens felé, nyugta visszatérésekor a kapcsolat bontása a socket lehetséges állapotai: FIN_WAIT_1 állapot: meger sítésre vár, FIN_WAIT_2 állapot: FIN jelet vár, FIN_WAIT_3 állapot: vár pár másodpercet, majd állapota CLOSED lesz, vagyis rezárult a kapcsolat. 15. Kapcsolat bontása a passzív oldalon Ebben az áttekint részben megnézzük, mi is történik a kapcsolat bontásakor a passzív oldalon: FIN üzenetet kap, hatására CLOSE_WAIT állapotba kerül, nyugtát küld a kapcsolat bontásáról, close metódus meghívása, FIN küldése az aktív oldalnak, állapota LAST_ACK-ra változik, nyugta érkezésekor állapota CLOSED lesz. 17

16. Egy korszer kliens-szerver megoldás Egy korszer klienssel szemben nincs sok elvárásunk - hálózati szemszögb l, hisz a szerverre van utalva, nélküle nem képes m ködni (tekinthetjük vékony kliensnek, de egy vastag kliensnek is szüksége van távoli kommunikációra). Feladat olyan szerverek készítése, melyek egyszerre több klienst tudnak kiszolgálni, amennyiben lehetséges (például er forrásigényes feladatoknál nincs értelme több klienst egyszerre kiszolgálni, de ahhoz, hogy ezt a labort sikeresen elvégezzük, nincs szükség ilyen igény feladatokra, és azt hiszem, elvárásként is szerepel több kliens egyszerre történ kiszolgálása). Ezt több folyamat (process) vagy több szál (thread) alkalmazásával tehetjük meg. A szálprogramozás mint általában minden programnyelvben nem könny feladat, így azt tanácsolom, hogy ne ezzel próbálkozzon meg az, aki ezt a jegyzetet olvassa. Természetesen megemlítem a szálak kezelését minimális szinten. Mint már említettem, a program futása blokkolódik mind az accept, mind pedig a read függvén meghívása után. Így felmerül az ötlet: mi volna, ha meg tudnánk nézni, van-e valami (valaki) a csatornán? 17. A legegyszer bb megoldás A legegyszer bb megoldást a select függvény adja. #include <sys / time. h> #include <sys / types. h> #include <unistd. h> int s e l e c t ( int n, fd_set readfds, fd_set w r i t e f d s, fd_set exceptfds, struct timeval timeout ) ; 18

A függvény visszatérési értékei: 0, ha lejárt a megadott id ; pozitív érték, ha valamelyik leírón adott m velet végezhet ; hiba esetén pedig negatív. A függvényben leíró csoportokat adunk meg: readfds Ennél a csoportnál arra vár, hogy legyen rajtuk olvasható adat, azaz az egyik leíróra meghívott read (fscanf) függvény meghívódjon. Ha nincs adat, a read metódus 0-val tér vissza. writefds Ez a csoport arra ügyel, hogy a write (fprintf) függvény ne blokkolódjon íráskor. exceptfds Ez a csoport a leírók kivételeire vár. Amelyik leírócsoportra nincs szükségünk, oda NULL-t adunk meg a metódus meghívásakor. A timeout paraméter a várakozási id t jelenti, melynek leteltével mindenképp visszatér a metódus. Ha NULL-t adunk meg, akkor addig vár, míg valami nem történik. A függvény a paraméterként átadott leírócsoportokat törli, és a válaszát ezekbe helyezi el, tehát visszatérés után a readfds változó fogja tartalmazni az olvasható leírókat. A függvény a gyel socketre érkezett kapcsolódásokat és a már kapcsolódott kliensek kéréseit is gyeli, azonban az esetleges hibákat nem, amire külön ügyelni kell. A leírócsoportok kezelésére léteznek speciális makrók: FD_ZERO(fd_set * set) csoport el készítése, FD_SET(int fd, fd_set * set) leírót ezzel tesszük be a csoportba, FD_CLR(int fd, fd_set * set) leíró kivétele a csoportból, FD_ISSET(int fd, fd_set * set) megnézi, hogy a leíró a csoportban van-e. 19

18. Kliensek párhuzamos kiszolgálása gyermek folyamatok segítségével Folyamaton a program egy futó példányát értjük. Egy multitasking operációs rendszer több folyamatot képes egyszerre m ködtetni. (Szerencsénkre az Ural2 képes egyszerre több folyamat kiszolgálására, és mivel nem cél belebonyolódni a szálakba, így azt tanácsolom, próbáljuk meg ezt a módszert alkalmazni. Igaz, vannak olyan feladatok, melyek csak szálakkal valósíthatóak meg.) Egy folyamat további folyamatokat (alfolyamatokat) indíthat, így gyakran használják több klienst egyszerre kiszolgálni tudó szerverekhez. PID (Process ID): a folyamat azonosítója (jól jön akkor, ha valamiért nem tudjuk normális módon lekapcsolni). GID (Group ID): csoportazonosító; minden folyamat beletartozik egy csoportba a shellr l egy sorban indítottak egy csoportba kerülnek, tehát ha mi magunk gépeljük be a program nevét a konzolban, akkor egyedüli lesz a csoportban. Alfolyamatokat a fork függvénnyel indíthatunk. #include <unistd. h> pid_t f o r k ( void ) ; A metódus meghívásával a folyamat duplikálja magát, így a szül és gyermek folyamatok nem látják egymás változóit; hiába fut ugyanaz a programkód, mégis más adatokon történik. Ebb l látható, hogy memóriaigényes programokat nem érdemes multiplikálni. A gyermek folyamat a szül csoportjába fog tartozni. A szül folyamat futása a fork metódus visszatérése után a függvény után következ sorral folytatódik. Mivel a fork függvény a gyerek folyamat csoportazonosítójával tér vissza a szül folyamatban, a gyerek folyamatban pedig 0-val (természetesen egy új alfolyamatot indító gyerek folyamatot ebben az esetben szül nek tekintünk), ezért az új folyamatnak 20

lehet speciális, a szül t l eltér parancsokat adni: int pid = f o r k ( ) ; i f ( pid == 0) { // parancsok i d e e x i t ( 0 ) ; // folyamat megsz nik } A kliensek csatlakozására különböz módokon (különböz helyekr l) várhatunk: a sz l folyamatban, vagy pedig a gyerek folyamatokban. Az els esetben csak akkor hozzuk létre a gyerekfolyamatokat, ha már kiépült a kapcsolat a klienssel. Ekkor, ha több kliens akar csatlakozni, várakozni kényszerülnek, míg az el ttük lév összes kérésnek elkészül a gyerek folyamat; ám el nye, hogy csak a szükséges mennyiség er forrást foglaljuk le. A második esetben el re létrehozzuk a gyerek folyamatokat, így több kliens egyszerre csatlakozásakor azonnal sorra kerülnek, senki sem várakozik a fork metódus visszatérésére. Ennek azonban megvan a hátránya: több er forrást kötünk le, mint amire szükségünk lehet. A gyermek folyamatokra (pontosabban azok megsz nésére) a wait és waitpid metódusokkal várhatunk. Ezek a metódusok operációs rendszerenként eltérnek, ezért nézzünk utána a manual-ban (man wait és man waitpid). A wait függvény hatására a szül folyamat felfüggeszti a futását, míg egy gyerek folyamat vissza nem tér (meg nem sz nik). A függvény visszatérési értéke a megsz nt folyamat azonosítója. A waitpid metódus meghívásakor a szül folyamat futását felfüggeszti, míg az adott folyamat azonosítójú (PID) gyermek vissza nem tér (meg nem sz nik). Ha bármelyik gyermek folyamat visszatérése megfelel nekünk, 0-t is megadhatunk a függvénnynek. A módszernek megadható a WNOHANG opció, mellyel a függvény akkor tér vissza, ha egy folyamat sem ér véget, tehát nem blokkolódik a program futása. 21

#include <sys / types. h> #i n c l u s e <sys / wait. h> pid_t wait ( int s t a t u s ) ; #include <sys / types. h> #i n c l u s e <sys / wait. h> pid_t waitpid ( pid_t pid, int status, int o p tions ) ; 19. Folyamatok közötti kommunikáció pipe-ok segítségével Vannak esetek, mikor a gyerek adatokat kér a szül folyamattól, ami nehéz feladat. Megoldása az inter-process communication (IPC). A pipe m ködése FIFO (First In First Out) elv ; pozícionálásra nincs lehet ség; nincs neve, hanem két fájlleíróval (a vezeték eleje és vége) azonosítjuk, amelyek közül az egyik ír, a másik olvas. Mivel a gyerek folyamatok öröklik a szül folyamatuk leíróit, így a vezetéket is öröklik, tehát kapcsolatot tudnak tartani. #include <unistd. h> int pipe ( int f i l e d e s [ 2 ] ) ; A paraméterben lév tömb els (azaz 0.) elemére lehet írni, a második elemér l pedig csak olvasni lehet. Mivel minden gyermek örököl, ezért több eleje és vége lesz a csatornáknak, a felesleges 22

végeket pedig le kell zárni mindkét oldalon. Az olvasható végek lezárásával a read függvény 0-val tér vissza, míg az írási végek lezárása után íráshiba lép fel. Egy vezeték addig él, míg legalább egy eleje és egy vége van. Amennyiben a pipe üres, a read függvény blokkolódik (mint ahogy azt a hálózati kommunikációnál már láttuk). 20. Kliensek kiszolgálása szálak segítségével 21. Signalok 22. Események, amelyek signalt válthatnak ki 23. A session fogalma 24. Daemon megvalósítása 25. Szolgáltatás nyújtása inetd segítségével 23