online kép - Fájl  tubefájl feltöltés file feltöltés - adja hozzá a fájlokat onlinefedezze fel a legújabb online dokumentumokKapcsolat
  
 

Letöltheto dokumentumok, programok, törvények, tervezetek, javaslatok, egyéb hasznos információk, receptek - Fájl kiterjesztések - fajltube.com

Online dokumentumok - kep
  
felso sarok kategória jobb felso sarok
 

Biológia állatok Fizikai Földrajz Kémia Matematika Növénytan Számítógépes
Filozófia
Gazdaság
Gyógyszer
Irodalom
Menedzsment
Receptek
Vegyes

 
bal also sarok   jobb also sarok
felso sarok   jobb felso sarok
 




































 
bal also sarok   jobb also sarok

TCP szerver készítése

számítógépes





felso sarok

egyéb tételek

jobb felso sarok
 
Rekurzió (particiószam, Hanoi tornyai, postfix konverzió)
Sémakezelés és tarolasi struktúrak hierarchikus adatbazisokban
Az írasjelek kódolasa
MOS/CMOS technológia és digitalis alkalmazasai
A WORD eszközei II
A Paint Shop Pro grafikai program hasznalata
Part Design CATIA V5 - Start
Hattértarak, tömegtarolók
Informaciós tarsadalom
Xara Webstyle
 
bal also sarok   jobb also sarok

TCP szerver készítése



A labor feladat célja, hogy a hallgatót megismertesse a TCP/IP protokollt használó programok készítésével. Ezen belül bemutatásra kerül a Berkley Socket API, amelynek segítségével lehetővé válik a hálózati kommunikáció implementálása Linux/Unix és MS Windows rendszereken.

A labor elvégzéséhez szükséges a C programozói tudás!


A Berkeley socket API ismertetése

A következő fejezetekben a Berkeley socket API használatába tekintünk be. Ennek használatával lehetséges a Linux/Unix és MS Windows rendszereken a hálózati kommunikáció implementálása.


A socket

A socket az applikációk közötti kommunikációt lehetővé tévő kétirányú kapcsolat elnevezéseként értelmezhető. Tulajdonképpen egy SOCKET típusú leíró[1], amelyet létre kell hoznunk, majd az egyes rendszerhívásoknál ezzel h 555j91f ivatkozhatunk az adott kommunikációs csatornára.

Új socketeket a socket() rendszerhívással hozhatunk létre. Létrehozáskor a sockethez egy protokollt rendelünk, amelyet az majd használni fog. Azonban ebben az állapotban a socket még nem kapcsolódik sehova, ezért kommunikációra még nem használható.




SOCKET socket(int domain, int type, int protocol);


A függvény 0-nál kisebb értékkel tér vissza hiba esetén. Ha sikeres, akkor egy socket leíróval, amely 0 vagy nagyobb érték.

A három paraméter a használandó protokollt definiálja. Az első (domain) megadja a protokoll családot. A következő táblázat néhány lehetséges értékét tartalmazza.


Protokoll

Jelentés

PF_UNIX, PF_LOCAL

Unix domain (gépen belüli kommunikáció)

PF_INET

IPv4 protokoll

PF_INET6

IPv6 protokoll


A következő paraméter (type) kiválasztja a protokoll családon belül a kommunikáció típusát. Lehetséges értékei az alábbiak:


Típus

Jelentés

SOCK_STREAM

Sorrendtartó, megbízható, kétirányú, kapcsolatalapú bájtfolyam-kommunikációt valósít meg.

SOCK_DGRAM

Datagramalapú (kapcsolatmentes, nem megbízható) kommunikáció.

SOCK_SEQPACKET

Sorrendtartó, megbízható, kétirányú, kapcsolatalapú kommunikációs vonal, fix méretű datagramok számára.

SOCK_RAW

Nyers hálózati protokoll hozzáférést tesz lehetővé.

SOCK_RDM

Megbízható datagramalapú kommunikációs réteg. (Nem sorrendtartó.)


A harmadik paraméter (protocol) a protokoll család kiválasztott típusán belül választ ki egy protokollt. Mivel egy protokoll családon belül egy kommunikáció típust többnyire csak egy protokoll implementál, ezért a tipikus értéke: 0


Példa

Az alábbi példa egy TCP socketet hoz létre:


SOCKET sock;

if((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)



A kapcsolat felépítése

Ha összeköttetés-alapú kommunikációt szeretnénk folytatni egy kliens és egy szerver program között, akkor ehhez stream jelegű socketet kell létrehoznunk. Azonban ez önmagában nem elegendő a kommunikációhoz. Össze kell kapcsolnunk a két fél socketét, hogy adatokat vihessünk át rajta. Ennek a kapcsolat felépítésnek a részleteit a következő ábrán láthatjuk.



. ábra A socket kapcsolat felépítése


Az ábrán a kliens és a szerver oldalon egymás után meghívandó függvények láthatóak. Elsőre látható, hogy a kapcsolat felépítési mechanizmus aszimmetrikus. A szerver oldalon a feladat, hogy egy bizonyos címen várja a program a kapcsolódási igényeket, majd amikor ezek befutnak, akkor kiépítse a kapcsolatot. A kliens oldalon ezzel szemben a feladat azl, hogy a szerver címére kapcsolódási kérést küldjünk, amelyre az reagálhat. Ezen feladatok végrehajtását tekintsük át lépésenként.


Szerver:

A szerver oldalon létre kell hoznunk egy socketet, amelyet a későbbiek során szerver socketként fogunk használni. A szerver socket kommunikációra nem alkalmas, csak kapcsolatok fogadására.

Össze kell állítanunk egy cím struktúrát, amelyben leírjuk, hogy a szerver hol várja a kapcsolódásokat.

Az előbbi lépésben összeállított címhez hozzá kell kötnünk a socketet.

Be kell kapcsolnunk a szerver-socket módot, vagyis hogy a socket várja a kapcsolódásokat.

Fogadnunk kell az egyes kapcsolódásokat. Ennek során minden kapcsolathoz létrejön egy kliens socket, amely az adott féllel való kommunikációra használható.


Kliens:

A kliens oldalon is létre kell hoznunk socketet. Ezt az előző esettől eltérően kliens-socketként, a kommunikációra fogjuk használni.



Össze kell állítanunk egy cím struktúrát, amelyben leírjuk, hogy a szerver milyen címen érhető el.

Kapcsolódnunk kell a szerverhez. Amikor ez a kapcsolat létrejött, akkor a továbbiakban a socket képes továbbítani az adatokat.


A következőkben áttekintjük, hogy az egyes lépések milyen függvények segítségével valósítható meg. Azonban mivel a feladat szerver készítése, ezért a szerver lépéseire koncentrálunk első sorban.


Cím összeállítása

Mielőtt a cím összeállításra rátérnénk meg kell vizsgálnunk egy problémát. A címek összeállítása során több bájtos adattípusokat használunk (pl.: IPv4 cím 4 bájtos long, a port szám 2 bájtos short). Azonban az egyes processzorok különböző módon tárolják az egyes adattípusokat. Két tárolási mód terjedt el: a big endian, amely a magasabb helyi értékű bájttal kezdi a tárolást, illetve ennek ellentéte a little endian. Big endian architektúra például a Sun Sparc, little endian pedig az Intel x86. Azonban a hálózaton ezeknek a különböző architektúráknak is meg kell érteniük egymást, ezért közös adatábrázolásra van szükség. Ezt nevezzük hálózati bájt-sorrendnek, amely a big endian ábrázolást követi. Az adott architektúrán használt ábrázolást pedig hoszt bájt-sorrendnek.

A két ábrázolás közötti váltásokat a következő függvények végzik:


Függvény

Jelentés

ntohs

Egy 16-bites számot a hálózati bájt-sorrendből a hoszt bájt-sorrend sorrendjébe vált át. 

ntohl

Egy 32-bites számot a hálózati bájt-sorrendből a hoszt bájt-sorrendjébe vált át.

htons

Egy 16-bites számot a hoszt bájt-sorrendjéből hálózati bájt-sorrendbe vált át.

htonl

Egy 32-bites számot a gép bájt-sorrendjéből hálózati hoszt bájt-sorrendbe vált át.


Azokon az architektúrákon, ahol szükséges a konverzió ezek a függvények megfordítják a bájt-sorrendet, ahol pedig nem, ott csak visszaadják a paraméter értékét.


A címábrázoláshoz a socket API a következő általános struktúrát alkalmazza:


struct sockaddr



A címet használó függvények paraméterként ilyen típust várnak. Azonban egyes protokollokhoz létezik ennek specializált változata is, amely az adott protokoll esetén könnyebben használható. IPv4 esetén ez a következő képen néz ki:


struct sockaddr_in



Ezen belül az in_addr struktúra az alábbi:


struct in_addr



Ebben helyezhetjük el a cím egyes bájtjait. Ne feledjük, hogy mind a címnek, mind a portnak hálózati bájtsorrendűnek kell lennie, ezért a korábban látott htonl() illetve htons() függvényeket kell használnunk a konverziójukhoz.

A szerver esetén a cím beállításnál valójában csak a portot kell megadnunk, míg a címre használhatjuk az INADDR_ANY makrót. Ez valójában a 0.0.0.0 címet jelenti, amely azt mondja meg, hogy minden hálózati interfész adott portján várjuk a kapcsolódást.


Példa

Az alábbiakban két példát láthatunk a cím összeállítására.

Az első példában egy szerver számára állítjuk össze a címhez kötéshez a cím struktúrát. A példában a szerver a számítógép össze hálózati interfészén az 1234-es porton lesz majd elérhető:


sockaddr_in addr;

addr.sin_family = AF_INET;

addr.sin_addr.s_addr = INADDR_ANY;

addr.sin_port = htons(1234);


A másik példa azt mutatja meg, hogy hogyan állíthatjuk össze a kliensben a címstruktúrát, amiben a szerver címe szerepel. A szerver IP címe esetünkben 192.168.0.1 és portja 1234 lesz:


sockaddr_in addr;

addr.sin_family = AF_INET;

addr.sin_addr.s_addr = inet_addr("192.168.0.1");

addr.sin_port = htons(1234);


A socket címhez kötése

Az előző lépésben összeállított címet hozzá kell rendelnünk a sockethez. Ezt a műveletet kötésnek nevezzük, és a következő rendszerhívással lehet végre hajtani:


int bind(SOCKET sock, struct sockaddr *my_addr, socklen_t addrlen


Az első paraméter a socket leírója, a második a címet leíró struktúra, az utolsó a címet leíró struktúra hossza. A visszatérési érték sikeres végrehajtás esetén 0, egyébként pedig mínusz érték.


Példa

Az alábbi példában a korábban összeállított címmel hajtjuk végre a címhez kötés műveletét:


if(bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0)



A szerver mód bekapcsolása

A címhez kötés után a socketet szerver módba kapcsolhatjuk. Ez után már kapcsolódhatunk hozzá klienssel. A szerver módba kapcsolást a következő rendszerhívással végezhetjük el:


int listen(SOCKET sock, int backlog);


Az első paraméter a socket leírója. A második paraméter, a backlog megadja, hogy hány kapcsolódni kívánó socket kérelme után utasítsa vissza az újakat. Ebbe csak azok a kapcsolódó socketek számítanak bele, amelyeket még nem fogadott a szerver. A visszatérési érték sikeres végrehajtás esetén 0, egyébként pedig mínusz érték.


Példa

A korábban már címhez kötött socketet az alábbi módon kapcsolhatjuk szerver-socket módba:


if(listen(sock, 5) < 0)



Kliensek kapcsolódásának fogadása

A kliensek kapcsolódását a következő rendszerhívás fogadja:




SOCKET accept(SOCKET sock, struct sockaddr *addr, socklen_t *addrlen);


Az első paraméter a szerver socket leírója. Az addr és addrlen paraméterekben a másik oldal címét kapjuk meg. Az addrlen egy integer szám, amely megadja az addr változó méretét. Amennyiben nem vagyunk kíváncsiak a csatlakozó kliens címére, akkor az addr és addrlen paramétereknek megadhatunk NULL értéket.

A függvény visszatérési értéke az új kapcsolat leírója, egy kliens socket. A későbbiekben ezt használhatjuk a kommunikációhoz. Ha a visszatérési érték 0-nál kisebb, akkor hibát jelez.


Kapcsolódás a szerverhez

A kliens oldalon a cím összeállítása után kapcsolódnunk kell a szerverhez. Ezt a következő rendszerhívással tehetjük meg:


int connect(SOCKET sock, struct sockaddr *addr, socklen_t addrlen);


Az első paraméter a kliens socket leírója. Az addr és addrlen paraméterekben a szerver címét adjuk meg. Az addr paraméter tartalmazza a címet, míg az addrlen az addr paraméterben átadott cím méretét kell, hogy megadja. A függvény 0 értékkel jelzi a sikeres végrehajtást, míg mínusz értékkel a hibát.


Példa

A korábbi példában összeállított szerver címre a kapcsolódást az alábbi példa mutatja be:


if(connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0)



Adatok küldése és fogadása

A kliens és a szerver program összekapcsolt kliens socketét egy kétirányú csővezetéknek tekinthetjük, amelyen bájtokat küldhetünk és fogadhatunk.

Az adatok küldésére a következő rendszerhívást használhatjuk:


int send(SOCKET s, const void *msg, size_t len, int flags);


Az első argumentum a socket leírója, a második az elküldendő adat pufferére mutató pointer, a harmadik az elküldendő adat mérete. A flags paraméter a kommunikáció jellegét módosító beállításokat tartalmazhat. A gyakorlat keretein belül használjuk a 0 értéket. A függvény visszatérési értéke tartalmazza az elküldött bájtok számát. A mínusz érték a hibát jelzi.


Adatokat a recv() rendszerhívással fogadhatunk. Ez a függvény addig nem tér vissza, amíg valamilyen információ nem érkezik. Az alakja a következő:


int recv(SOCKET s, void *buf, size_t len, int flags);


Az első paraméter itt is a kliens socket leírója, a második a fogadó puffer mutatója, a harmadik pedig a puffer mérete. A flags paraméter itt is a kommunikáció jellegét módosító beállításokat tartalmazhat, azonban jelenleg használjuk a 0 értéket. A függvény visszatérési értéke a beolvasott bájtok számát tartalmazza. Ez kisebb vagy egyenlő azzal, amit mi puffer méretnek megadtunk. Figyeljünk arra, hogy a puffer tartalmából csak annyi bájt a hasznos információ, amennyit ez a visszatérési érték megad. Ha a visszatérési érték 0, akkor az azt jelzi, hogy a kapcsolat lezárult. Ebben az esetben befejeződött a kommunikáció és le kell zárnunk a socketet. Ha a visszatérési érték mínusz szám, akkor az hibát jelez.


A kapcsolat lezárása

A kapcsolatot a következő rendszerhívással zárhatjuk le[2]:


int closesocket(SOCKET s);


A paraméter a socket leírója. A visszatérési érték sikeres végrehajtás esetén 0, egyébként pedig mínusz érték.


Ellenőrző kérdések

Mi a különbség a szerver és a kliens socket között?

A cím összeállításánál miért szükséges a számokat konvertálni?

Miért szükséges a szerver socketet címhez kötni és miért nem kell a kliens socketet?

Az accept() függvény meghívásakor mi történik, ha éppen nincs bejövő kapcsolat?

A kommunikációs kapcsolatot hogyan zárhatja le a kliens, illetve a szerver oldal?

Írjon C nyelvű kódrészletet, amely az s leíróval reprezentált kliens socketből képes 16 byte adat fogadására!

Írjon C nyelvű kódrészletet, amely az s leíróval reprezentált kliens socketen keresztül elküldi a "hello" stringet!

Írjon C nyelvű kódrészletet, amely megvizsgálja, hogy az str1 és str2 nevű karakter tömbök tartalma megegyezik-e!

Írjon C nyelvű kódrészletet, amely megvizsgálja, hogy az str1 nevű karakter tömb tartalmazza-e az str2 nevű karakter tömb értékét!

Írjon C nyelvű kódrészletet, amely megvizsgálja, hogy az str1 nevű karakter tömb tartalmazza-e a ch nevű karaktert!


A feladat

A gyakorlat során a hallgató feladata, hogy elkészítsen egy TCP szerver applikációt C nyelven, amely a következőket teljesíti:

Egy paraméterként megadott TCP porton fogadja a kliensek kapcsolódásait! Vagyis megvalósítja azokat a funkciókat, amelyeket a szerver esetén a segédlet leír.

Egy kapcsolat fogadása után elegendő, ha csak az adott klienssel foglalkozik. Azonban amikor lezárul a kapcsolat, akkor fogadja új kliens kapcsolódását!

A klienssel való kommunikáció során elvégzendő műveleteket a gyakorlatvezető ismerteti!


Megjegyzés

A gyakorlat során létrehozott szerver program az "Egyszerű Web szerver készítése" gyakorlaton kiindulási pontként használható, ezért célszerű megőrizni és a gyakorlatra elhozni!


Függelék A - MS Windows segítség

Az MS Windows alatti fejlesztést MS Visual Studio program használatával végezzük. A project létrehozásakor célszerű az üres C++ alkalmazás választani kiindulási pontnak.


A program lefordítása és futtatása

Az elkészült program lefordításához szükséges a ws2_32.lib könyvtár állomány használata, mert különben a hálózatkezelő függvényeink implementációját a linker nem találja meg. Vagyis a project beállításainál a linkelés parancssorához vegyük hozzá a ws2_32.lib állományt.



A programot paraméterezve kell futtatnunk. Ennek legegyszerűbb módja, ha nyitunk egy parancsterminált (cmd.exe). Megkeressük a lefordított exe állományt és lefuttatjuk. Paraméterként meg kell adnunk a port számát.


A szerver kipróbálása

Az elkészült TCP szerver programot a telnet program segítségével próbálhatjuk ki, amely egy TCP kliens. A telnet programnak első paraméterként meg kell adnunk a szerver gép nevét, amely lokális futtatás esetén localhost, második paraméterként pedig a szerver portját. Például:


telnet localhost 1234


A program jelzi, ha nem sikerül kapcsolódnia a szerverhez. Ha a kapcsolat felépült, akkor a begépelt sorokat elküldi a szerver programnak. A telnet program MS Windows implementációja karakterenként küldi el azt, amit begépeltünk, így a szervernek egyből reagálnia kell minden karakter után.


A program váza

Az alábbi C (C++) nyelvű programváz segítséget nyújt a fejlesztés elkezdéséhez MS Windows alatt:


#include <stdio.h>

#include <winsock2.h>


int main(int argc, char* argv[])



if(argc < 2)



//TODO


WSACleanup();


return 0;



Függelék B - Linux segítség

A feladat megvalósításához szükség van egy terminál, egy szövegszerkesztő, és a gcc program használatára. Szövegszerkesztőnek a kwrite program használata javasolt, mivel kezelőfelülete egyszerű és rendelkezik szintaxis kiemelő funkcióval. Az alábbiakban ismertetett parancsokat a terminál ablakba kell beírni.


A programozói segítség

Az egyes rendszerhívásokról és C függvényekről a man parancs segítségével angol nyelvű segítséget érhetünk el. Ennek formája:

Rendszerhívások esetén:


man 2 rendszerhívás


C függvények esetén:


man 3 C-függvény


A program lefordítása és futtatása

Az elkészült forráskódot a gcc parancs segítségével fordíthatjuk le. Ennek paraméterezése a következő:


gcc -Wall -o kimenet forrás.c


A fordító sikeres fordítás esetén nem ír ki semmit, csak előállítja a futtatható program kódot. Probléma esetén a problémákat három csoportra oszthatjuk:


Típus

Leírás

Figyelmeztetés

(warning)

A jelzett sor szintaktikailag nem hibás, de érdemes ellenőrizni, mert elvi hibás lehet. Viszont a program lefordul és használható.

Fordítási hiba

(compiler error)

A jelzett sor szintaktikailag hibás, vagy valamely korábbi sorban elkövetett hiba hatása.

Linker hiba

(linker error)

A linker nem találja a hivatkozott kódot. Többnyire függvény név elírások okozzák.


A lefordított programot a következő módon lehet futtatni:


./program param1


Ez az aktuális könyvtárból lefuttatja a program nevű programot a param1 paraméterrel. A programot leállítani a Ctrl+C billentyű kombinációval lehet.


A szerver kipróbálása

Az elkészült TCP szerver programot a telnet program segítségével próbálhatjuk ki, amely egy TCP kliens. A telnet programnak első paraméterként meg kell adnunk a szerver gép nevét, amely lokális futtatás esetén localhost, második paraméterként pedig a szerver portját. Például:


telnet localhost 1234


A program jelzi, ha nem sikerül kapcsolódnia a szerverhez. Ha a kapcsolat felépült, akkor a begépelt sorokat elküldi a szerver programnak. Figyeljünk arra, hogy mivel a terminál kanonikus, ezért sorokat fog elküldeni az Enter lenyomása után!


A program váza

Az alábbi C nyelvű programváz segítséget nyújt a fejlesztés elkezdéséhez Linux alatt:


#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <sys/socket.h>

#include <netinet/in.h>


int main(int argc, char* argv[])



if((ssock = socket(PF_INET, SOCK_STREAM, 0)) < 0)



setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));


//TODO


return 0;






Linux alatt: int típusú

Linux alatt: int close(int s)

Találat: 2117







Felhasználási feltételek