2024. május 2., csütörtök

Gyorskeresés

Útvonal

Cikkek » Számtech rovat

Bevezetés a hálózati kommunikáció programozástechnikájába

Legújabb írásomban a hálózati kommunikáció C-ben történő művelésébe próbálok betekintést nyújtani.

[ ÚJ TESZT ]

Összeköttetés alapú kommunikáció

Elsőként részletesebben az összeköttetés alapú kommunikációval (stream) foglalkozunk. Sajátossága ennek a működésnek, hogy a kapcsolatot fel kell építeni, majd kommunikálni a vonalon és lezárni azt. A kapcsolódás aszimmetrikus mivolta okozza azt, hogy külön beszélhetünk kliensről és szerverről. Előbbi kéri fel a szervert adatközlésre, utóbbinak pedig szolgáltatást kell nyújtania, és folyamatosan nyitottnak kell lennie az újabb kérésekre is. Az ezekhez tartozó konnektorokat nevezzük kliens socketnek és szerver socketnek.
A kliens socketen keresztül, miután az kapcsolódott egy szerver sockethez (aki elfogadta a kapcsolatot), egyszerűen az állományleírón keresztül kommunikálhatunk. A szerver socket várakozik egy bizonyos előre definiált címen, majd, ha kapcsolódási kérelem érkezik – melyet el is fogad –, létrehoz egy kliens socketet, mely kvázi a kommunikációt innentől végzi. A szerver socket gyakorlatilag csak a kapcsolódási kérelmeket menedzseli, a konkrét adatkommunikációval nem törődik.
A szerver socket, mint írtam, egy bizonyos címen várakozik. Szerver esetében fontos, hogy mindig ugyanazon a címen legyen elérhető; elég kaotikus lenne a világunk, ha például napról napra változna a Google IP címe. A sockethez való cím hozzárendelését a bind () függvénnyel tehetjük meg. A bind () függvény felépítése a következő:
int bind (int sock, struct sockaddr *my_addr, socklen_t addrlen)

A sock paraméter a socket fájlleírója, *my_addr pedig a címet leíró struktúrára mutat. A struct sockaddr egy a netinet/in.h fejlécállományban definiált struktúra, mely a következő részekből áll:

struct sockaddr
{
unsigned short sa_family;
char sa_data [14];
}

Az sa_family a címcsaládot tartalmazza (AF_INET, AF_INET6), az sa_data pedig a címet és a portszámot tartalmazza 14 bájtban. **A hálózati struktúrákra és használatukra bővebben kitérek a 6. oldalon.
A bind () függvény harmadik paramétere, az addrlen pedig a címet leíró struktúra hosszát jelzi, ezt minden alkalommal meg kell adnunk! (Ez megtehető a sizeof (struct sockaddr) utasítással.)
A függvény hiba esetén mínusz eggyel tér vissza, sikeres futáskor nullával.

A címhez kötött socket önmagában nem képes kapcsolatok fogadására, ahhoz, hogy ez megtörténjen, a listen () függvényt kell használnunk. Ez a parancs állítja be a socketet, hogy az fogadja a kapcsolódási kérelmeket. A listen () függvény alakja a következő:
int listen (int sock, int backlog)

A sock paraméter nyilván a socket fájlleírója, a backlog pedig megadja, hogy egyszerre hány várakozó kapcsolatot tarthat fenn a program. Magyarán a függőben lévő kérések maximális számát adja meg.
Egy kérelem beérkezése után azonban nem épül fel automatikusan a kommunikáció, a kapcsolati kérelmet el is kell fogadnunk! Erre jó az accept () függvény. Alakja és használata a következő:
int accept (int sock, struct sockaddr *addr, socklen_t *addrlen)

A sock helyére a socket leírónkat kell megadni, az addr paraméter a kliensoldal címét mutatja, az addrlen pedig ezen struktúra méretét.
A listen () és accept () függvények is mínusz eggyel térnek vissza hibás lefutás esetén. A listen () zérót ad megfelelő futáskor, az accept () egy nemnegatív számot, mely az akceptált socket fájlleírójának sorszáma.

Eddig a szerver oldaláról követtük nyomon a kapcsolódási folyamatot, de nyilván mindehhez szükség van egy, a szolgáltatást igénybe vevő kliensre. A kliens, miután létrehozta saját socket-jét, a connect () függvényt alkalmazza. A connect függvény alakja a következő:
int connect (int sock, struct sockaddr *addr, socklen_t addrlen)

A paraméterek használata gondolom, mindenki számára egyértelmű; előre kerül a socket, második helyre a célcím mutatója, harmadikra a célcím struktúra mérete.
A rendszerutasítás nullát ad helyes futáskor, mínusz egyet hiba esetén.

Amint felépült a kapcsolatunk, az egész kommunikáció a szerver és kliens között folytatható a read () és write () függvényekkel; mintha csak egy egyszerű fájlt írnánk, olvasnánk.
Avagy használhatjuk a send () és recv () utasításokat is. A send () utasítás alakja:
int send (int sockfd, const void *msg, int len, int flags)

A sockfd természetesen megkapja a célsocketet, az *msg egy mutató a küldendő információra; a len pedig ennek hossza bájtokban. A flags értékét pedig állítsuk nullára.

A recv hívás használata pedig:
int recv (int sockfd, void *buf, int len, unsigned int flags)

A sockfd a forrásunk socket-je, a buf arra a bufferre mutat, ahova a kapott adatot eltároljuk. A len mezőben meg kell adnunk a buffer maximális méretét, a flags pedig itt is nullát kap.
A send () a küldött bájtok számával, a recv () pedig a bufferbe írottak számával tér vissza helyes működés esetén. Hibánál mínusz egyet térítenek vissza.

A kapcsolat lezárása történhet a close (), vagy a shutdown () függvényekkel. A kettő között a különbség, hogy a close () utasítás az állományleírót zárja le, a shutdown () pedig a kommunikációs csatornát. A close () függvény használata:
int close (sockfd)

Itt csak a socket leíróját kell megadnunk.

A shutdown függvény alakja:
int shutdown (int sockfd, int how)

Első paraméterként itt is nyilván a socket fájlleírója szerepel, a második paraméter azonban érdekes opciókat ad számunkra. Amennyiben ide 0-t adunk meg, akkor a további olvasást tiltjuk meg. 1-es megadásával az írást tiltjuk, 2-essel pedig mindkettőt; ez gyakorlatilag a close () függvénynek feleltethető meg.
A két függvény sikeres végrehajtódáskor nullát, sikertelen futáskor mínusz egyet ad.


Forrás: Asztalos Márk, Bányász Gábor, Levendovszky Tihamér - Linux programozás

A cikk még nem ért véget, kérlek, lapozz!

Hirdetés

Copyright © 2000-2024 PROHARDVER Informatikai Kft.