WinSock: Programovßnφ internetov²ch aplikacφ (1)

Chcete-li do svΘ aplikace naprogramovat sφ¥ovΘ funkce a vaÜim pot°ebßm neodpovφdß ₧ßdnß existujφcφ hotovß knihovna nebo komponenta, mßte mo₧nost si vÜe vytvo°it sami pomocφ funkcφ knihovny WinSock. ProblΘmem ale je nedostupnost pot°ebn²ch informacφ. M∙₧ete si p°eΦφst nßpov∞du ke knihovn∞ dodßvanou s v²vojov²mi nßstroji pro Windows, ale nenφ to ono. ZaΦßteΦnφk obvykle neznß vÜechny pojmy, kterΘ jsou v nßpov∞d∞ pou₧φvßny jako samoz°ejmost, a nevφ, jak tyto pojmy souvisejφ s tφm, co chce programovat. Stejn∞ tak zkoumßnφ cizφch zdrojov²ch k≤d∙ nevede jednoduÜe k cφli, proto₧e jsou nezbytnΘ i zßkladnφ teoretickΘ znalosti. Tento Φlßnek by m∞l nabφdnout takov² celkov² pohled na teoretickΘ i praktickΘ oblasti problematiky sφ¥ovΘho programovßnφ pro zaΦßteΦnφky a m∞l by dßt nßvod, jak realizovat konkrΘtnφ b∞₧nΘ ·koly. Toto je prvnφ ze dvou dφl∙.

Obsah I. dφlu:

Programovßnφ pro Internet
Co je to WinSock?
Jak funguje Internet
Protokol IP
Protokol TCP
Protokol UDP
TCP nebo UDP?
WinSock a jeho verze
WinSock a programovacφ jazyky
Inicializace a ·klid
Starosti s byty
Prßce s adresami
Vytvo°enφ a zav°enφ socketu
Ustavenφ relace TCP
Odesφlßnφ a p°φjem dat
Chovßnφ p°enosov²ch funkcφ
ZruÜenφ relace TCP
Stahovßnφ strßnek WWW
Klient nad UDP
P°enos datagram∙
Zßv∞r

Programovßnφ pro Internet

Pod pojmem internetovß aplikace se rozumφ vφce odliÜn²ch v∞cφ. Typicky se jednß o serverovou aplikaci, nejΦast∞ji psanou ve skriptovacφm jazyku typu PHP nebo ASP, kterß jako svΘ u₧ivatelskΘ rozhranφ generuje WWW strßnky. Jindy se internetovou aplikacφ rozumφ applety umφst∞nΘ na webov²ch strßnkßch a provßd∞nΘ klientsk²m poΦφtaΦem. NicmΘn∞ tou skupinou, kterß si oznaΦenφ internetovΘ aplikace zaslou₧φ asi nejvφce, jsou klientskΘ a serverovΘ programy komunikujφcφ p°es Internet pomocφ protokol∙ typu TCP/IP. Prßv∞ programovßnφm takov²ch aplikacφ v prost°edφ operaΦnφho systΘmu Windows se budeme v tomto Φlßnku zab²vat.

Mezi znßmΘ reprezentanty tΘto kategorie pat°φ nap°φklad webovΘ prohlφ₧eΦe, e-mailovΘ programy, klienti FTP, programy pro stahovßnφ soubor∙, offline browsery, chatovacφ programy, klienti diskuznφch skupin, p°ehrßvaΦe internetov²ch rßdiφ a jin²ch multimedißlnφch p°enos∙, programy pro sdφlenφ soubor∙, nßstroje pro vzdßlenou sprßvu poΦφtaΦ∙ a sφtφ, firewally a mnoho dalÜφch. V∞tÜina aplikacφ mß i svou serverovou stranu, tak₧e pot°ebujeme servery HTTP, FTP, mailservery, servery pro internetovΘ vysφlßnφ atd.

Aplikace nad TCP/IP tedy implementujφ nejr∙zn∞jÜφ druhy slu₧eb Internetu, zatφmco prve zmφn∞nΘ webovΘ aplikace pracujφ na vyÜÜφ ·rovni, pouze v rßmci slu₧by WWW.

NaÜφm cφlem bude seznßmit se s principy prßce internetovΘho klienta i serveru a zp∙soby jejich implementace pomocφ knihovny WinSock. Tyto znalosti se mohou hodit p°i prßci na nejr∙zn∞jÜφch programech a systΘmech, proto₧e Internet je n∞jak²m zp∙sobem vyu₧φvßn tΘm∞° ve vÜem.

Co je to WinSock?

Windows Sockets, zkrßcen∞ WinSock, je jednou ze souΦßstφ programßtorskΘho rozhranφ Win32 API, kterß nabφzφ zßkladnφ prost°edky pro sφ¥ovou komunikaci. Je nutno hned dodat, ₧e tato knihovna nenφ zrovna jednoduchß a m∙₧e zaΦφnajφcφmu programßtorovi obΦas p°ipravit nemilß p°ekvapenφ (tedy p°esn∞ji °eΦeno: m∙₧e zaΦφnajφcφho programßtora uΦinit konΦφcφm programßtorem). Proto se musφme na programovßnφ nad WinSock po°ßdn∞ p°ipravit.

WinSock vychßzφ z Berkeley Sockets, co₧ je internetovΘ rozhranφ p∙vodem z unixovΘho operaΦnφho systΘmu BSD. Rozhranφ Sockets se rozÜφ°ilo i na ostatnφ unixovΘ i neunixovΘ platformy a WinSock je jeho realizacφ pro systΘmy Windows. WinSock nenφ ·pln∞ kompatibilnφ se Sockets na jin²ch platformßch, mß svß rozÜφ°enφ a svΘ odliÜnosti (a svΘ chyby v implementaci). NicmΘn∞ programy b∞₧φcφ na odliÜn²ch systΘmech s odliÜn²mi knihovnami spolu mohou bez problΘm∙ komunikovat a spolupracovat.

Centrßlnφ myÜlenkou knihoven Sockets je abstrakce tzv. socketu (v p°ekladu zßsuvka nebo objφmka, ale rad∞ji budu pou₧φvat termφn socket). Socket je jak²si p°φpojn² bod, p°es kter² m∙₧e aplikace posφlat data na Internet nebo je naopak p°ijφmat. Spojenφ mezi dv∞ma aplikacemi b∞₧φcφmi na r∙zn²ch poΦφtaΦφch v sφti si tedy m∙₧eme nßzorn∞ p°edstavit jako fiktivnφ propojovacφ kabel, kter² vede z jednΘ zßsuvky do druhΘ a proudφ jφm data.

Shr≥me si nejd°φve, jak²m zp∙sobem probφhß komunikace poΦφtaΦ∙ na Internetu a co z toho vypl²vß pro programßtora.

Jak funguje Internet

┌kolem ka₧dΘ aplikace, a¥ u₧ je to WWW prohlφ₧eΦ nebo t°eba klient pro zjiÜt∞nφ p°esnΘho Φasu, je poslat urΦitß data jinΘ aplikaci na libovolnΘm poΦφtaΦi v sφti a eventußln∞ p°ijmout jinß data jako odpov∞∩. N∞kdy se komunikace odbude jedin²m dotazem a jedinou odpov∞dφ, jindy jde o delÜφ konverzaci. T°eba FTP klient se na delÜφ dobu p°ipojφ k serveru a posφlß mu p°φkazy.

Podle tzv. modelu OSI (Open Systems Interconnect) je odpov∞dnost za provoz Internetu rozd∞lena do sedmi vrstev, do nich₧ spadajφ jednotlivΘ hardwarovΘ a softwarovΘ komponenty. Ka₧dß vrstva ke svΘ prßci vyu₧φvß slu₧eb vrstev ni₧Üφch, a to p°i odesφlßnφ dat i p°i jejich p°φjmu. Zdßnliv∞ spolu v₧dy komunikujφ prvky odpovφdajφcφ vrstvy na obou stranßch, skuteΦnß komunikace ale probφhß na vrstv∞ nejni₧Üφ - fyzickΘ.

Podφvejme se te∩ na vrstvy modelu OSI z naÜeho programßtorskΘho pohledu: Protokoly sφ¥ovΘ a transportnφ vrstvy v prost°edφ Internetu se obvykle oznaΦujφ jako protokoly TCP/IP. ╪ekne-li se TCP/IP, nejsou tφm mφn∞ny pouze protokol TCP a protokol IP, ale vÜechny souvisejφcφ protokoly vΦetn∞ UDP, ICMP atd.

Protokol IP

IP (Internet Protocol) je tak trochu zßkladnφm stavebnφm kamenem Internetu. Tento protokol umo₧≥uje propojenφ za°φzenφ a sφtφ r∙zn²ch standard∙ do sφt∞ jedinΘ a umo₧≥uje p°enos dat mezi nimi.

JednotlivΘ poΦφtaΦe majφ p°i°azeny logickΘ adresy IP, kterΘ je jednoznaΦn∞ identifikujφ. Adresa m∙₧e b²t p°i°azena trvale nebo dynamicky (p°i ka₧dΘm p°ipojenφ m∙₧e b²t jinß, co₧ je p°φpad modem∙). Adresa IP je 32bitovß, z toho n∞kterΘ hodnoty majφ zvlßÜtnφ v²znam. Pro Φiteln² zßpis adresy se pou₧φvß dekadickß teΦkovß notace, Φty°i Φφsla od 0 do 255 odd∞lenß teΦkami, nap°φklad 192.91.16.1. P°i skuteΦnΘm pou₧itφ v protokolu IP je adresa intern∞ reprezentovßna 32bitov²m Φφslem.

P°enßÜenß data jsou balena do datagram∙ IP. Datagram se sklßdß z hlaviΦky IP nßsledovanΘ u₧iteΦn²mi daty. HlaviΦka mß minimßln∞ 20 byt∙ a obsahuje krom∞ zdrojovΘ a cφlovΘ IP adresy mnoho systΘmov²ch informacφ nutn²ch pro doruΦenφ. P°i pou₧itφ transportnφho protokolu TCP nebo UDP nßsleduje jeho hlaviΦka za hlaviΦkou IP.

Datagramy cestujφ p°es sm∞rovaΦe, kterΘ Φtou cφlovou adresu a podle sv²ch sm∞rovacφch tabulek je p°eposφlajφ dßle sm∞rem k cφli. Datagram m∙₧e b²t podle pot°eby po cest∞ rozd∞len - fragmentovßn. Ka₧d² fragment dostane svou vlastnφ hlaviΦku IP, nesoucφ i informace nutnΘ k pozd∞jÜφmu posklßdßnφ p∙vodnφho datagramu. JednotlivΘ datagramy nebo i fragmenty jednoho datagramu mohou cestovat sφtφ po odliÜn²ch trasßch.

Protokol IP nezaruΦuje doruΦenφ datagram∙ ve stejnΘm po°adφ, jako byly odeslßny. Dßle n∞kterΘ datagramy mohou b²t po cest∞ ztraceny a jinΘ mohou dorazit vφcekrßt. Jestli₧e se ztratφ jedinß Φßst fragmentovanΘho datagramu, je cel² datagram zahozen. Navφc sprßvnost dat krom∞ hlaviΦky nenφ kontrolovßna, obsah datagram∙ m∙₧e b²t poÜkozen. Pro tyto vlastnosti je p°enos dat pomocφ IP oznaΦovßn jako nespolehliv². O zajiÜt∞nφ spolehlivosti se musφ postarat protokoly vyÜÜφch vrstev nap°φklad kontrolou a opakovan²m vysφlßnφm ztracen²ch a poÜkozen²ch datagram∙.

Pro ·plnost se zmφnφm o verzφch protokolu IP. Dnes se pou₧φvß verze 4 (IPv4), novß verze mß Φφslo 6 (IPv6). V novΘ verzi bude adresa IP 128bitovß, co₧ zajistφ prakticky nevyΦerpateln² poΦet dostupn²ch unikßtnφch adres. HlaviΦka IPv6 bude mφt dΘlku 40 byt∙ mφsto souΦasn²ch 20. P°echod na verzi 6 je zatφm velmi pomal², nap°ed bude nutnß podpora v∞tÜiny softwaru.

Protokol TCP

Protokol TCP (Transmission Control Protocol) zajiÜ¥uje spolehliv² p°enos dat mezi aplikacemi. K p°enosu se sice vyu₧φvß protokol IP, narozdφl od n∞ho je ale zajiÜt∞no, ₧e vÜechna zaslanß data budou doruΦena. P°ijφmajφcφ strana musφ potvrzovat p°φchozφ pakety. Jestli₧e se n∞kter² paket po cest∞ ztratφ nebo poÜkodφ a potvrzenφ o jeho p°φjmu nep°ijde, je vyslßn opakovan∞. Protokol se dßle starß o odstran∞nφ p°φpadn²ch duplikovan²ch paket∙ a o sestavenφ vÜech p°ijφman²ch dat v po°adφ, ve kterΘm byla odeslßna.

TCP je tzv. spojovan² protokol. P°ed zaΦßtkem komunikace je nutno ustavit TCP spojenφ a po skonΦenφ jej zase korektnφm zp∙sobem zruÜit. Navßzßnφm spojenφ se vytvo°φ komunikaΦnφ kanßl, kter² je pln∞ duplexnφ, tj. umo₧≥uje zasφlßnφ a p°φjem dat ob∞ma stranami nezßvisle na sob∞.

Typickou vlastnostφ protokolu TCP je p°enos dat ve form∞ proudu. Data jsou sice vysφlßna v podob∞ datagram∙ IP, nicmΘn∞ hranice t∞chto datagram∙ jsou komunikujφcφm aplikacφm skryty. Jestli₧e tedy jedna strana vyÜle stokrßt Φty°bytovΘ Φφslo nebo jednou 400bytov² blok, nem∙₧e poΦφtat s tφm, ₧e prot∞jÜek obdr₧φ data ve stejn²ch kouscφch. Data mohou b²t podle pot°eby libovoln∞ spojovßna do v∞tÜφch blok∙ nebo naopak d∞lena, a to jak implementacφ TCP na odesφlajφcφm poΦφtaΦi, tak i na stran∞ p°φjemce. Aplikace tedy musφ chßpat p°enßÜenß data jako souvisl² proud. K rozpoznßvßnφ zaΦßtk∙ a konc∙ zprßv v datovΘm proudu se pou₧φvajφ bu∩ zvlßÜtnφ odd∞lovacφ znaky (t°eba konce °ßdk∙), nebo ka₧dß zprßva obsahuje informace o svΘ dΘlce v bytech, p°φpadn∞ je dΘlka zprßvy jinak odvoditelnß z jejφho obsahu.

Blok dat, kter² je zabalen do jednoho datagramu IP a odeslßn na sφ¥, se naz²vß segmentem TCP. Segment TCP mß svou vlastnφ hlaviΦku o minimßlnφ velikosti 20 byt∙, spolu s hlaviΦkou IP Φinφ tedy re₧ie protokol∙ 40 byt∙ na jeden datagram. HlaviΦka TCP obsahuje p°edevÜφm Φφsla port∙ odesφlajφcφ i cφlovΘ aplikace. Dßle je tu uvedena pozice prvnφho bytu u₧iteΦn²ch dat segmentu v rßmci p°enßÜenΘho proudu. Tak je mo₧nΘ zajistit rekonstrukci proudu na stran∞ p°φjemce ve sprßvnΘm po°adφ. K dalÜφm polφm hlaviΦky TCP se vrßtφme pozd∞ji, a₧ se budeme vφce v∞novat praktickΘmu pou₧itφ protokolu.

Knihovna WinSock nabφzφ pro p°enos dat pomocφ TCP tzv. proudovΘ sockety (stream sockets).

Protokol UDP

Protokol UDP (User Datagram Protocol) je alternativou protokolu TCP pro p°enos dat s tΘm∞° diametrßln∞ odliÜn²mi vlastnostmi.

Stejn∞ jako v p°pad∞ protokolu IP je p°enos dat nespolehliv². Jedin²m vylepÜenφm proti IP je kontrola sprßvnosti obsahu celΘho datagramu. Jestli₧e jsou data poÜkozena, je datagram zahozen a nenφ p°edßn cφlovΘ aplikaci. Aplikace musφ poΦφtat se ztracen²mi a duplikovan²mi datagramy a s nep°edvφdateln²m po°adφm jejich p°φjmu. Na druhΘ stran∞ UDP narozdφl od TCP zaruΦuje zachovßnφ hranic datagram∙ a jejich p°edßnφ aplikaci ve stejnΘ podob∞, jako byly odeslßny. Nedochßzφ tedy ke sklßdßnφ datagram∙ do proudu.

UDP je tzv. nespojovan² protokol. Datagram UDP je mo₧nΘ okam₧it∞ poslat na libovolnou adresu, ani₧ by bylo nutnΘ navazovat s touto adresou spojenφ. Navφc UDP podporuje tzv. broadcasting, co₧ je hromadnΘ odeslßnφ datagramu na vÜechny adresy v danΘ sφti. V∞tÜinou je toto mo₧nΘ jen v rßmci uzav°enΘ lokßlnφ sφt∞, sm∞rovaΦe na Internetu pokus o broadcasting do cizφ sφt∞ nedovolφ.

Ka₧d² datagram UDP je balen do jednoho datagramu IP. Datagram UDP mß 8bytovou hlaviΦku UDP, obsahujφcφ jen zdrojov² a cφlov² port, velikost datagramu a kontrolnφ souΦet. Po zabalenφ do paketu IP tedy tvo°φ data obou hlaviΦek jen 28 byt∙ na ka₧d² datagram.

V knihovn∞ WinSock jsou k dispozici tzv. datagramovΘ sockety implementujφcφ protokol UDP.

TCP nebo UDP?

To je otßzka, na kterou si musφme odpov∞d∞t, ne₧ se dßme do v²voje. Oba protokoly majφ svΘ v²hody pro konkrΘtnφ druhy aplikacφ.

V²hody TCP jsou nßsledujφcφ:

Protokol UDP mß tyto p°ednosti: V p°φpad∞, ₧e programujeme klienta nebo server n∞kterΘho zavedenΘho protokolu, musφme se dr₧et transportnφho protokolu, kter² je p°edepsßn. V∞tÜina protokol∙ p°enßÜejφcφch souvislΘ bloky dat je zalo₧ena na TCP. Pat°φ mezi n∞ HTTP, FTP, SMTP (p°enos e-mailu), POP3 (stahovßnφ e-mailu), IRC (chat). UDP pou₧φvajφ nejΦast∞ji jednoduÜÜφ slu₧by, pracujφcφ zp∙sobem dotaz-odpov∞∩, nap°φklad zjiÜt∞nφ p°esnΘho Φasu nebo p°eklad domΘnovΘho jmΘna na adresu IP (DNS). Protokoly vyu₧φvajφcφ UDP n∞kdy voliteln∞ podporujφ i TCP jako alternativnφ transportnφ protokol.

Budeme-li vyvφjet vlastnφ slu₧bu, rozhodneme se pro protokol TCP, jestli₧e aplikace bude p°enßÜet v∞tÜφ objemy dat mezi dv∞ma poΦφtaΦi. UDP zvolφme v p°φpad∞, ₧e objem p°enesen²ch dat mezi ka₧d²mi dv∞ma poΦφtaΦi je mal², p°itom spolu m∙₧e komunikovat i v∞tÜφ poΦet ·Φastnφk∙ najednou. Kandidßtem na vyu₧itφ UDP jsou r∙znΘ informaΦnφ a vyhledßvacφ slu₧by.

P°enos dat ve form∞ datagram∙ m∙₧e lßkat programßtora k pou₧itφ UDP v ka₧dΘ aplikaci komunikujφcφ pomocφ r∙zn²ch krßtk²ch zprßv a odpov∞dφ. To ale nenφ sprßvnß cesta v p°φpad∞, ₧e p°enesen²ch zprßv je velkΘ mno₧stvφ a ka₧dß zprßva putuje ve vlastnφm datagramu. Protokol TCP by v takovΘm p°φpad∞ spojoval vφce zprßv do jednoho segmentu a p°enos by byl mnohem efektivn∞jÜφ.

WinSock a jeho verze

To byla tedy ·vodnφ teoretickß pr∙prava do problematiky aplikacφ nad TCP/IP. N∞kterΘ dalÜφ technologickΘ podrobnosti si p°iblφ₧φme, a₧ se budeme v∞novat pokroΦilejÜφm tΘmat∙m a jejich konkrΘtnφm problΘm∙m. Te∩ se vra¥me k materißlu, se kter²m budeme pracovat, tedy ke knihovn∞ WinSock.

Knihovna se v souΦasnosti vyskytuje ve dvou hlavnφch verzφch - WinSock 1 (p°esn∞ji obvykle 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:

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Θ variant∞ 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, kte°φ majφ zßjem programovat s knihovnou WinSock v Delphi, uvßdφm nßsledujφcφ p°ehled konvencφ: Sockety zabalenΘ do objektov²ch typ∙ 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∙ z knihy Gulliverovy cesty, 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°φklad 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 definice polo₧ky sin_addr dost komplikovanß, 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);
Te∩ u₧ m∙₧eme p°ikroΦit k tomu nejd∙le₧it∞jÜφmu, k princip∙m programovßnφ klientsk²ch a serverov²ch aplikacφ. ZaΦneme klientem pracujφcφm s transportnφm protokolem TCP.

Ustavenφ relace TCP

Proto₧e TCP je protokol spojovan², prvnφ, co musφme provΘst, je p°ipojenφ k serveru. Po p°ipojenφ je mo₧nΘ zasφlat serveru data a p°ijφmat odpov∞di bez opakovanΘho udßvßnφ cφlovΘ adresy. Ta je po p°ipojenφ pevn∞ urΦena a socket nenφ mo₧nΘ pou₧φvat pro komunikaci s jin²mi servery.

P°ipojenφ provedeme zavolßnφm funkce connect. Prvnφm parametrem je d°φve ·sp∞Ün∞ vytvo°en² socket typu SOCK_STREAM. Za druh² parametr dosadφme ukazatel na sprßvn∞ vypln∞nou strukturu SOCKADDR_IN vΦetn∞ Φφsla portu. Proto₧e funkce connect poΦφtß i s jin²mi protokoly, ne₧ je TCP/IP, je na mφst∞ tohoto parametru oΦekßvßna obecn∞jÜφ struktura SOCKADDR. NßÜ ukazatel proto musφme sprßvn∞ p°etypovat. T°etφ a poslednφ parametr urΦuje velikost nßmi zadanΘ adresnφ struktury v bytech. V p°φpad∞, ₧e je spojenφ ·sp∞Ün∞ navßzßno, funkce vrßtφ nulu, jinak vrßtφ k≤d SOCKET_ERROR.

Pro objasn∞nφ nßsleduje krßtk² v²pis:

SOCKET client_sock; SOCKADDR_IN si; ... /* vytvoreni socketu */ /* vyplneni adresy */ connect(client_sock, (SOCKADDR *) &si, sizeof si); ...

Odesφlßnφ a p°φjem dat

Kdy₧ jednou mßme socket p°ipojen² k serveru, m∙₧eme zaΦφt se samotnou komunikacφ. K odesφlßnφ dat slou₧φ funkce send a ke Φtenφ dat p°ijat²ch mßme funkci recv.

Funkce send mß Φty°i parametry. Prvnφm z nich je socket, jφm₧ chceme posφlat data. Tento socket musφ b²t ·sp∞Ün∞ vytvo°en² a p°ipojen². Jako druh² parametr se zadßvß ukazatel na buffer obsahujφcφ data k odeslßnφ. T°etφ parametr udßvß poΦet byt∙ z bufferu, kterΘ majφ b²t odeslßny. Poslednφ parametr specifikuje volby, kterΘ nebudeme pou₧φvat, proto sem budeme dosazovat nulu. Nßvratovou hodnotou je poΦet skuteΦn∞ odeslan²ch byt∙. Jestli₧e funkce sel₧e, je navrßcena hodnota SOCKET_ERROR.

Musφme si uv∞domit, ₧e funkce m∙₧e odeslat mΘn∞ dat, ne₧ jsme po₧adovali. V takovΘm p°φpad∞ musφme posunout ukazatel o poΦet poslan²ch byt∙, o stejnou hodnotu snφ₧it po₧adavek na mno₧stvφ dat, kterß zb²vß odeslat, a zavolat funkci send znovu s t∞mito nov²mi parametry. Toto je nutnΘ opakovat, dokud nejsou odbavena vÜechna naÜe data. Nßsledujφcφ k≤d zajistφ odeslßnφ vÜech dat z bufferu urΦitΘ velikosti.

int offset; char buf[256]; ... offset = 0; while (offset < sizeof buf) { /* pokusime se odeslat zbytek bufferu */ /* posuneme se o pocet skutecne odeslanych bytu */ offset += send(sock, buf + offset, sizeof buf - offset, 0); }
V tomto k≤du, stejn∞ jako v n∞kolika nßsledujφcφch, se dopouÜtφm p°φmo kriminßlnφho postupu. V ka₧dΘ aplikaci je nutno po Φtenφ a zßpisu nejd°φve zkontrolovat nßvratovou hodnotu, jestli nedoÜlo k chyb∞ nebo k ukonΦenφ relace, a pak je teprve mo₧nΘ ji pova₧ovat za poΦet p°enesen²ch byt∙. M²m d∙vodem je jen a pouze snaha o koncentraci na vysv∞tlenφ samotnΘho p°enosu dat.

Uve∩me si jeÜt∞ jeden p°φklad, kter² je v praxi obvyklejÜφ. Tentokrßt mßme v bufferu pevnΘ velikosti ulo₧en nulou zakonΦen² textov² °et∞zec, kter² pot°ebujeme odeslat. UkonΦujφcφ nulu nikdy neodesφlßme, pokud to aplikaΦnφ protokol v²slovn∞ ne₧ßdß, jinak by prot∞jÜφ strana naÜφm zprßvßm nemusela v∙bec rozum∞t.

#include <string.h> ... int len, offset; char buf[256]; ... strcpy(buf, "Nejaka zprava, treba LIST /pub\r\n"); len = strlen(buf); offset = 0; while (offset < len) { offset += send(sock, buf + offset, len - offset, 0); }
╚tenφ dat se provßdφ podobn²m zp∙sobem. Funkce recv mß op∞t Φty°i parametry s podobn²m v²znamem, jako tomu bylo v p°φpad∞ funkce send. Ukazatel p°edßvan² druh²m parametrem tentokrßt ukazuje na p°ipraven² buffer, do n∞ho₧ budou zapsßna p°ijatß data. T°etφ parametr urΦuje mno₧stvφ dat v bytech, kterΘ se do naÜeho bufferu vejde. Funkce vracφ poΦet p°ekopφrovan²ch byt∙, v p°φpad∞ chyby pak SOCKET_ERROR. PoΦet p°eΦten²ch byt∙ op∞t m∙₧e b²t menÜφ ne₧ zadanß velikost bufferu. Jestli₧e pot°ebujeme k dalÜφ prßci programu vφce dat, musφme volat funkci opakovan∞, podobn∞ jako tomu bylo v p°φpad∞ funkce send.

╚asto b²vajφ zprßvy odesφlanΘ klientem nebo serverem ukonΦeny specißlnφm znakem k tomu urΦen²m, nap°φklad koncem °ßdku. V takovΘm p°φpad∞ pot°ebujeme opakovan∞ volat recv, dokud v p°ijat²ch datech nenajdeme odd∞lovacφ znak. Nßsledujφcφ v²pis k≤du ukazuje, jak je mo₧nΘ p°eΦφst zprßvu ze serveru zakonΦenou znakem line feed (v C znßm²m jako new line).

#include <string.h> ... int offset, last_offset, bytes; /* do bufferu se musi vejit cela zprava */ char buf[2048]; ... offset = 0; do { /* zapamatujeme si zacatek prijimaneho retezce */ last_offset = offset; /* pokusime se precist co nejvice bytu */ offset += recv(sock, buf + offset, sizeof buf - offset - 1, 0); /* ukoncime vznikly retezec nulou */ buf[offset] = 0; /* skoncime, kdyz nalezneme znak LF */ } while (strchr(buf + last_offset, '\n') == NULL);
P°ijφmßme-li textovß data, s nimi₧ chceme pracovat pomocφ °et∞zcov²ch funkcφ, musφme sami doplnit ukonΦujφcφ nulu podobn∞ jako v p°edchozφm p°φklad∞. Funkce recv to za nßs neud∞lß, Φemu₧ se nesmφme divit, proto₧e tato funkce nemß tuÜenφ, jestli uklßdß data textovß nebo binßrnφ. Jestli₧e na to zapomeneme, je na sv∞t∞ dosti odpornß chyba, proto₧e budeme p°edpoklßdat problΘm spφÜe v sφ¥ov²ch funkcφch ne₧ v naÜem vlastnφm poli.

Chovßnφ p°enosov²ch funkcφ

Jak u₧ bylo °eΦeno, funkce send a recv vracejφ jako sv∙j v²sledek poΦet skuteΦn∞ p°enesen²ch byt∙, kter² m∙₧e b²t ni₧Üφ ne₧ poΦet po₧adovan².

Nenφ-li mo₧nΘ odeslat data okam₧it∞, funkce send Φekß, dokud se jφ nepoda°φ odeslat alespo≥ Φßst dat. Potom teprve jejφ Φinnost skonΦφ a je vrßcen poΦet byt∙. To, co funkce send ve skuteΦnosti d∞lß, je zßpis dat do bufferu TCP. Samotn² p°enos p°es sφ¥ovΘ rozhranφ zajiÜ¥uje operaΦnφ systΘm asynchronn∞ mimo v∞domφ naÜeho programu. Jestli₧e p°i volßnφ funkce send jako poΦet byt∙ k odeslßnφ zadßme k nulu, funkce jednoduÜe vrßtφ nulu, nic neodeÜle, neohlßsφ chybu.

Chovßnφ funkce recv je trochu odliÜnΘ. Mohlo by se oΦekßvat, ₧e pokud nejsou k dispozici ₧ßdnß p°φchozφ data ze serveru, pak funkce vrßtφ nulu. SkuteΦnost je jinß. Funkce se pokusφ p°ekopφrovat co nejvφce byt∙ z bufferu TCP do bufferu naÜeho. Nejsou-li ₧ßdnß data k dispozici, funkce Φekß, dokud n∞jakß nedorazφ, a teprve po jejich zkopφrovßnφ skonΦφ svou Φinnost. Jestli₧e tedy v programu zavolßme recv v okam₧iku, kdy server nemß ·mysl odeslat ₧ßdnß dalÜφ data, program nav₧dy zatuhne. Funkce recv vrßtφ nulu v p°φpad∞, ₧e prot∞jÜφ poΦφtaΦ zruÜil spojenφ TCP korektnφm zp∙sobem. Kdy₧ dojde k nestandardnφmu v²padku spojenφ, tak funkce send i recv vracejφ zßporn² chybov² k≤d SOCKET_ERROR.

Existuje zp∙sob, jak jednoduÜe zjistit, jestli jsou na socketu n∞jakß p°φchozφ data. K tomuto ·Φelu m∙₧eme pou₧φt funkci ioctlsocket, kterß slou₧φ i k n∞kter²m dalÜφm mΘn∞ obvykl²m operacφm se socketem. Za prvnφ parametr dosadφme pat°iΦn² socket, za druh² konstantu FIONREAD urΦujφcφ druh operace - zjiÜt∞nφ dostupnΘho mno₧stvφ p°φchozφch dat. T°etφm parametrem musφ b²t ukazatel na 32bitovou celoΦφselnou prom∞nnou, do nφ₧ bude zapsßn poΦet byt∙ p°φtomn²ch v bufferu, kterΘ mohou b²t zφskßny jednφm volßnφm funkce recv. Funkce vrßtφ nulu nebo SOCKET_ERROR, jak u₧ jsme zvyklφ.

long bytes; ... ioctlsocket(sock, FIONREAD, &bytes); if (bytes > 0) recv(sock, buf, sizeof buf, 0); ...
SkuteΦnost, ₧e funkce recv a send mohou Φekat a blokovat zbytek programu, m∙₧e b²t nemilß. Ze zatφm probran²ch funkcφ se podobn²m zp∙sobem chovajφ takΘ funkce gethostbyname a connect. Ob∞ nejd°φve Φekajφ na odpov∞∩ vzdßlenΘho poΦφtaΦe, a potom teprve vracejφ svΘ nßvratovΘ hodnoty. Proto i tyto funkce mohou zp∙sobit doΦasnΘ "zaseknutφ" programu. Toto je tolerovatelnΘ v programech pracujφcφch v dßvkovΘm re₧imu nebo ovlßdan²ch z p°φkazovΘ °ßdky. V normßlnφm udßlostmi °φzenΘm programu s grafick²m u₧ivatelsk²m rozhranφm by takovΘ chovßnφ bylo dost nesluÜnΘ a takov² program by urΦit∞ d∙v∞ru nezφskal. V t∞chto programech se pou₧φvajφ sockety v tzv. neblokujφcφm re₧imu, p°φpadn∞ blokujφcφ funkce b∞₧φcφ v samostatnΘm vlßkn∞. My zatφm z∙staneme v blokujφcφm re₧imu, kter² je jednoduÜÜφ a k vysv∞tlovßnφ princip∙ prßce se sockety vhodn∞jÜφ. K funkcφm neblokujφcφm se vrßtφme pozd∞ji.

ZruÜenφ relace TCP

PotΘ, co jsme ukonΦili komunikaci se vzdßlen²m poΦφtaΦem, je nunΘ se sprßvn∞ odpojit. Spojenφ TCP je obousm∞rnΘ, a proto existuje mo₧nost uzav°φt spojenφ v ka₧dΘm sm∞ru zvlßÜ¥. K tomu pou₧φvßme funkci shutdown. Jejφm prvnφm parametrem je samoz°ejm∞ socket, kter² pot°ebujeme odpojit. Druh² parametr udßvß, ve kterΘm sm∞ru se mß spojenφ ukonΦit. Konstanta 1 znamenß, ₧e ze socketu jeÜt∞ m∙₧eme Φφst, ale odesφlßnφ dalÜφch dat u₧ je nemo₧nΘ. Aplikace na prot∞jÜφ stran∞ spojenφ obdr₧φ tzv. paket FIN, kter² oznamuje, ₧e tato polovina spojenφ je uzav°ena. Konstanta 0 naopak zp∙sobφ ukonΦenφ p°φjmu dat a hodnota 2 uzav°enφ v obou sm∞rech.

V praxi se pou₧φvß tento postup:

V n∞kter²ch protokolech, nap°φklad v p°φpad∞ datovΘho spojenφ FTP, ukonΦuje relaci nejd°φve server. V takovΘm p°φpad∞ po p°eΦtenφ vÜech dat zjistφme, ₧e server zav°el svou odesφlajφcφ stranu spojenφ. TotΘ₧ ud∞lßme my pomocφ funkce shutdown a m∙₧eme okam₧it∞ zav°φt socket pomocφ closesocket. N∞kdy se jedna polovina spojenφ uzavφrß pom∞rn∞ brzy po navßzßnφ spojenφ a ke komunikaci se pou₧φvß jen polovina druhß. Nap°φklad WWW prohlφ₧eΦ poÜle serveru pom∞rn∞ krßtk² po₧adavek HTTP, zav°e svou odesφlacφ stranu socketu, a pak u₧ jen p°ijφmß data.

Program by m∞l v₧dy zavφrat jen svou odesφlacφ polovinu spojenφ a nem∞l by pou₧φvat jinΘ, nßsilnΘ postupy.

Stahovßnφ strßnek WWW

V tuto chvφli mßme probrßno pom∞rn∞ hodn∞, a proto si zaslou₧φme trochu efektn∞jÜφ p°φklad. Bude jφm program pro stahovßnφ WWW strßnek a jejich uklßdßnφ na disk. Program (pracuje v konzolovΘm re₧imu) se nejd°φve zeptß na adresu serveru (nap°φklad www.chip.cz), pak na cestu k dokumentu v rßmci serveru (v nejjednoduÜÜφm p°φpad∞ /) a nakonec na jmΘno souboru, do kterΘho nßsledn∞ ulo₧φ sta₧enou strßnku. V zßjmu jednoduchosti program nepracuje s adresami URL a jsou ignorovßny vÜechny mo₧nΘ chyby s v²jimkou neexistujφcφ adresy.

Sta₧enφ strßnky mß na starost protokol HTTP. V nßmi pou₧itΘ verzi HTTP 0.9 mß po₧adavek na strßnku tvar GET /cesta<CR><LF>. Server jako odpov∞∩ odeÜle obsah po₧adovanΘho dokumentu. V pokroΦilejÜφch verzφch je p°φmo v po₧adavku uvedena verze protokolu a komunikace je bohatÜφ o tzv. hlaviΦky, pomocφ nich₧ si klient a server vym∞≥ujφ r∙znΘ informace.

Nßsleduje kompletnφ v²pis programu.

#include <stdio.h> #include <string.h> #include <winsock.h> int main(void) { WSADATA WSAData; SOCKADDR_IN si; SOCKET sock; unsigned long ip_addr; char buf[2048]; int len, offset; FILE *fw; WSAStartup(0x0101, &WSAData); printf("Zadejte adresu serveru: "); gets(buf); /* preklad adresy */ ip_addr = inet_addr(buf); if (ip_addr == INADDR_NONE) { HOSTENT *phe = gethostbyname(buf); if (phe != NULL) ip_addr = *(unsigned long *) (phe->h_addr); } if (ip_addr == INADDR_NONE) { printf("Server nenalezen.\r\n"); return 0; } /* pripojeni k serveru HTTP */ si.sin_family = AF_INET; si.sin_addr.s_addr = ip_addr; si.sin_port = htons(80); sock = socket(AF_INET, SOCK_STREAM, 0); connect(sock, (SOCKADDR *) &si, sizeof si); /* sestaveni a odeslani pozadavku */ strcpy(buf, "GET "); printf("Zadejte cestu zacinajici lomitkem: "); gets(buf + strlen(buf)); strcat(buf, "\r\n"); len = strlen(buf); offset = 0; while (offset < len) offset += send(sock, buf + offset, len - offset, 0); shutdown(sock, 1); /* prijem a ulozeni dokumentu */ printf("Zadejte jmeno mistniho souboru: "); gets(buf); fw = fopen(buf, "w"); /* cteme, dokud se server neodpoji */ while ((len = recv(sock, buf, sizeof buf, 0)) > 0) fwrite(buf, 1, len, fw); fclose(fw); closesocket(sock); WSACleanup(); return 0; }

Klient nad UDP

Prßce se socketem protokolu UDP je odliÜnß a o n∞co jednoduÜÜφ, ne₧ tomu bylo v p°φpad∞ protokolu TCP. Nenφ ustavovßno ₧ßdnΘ spojenφ, a proto odpadß volßnφ funkcφ connect a shutdown. Po vytvo°enφ socketu typu SOCK_DGRAM je mo₧nΘ okam₧it∞ mo₧nΘ posφlat a p°ijφmat data. K tomu slou₧φ funkce sendto, kterß odesφlß datagram na uvedenou adresu a funkce recvfrom, kterß p°eΦte p°ijat² datagram a zßrove≥ ulo₧φ adresu, ze kterΘ p°iÜel. Jedin² socket tedy m∙₧eme pou₧φvat pro komunikaci s n∞kolika poΦφtaΦi nebo aplikacemi. Na podrobnosti p°enosu datagram∙ se podφvßme nφ₧e.

MΘn∞ obvykl²m, ale takΘ mo₧n²m postupem je svßzßnφ socketu s jednou adresou pomocφ funkce connect. Po zavolßnφ tΘto funkce nedojde ustavenφ ₧ßdnΘho spojenφ, pouze k omezenφ socketu na jedinou vzdßlenou adresu. P°enos datagram∙ se pak provßdφ pomocφ nßm u₧ znßm²ch funkcφ send a recv, u kter²ch se neuvßdφ ₧ßdnß adresa. Datagramy p°ijatΘ z jin²ch adres jsou zahazovßny. Zm∞nit adresu, ke kterΘ je socket "p°ipojen" je mo₧nΘ opakovan²m volßnφm funkce connect. Jestli₧e chceme omezenφ zruÜit, zavolßme connect se strukturou typu SOCKADDR_IN obsahujφcφ adresu IP 0.0.0.0. PotΘ m∙₧eme socket UDP pou₧φvat zase standardnφm zp∙sobem. Po skonΦenφ prßce se socketem jej jednoduÜe zruÜφme pomocφ volßnφ closesocket. Nikdy nevolßme funkci shutdown, ani v p°φpad∞, ₧e byl socket "p°ipojen" pomocφ funkce connect.

P°enos datagram∙

Funkce sendto mß prvnφ Φty°i parametry podobnΘ jako funkce send. Jsou to socket, ukazatel na buffer, velikost datagramu v bytech a volby. Pßt²m parametrem je ukazatel na cφlovou adresu, kterß mß obdr₧et datagram. Ukazatel na vypln∞nou strukturu SOCKADDR_IN musφme op∞t p°etypovat na typ SOCKADDR*. Poslednφm parametrem je velikost naÜφ adresnφ struktury v bytech.
#include <string.h> ... SOCKET sock; SOCKADDR_IN si; char buf[128] ... sock = socket(AF_INET, SOCK_DGRAM, 0); /* vyplneni adresy si */ strcpy(buf, "Servere, toto je muj datagram!"); sendto(sock, buf, strlen(buf), 0, (SOCKADDR *) si, sizeof si); ...
Funkce sendto vracφ poΦet odeslan²ch byt∙, v p°φpad∞ selhßnφ SOCKET_ERROR. Velikost datagramu nesmφ b²t p°φliÜ velkß, jinak se zvyÜuje nebezpeΦφ jeho ztrßty po cest∞, p°φpadn∞ jeho odeslßnφ nemusφ b²t v∙bec mo₧nΘ. JednotlivΘ datagramy by m∞ly b²t velkΘ °ßdov∞ do stovek byt∙, nad 512 byt∙ obvykle dochßzφ k fragmentaci. Hornφm limitem, ke kterΘmu u₧ by se aplikace nem∞ly p°φliÜ p°ibli₧ovat, je 8 KB pro jeden datagram. Funkce sendto neohlßsφ ₧ßdnou chybu, jestli₧e byl datagram ·sp∞Ün∞ odeslßn, ale nebyl doruΦen k cφli.

Funkce recvfrom Φte data jednoho p°ijatΘho datagramu. Parametry jsou op∞t analogickΘ: socket, buffer pro p°φjem, maximßlnφ poΦet byt∙, volby. Pßt² parametr je ukazatel na strukturu SOCKADDR_IN, do kterΘ mß b²t ulo₧ena adresa, ze kterΘ datagram p°iÜel. Jednß se o parametr v²stupnφ, adresa je vypln∞na a₧ funkcφ recvfrom. èest² parametr se liÜφ. Je to ukazatel na celoΦφselnou prom∞nnou obsahujφcφ velikost bufferu pro adresu. P°ed volßnφm musφ b²t v tΘto prom∞nnΘ ulo₧en poΦet byt∙ pro ulo₧enφ adresy, funkce toto Φφslo zm∞nφ na skuteΦnou velikost ulo₧enΘ adresnφ struktury. Jestli₧e nßs adresa odesφlatele nezajφmß, m∙₧eme dosadit za poslednφ dva parametry NULL.

Funkce vracφ poΦet byt∙ z p°eΦtenΘho datagramu. Jestli₧e nßÜ buffer nenφ dost velk², aby se do n∞j cel² datagram veÜel, je zbytek datagramu zahozen a funkce navφc vrßtφ chybov² k≤d. Ka₧dΘ p°φÜtφ volßnφ recvfrom Φte v₧dy nov² datagram. Velikost prvnφho p°ijatΘho datagramu, kter² Φekß na p°eΦtenφ, m∙₧eme urΦit pomocφ funkce ioctlsocket s parametrem FIONREAD, kterou jsme u₧ zmφnili u proudov²ch socket∙ TCP. LepÜφ je ale znßt pevnou maximßlnφ velikost datagramu, kter² m∙₧eme obdr₧et, a Φφst v₧dy do bufferu s touto velikostφ.

SOCKET sock; char buf[512]; int bytes; ... /* vytvoreni socketu */ /* komunikace se serverem */ bytes = recvfrom(sock, buf, sizeof buf, 0, NULL, NULL); /* v bufferu mame datagram, v promenne bytes jeho delku */
Funkce sendto a recvfrom Φekajφ, dokud nenφ cel² datagram p°enesen, a proto stejn∞ jako send a recv blokujφ dalÜφ Φinnost programu.

Zßv∞r

Te∩ u₧ vφme, ₧e funkΦnφ internetov² klient nemusφ b²t a₧ tak slo₧itou v∞cφ. V p°φÜtφm dφlu nßs Φekajφ dalÜφ tΘmata, jako je programovßnφ serveru nad TCP a UDP, problΘmy komunikace klienta se serverem, pou₧φvßnφ neblokujφcφch si¥ov²ch funkcφ a vyu₧φvßnφ zprßv Windows a dalÜφ.

Ond°ej Hrabal (e-mail)


Autor (1981) je studentem FI Masarykovy univerzity. Jeho hlavnφ oblasti zßjmu v oboru informaΦnφch technologiφ jsou nap°φklad teorie a praxe programovacφch jazyk∙, sφ¥ovΘ technologie a komprese dat.