InternetovΘ programovßnφ s WinSock (3)
Minule jsme vytvo°ili nßÜ prvnφ socket a dnes si koneΦn∞ p°edvedeme, k Φemu je u₧iteΦn². NauΦφme se navßzat spojenφ se serverem a p°enßÜet data pomocφ protokol∙ TCP i UDP.
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. Jako 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φme 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∙. NaÜφm d∙vodem je snaha o Üet°enφ mφstem a koncentrace 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∞ dost nep°φjemnß 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. Teprve potom 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ß nep°ijdou, a teprve po jejich zkopφrovßnφ ukonΦφ svou Φinnost. Jestli₧e tedy v programu zavolßme recv v okam₧iku, kdy server nemß v ·myslu 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φ, 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 nep°φjemnß. Ze zatφm probran²ch funkcφ se podobn²m zp∙sobem chovajφ takΘ funkce gethosbyname 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. To 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 nevhodnΘ 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 nutnΘ 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:
╖ Po odeslßnφ vÜech dat zavolßme funkci shutdown s volbou 1, Φφm₧ oznßmφme vzdßlenΘmu poΦφtaΦi, ₧e z naÜφ strany se u₧ dat nedoΦkß.
╖ Pomocφ funkce recv p°eΦteme vÜechna data, kterß nßm server jeÜt∞ poÜle.
╖ Server zav°e druhou polovinu spojenφ, co₧ poznßme podle toho, ₧e recv vrßtφ nulu (nebo chybov² k≤d, p°eruÜφ-li se spojenφ nestandardnφm zp∙sobem).
╖ Teprve te∩ zruÜφme socket pomocφ funkce closesocket.
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∞ dost lßtky, 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°. 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 starosti 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∞ 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 dßle.
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 zase tak slo₧it². P°φÜt∞ se podφvßme na Φinnosti typickΘ pro serverovΘ aplikace a dalÜφ d∙le₧itΘ oblasti.