Számítógép-hálózatok: Labor 5
TCP szerver
A gyakorlat célja: TCP szerver alkalmazás írásának az elsajátítása TCP protokoll tulajdonságainak a tanulmányozása kisérleti úton Elméleti bevezető: TCP kilens-szerver alkalmazás:
Számítógép-hálózatok: Labor 5 Amint a fenti ábrából is kiderül a TCP klienshez képest megváltozik az alkalmazás, kapcsolódást kezelő része. Itt is, mint az UDP szerver esetében hozzárendelünk egy hosszúéletű portot a sockethez a bind rendszerhívás segítségével TCP kapcsolat felépítése:
A kapcsolat normális felépítése, “3-utas kézfogással” történik. Az egyidejűleg kezdeményezet kapcsolat felépítésnek is csak egyetlen kapcsolat az eredménye amelynek az azonosítója xy. Gyakorlatban ritkán törtánik meg az egyidejű kapcsolat felépítés mert előre kiosztjáka szerepeket. Ez úgy történik, hogy az egyik kommunikációs fél előlépik szerverré és várja akapcsolodás kéréseket, a másik peddig kliens lesz és kapcsolódik a szerverhez. A socket létrehozásakor alapértelemszerűen aktív szokentként jön létre (kliens) és várja a következő lépést a kapcsolódást. Ha szervert szeretnénk létrehozni, akkor az aktív szocketet át kell alakítani passzív socketté, amelyik a bejövő kéréseket fogadja. A listen függvény alakítja át az aktív szocketett passzívá. Ahoz, hogy egy socket kapcsolódásokat tudjon fogadni szükséges erőforrásokat lefoglajon, két sort, amelyben tárolja a folyamatban lévő kapcsolódás adatait. A listen létrehozza ezt két sort. Az egyik a befejezetlen kapcsolódás sora ebben tárolja a bérkezett kapcsolódás kéréseket amig meg nem érkezik a kapcsolódás elfogadására a nyugta. Majd innen átkerül a befejezett kapcsolatok sorába addig amíg a szerver ki nem veszi hogy felhasználja. int listen( __in SOCKET s, __in int backlog );
s [in]
Számítógép-hálózatok: Labor 5 Egy socket azonósító egy kapcsolat nélküli socketet azonósít. backlog [in]
A maximum hossza a várakozó kapcsolatok sorának. Ha SOMAXCON van beeállítva, az s socketért felelős szolgáltató backlog-ot a mximális elfogadható értékre állítja. Abban az esetben ha a listen sikeres volt 0-t térít vissza máskép SOCKET_ERROR –t, amelyet le lehet kérdezni. A második sorból a kész kapcsolatokat az accept metodussal lehet kivenni. Az accept metodus egy várásos metodus, amely csak akkor tér vissza a hivásból, ha SOCKET_ERROR van vagy egy kapcsolódás sikeresen befejeződött. SOCKET accept( __in SOCKET s, __out struct sockaddr *addr, __inout int *addrlen );
s [in] Egy passzív socketnek az azonósítója (elötte meg volt híva a listen). addr [out] Egy opcionális mutató egy pufferre amellyet feltölthet kapcsolat entitás címével. Az addr paramétert meghatározza a címcsalád amely a socket létrehozásakor használva volt. addrlen [in, out] Az opcionális paraméter által használt puffer mérete bemenetként kijevetként az oda feltöltött adat mennyisége. Fontos megjegyezni, hogy a kapcsolat nem a várakozó (szerver) socket és a kapcoslódni kivánó socket között jön létre hanem minde kapcsolat kiépítésekor a szerver socket egy új csatlakozot (socketet) hoz létre az új kapcsolatnak, amelyet visszatérítési érttékként vissza térít. Ha hiva történik akkor a visszatérítés értéke egy INVALID_SOCKET.
Feladat: 1. Valósítsatok meg egy visszhang szervert. Az elöző laboron megírt gyakorlatott a következő képpen kell modósítani. a connect helyett bind,listen, accept függvényeket kell használni.
Számítógép-hálózatok: Labor 5 Az alkalmazás szerkezetét lásd 3.ábrán. 2. A kliens fokozatosan nagyobb és nagyobb csomagokat kell küldjön a szervernek. A szerver kell figyelje hogy a csomag egy vagy több csomagban érkezik meg. Amiután megérkezett a teljes üzenet egy nyugtát küld és új adatot vár. Protokoll: az üzenet formátuma: Hossz
Adat
hossz: int tipusú érték (4 bájt) és az üzenet hosszát tartalmazza adat: bármilyen adat A nyugata hasonló: Hossz=0
Adat (nincsen)
Megvalósítás: A csomagok létrehozását legegyszerübben memcpy-val lehet megvalósítani. Kliens: Adatcsomag összeállítása: puffer elejére bemásoljuk, az int típusúcsomag méretet majd eltoljuk a mutatot négy bájtal és bemásoljuk az adatot. Szerver: egy négybájtos puffert lenullázunk és kész is van a nyugta. Szerver: Fogadok annyi bájtott amennyi érkezik. A fogadott adat, első négy bájtján tipuskonverziót hajtok végre és int tipusúvá alakítom. Ellenörzöm, hogy megérkezett-e az egész adat ha igen küldök egy nyugtát ha nem akkor vaárom addig az adatot amig az összes megérkezik. Számolom közben hány csomagot kapok. Ezt az információt kiíratom a képernyőre. Kliens: Létrehozom a csomagot és ciklusban addig küldöm amíg az összes adat el nem megy. A küldés után várom a szervertől a nyugtát. Majd ismételem, az egészet elölről csak növelem a csomag méretét. 3. Egymás után a kliens kis ötbájtos csomagokat küld a szervernek. Az elküldött csomagok között az időt folyamatosan csökkentem. A Szerver minden egyes kapott csomag hosszát kiíratja a képernyőre. Fontos mind a három feladatnál, hogy a szerver fogadó puffere nagyméretű legyen! Példaprogram: #include <stdio.h> #include "winsock2.h"
Számítógép-hálózatok: Labor 5 void main() { // Initialize Winsock. WSADATA wsaData; int iResult = WSAStartup(MAKEWORD(2,2), &wsaData); if (iResult != NO_ERROR) { printf("Error at WSAStartup()\n"); return 1; } //---------------------// Create a SOCKET for listening for // incoming connection requests. SOCKET ListenSocket; ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ListenSocket == INVALID_SOCKET) { printf("Error at socket(): %ld\n", WSAGetLastError()); WSACleanup(); return 1; } //---------------------// The sockaddr_in structure specifies the address family, // IP address, and port for the socket that is being bound. sockaddr_in service; service.sin_family = AF_INET; service.sin_addr.s_addr = inet_addr("127.0.0.1"); service.sin_port = htons(27015); if (bind( ListenSocket, (SOCKADDR*) &service, sizeof(service)) == SOCKET_ERROR) { printf("bind() failed.\n"); closesocket(ListenSocket); WSACleanup(); return 1; } //---------------------// Listen for incoming connection requests. // on the created socket if (listen( ListenSocket, 1 ) == SOCKET_ERROR) { printf("Error listening on socket.\n"); closesocket(ListenSocket); WSACleanup(); return 1; } //---------------------// Create a SOCKET for accepting incoming requests. SOCKET AcceptSocket; printf("Waiting for client to connect...\n"); //---------------------// Accept the connection. AcceptSocket = accept( ListenSocket, NULL, NULL ); if (AcceptSocket == INVALID_SOCKET) {
Számítógép-hálózatok: Labor 5 printf("accept failed: %d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } else printf("Client connected.\n"); //----------------------------------------------// Call the recvfrom function to receive datagrams // on the bound socket. printf("Receiving datagrams...\n"); recvfrom(AcceptSocket, RecvBuf, BufLen, 0); // ido lekerese es elkuldese //--------------------------------------------// Send a datagram printf("Sending a datagram...\n"); send(AcceptSocket, SendBuf, BufLen, 0 ); //--------------------------------------------// When the application is finished sending, close the socket. printf("Finished sending. Closing socket.\n"); closesocket(SendSocket); //--------------------------------------------// Clean up and quit. printf("Exiting.\n"); WSACleanup(); return; }
Kérdések: 1. A viszhang szerverhez egyidejűleg hánykliens probálhat meg csatlakozni? 2. Hány üzenetet tud a viszhangszerver visszaküldeni egyidejűleg? 3. Megtartja-e a TCP protokoll az üzenethatárokat? Könyvészet: [1]. A. Tanenbaum : Számítógéphálózatok. Bp., Panem Könyvkiad ó, 1999. [2]. http://msdn.microsoft.com/en-us/library/ms741416(VS.85).aspx