InternetovΘ programovßnφ s WinSock (2)

 

V prvnφm dφle jsme se seznßmili s WinSockem, dnes zaΦneme poprvΘ nasazovat knihovnu WinSock v praxi. NauΦφme se zaΦlenit WinSock do naÜeho programu a sprßvn∞ jej inicializovat. Seznßmφme se s pou₧itφm IP adres a port∙. NauΦφme se zφskßvat adresy IP z domΘnov²ch jmen a vytvo°φme prvnφ socket. Krom∞ toho pochytφme n∞kolik dalÜφch drobn²ch ale d∙le₧it²ch dovednostφ.

 

Verze knihovny WinSock

 

Knihovna se v souΦasnosti vyskytuje ve dvou hlavnφch verzφch - WinSock 1 (p°esn∞ji 1.1) a WinSock 2 (neboli 2.2). V∞tÜina systΘm∙ obsahuje knihovnu ve verzi 2.2. Jen n∞kterΘ starÜφ exemplß°e Windows 95 a samoz°ejm∞ Windows 3.x majφ jen knihovnu starÜφ verze.

Novinky ve WinSock 2 jsou zhruba tyto:

            WinSock 2 zavßdφ krom∞ TCP/IP systematickou podporu dalÜφch transportnφch a sφ¥ov²ch protokol∙ a jejich implementacφ. Zßrove≥ jsou podporovßny i jinΘ jmennΘ systΘmy ne₧ DNS. My si pln∞ vystaΦφme s TCP/IP a DNS, a proto pro nßs tyto mo₧nosti nejsou p°φliÜ zajφmavΘ.

            Se sockety je mo₧nΘ pracovat pomocφ vstupn∞-v²stupnφch funkcφ Windows, jako jsou ReadFile a WriteFile. Je podporovßn tzv. p°ekryvn² vstup a v²stup (overlapped I/O), co₧ je zvlßÜtnφ optimalizovan² a neblokujφcφ druh vstupu a v²stupu dostupn² jen ve Windows s jßdrem NT (Windows NT, 2000, XP). Ani t∞mito schopnostmi se tady nebudeme zab²vat.

            Jsou zavedeny tzv. udßlosti, kterΘ umo₧≥ujφ testovat stav operacφ se socketem. Tyto udßlosti jsou podobnΘ udßlostem pou₧φvan²m pro synchronizaci v multithreadov²ch aplikacφch.

            Sockety je mo₧nΘ organizovat do skupin, nastavovat jejich priority, sdφlet je mezi procesy, nastavovat parametry tzv. kvality slu₧eb (quality of service, QoS) apod.

            ╚ßsteΦn∞ je vylepÜena podpora tzv. "surov²ch" socket∙ (raw sockets), kterΘ umo₧≥ujφ pracovat na ·rovni ni₧Üφch protokol∙, nap°. ICMP.

Pro v∞tÜinu obvykl²ch ·loh je mo₧nΘ vystaΦit s WinSock 1.1.

V C/C++ zaΦlenφme do programu hlaviΦkov² soubor winsock.h nebo winsock2.h, podle toho, kterou verzi chceme importovat. V Delphi nebo jinΘm Pascalu pro Windows p°idßme do sekce uses jmΘno jednotky WinSock.

V systΘmovΘm adresß°i Windows m∙₧eme najφt knihovny DLL pro jednotlivΘ verze WinSock. Verze WinSock 1.x p°eb²vß v souboru wsock32.dll, v 16bitovΘ verzi se jmenuje winsock.dll. Verzi WinSock 2.x pak najdeme v souboru ws2_32.dll. JmΘna odpovφdajφcφch staticky linkovan²ch knihoven jsou stejnß, liÜφ se jen extenzφ .lib.

 

WinSock a programovacφ jazyky

 

Pro ukßzky zdrojov²ch k≤d∙ budu pou₧φvat jazyk C, proto₧e ten se v podobn²ch oblastech vyskytuje nejΦast∞ji. Pro ty, kdo majφ zßjem programovat s knihovnou WinSock v Delphi, uvßdφm nßsledujφcφ p°ehled konvencφ:

            V Pascalu je samoz°ejm∞ mo₧nΘ pou₧φvat identifikßtory s odliÜnou velikostφ pφsmen.

            DatovΘ typy majφ prefix T (TSocket mφsto SOCKET, TWSAData mφsto WSADATA apod.). Pro struktury/zßznamy jsou definovßny odpovφdajφcφ ukazatelovΘ typy liÜφcφ se prefixem P.

            Parametry funkcφ p°edßvanΘ pomocφ ukazatele jsou nahrazeny parametry p°edßvan²mi odkazem (s modifikßtorem var). To mß jednu nev²hodu. Za ukazatel je v∞tÜinou dovoleno dosadit NULL/nil, jestli₧e parametr nenφ pot°ebn², tady je nutnΘ v₧dy dodat prom∞nnou.

            Mφsto maker jsou definovßny funkce se stejn²m v²znamem.

Sockety zabalenΘ do objekt∙ nabφzejφ komponentovΘ knihovny VCL a MFC stejn∞ jako r∙znΘ freewarovΘ produkty. My se budeme soust°edit na programovßnφ pomocφ funkcφ API, kterΘ jsou dostupnΘ vÜem programßtor∙m. P°ejφt na objektovou architekturu nenφ problΘm, proto₧e principy jsou stejnΘ.

 

Inicializace a ·klid

 

Pro inicializaci knihovny zavolßme funkci WSAStartup. Ta musφ b²t v₧dy prvnφ volanou funkcφ WinSock. Prvnφm parametrem je 16bitovΘ Φφslo specifikujφcφ po₧adovanou verzi knihovny. Ni₧Üφá byte obsahuje prvnφ slo₧ku Φφsla verze a vyÜÜφ byte slo₧ku druhou. Druh²m parametrem povinn∞ p°edßvßme platn² ukazatel na strukturu WSADATA, kterß bude napln∞na informacemi o inicializovanΘ verzi a jejich vlastnostech. Struktura nemusφ b²t inicializovßna p°ed volßnφm WSAStartup, jde jen o v²stupnφ parametr. Z tΘto struktury je podstatnß p°edevÜφm polo₧ka wVersion, kterß obsahuje Φφslo skuteΦn∞ inicializovanΘ verze knihovny. Je to bu∩ Φφslo po₧adovanΘ p°i volßnφ WSAStartup, nebo, v p°φpad∞, ₧e takovß verze nenφ k dispozici, je vybrßna nejvyÜÜφ verze dostupnß na systΘmu. Funkce vrßtφ nulu, jestli₧e inicializace prob∞hla ·sp∞Ün∞, jinak vrßtφ chybov² k≤d a slu₧by knihovny nebude mo₧nΘ pou₧φt.

Po skonΦenφ prßce s knihovnou musφ b²t volßna ·klidovß funkce WSACleanup, kterß nemß ₧ßdnΘ parametry.

Nßsleduje v²pis zßkladnφ kostry programu pou₧φvajφcφho knihovnu WinSock. V dalÜφch p°φkladech budu uvßd∞t u₧ jen jejich funkΦnφ "vnit°ek".

 

#include <winsock2.h>

 

int main (void)

{

á WSADATA WSAData;

 

á if (WSAStartup(0x0202, &WSAData) != 0) {

ááá /* stala se chyba */

á }

á /* pouzivam WinSock */

á WSACleanup();á

á return 0;

}

 

Starosti s byty

 

Nenφ procesor jako procesor a jednou z mo₧n²ch odliÜnostφ mezi r∙zn²mi hardwarov²mi platformami je i zp∙sob reprezentace cel²ch Φφsel.

Procesory Intelu uklßdajφ Φφslo v pam∞ti tak, ₧e nejmΘn∞ v²znamn² byte le₧φ na nejni₧Üφ adrese a nejv²znamn∞jÜφ byte na adrese nejvyÜÜφ. To znamenß p°esn∞ naopak, ne₧ jak to Φlov∞ku p°ipadß p°irozenΘ. Kdy₧ do 32bitovΘ celoΦφselnΘ prom∞nnΘ p°i°adφme Φφslo 258, obsah pam∞ti v tomto mφst∞ bude 02 01 00 00, tedy 2 + 256 + 0 + 0. Naproti tomu procesory Motorola a v∞tÜina ostatnφch ulo₧φ v²Üe uvedenΘ Φφslo ve tvaru 00 00 01 02, tj. od nejvyÜÜφho bytu na nejni₧Üφ adrese. Formßt Intelu se naz²vß little-endian (LE), formßt Motoroly big-endian (BE). Tyto termφny jsou inspirovßny jmΘny dvou liliputßnsk²ch kmen∙, kterΘ se liÜily tφm, na kterΘm konci natloukaly vajφΦka natvrdo.

Aby si r∙znΘ poΦφtaΦe na Internetu mezi sebou rozum∞ly, musφ se protokoly typu TCP/IP dr₧et jednotnΘho formßtu (tzv. sφ¥ovΘho). Za tento formßt byl zvolen tvar big-endian, a proto se programy b∞₧φcφ na platform∞ Intel musφ starat o konverzi p°edßvan²ch Φφseln²ch dat, jako jsou adresy IP a Φφsla port∙.

Rozhranφ WinSock pro tyto ·Φely nabφzφ Φty°i konverznφ funkce. Funkce htons konvertuje 16bitovΘ Φφslo z formßtu hostitele (low-endian) do formßtu sφ¥ovΘho (big-endian). Funkce htonl pak pracuje s Φφslem 32bitov²m. Funkce ntohs a ntohl jsou jmΘna pro funkce provßd∞jφcφ stejnou konverzi v opaΦnΘm sm∞ru.

 

Prßce s adresami

 

Adresy IP budeme pot°ebovat nap°φklad p°i navßzßnφ spojenφ TCP nebo p°i odesφlßnφ datagram∙ nespojovan²m protokolem UDP.

Adresa IP je vnit°n∞ reprezentovßna 32bitov²m cel²m Φφslem. Jestli₧e pot°ebujeme zkonvertovat adresu IP z textovΘho tvaru do vnit°nφ reprezentace, mßme k dispozici funkci inet_addr. TΘ p°edßme jako parametr ukazatel na nulou zakonΦen² °et∞zec obsahujφcφ jedno a₧ Φty°i Φφsla v rozsahu 0 a₧ 255 odd∞lenß teΦkami (nap°. 192.111.8.1 nebo t°eba 80.16). Funkce vracφ v²sledek konverze. V p°φpad∞, ₧e zadanß adresa m∞la neplatn² tvar, je navrßcena konstanta INADDR_NONE, odpovφdajφcφ adrese 255.255.255.255. Mß-li adresa IP sprßvn² tvar, pak funkce nekontroluje, jestli adresa skuteΦn∞ existuje.

TakΘ konverze v opaΦnΘm sm∞ru je mo₧nß, a to pomocφ funkce inet_ntoa. Funkci zadßme adresu v celoΦφselnΘ podob∞, kterou jeÜt∞ musφme zabalit do struktury in_addr (viz p°φklad nφ₧e), a obdr₧φme ukazatel na vytvo°en² textov² °et∞zec. Tento °et∞zec musφme ihned p°ekopφrovat do vlastnφho bufferu, proto₧e je umφst∞n v pam∞ti spavovanΘ knihovnou a nßsledujφcφm volßnφm funkcφ WinSock m∙₧e b²t ztracen.

Jestli₧e budeme zapisovat adresu p°φmo v ΦφselnΘ podob∞, nesmφme zapomn∞t na zßpis jednotliv²ch byt∙ v opaΦnΘm po°adφ. Nap°φklad adresu 127.0.0.1 zapφÜeme hexadecimßln∞ jako 0x0100007F a ne jako 0x7F000001. (P°φpadn∞ pou₧ijeme druh² tvar a zkonvertujeme jej funkcφ htonl.)

Adresa 127.0.0.1 mß specißlnφ v²znam. P°edstavuje v₧dy samotn² mφstnφ poΦφtaΦ, kter² adresu pou₧il. Tato adresa se v²born∞ hodφ pro testovßnφ klientskΘ a serverovΘ aplikace na jednom poΦφtaΦi bez nutnosti sφ¥ovΘho p°ipojenφ. V API WinSock pro ni existuje p°eddefinovanß konstanta INADDR_LOOPBACK. Pozor, dokonce i tuto konstantu musφme konvertovat pomocφ ntohl.

U₧ivatel naÜeho programu pravd∞podobn∞ nebude mφt zßjem pamatovat si adresy IP a rad∞ji bude pracovat s domΘnov²mi jmΘny typu www.chip.cz. Vznikß tedy pot°eba p°elo₧it domΘnovΘ jmΘno na IP adresu. Tuto prßci mß na starost protokol DNS, resp. DNS klient (resolver) dotazujφcφ se DNS serveru. DNS je aplikaΦnφ protokol, ale naÜt∞stφ jej programßtor nemusφ sßm implementovat. Podpora DNS je obsa₧ena v knihovn∞ WinSock.

Pro p°eklad jmΘna na adresu IP pou₧ijeme funkci gethostbyname. Ta dostane jako parametr °et∞zec s domΘnov²m jmΘnem poΦφtaΦe a vrßtφ ukazatel na strukturu HOSTENT, kterou nesmφme modifikovat, ale zato m∙₧eme p°eΦφst jejφ polo₧ku h_addr, co₧ je ukazatel na adresu IP v ΦφselnΘm tvaru. Ukazatel je definovßn pro znakov² typ a je proto nutno jej p°etypovat.

Nßsledujφcφ programov² k≤d p°eΦte u₧ivatelem zadanou adresu (domΘnovΘ jmΘno), p°elo₧φ ji a vytiskne adresu IP v ΦitelnΘm tvaru.

 

#include <stdio.h>

#include <string.h>

...

HOSTENT* phe;

struct in_addr addr;

char buf[50];

...

printf("Zadejte adresu: ");

scanf("%s", buf);

if ((phe = gethostbyname(buf)) == NULL)

á printf("Vzdaleny pocitac nenalezen.");

else {

á addr.s_addr = *((unsigned long *) phe->h_addr);

á strcpy(buf, inet_ntoa(addr));

á printf(buf);

}

 

Dopl≥uji, ₧e funkce gethostbyname sel₧e, jestli₧e jφ dosadφme za parametr adresu IP. Proto je vhodnΘ nejd°φve se pokusit konvertovat textov² °et∞zec na adresu IP pomocφ inet_addr a pou₧φt systΘm DNS, jen kdy₧ konverzφ zφskßme neplatn² v²sledek.

P°i prßci s funkcemi WinSock je adresa zadßvßna obvykle v podob∞ struktury SOCKADDR_IN. Ta obsahuje krom∞ adresy IP takΘ Φφslo portu. V prvnφ °ad∞ musφme vyplnit polo₧ku sin_family, kterß specifikuje pou₧itou sadu protokol∙. V naÜem p°φpad∞ to bude v₧dy konstanta AF_INET p°edstavujφcφ protokoly TCP/IP. Do polo₧ky sin_port zadßme 16bitovΘ Φφslo portu, kterΘ nezapomeneme konvertovat pomocφ funkce htons na sφ¥ov² formßt. Samotnou adresu IP umφs¥ujeme do polo₧ky, kterß se jmenuje sin_addr.s_addr, aby to nebylo p°φliÜ jednoduchΘ. Ve skuteΦnosti je polo₧ka sin_addr mnohem komplikovan∞jÜφ, ale t∞mito detaily si rad∞ji v∙bec nebudeme plΘst hlavu. Jednß se o stejnou strukturu in_addr jako ve v²Üe uvedenΘm p°φklad∞.

Nßsleduje p°φklad korektn∞ inicializovanΘ struktury SOCKADDR_IN pro p°ipojenφ k HTTP serveru na lokßlnφm poΦφtaΦi.

 

SOCKADDR_IN si;

...

si.sin_familyááááá = AF_INET;

/* adresa lokalniho pocitace */

si.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

/* obvykly port protokolu HTTP */

si.sin_portááááááá = htons(80);

 

Vytvo°enφ a zav°enφ socketu

 

Te∩ p°ikroΦφme k d∙le₧itΘmu kroku na cest∞ k p°enosu dat - vytvo°enφ socketu. Sockety jsou reprezentovßny Φφseln²mi deskriptory, podobn²mi handl∙m oken, grafick²ch objekt∙, soubor∙ apod. Datov² typ pro tyto deskriptory se jmenuje SOCKET. Nov² socket m∙₧e b²t vytvo°en funkcφ se stejn²m jmΘnem, psßno mal²mi pφsmeny, tedy socket. Prvnφm parametrem je rodina protokol∙, co₧ je nßm u₧ znßm² AF_INET. Pro nßs nejv²znamn∞jÜφ je parametr druh², typ socketu. Jestli₧e zadßme konstantu SOCK_STREAM, bude vytvo°en socket pro proudov², spojovan², spolehliv² p°enos dat pomocφ transportnφho protokolu TCP. Pou₧ijeme-li konstantu SOCK_DGRAM, dostaneme socket pro p°enos nespojovan², nespolehliv², ve form∞ datagram∙, tedy socket protokolu UDP. Za t°etφ parametr budeme dosazovat nulu. Funkce vracφ deskriptor prßv∞ vytvo°enΘho socketu. V p°φpad∞ ne·sp∞chu je navrßcena konstanta -1 neboli INVALID_SOCKET.

Po skonΦenφ prßce musφ aplikace zav°φt vÜechny ·sp∞Ün∞ vytvo°enΘ sockety volßnφm funkce closesocket.

 

SOCKET sock;

...

sock = socket(AF_INET, SOCK_STREAM, 0);

closesocket(sock);

 

Zßv∞r

 

Te∩ u₧ dokß₧eme vytvo°it socket a jsme jen mal² kr∙Φek od napsßnφ kompletnφho funkΦnφho programu.

Tento kr∙Φek ud∞lßme v p°φÜtφm dφle serißlu, ve kterΘm se nauΦφme programovat kompletnφ klientskou aplikaci nad protokoly TCP i UDP.