470 likes | 651 Vues
Sistemsko programiranje. IX. poglavlje Soketi kao sredstvo IPC. Ciljevi. Nakon svladavanja ovog poglavlja trebali bi znati : Razumjeti osnovne API funkcije za Windows Sockets Odrediti probleme portabilnosti između Berkeley-evih i Windows Socketa
E N D
Sistemsko programiranje IX. poglavlje Soketi kao sredstvo IPC
Ciljevi Nakon svladavanja ovog poglavlja trebali bi znati: • Razumjeti osnovne API funkcije za Windows Sockets • Odrediti probleme portabilnosti između Berkeley-evih i Windows Socketa • Koristiti Windows Sockete preko TCP/IP za peer-to-peer i klijent/server aplikacije • Uzeti u obzir promjene uvedne s Windows Sockets 2
Dnevni red • 1. dio Uvod u WinSock • 2. dio Server (bind-listen-accept) • 3. dio Klijent • 4. dio Razmjena podataka • 5. dio Sažetak
1. dio Uvod u WinSock
Windows Sockets (1/2) • Proširenje API-ja za Berkeley-eve Sockete • Prilagođeno za Windows okruženje • Portabilnost koda već ugrađena za Berkeley-eve Sockete • Windows stanice jednostavno integrirani u TCP/IP mreže • Izuzetci su kada su soketi tretirani kao opisnici datoteka • Pod UNIX-om • Također, proširene funkcije s prefiksom WSA
Windows Sockets (2/2) • Funkcije koje manipuliraju s datotekama također rade sa socketima read() ioctl() write() close() • Ponašaju se kao Windows handleovi datoteka u načinu rada “overlapped” • Postoje i druge opcije mrežne komunikacije • Imenovane pipe • Remote Procedure Calls (RPC)
Inicijalizacija WinSock DLL-a (1/2) int WSAStartup( WORD wVersionRequired, LPWSADATA lpWSAData); Za verziju može se koristiti Windows makro MAKEWORD(1,1) preko kojeg možemo zadati verziju i podverziju koja je u ovom primjeru 1.1
Inicijalizacija WinSock DLL-a (2/2) • wVersionRequired • Određuje najvišu verziju WinSock DLL-a kojeg trebamo • Vraća vrijednsot različitu od nule ako DLL ne podržava verziju koju želimo • Niži byte određuje višu verziju • Viši byte određuje podverziju • 0x0202 verzija 2.2 • lpWSAData je pokazivač na strukturu WSADATA koja se popuni informacijama o konfiguraciji DLL-a • Pozvati WSAGetLastError() za određivanje broja pogreške
Deklaracija i alociranje socketa (1/3) • Socketi su analgoni handle-ovima • Ali to je komunikacijski kanal • Pozivamo funkciju socket(...) kako bi kreirali (ili otvorili) socket • Zapravo HANDLE
Deklaracija i alociranje socketa (2/3) • Server: socket koji “sluša” zahtjev za konekcijom od strane jednog ili više klijenata • Klijent: socket koji se spaja na nekog servera (hosta) koji je u stanju slušanja i koji će eventualno prihvatiti konekciju typedef unsigned int SOCKET; SOCKET socket(int af, int type, int protocol);
Deklaracija i alociranje socketa (3/3) • af određuje familiju adresa • PF_INET ili AF_INET označava Internet protokol • type određuje način komunikacije koji može biti konekcijski orijentiran (SOCK_STREAM) ili ‘datagram’ komunikaciju (SOCK_DGRAM), tj. u ovisnosti od primjene biramo TCP ili UDP način komunikacije. • protocol neobavezno staviti za TCP/IP • Koristimo 0 socket vraća INVALID_SOCKET prilikom greške
2. dio Server (bind-listen-accept)
Povezivanje (bind) (1/3) • Nakon stvaranja socketa, potrebno ga je povezati sa svojom adresom i određenim servisom int bind ( SOCKET s, const struct sockaddr *saddr, int namelen); • s je “nepovezani” SOCKET vraćen funkcijom socket() • saddr određuje familiju adresa i informacije specifične za protokol • namelen jeste sizeof(sockaddr) • Vraća SOCKET_ERROR u slučaju pogreške
Povezivanje (bind) (2/3) SOCKADDR struktura struct sockaddr { u_short sa_family; char sa_data[14]; }; typedef struct sockaddr SOCKADDR, *PSOCKADDR;
Povezivanje (bind) (3/3) • sa_data je specifično za određeni protokol • za TCP/IP koristimo specijalno sockaddr_in: struct sockaddr_in { short sin_family; // AF_INET u_short sin_port; struct in_addr sin_addr; //4-byte IP addr char sin zero [8]; }; typedef struct sockaddr_in SOCKADDR_IN, *PSOCKADDR_IN;
Neka svojstva povezivanja • Lista hostova (mapirana na IP adrese) može se naći u %SystemRoot%\system32\drivers\etc\hosts • Lista servisa može se naći u datoteci %SystemRoot%\system32\drivers\etc\services • Ako se povežete na određenu adresu, možete primati dolazeće pakete samo preko te adrese • Ako imamo više od jedne IP adrese, povezuje se pomoću hotnl(INADDR_ANY)
Primjer pripreme za povezivanje (1/3) BOOL WINAPI WNetGetHostAddress( LPCSTR lpszHost, LPCSTR lpszService, LPCSTR lpszProto, LPSOCKADDR lpAddr) /* Popunjava strukturu SOCKADDR koristeći ime hosta, protokola i servisa */ { LPHOSTENT lpHost; LPSERVENT lpServ; SOCKADDR_IN sin; ZeroMemory(&sin, sizeof(sin));
Primjer pripreme za povezivanje (2/3) sin.sin_family = PF_INET; sin.sin_addr.s_addr = htonl(INADDR_ANY); if(lpszHost != NULL) { lpHost = gethostbyname(lpszHost); if(lpHost != NULL) { CopyMemory(&sin.sin_addr, lpHost->h_addr_list[0], lpHost->h_length); } } lpServ = getservbyname(lpszService, lpszProto);
Primjer pripreme za povezivanje (3/3) if(lpServ != NULL) { sin.sin_port = lpServ->s_port; ZeroMemory(sin.sin_zero, sizeof(sin.sin_zero)); CopyMemory(lpAddr, &sin, sizeof(SOCKADDR)); return TRUE; /* lpAddr je sada spremna za bind() */ } return FALSE; } Adresa vraćena pozivom WNetGetHostAddress(...) može se direktno upotrijebiti u bind() funkciji
listen (...) • listen() omogućava serveru da prima konekcije klijenata • Socket prelazi iz stanja “povezan” u stanje “slušanja” int listen(SOCKET s, int nQueueSize); • nQueueSize pokazuje broj konekcijskih zahtjeva koliko želimo da čekaju u redu na konekciju • Najviše do SOMAXCON (5 za verziju 1.1, “neograničeno” kod verzije 2.0)
accept (...) (1/2) • Poziv funkcije listen() stavlja socket u stanje slušanja • Aplikacija server poziva funkciju accept() • Vraća “konektirani socket” • accept() je blokirajuća funkcija i ona blokira sve dok ne dođe zahtjev za konekcijom od strane klijenta • accept() vraća vrijednsot koja daje serveru novi socket za razmjenu informacija
accept (...) (2/2) SOCKET accept( SOCKET s, /* Socket koji je u stanju slušanja */ LPSOCKADDR lpAddr, /* Tu možemo naći detalje o klijentu */ LPINT lpnAddrLen /* Duljina vraćene strukture */ );
3. dio Klijent
Strana klijenta (1/2) • Klijent koji se želi spojiti na server mora također deklarirati i kreirati socket pozivajući funkciju socket(...) • Ako socket nije povezan s adresom, Windows Socketi će mu pridjeliti jedinstvenu adresu koja će se koristiti prilikom trajanja konekcije
Strana klijenta (2/2) int connect( SOCKET s, LPSOCKADDR lpName, int nNameLen); • lpName je pokazivač na strukturu SOCKADDR u kojoj je upisana adresa servera, a također i port na kojem server sluša
Primjer aplikacije klijenta (1/2) #define S_IP “193.198.167.96” #define S_PORT 6784 main() { SOCKET sck; sockaddr_in s; // Startanje WinSock s WSAStartup(...) sck=socket(AF_INET,SOCK_STREAM,0); s.sin_family = AF_INET; s.sin_addr.s_addr = inet_addr( S_IP ); s.sin_port = htons( S_PORT );
Primjer aplikacije klijenta (2/2) if (connect( sck, (SOCKADDR*) &s, sizeof(s) ) == SOCKET_ERROR ) { printf( ”Greška pri spajanju.\n" ); return; } printf( ”Uspješno spajanje na server.\n" ); .... // Tu dolazi komunikacija sa serverom }
4. dio Razmjena podataka
Razmjena podataka (1/2) • Dvije strane koje komuniciraju razmjenjuju podatke koristeći funkcije send() i recv() • send() i recv() imaju jednake parametre: int send ( int recv ( SOCKET s, SOCKET s, LPSTR lpBuffer, LPSTR lpBuffer, int nBufferLen, int nBufferLen, int nFlags); int nFlags);
Razmjena podataka (2/2) • nFlags == MSG_OOB označava hitnost • OOB znači “out-of-band” • MSG_PEEK može se koristiti ako hoćemo samo “pogledati” podatke bez da ih maknemo iz buffera • Ovo su standardni pozivi i kod Berkeley-evih Socketa • Ali, read() i write() su uobičajeni pod UNIX-om • Nisu “atomske” (da primaju byte po byte) niti su orijenitrane na poruke • Staviti u petlju dok se ne primi cijela poruka • Ili kod slanja, ali vrlo je rijetko da je send() nepotpun
Datagram servis (1/2) • Strane razmjenjuju poruke koristeći sendto() i recvfrom() • Imaju iste parametre kao i send() i recv(), ali imaju dodatno još dva parametra s kojim se definira druga strana komunikacije int sendto ( int recvfrom ( SOCKET s, SOCKET s, LPSTR lpBuffer, LPSTR lpBuffer, int nBufferLen, int nBufferLen, int nFlags, int nFlags, LPSOCKADDR lpAddr, LPSOCKADDR lpAddr, int nAddrLen); LPINT pnAddrLen);
Datagram servis (2/2) • Kod datagrama, nFlags parametar ne može biti MSG_OOB • Ne može se slati ili primati na hitan način • UDP (User Datagram Protocol), nekad U koristimo i kao Universal ili Unreliable.
Zatvaranje socketa • Kod standardnih Berkeley-evih Socketa, pozivamo close() • Kod Windows Socketa pozivamo void closesocket(SOCKET s); • Za konekcijski orijenitranu komunikaciju, server zatvara samo socket kreiran s accept() • Ne onaj koji je vraćen od funkcije socket() • Samo onda kada se server u potpunosti gasi, onda treba zatvoriti socket koji sluša • Naposljetku, poziva se void WSACleanup(void); • Da prekinemo rad s WSOCK32.DLL • Ova funkcija također nije portabilna
5. dio Sažetak
Sažetak rada sa socketima (1/2) Servis koji je konekcijski orijentiran (najčešće TCP) Server Klijent socket() socket() bind() connect() listen() accept() send()/recv() sendto()/recvfrom() closesocket() closesocket()
Sažetak rada sa socketima (2/2) Bez stalne konekcije (Datagram) Server Klijent socket() socket() listen() connect() send()/recv() sendto()/recvfrom() closesocket() closesocket()
Berkeley-evi vs Windows sockets • Standardni Berkeley-evi Socketi će biti portabilni s Windows Socketima, sa slijedećim iznimkama: • Mora se pozvati WSAStartup() kako bi inicijalizirali odgovarajući DLL • Iako se mogu koristiti UNIX-ovski read() i write() za primanje i slanje podataka, mora se prvo konvertirati Windows socket na handle operacijskog sustava pozivom _open_osfhandle() • Kod verzije socketa 1.1, također se mora pozvati setsockopt() kako bi prisilili sockete da se otvore kao handle-ovi koji nemaju “overlapped” svojstvo • Mora se koristiti closesocket() (ova funkcija nije portabilna), radije nego close() (koja jeste portabilna), kako bi zatvorili socket • Moramo pozvati WSACleanup() kako bi završili rad s DLL-om
Overlapped I/O s Windows Socketima (1/2) • Kod WinSock 1.1, Windows otvaraju sockete kao preklapajuće (overlapped) datotečne handle-ove • Možemo dati socket kao parametar u ReadFile() ili WriteFile() bez ikakve modifikacije • Čekamo da operacije završe tako da ih pridružimo handle-u događaja (event handle) i pozivamo WaitForSingleObject() WaitForMultipleObjects() ili MsgWaitForMultipleObjects
Overlapped I/O s Windows Socketima (2/2) • Koristimo I/O rutine za završavanje s ReadFileEx() / WriteFileEx() i proširene funkcije za čekanje WaitForSingleObjectEx(), WaitForMultipleObjectsEx(), i SleepEx() • Koristimo I/O port za završavanje s ReadFile() / WriteFile(), CreateIoCompletionPort(), i GetQueuedCompletionStatus()
Windows Sockets 2 (1/2) • Windows Sockets 2, omogućeni u NT 4.0, dodaju još nekoliko područja funkcionalnosti • Primjedba: Koristiti 1.1 zbog razloga interoperabilnosti • Standardizirana podrška za preklapajući (overlapped) I/O • Rasprši/prikupi (Scatter/gather) I/O • Slanje i primanje iz ne-kontinuiranih memorijskih spremnika
Windows Sockets 2 (2/2) • Mogućnost da zahtjevamo kvalitet usluge (QoS - quality of service) od sloja za podršku socketa • Brzina i pouzdanost kod prijenosa podataka • Mogućnost da organiziramo sockete u grupe • može se konfigurirati QoS za cijelu grupu socketa • to se ne mora raditi na bazi socket-po-socket • možemo uvesti prioritete za sockete koji pripadaju nekoj grupi • Višestruke konekcije (npr. konferencijski pozivi)
Standardizacija preklapajućeg I/O (1/2) • Najvažniji dodatak kod Windows Socketa 2 je standardizacija preklapajućeg I/O • Socketi više nisu automatski kreirani kao preklapajući datotečni handle-ovi • socket() će kreirati ne-preklapajući handle • Ako želimo kreirati preklapajući socket, moramo pozvati WSASocket() i eksplicitno ga zahtjevati
Standardizacija preklapajućeg I/O (2/2) SOCKET WSAAPI WSASocket( int iAddressFamily, int iSocketType, int iProtocol, LPWSAPROTOCOL_INFO lpProtocolInfo, GROUP g, DWORD dwFlags);
Scatter/Gather I/O (1/4) • Kod WinSock 2 imamo dodatnu mogućnost da skupljamo i distribuiramo podatke za slanje ili primanje iz nekontinuiranih memorijskih spremnika (buffera) typedef struct WSABUF { u_long len; char *buf; } WSABUF, *LPWSABUF;
Scatter/Gather I/O (2/4) • buf je pokazivač na podatke • len je broj byte-ova u tome spremniku • WSASend(), WSARecv(), WSASendTo(), i WSARecvFrom() koriste niz WSABUF struktura
Scatter/Gather I/O (3/4) int WSARecv( SOCKET s, LPWSABUF lpRecvBuffers, DWORD dwBuffers, LPDWORD lpdwBytesReceived, LPDWORD lpdwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
Scatter/Gather I/O (4/4) • lpRecvBuffers je pokazivač na niz WSABUF struktura • dwBuffers je broj struktura u nizu • Kada podaci stignu, WinSock 2 driveri ih raspodijele po svim poslanim spremnicima