Sunteți pe pagina 1din 32

Acesta este capitolul 8 Programarea n ret ea introducere al edit iei electronic a a c art ii Ret ele de calculatoare, publicat a la Casa

Casa C art ii de S tiint a, n 2008, ISBN: 978-973-133-377-9. Drepturile de autor apart in subsemnatului, Radu-Lucian Lup sa. Subsemnatul, Radu-Lucian Lup sa, acord oricui dore ste dreptul de a copia cont inutul acestei c art i, integral sau part ial, cu condit ia atribuirii corecte autorului si a p astr arii acestei notit e. Cartea, integral a, poate desc arcat a gratuit de la adresa http://www.cs.ubbcluj.ro/~rlupsa/works/retele.pdf

c 2008, Radu-Lucian Lup sa


231

Capitolul 8

Programarea n ret ea introducere

8.1. Interfat a de programare socket BSD


Interfat a socket este un ansamblu de funct ii sistem utilizate de programe (de fapt, de procese) pentru a comunica cu alte procese, aate n execut ie pe alte calculatoare. Interfat a socket a fost dezvoltat a n cadrul sistemului de operare BSD (sistem de tip UNIX, dezvoltat la Universitatea Berkley) de aici denumirea de socket BSD. Interfat a socket este disponibil a n aproape toate sistemele de operare actuale. Termenul socket se utilizeaz a at at pentru a numi ansamblul funct iilor sistem legate de comunicat ia prin ret ea, c at si pentru a desemna ecare cap at al unei conexiuni deschise n cadrul ret elei.
proces utilizator socket nucleul S.O. proces utilizator

nucleul S.O. leg atur a logic a ret ea

Figura 8.1: Comunicat ia ntre dou a procese prin ret ea

Prezent am n continuare principiile de baz a ale interfet ei socket (vezi

c 2008, Radu-Lucian Lup sa


232 8.1. Interfat a de programare socket BSD

si gura 8.1): Pe ecare calculator ruleaz a mai multe procese si ecare proces poate avea mai multe c ai de comunicat ie deschise. Prin urmare, pe un calculator trebuie s a poat a exista la un moment dat mai multe leg aturi (conexiuni) active. Realizarea comunic arii este intermediat a de sistemele de operare de pe calculatoarele pe care ruleaz a cele dou a procese. Deschiderea unei conexiuni, nchiderea ei, transmiterea sau recept ionarea de date pe o conexiune si congurarea parametrilor unei conexiuni se fac de c atre sistemul de operare, la cererea procesului. Cererile procesului se fac prin apelarea funct iilor sistem din familia socket. In cadrul comunicat iei dintre procesul utilizator si sistemul de operare local (prin intermediul apelurilor din familia socket ), capetele locale ale conexiunilor deschise sunt numite socket -uri si sunt identicate prin numere ntregi, unice n cadrul unui proces la ecare moment de timp. Fiecare entitate care comunic a n cadrul ret elei este identicat printr-o adres a unic a. O adresa este asociat a de fapt unui socket. Adresa este format a conform regulilor protocolului de ret ea utilizat. Interfat a socket cont ine funct ii pentru comunicat iei at at conform modelului conexiune c at si conform modelului cu datagrame. Funct iile sistem oferite permit stabilirea comunicat iei prin diferite protocoale (de exemplu, IPv4, IPv6, IPX), dar au aceea si sintax a de apel independent de protocolul dorit.

8.1.1. Comunicat ia prin conexiuni


In cele ce urmeaz a, prin client desemn am procesul care solicit a n mod activ deschiderea conexiunii c atre un partener de comunicat ie specicat printr-o adres a, iar prin server nt elegem procesul care a steapt a n mod pasiv conectarea unui client. Vom da n cele ce urmeaz a o scurt a descriere a operat iilor pe care trebuie s a le efectueze un proces pentru a deschide o conexiune si a comunica prin ea. Descrierea este mp art it a n patru p art i: deschiderea conexiunii de c atre client, deschiderea conexiunii de c atre server, comunicat ia propriu-zis a si nchiderea conexiunii. O descriere mai am anunt it a a funct iilor sistem apelate si a parametrilor mai des utilizat i este f acut a separat ( 8.1.3), iar pentru detalii suplimentare se recomand a citirea paginilor corespunz atoare din documentat ia on-line.

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere 233

8.1.1.1. Deschiderea conexiunii de c atre client Procesul client trebuie s a cear a mai nt ai sistemului de operare local crearea unui socket. Trebuie specicat protocolul de ret ea utilizat (TCP/IPv4, TCP/IPv6, etc), dar nc a nu se specic a partenerul de comunicat ie. Socket-ul proasp at creat este n starea neconectat. Dup a crearea socket-ului, clientul cere sistemului de operare conectarea socket-ului la un anumit server, specicat prin adresa socket -ului serverului. De exemplu, pentru protocolul TCP/IPv4, adresa partenerului se specic a prin adresa IP (vezi 10.1) si num arul portului (vezi 10.3.1). Funct iile sistem apelate sunt: socket() pentru crearea socket-ului si connect() pentru deschiderea efectiv a a conexiunii.

8.1.1.2. Deschiderea conexiunii de c atre server Procesul server ncepe tot prin a cere sistemului de operare crearea unui socket de tip conexiune pentru protocolul dorit. Acest socket nu va servi pentru conexiunea propriu-zis a cu un client, ci doar pentru a steptarea conect arii client ilor; ca urmare este numit uneori socket de a steptare. Dup a crearea acestui socket, serverul trebuie s a cear a sistemului de operare stabilirea adresei la care serverul a steapt a cereri de conectare (desigur, acea parte din adres a care identic a ma sina serverului nu este la alegerea procesului server) si apoi cere efectiv nceperea a stept arii client ilor. Funct iile apelate n aceast a faz a sunt, n ordinea n care trebuie apelate: socket() pentru crearea socketului, bind() pentru stabilirea adresei si listen() pentru nceperea a stept arii client ilor. Preluarea efectiv a a unui client conectat se face prin apelarea unei funct ii sistem numit a accept(). La apelul funct iei accept(), sistemul de operare execut a urm atoarele: a steapt a cererea de conectare a unui client si deschide conexiunea c atre acesta; creaz a un nou socket, numit socket de conexiune, care reprezint a cap atul dinspre server al conexiunii proasp at deschise; returneaz a apelantului (procesului server) identicatorul socket-ului de conexiune creat. Dup a un apel accept(), socket-ul de a steptare poate utilizat pentru a a stepta noi client i, iar socket-ul de conexiune nou creat se utilizeaz a pentru a comunica efectiv cu acel client.

c 2008, Radu-Lucian Lup sa


234 8.1. Interfat a de programare socket BSD

8.1.1.3. Comunicat ia propriu-zis a O dat a deschis a conexiunea, clientul poate trimite siruri de octet i c atre server si invers, serverul poate trimite siruri de octet i c atre client. Cele dou a sensuri de comunicat ie funct ioneaz a identic (nu se mai distinge cine a fost client si cine a fost server) si complet independent (trimiterea datelor pe un sens nu este condit ionat a de recept ionarea datelor pe cel alalt sens). Pe ecare sens al conexiunii, se poate transmite un sir arbitrar de octet i. Octet ii trimi si de c atre unul dintre procese spre cel alalt sunt plasat i ntr-o coad a, transferat i prin ret ea la cel alalt cap at si citit i de c atre procesul de acolo. Comportamentul acesta este similar cu cel al unui pipe UNIX. Trimiterea datelor se face prin apelul funct iei send() (sau, cu funct ionalitate mai redus a, write()). Apelul acestor funct ii plaseaz a datele n coad a spre a transmise, dar nu a steapt a transmiterea lor efectiv a (returneaz a, de principiu, imediat controlul c atre procesul apelant). Dac a dimensiunea datelor din coad a este mai mare dec at o anumit a valoare prag, (aleas a de sistemele de operare de pe cele dou a ma sini), apelul send() se blocheaz a, return and controlul procesului apelant abia dup a ce partenerul de comunicat ie cite ste date din coad a, duc and la sc aderea dimensiunii datelor din coad a sub valoarea prag. Recept ionarea datelor trimise de c atre partenerul de comunicat ie se face prin intermediul apelului sistem recv() (cu functionalitate mai redus a se poate utiliza read()). Aceste funct ii returneaz a procesului apelant datele deja sosite pe calculatorul receptor si le elimin a din coad a. In cazul n care nu sunt nc a date disponibile, ele a steapt a sosirea a cel put in un octet. Sistemul garanteaz a sosirea la destinat ie a tuturor octet ilor trimi si (sau n stiint area receptorului, printr-un cod de eroare, asupra c aderii conexiunii), n ordinea n care au fost trimi si. Nu se p astreaz a ns a demarcarea ntre secvent ele de octet i trimise n apeluri send() distincte. De exemplu, este posibil ca emit atorul s a trimit a, n dou a apeluri succesive, sirurile abc si def, iar receptorul s a primeasc a, n apeluri recv() succesive, sirurile ab, cde si f. 8.1.1.4. Inchiderea conexiunii Inchiderea conexiunii se face separat pentru ecare sens si pentru ecare cap at. Exist a dou a funct ii: shutdown() nchide, la cap atul local al conexiunii, sensul de comunicat ie cerut de procesul apelant; close() nchide la cap atul local ambele sensuri de comunicat ie si n plus distruge socket-ul, eliber and resursele alocate (identicatorul de socket

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere 235

si memoria alocat a n spat iul nucleului). Terminarea unui proces are efect identic cu un apel close() pentru toate socket-urile existente n acel moment n posesia acelui proces. Dac a cap atul de emisie al unui sens de comunicat ie a fost nchis, receptorul poate citi n continuare datele existente n acel moment n coad a, dup a care un eventual apel recv() va semnaliza apelantului faptul c a a fost nchis a conexiunea. Dac a cap atul de recept ie al unui sens a fost nchis, o scriere ulterioar a de la cel alalt cap at este posibil s a returneze un cod de eroare (pe sistemele de tip UNIX, scrierea poate duce si la primirea, de c atre procesul emit ator, a unui semnal SIGPIPE).

8.1.2. Comunicat ia prin datagrame


In comunicat ia prin datagrame, datagramele sunt transmise independent una de cealalt a si ecare datagram a are o adres a surs a, o adres a destinat ie si ni ste date utile. Un proces ce dore ste s a trimit a sau s a primeasc a datagrame trebuie mai nt ai s a creeze un socket de tip dgram ; un astfel de socket cont ine n principal adresa de ret ea a procesului posesor al socket-ului. Dup a crearea unui socket, se poate cere sistemului de operare s a asocieze socket-ului o anumit a adres a sau se poate l asa ca sistemul de operare s a-i atribuie o adres a liber a arbitrar a. Crearea unui socket se face prin apelul funct iei socket(), iar atribuirea unei adrese se face prin apelul bind(). O dat a creat un socket, procesul poate trimite datagrame de pe acel socket, prin apelul funct iei sendto(). Datagramele trimise vor avea ca adres a surs a adresa socket-ului si ca adres a destinat ia si cont inut util valorile date ca parametri funct iei sendto(). De pe un socket se pot trimite, succesiv, oric ate datagrame si oric ator destinatari. Datagramele emise sunt transmise c atre sistemul de operare al destinatarului, unde sunt memorate n buer-ele sistemului. Destinatarul poate citi o datagram a apel and funct ia recvfrom(). Aceast a funct ie ia urm atoarea datagram a adresat a socket-ului dat ca parametru la recvfrom() si o transfer a din buer-ele sistemului local n memoria procesului apelant. Funct ia ofer a apelantului cont inutul datagramei (datele utile) si, separat, adresa expeditorului datagramei. In ciuda numelui, recvfrom() nu poate instruit a s a ia n considerare doar datagramele expediate de la o anumit a adres a. Sistemul nu garanteaz a livrarea tuturor datagramelor (este posibil a pierderea unor datagrame) si nici nu ofer a vreun mecanism de informare a expeditorului n cazul unei pierderi. Mai mult, exist a posibilitatea (e drept, rar a) ca o datagam a s a e duplicat a (s a ajung a dou a copii la destinatar) si

c 2008, Radu-Lucian Lup sa


236 8.1. Interfat a de programare socket BSD

este posibil ca dou a sau mai multe datagrame adresate aceluia si destinatar s a ajung a la destinat ie n alt a ordine dec at cea n care au fost emise. Dac a astfel de situat ii sunt inadmisibile pentru aplicat ie, atunci protocolul de comunicat ie trebuie s a prevad a conrm ari de primire si repetarea datagramelor pierdute, precum si numere de secvent a sau alte informat ii pentru identicarea ordinii corecte a datagramelor si a duplicatelor. Implementarea acestor mecanisme cade n sarcina proceselor. La terminarea utiliz arii unui socket, procesul posesor poate cere distrugerea socket-ului si eliberarea resurselor asociate (identicatorul de socket, memoria ocupat a n sistemul de operare pentru datele asociate socket-ului, portul asociat socket-ului). Distrugerea socket-ului se face prin apelul funct iei close(). In mod curent, ntr-o comunicat ie prin datagrame, unul dintre procese are rol de client, n sensul c a trimite cereri, iar cel alalt act ioneaz a ca server, n sensul c a prelucreaz a cererile clientului si trimite napoi clientului r aspunsurile la cereri. Intr-un astfel de scenariu, serverul creaz a un socket c aruia i asociaz a o adres a prestabilit a, dup a care a steapt a cereri, apel and n mod repetat recvfrom(). Clientul creaz a un socket, c aruia nu-i asociaz a o adres a (nu execut a bind()). Clientul trimite apoi cererea sub forma unei datagrame de pe socket-ul creat. La trimiterea primei datagrame, sistemul de operare d a o adres a socket-ului; datagrama emis a poart a ca adres a surs a acest a adres a. La primirea unei datagrame, serverul recupereaz a datele utile si adresa surs a, proceseaz a cererea si trimite r aspunsul c atre adresa surs a a cererii. In acest fel, r aspunsul este adresat exact socket-ului de pe care clientul a trimis cererea. Clientul obt ine r aspunsul execut and recvfrom() asupra socket-ului de pe care a expediat cererea. Cu privire la tratarea datagramelor pierdute, un caz simplu este acela n care clientul pune doar ntreb ari (interog ari) serverului, iar procesarea interog arii nu modic a n nici un fel starea serverului. Un exemplu tipic n acest sens este protocolul DNS ( 10.4). In acest caz, datagrama cerere cont ine interogarea si daatgrama r aspuns cont ine at at cererea la care se r aspunde c at si r aspunsul la interogare. Serverul ia ( n mod repetat) c ate o cerere, calculeaz a r aspunsul si trimite o napoi o datagram a cu cererea primit a si r aspunsul la cerere. Clientul trimite cererile sale si a steapt a r aspunsurile. Deoarece ecare r aspuns cont ine n el si cererea, clientul poate identica ecare r aspuns la ce cerere i corespunde, chiar si n cazul invers arii ordinii datagramelor. Dac a la o cerere nu prime ste r aspuns ntr-un anumit interval de timp, clientul repet a cererea; deoarece procesarea unei cereri nu modic a starea serverului, duplicarea cererii de c atre ret ea sau repetarea cererii de c atre client ca urmare a pierderii

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere 237

r aspunsului nu au efecte nocive. Clientul trebuie s a ignore r aspunsurile duplicate la o aceea si interogare.

8.1.3. Principalele apeluri sistem


8.1.3.1. Funct ia socket() Funct ia are sintaxa:
int socket(int proto_family, int type, int protocol)

Funct ia creaz a un socket si returneaz a identicatorul s au. Parametrii sunt: type: desemneaz a tipul de servicii dorite: SOCK STREAM:conexiune punct la punct, ux de date bidirect ional la nivel de octet, asigur and livrare sigura, cu pastrarea ordinii octet ilor si transmisie fara erori. SOCK DGRAM:datagrame, at at punct la punct c at si difuziune; transmisia este garantat a a f ar a erori, dar livrarea nu este sigura si nici ordinea datagramelor garantata. SOCK RAW:acces la protocoale de vel cobor at; este de exemplu utilizat de c atre comanda ping pentru comunicat ie prin protocolul ICMP. a tipul de ret ea cu care se lucreaz a (IP, IPX, etc). proto family identic Valori posibile: PF INET:protocol Internet, versiunea 4 (IPv4) PF INET6:protocol Internet, versiunea 6 (IPv6) PF UNIX:comunicat ie local a pe o ma sin a UNIX. protocol selecteaz a protocolul particular de utilizat. Acest parametru este util dac a pentru un tip de ret ea dat si pentru un tip de serviciu xat exist a mai multe protocoale utilizabile. Valoarea 0 desemneaz a protocolul implicit pentru tipul de ret ea si tipul de serviciu alese. 8.1.3.2. Funct ia connect() Funct ia are sintaxa:
int connect(int sock_id, struct sockaddr* addr, int addr_len)

Funct ia are ca efect conectarea socketului identicat de primul parametru care trebuie s a e un socket de tip conexiune proasp at creat ( nc a neconectat)

c 2008, Radu-Lucian Lup sa


238 8.1. Interfat a de programare socket BSD

la serverul identicat prin adres a dat a prin parametrii addr si addr len. La adresa respectiv a trebuie s a existe deja un server care s a a stepte conexiuni (s a fost deja executat apelul listen() asupra socket-ului serverului). Adresa trebuie plasat a, nainte de apelul connect(), ntr-o structur a av and un anumit format; cont inutul acestei structuri va descris n 8.1.3.6. Adresa n memorie a acestei structuri trebuie dat a ca parametrul addr, iar lungimea structurii de adres a trebuie dat a ca parametrul addr len. Motivul acestei complicat ii este legat de faptul c a funct ia connect() trebuie s a poat a lucra cu formate diferite de adres a, pentru diferite protocoale, iar unele protocoale au adrese de lungime variabil a. Funct ia connect() returneaz a 0 n caz de succes si 1 n caz de eroare. Eroarea survenit a poate constatat a e veric and valoarea variabilei globale errno, e apel and funct ia perror() imediat dup a funct ia sistem ce a nt ampinat probleme. Eroarea cea mai frecvent a este lipsa unui server care s a asculte la adresa specicat a. 8.1.3.3. Funct ia bind()
int bind(int sd, struct sockaddr* addr, socklen_t len)

Funct ia are ca efect atribuirea adresei specicate n parametrul addr socket-ului identicat prin identicatorul sd. Aceast a funct ie se apeleaz a n mod normal dintr-un proces server, pentru a preg ati un socket stream de a steptare sau un socket dgram pe care se a steapt a cereri de la client i. Partea, din adresa de atribuit socket-ului, ce cont ine adresa ma sinii poate e una dintre adresele ma sinii locale, e valoarea special a INADDR_ANY (pentru IPv4) sau IN6ADDR_ANY_INIT (pentru IPv6). In primul caz, socket-ul va primi doar cereri de conexiune (sau, respectiv, pachete) adresate adresei IP date socket-ului, si nu si cele adresate altora dintre adresele ma sinii server. Exemplul 8.1: S a presupunem c a ma sina server are adresele 193.226.40.130 si 127.0.0.1. Dac a la apelul funct iei bind() se d a adresa IP 127.0.0.1, atunci socket-ul respectiv va primi doar cereri de conectare destinate adresei IP 127.0.0.1, nu si adresei 193.226.40.130. Dimpotriv a, dac a adresa acordat a prin bind() este INADDR_ANY, atunci socket-ul respectiv va accepta cereri de conectare adresate oric areia dintre adresele ma sinii locale, adic a at at adresei 193.226.40.130 c at si adresei 127.0.0.1. Adresa atribuit a prin funct ia bind() trebuie s a e liber a n acel moment. Dac a n momentul apelului bind() exist a un alt socket de acela si tip av and aceea si adres a, apelul bind() e sueaza.

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere 239

Pe sistemele de tip UNIX, pentru atribuirea unui num ar de port mai mic dec at 1024 este necesar ca procesul apelant s a ruleze din cont de administrator. Funct ia bind() poate apelat a doar pentru un socket proasp at creat, c aruia nu i s-a atribuit nc a o adres a. Aceasta nseamn a c a funct ia bind() nu poate apelat a de dou a ori pentru acela si socket. De asemenea, funct ia bind() nu poate apelat a pentru un socket de conexiune creat prin funct ia accept() si nici pentru un socket asupra c aruia s-a apelat n prealabil vreuna dintre funct iile connect(), listen() sau sendto() aceste funct ii av and ca efect atribuirea unei adrese libere aleatoare. Funct ia returneaz a 0 n caz de succes si 1 n caz de eroare. Eroarea cea mai frecvent a este c a adresa dorit a este deja ocupat a. 8.1.3.4. Funct ia listen()
int listen(int sd, int backlog)

Funct ia cere sistemului de operare s a accepte, din acel moment, cererile de conexiune pe adresa socket-ului sd. Dac a socketului respectiv nu i s-a atribuit nc a o adres a (printr-un apel bind() anterior), funct ia listen() i atribuie o adres a aleas a aleator. Parametrul backlog xeaz a dimensiunea cozii de a steptare n acceptarea conexiunilor. Anume, vor putea exista backlog client i care au executat connect() f ar a ca serverul s a creat nc a pentru ei socket-uri de conexiune prin apeluri accept(). De notat c a nu exist a nici o limitare a num arului de client i conectat i, preluat i deja prin apelul accept(). 8.1.3.5. Funct ia accept()
int accept(int sd, struct sockaddr *addr, socklen_t *addrlen)

Apelul funct iei accept() are ca efect crearea unui socket de conexiune, asociat unui client conectat (prin apelul connect()) la socket-ul de a steptare sd. Dac a nu exist a nc a nici un client conectat si pentru care s a nu se creat socket de conexiune, funct ia accept() a steapt a p an a la conectarea urm atorului client. Funct ia returneaz a identicatorul socket-ului de conexiune creat. Dac a procesul server nu dore ste s a ae adresa clientului, va da valori NULL parametrilor addr si addrlen. Dac a procesul server dore ste s a ae adresa clientului, atunci va trebui s a aloce spat iu pentru o structur a pentru memorarea adresei clientului, s a pun a adresa structurii respective n parametrul

c 2008, Radu-Lucian Lup sa


240 8.1. Interfat a de programare socket BSD

addr, s a plaseze ntr-o variabil a de tip ntreg dimensiunea memoriei alocate pentru adresa clientului si s a pun a adresa acestui ntreg n parametrul adrlen. In acest caz, la revenirea din apelul accept(), procesul server va g asi n structura de adres a adresa socket-ului client si n variabila ntreag a a c arui adres a a fost dat a n parametrul adrlen va g asi dimensiunea efectiv utilizat a de sistemul de operare pentru a scrie adresa clientului. 8.1.3.6. Formatul adreselor Pentru funct iile socket ce primesc de la apelant (ca parametru) o adres a din ret ea (bind(), connect() si sendto()), precum si pentru cele ce returneaz a apelantului adrese de ret ea (accept(), recvfrom(), getsockname() si getpeername()), sunt denite structuri de date (struct) n care se plaseaz a adresele socket-urilor. Pentru ca funct iile de mai sus s a poat a avea aceea si sintax a de apel independent de tipul de ret ea ( si, n consecint a, de structura adresei), funct iile primesc adresa printr-un pointer la zona de memorie ce cont ine adresa de ret ea. Structura zonei de memorie respective depinde de tipul ret elei utilizate. In toate cazurile, aceasta ncepe cu un ntreg pe 16 bit i reprezent and tipul de ret ea. Dimensiunea structurii de date ce cont ine adresa de ret ea depinde de tipul de ret ea si, n plus, pentru anumite tipuri de ret ea, dimensiunea este variabil a. Din acest motiv: funct iile care primesc de la apelant o adres a (connect(), bind() si sendto()) au doi parametri: un pointer c atre structura de adres a si un ntreg reprezent and dimensiunea acestei structuri; funct iile care furnizeaz a apelantului o adres a (accept(), recvfrom(), getsockname() si getpeername()) primesc doi parametri: un pointer c atre structura de adres a si un pointer c atre o variabil a de tip ntreg pe care apelantul trebuie s-o init ializeze, naintea apelului, cu dimensiunea pe care a alocat-o pentru structura de adres a si n care funct ia pune, n timpul apelului, dimensiunea utilizat a efectiv de astuctura de adres a. In ambele cazuri, parametrul pointer c atre structura de adres a este declarat ca ind de tip struct sockaddr*. La apelul acestor funct ii este necesar a conversia a pointer-ului c atre structura de adres a de la pointer-ul specic tipului de ret ea la struct sockaddr*. O adres a a unui cap at al unei conexiuni TCP sau a unei leg aturi prin datagrame UDP este format a din adresa IP a ma sinii si num arul de port (vezi 10.2.3.1, 10.3.1.6 si 10.3.2). Pentru nevoile funct iilor de mai sus, adresele socket-urilor TCP si UDP se pun, n funct ie de protocolul de nivel

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere 241

ret ea (IPv4 sau IPv6), ntr-o structur a de tip sockaddr_in pentru IPv4 sau sockaddr_in6 pentru IPv6. Pentru adrese IPv4 este denit a structura sockaddr_in av and urm atorii membrii: sin family:trebuie s a cont in a constanta AF_INET; sin port:de tip ntreg de 16 bit i (2 octet i), f ar a semn, n ordine ret ea (cel mai semnicativ octet este primul), reprezent and num arul portului; sin addr:cont ine adresa IP. Are tipul struct in_addr, av and un singur c amp, s_addr, de tip ntreg pe 4 octet i n ordine ret ea. Adresa IPv4 poate convertit a de la notat ia obi snuit a (notat ia zecimal a cu puncte) la struct in_addr cu ajutorul funct iei
int inet_aton(const char *cp, struct in_addr *inp);

Conversia invers a, de la structura in_addr la string n notat ie zecimal a cu punct se face cu ajutorul funct iei
char *inet_ntoa(struct in_addr in);

care returneaz a rezultatul ntr-un buer static, apelantul trebuind s a copieze rezultatul nainte de un nou apel al functiei. Pentru adrese IPv6 este denit a structura sockaddr_in6 av and urm atorii membrii: a cont in a constanta AF_INET6; sin6 family:trebuie s ntreg de 16 bit i (2 octet i), f ar a semn, n ordine ret ea (cel sin6 port:de tip mai semnicativ octet este primul), reprezent and num arul portului; sin6 flow:eticheta de ux. sin6 addr:cont ine adresa IP. Are tipul struct in6_addr, av and un singur c amp, s6_addr, de tip tablou de 16 octet i. Obt inerea unei adrese IPv4 sau IPv6 cunosc and numele de domeniu (vezi 10.4) se face cu ajutorul funct iei
struct hostent *gethostbyname(const char *name);

care returneaz a un pointer la o structur a ce cont ine mai multe c ampuri dintre care cele mai importante sunt: int h addrtype:tipul adresei, AF_INET sau AF_INET6;

c 2008, Radu-Lucian Lup sa


242 8.1. Interfat a de programare socket BSD

char **h addr list:pointer la un sir de pointeri c atre adresele IPv4 sau IPv6 ale ma sinii cu numele name, n formatul in_addr sau respectiv in6_addr; int h length:lungimea sirului h_addr_list. 8.1.3.7. Interact iunea dintre connect(), listen() si accept() La apelul connect(), sistemul de operare de pe ma sina client trimite ma sinii server o cerere de conectare. La primirea cererii de conectare, sistemul de operare de pe ma sina server act ioneaz a astfel: dac a adresa din cerere nu corespunde unui socket pentru care s-a efectuat deja apelul listen(), refuz a conectarea; dac a adresa corespunde unui socket pentru care s-a efectuat listen(), ncearc a plasarea clientului ntr-o coad a de client i conectat i si nepreluat i nc a prin accept(). Dac a plasarea reu se ste (coada ind mai mic a dec at valoarea parametrului backlog din apelul listen()), sistemul de operare trimite sistemului de operare de pe ma sina client un mesaj de acceptare; n caz contrar trimite un mesaj de refuz. Apelul connect() revine n procesul client n momentul sosirii acceptului sau refuzului de la sistemul de operare de pe ma sina server. Revenirea din apelul connect() nu este deci condit ionat a de apelul accept() al procesului server. Apelul accept() preia un client din coada descris a mai sus. Dac a coada este vid a n momentul apelului, funct ia a steapt a sosirea unui client. Dac a coada nu este vid a, apelul accept() returneaz a imediat. Parametrul backlog al apelului listen() se refer a la dimensiunea cozii de client i conectat i (prin connect()) si nc a nepreluat i prin accept(), si nu la client ii deja preluat i prin accept(). 8.1.3.8. Funct iile getsockname() si getpeername()
int getsockname(int sd, struct sockaddr *name, socklen_t *namelen); int getpeername(int sd, struct sockaddr *name, socklen_t *namelen);

Funct ia getsockname() furnizeaz a apelantului adresa socket-ului sd. Funct ia getpeername(), apelat a pentru un socket de tip conexiune deja conectat, furnizeaz a adresa partenerului de comunicat ie. Funct ia getsockname() este util a dac a un proces act ioneaz a ca server, cre nd n acest scop un socket de a steptare, dar num arul portul pe care a steapt a conexiunile nu este prestabilit ci este transmis, pe alt a cale, viitorilor client. In acest caz, procesul server creaz a un socket (apel and socket()),

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere 243

cere primirea cererilor de conexiune (apel and listen(), dar f ar a a apelat bind()) dup a care determin a, prin apelul getsockname(), adresa atribuit a la listen() socket-ului respectiv si transmite aceast a adres a viitorilor client i. 8.1.3.9. Funct iile send() si recv() Apelurile sistem send() si recv() sunt utilizate n faza de comunicat ie pentru socket-uri de tip conexiune. Descriem n continuare utilizarea acestor funct ii consider and un singur sens de comunicat ie si ca urmare ne vom referi la un proces emit a tor si un proces receptor n raport cu sensul considerat. De notat ns a c a o conexiune socket stream este bidirect ional a si comunicarea n cele dou a sensuri se desf a soar a independent si prin acelea si mecanisme. Sintaxa funct iilor este:
ssize_t send(int sd, const void *buf, size_t len, int flags); ssize_t recv(int sd, void *buf, size_t len, int flags);

Funct ia send() trimite pe conexiunea identicat a prin socket-ul sd un num ar de len octet i din variabila a c arui adres a este indicat a de pointer-ul buf. Funct ia returneaz a controlul dup a plasarea datelor de transmis n buerele sistemului de operare al ma sinii locale. Valoarea returnat a de funct ia send() este num arul de octet i scri si efectiv, sau 1 n caz de eroare. Datele plasate n buer-e prin apelul send() urmeaz a a trimise spre receptor f ar a alte act iuni din partea emit atorului. In modul normal de lucru, dac a nu exist a spat iu sucient n buer-ele sistemului de operare, funct ia send() a steapt a ca aceste buer-e s a se elibereze (prin transmiterea efectiv a a datelor c atre sistemul de operare al receptorului si citirea lor de c atre procesul receptor). Aceast a a steptare are ca rol fr anarea procesului emit ator dac a acesta produce date la un debit mai mare dec at cel cu care este capabil a ret eaua s a le transmit a sau procesul receptor s a le preia. Prin plasarea valorii MSG_DONTWAIT n parametrul flags, acest comportament este modicat. Astfel, n acest caz, dac a nu exist a sucient spat iu n buer-ele sistemului de operare, funct ia send() scrie doar o parte din datele furnizate si returneaz a imediat controlul procesului apelant. In cazul n care funct ia send() nu scrie nimic, ea returneaz a valoarea 1 si seteaz a variabila global a errno la valoarea EAGAIN. In cazul n care funct ia send() scrie cel put in un octet, ea returneaz a num arul de octet i scri si efectiv. In ambele cazuri, este sarcina procesului emit ator s a apeleze din nou, la un moment ulterior, funct ia send() n vederea scrierii octet ilor r ama si. Deoarece funct ia send() returneaz a nainte de transmiterea efectiv a a datelor, eventualele erori legate de transmiterea datelor nu pot raportate

c 2008, Radu-Lucian Lup sa


244 8.1. Interfat a de programare socket BSD

apelantului prin valoarea returnat a de send(). Pot s a apar a dou a tipuri de erori: c aderea ret elei si nchiderea conexiunii de c atre receptor. Aceste erori vor raportate de c atre sistemul de operare al emit atorului procesului emit ator prin aceea c a apeluri send() ulterioare pentru acela si socket vor returna 1. In plus, pe sistemele de tip UNIX, apelul send() pentru o conexiune al c arui cap at destinat ie este nchis duce la primirea de c atre procesul emit ator a unui semnal SIGPIPE, care are ca efect implicit terminarea imediat a a procesului emit ator. Funct ia recv() extrage date sosite pe conexiune si aate n buerul sistemului de operare local. Funct ia prime ste ca argumente identicatorul socket-ului corespunz ator conexiunii, adresa unei zone de memorie unde s a plaseze datele citite si num arul de octet i de citit. Num arul de octet i de citit reprezint a num arul maxim de octet i pe care funct ia i va transfera din buer-ul sistemului de operare n zona procesului apelant. Dac a num arul de octet i disponibili n buer-ele sistemului de operare este mai mic, doar octet ii disponibili n acel moment vor transferat i. Dac a n momentul apelului nu exist a nici un octet disponibil n buer-ele sistemului de operare local, funct ia recv() a steapt a sosirea a cel put in un octet. Funct ia returneaz a num arul de octet i transferat i (citit i de pe conexiune). Comportamentul descris mai sus poate modicat prin plasarea uneia din urm atoarele valori n parametrul flags: n cazul n care nu este nici un octet disponibil, funct ia MSG DONTWAIT: recv() returneaz a valoarea 1 si seteaz a variabila global a errno la valoarea EAGAIN; ia recv() a steapt a s a e disponibili cel put in len octet i MSG WAITALL:funct si cite ste exact len octet i. Este important de notat c a datele sunt transmise de la sistemul de operare emit ator spre cel receptor n fragmente (pachete), c a mp art irea datelor n fragmente este independent a de modul n care au fost furnizate prin apeluri send() succesive si c a, n nal, fragmentele ce vor disponibile succesiv pentru receptor sunt independente de fragmentele furnizate n apelurile send(). Ca urmare, este posibil ca emit atorul s a trimit a, prin dou a apeluri send() consecutive, sirurile de octet i abc si def, iar receptorul, apel and repetat recv() cu len=3 si flags=0, s a primeasc a ab, cd si ef. Singurul lucru garantat este c a prin concatenarea tuturor fragmentelor trimise de emit ator se obt ine acela si sir de octet i ca si prin concatenarea tuturor fragmentelor primite de receptor. In cazul nchiderii conexiunii de c atre emit ator, apelurile recv() efectuate de procesul receptor vor citi mai nt ai datele r amase n buer-e, iar

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere 245

dup a epuizarea acestora vor returna valoarea 0. Prin urmare, funct ia recv() returneaz a valoarea 0 dac a si numai dac a emit atorul a nchis conexiunea si toate datele trimise nainte de nchiderea conexiunii au fost deja citite. Dealtfel, valoarea 0 returnat a de recv() sau read() este semnalizarea uzual a a termin arii datelor de citit si se utilizeaz a si pentru a semnaliza sf ar situl unui sier sau nchiderii cap atului de scriere ntr-un pipe UNIX. 8.1.3.10. Funct iile shutdown() si close()
int shutdown(int sd, int how); int close(int sd);

Funct ia shutdown() nchide sensul de emisie, sensul de recept ie sau ambele sensuri de comunicat ie ale conexiunii identicate de indeticatorul de socket sd, conform valorii parametrului how: SHUT_WR, SHUT_RD sau respectiv SHUT_RDWR. Utilitatea principal a a funct iei este nchiderea sensului de emisie pentru a semnaliza celuilalt cap at terminarea datelor transmise (apelurile recv() din procesul de la cel alalt cap at al conexiunii vor returna 0). Funct ia shutdown() poate apelat a doar pe un socket conectat si nu distruge socketul. Funct ia close() distruge socket-ul sd. Dac a socket-ul era un socket conectat n acel moment, nchide ambele sensuri de comunicat ie. Dup a apelul close(), identicatorul de socket este eliberat si poate utilizat ulterior de c atre sistemul de operare pentru a identica socket-uri sau alte obiecte create ulterior. Apelul close() este necesar pentru a elibera resursele ocupate de socket. Poate efectuat oric and asupra oric arui tip de socket. Terminarea unui proces, indiferent de modul de terminare, are ca efect si distrugerea tuturor socket-urilor existente n acel moment, printr-un mecanism identic cu c ate un apel close() pentru ecare socket. 8.1.3.11. Funct iile sendto() si recvfrom()
ssize_t sendto(int sd, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen); ssize_t recvfrom(int sd, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);

Funct ia sendto() trimite o datagram a de pe un socket dgram. Parametrii reprezint a: sd: socket-ul de pe care se transmite datagrama, adic a a c arui adres a va utilizat a ca adres a surs a a datagramei;

c 2008, Radu-Lucian Lup sa


246 8.1. Interfat a de programare socket BSD

to: pointer spre structura ce cont ine adresa de ret ea a destinatarului; tolen reprezint a lungimea structurii pointate de to; buf: pointer spre o zon a de memorie ce cont ine datele utile; len reprezint a lungimea datelor utile. Datele utile sunt un sir arbitrar de octet i. Funct ia returneaz a num arul de octet i ai datagramei trimise (adic a valoarea lui len) n caz de succes si 1 n caz de eroare. Funct ia returneaz a controlul apelantului nainte ca pachetul s a e livrat destinatarului si ca urmare eventuala pierdere a pachetului nu poate raportat a apelantului. Funct ia recvfrom() cite ste din buerele sistemului de operare local urm atoarea datagram a adresat a socket-ului dat ca parametru. Dac a nu exist a nici o datagram a, funct ia a steapt a sosirea urm atoarei datagrame, cu except ia cazului n care flags cont ine valoarea MSG_DONTWAIT, caz n care recvfrom() returneaz a imediat valoarea 1 si seteaz a errno la valoarea EAGAIN. Datagrama este citit a n zona de memorie pointat a de parametrul buf si a c arei dimensiune este dat a n variabila len. Funct ia recvfrom() returneaz a dimensiunea datagramei. Dac a datagrama este mai mare dec at valoara parametrului len, nalul datagramei este pierdut; funct ia recvfrom() nu scrie niciodat a dincolo de len octet i n memoria procesului. Adresa emit atorului datagramei este plasat a de funct ia recvfrom() n variabila pointat a de from. Parametrul fromlen trebuie s a pointeze la o variabil a de tip ntreg a c arui valoare, nainte de apelul recvfrom(), trebuie s a e egal a cu dimensiunea, n octet i, a zonei de memorie alocate pentru adresa emit atorului. Funct ia recvfrom() modic a aceast a variabil a, pun and n ea dimensiunea utilizat a efectiv pentru scrierea adresei emit atorului.

8.1.4. Exemple
8.1.4.1. Comunicare prin conexiune D am mai jos textul surs a ( n C pentru Linux) pentru un client care se conecteaz a la un server TCP/IPv4 specicat prin numele ma sinii si num arul portului TCP (date ca argumente n linia de comand a), i trimite un sir de caractere xat (abcd), dup a care cite ste si a seaz a tot ce trimite server-ul.
#include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <unistd.h> #include <string.h> int main(int argc, char* argv[]) {

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere int port, sd, r; struct hostent* hh; struct sockaddr_in adr; char buf[100]; if(argc!=3){ fprintf(stderr, "Utilizare: cli adresa port\n"); return 1; } memset(&adr, 0, sizeof(adr)); adr.sin_family = AF_INET; if(1!=sscanf(argv[2], "%d", &port)){ fprintf(stderr, "numarul de port trebuie sa fie un numar\n"); return 1; } adr.sin_port = htons(port); hh=gethostbyname(argv[1]); if(hh==0 || hh->h_addrtype!=AF_INET || hh->h_length<=0){ fprintf(stderr, "Nu se poate determina adresa serverului\n"); return 1; } memcpy(&adr.sin_addr, hh->h_addr_list[0], 4); sd=socket(PF_INET, SOCK_STREAM, 0); if(-1==connect(sd, (struct sockaddr*)&adr, sizeof(adr)) ) { perror("connect()"); return 1; } send(sd, "abcd", 4, 0); shutdown(sd, SHUT_WR); while((r=recv(sd, buf, 100, 0))>0){ write(1,buf,r); } if(r==-1){ perror("recv()"); return 1; } close(sd); return 0; } 247

D am n continuare textul surs a pentru un server care a steapt a conectarea unui client pe portul specicat n linia de comand a, a seaz a adresa de la care s-a conectat clientul (adresa IP si num arul de port), cite ste de pe conexiune

c 2008, Radu-Lucian Lup sa


248 8.1. Interfat a de programare socket BSD

si a seaz a pe ecran tot ce transmite clientul (p an a la nchiderea sensului de conexiune de la client la server) si apoi trimite napoi textul xyz.
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <stdio.h> #include <unistd.h> #include <string.h> int main(int argc, char* argv[]) { int sd, sd_c, port, r; struct sockaddr_in my_addr, cli_addr; socklen_t cli_addr_size; char buf[100]; if(argc!=2){ fprintf(stderr, "Utilizare: srv port\n"); return 1; } memset(&my_addr, 0, sizeof(my_addr)); my_addr.sin_family = AF_INET; if(1!=sscanf(argv[1], "%d", &port)){ fprintf(stderr, "numarul de port trebuie sa fie un numar\n"); return 1; } my_addr.sin_port=htons(port); my_addr.sin_addr.s_addr=htonl(INADDR_ANY); sd=socket(PF_INET, SOCK_STREAM, 0); if(-1==bind(sd, (struct sockaddr*)&my_addr, sizeof(my_addr)) ) { perror("bind()"); return 1; } listen(sd, 1); cli_addr_size=sizeof(cli_addr); sd_c = accept(sd, (struct sockaddr*)&cli_addr, &cli_addr_size); printf("client conectat de la %s:%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port) ); close(sd); while((r=recv(sd_c, buf, 100, 0))>0){

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere write(1,buf,r); } if(r==-1){ perror("recv()"); return 1; } send(sd_c, "xyz", 3, 0); close(sd_c); return 0; } 249

8.1.4.2. Comunicare prin datagrame Mai jos este descris un client UDP/IPv4 care se conecteaz a la un server specicat prin numele ma sinii sau adresa IP si num arul de port. Clientul trimite serverului o datagram a de 4 octet i cont in and textul abcd si a steapt a o datagram a ca r aspuns, a c arei cont inut l a seaz a.
#include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> int main(int argc, char* argv[]) { int port, sd, r; struct hostent* hh; struct sockaddr_in adr; socklen_t adr_size; char buf[100]; if(argc!=3){ fprintf(stderr, "Utilizare: cli adresa port\n"); return 1; } memset(&adr, 0, sizeof(adr)); adr.sin_family = AF_INET; if(1!=sscanf(argv[2], "%d", &port)){ fprintf(stderr, "numarul de port trebuie sa fie un numar\n"); return 1; } adr.sin_port = htons(port); hh=gethostbyname(argv[1]); if(hh==0 || hh->h_addrtype!=AF_INET || hh->h_length<=0){

c 2008, Radu-Lucian Lup sa


250 8.1. Interfat a de programare socket BSD fprintf(stderr, "Nu se poate determina adresa serverului\n"); return 1; } memcpy(&adr.sin_addr, hh->h_addr_list[0], 4); sd=socket(PF_INET, SOCK_DGRAM, 0); if(sd==-1){ perror("socket()"); return 1; } if(-1==sendto(sd, "abcd", 4, 0, (struct sockaddr*)&adr, sizeof(adr)) ) { perror("sendto()"); return 1; } adr_size=sizeof(adr); r=recvfrom(sd, buf, 100, 0, (struct sockaddr*)&adr, &adr_size); if(r==-1){ perror("recvfrom()"); return 1; } printf("datagrama primita de la de la %s:%d\n", inet_ntoa(adr.sin_addr), ntohs(adr.sin_port) ); buf[r]=0; printf("continut: \"%s\"\n", buf); close(sd); return 0; }

In continuare descriem un server UDP/IPv4. Acesta a steapt a o datagram a de la un client, a seaz a adresa de la care a fost trimis a datagrama precum si cont inutul datagramei primite. Apoi trimite napoi, la adresa de la care a sosit datagrama de la client, o datagram a cont in and sirul de 3 octet i xyz.
#include #include #include #include #include #include #include <sys/socket.h> <netinet/in.h> <arpa/inet.h> <netdb.h> <stdio.h> <unistd.h> <string.h>

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere int main(int argc, char* argv[]) { int sd, port, r; struct sockaddr_in my_addr, cli_addr; socklen_t cli_addr_size; char buf[101]; if(argc!=2){ fprintf(stderr, "Utilizare: srv port\n"); return 1; } memset(&my_addr, 0, sizeof(my_addr)); my_addr.sin_family = AF_INET; if(1!=sscanf(argv[1], "%d", &port)){ fprintf(stderr, "numarul de port trebuie sa fie un numar\n"); return 1; } my_addr.sin_port=htons(port); my_addr.sin_addr.s_addr=htonl(INADDR_ANY); sd=socket(PF_INET, SOCK_DGRAM, 0); if(-1==bind(sd, (struct sockaddr*)&my_addr, sizeof(my_addr)) ) { perror("bind()"); return 1; } cli_addr_size=sizeof(cli_addr); r=recvfrom(sd, buf, 100, 0, (struct sockaddr*)&cli_addr, &cli_addr_size); if(r==-1){ perror("recvfrom()"); return 1; } printf("datagrama primita de la de la %s:%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port) ); buf[r]=0; printf("continut: \"%s\"\n", buf); sendto(sd, "xyz", 3, 0, (struct sockaddr*)&cli_addr, cli_addr_size); close(sd); return 0; } 251

c 2008, Radu-Lucian Lup sa


252 8.2. Formatarea datelor

8.2. Formatarea datelor


Diferite formate de reprezentare a datelor pe conexiune au fost descrise n capitolul 7. In acest paragraf ne vom ocupa de problemele privind transmiterea si recept ia datelor n astfel de formate.

8.2.1. Formate binare


Formatele binare sunt asem an atoare cu formatele utilizate de programele compilate pentru stocarea datelor n variabilele locale. P an a la un punct, este rezonabil a transmiterea unei informat ii prin instruct iuni de forma
Tip msg; ... send(sd, &msg, sizeof(msg), 0);

si recept ia prin
Tip msg; ... recv(sd, &msg, sizeof(msg), MSG_WAITALL);

unde Tip este un tip de date oarecare declarat identic n ambele programe (emit ator si receptor). Exist a ns a c ateva motive pentru care o astfel de abordare nu este, n general, acceptabil a. Vom descrie n continuare problemele legate de ecare tip de date n parte, precum si c ateva idei privind rezolvarea lor. 8.2.1.1. Tipuri ntregi La transmiterea variabilelor ntregi apar dou a probleme de portabilitate: dimensiunea unui ntreg nu este, n general, standardizat a exact ( n C/C++ un int poate avea 16, 32 sau 64 de bit i); ordinea octet ilor n memorie (big endian sau little endian ) depinde de arhitectura calculatorului. Dac a scriem un program pentru un anumit tip de calculatoare si pentru un anumit compilator, pentru care stim exact dimensiunea unui int si ordinea octet ilor, putem transmite si recept iona date prin secvent e de tipul:
int a; ... send(sd, &a, sizeof(a), 0);

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere 253

pentru emit ator si


int a; ... recv(sd, &a, sizeof(a), MSG_WAITALL);

pentru receptor. Dac a ns a emit atorul este compilat pe o platform a pe care int are 16 bit i si este reprezentat big endian, iar receptorul este compilat pe o platform a pe care int are 32 de bit i si este little endian, cele dou a programe nu vor comunica corect. Pentru a putea scrie programe portabile, biblioteca C standard pe sisteme de tip UNIX cont ine, n header-ul arpa/inet.h: typedef-uri pentru tipuri ntregi de lungime standardizat a (independent a de compilator): uint16_t de 16 bit i si uint32_t de 32 de bit i; funct ii de conversie ntre formatul locat (little endian sau big endian, dup a caz) si formatul big endian, utilizat cel mai adesea pentru datele transmise n Internet. Aceste funct ii sunt: htons() si htonl() (de la host to network, short, respectiv host to network, long ), pentru conversia de la format local la format big endian (numit si format ret ea ), si ntohs() si ntohl() pentru conversia n sens invers. Variantele cu s (htons() si ntohs()) convertesc ntregi de 16 bit i (de tip uint16_t, iar cele cu l convertesc ntregi de 32 de bit i (uint32_t). Implementarea acestor typedef-uri si functii depinde de platform a (de arhitectur a si de compilator). Utilizarea lor permite ca restul sursei programului s a nu depind a de platform a. Transmiterea unui ntreg, ntr-un mod portabil, se face astfel:
uint32_t a; ... a=htonl(a); send(sd, &a, sizeof(a), 0);

uint32_t a; ... recv(sd, &a, sizeof(a), MSG_WAITALL); a=ntohl(a);

Indiferent pe ce platform a sunt compilate, fragmentele de mai sus emit, respectiv recept ioneaz a, un ntreg reprezentat pe 32 de bit i n format big endian.

c 2008, Radu-Lucian Lup sa


254 8.2. Formatarea datelor

8.2.1.2. S iruri de caractere si tablouri Transmiterea sau memorarea unui tablou necesit a transmiterea (respectiv memorarea), ntr-un fel sau altul, a num arului de elemente din tablou. Dou a metode sunt frecvent utilizate n acest scop: transmiterea n prealabil a num arului de elemente si transmiterea unui element cu valoare speciale (terminator) dup a ultimul element. Pe l ang a num arul de elemente efective ale tabloului este necesar a cunoa sterea num arului de elemente alocate. La reprezentarea n memorie sau n siere pe disc, sunt utilizate frecvent tablouri de dimensiune xat a la compilare. Avantajul dimensiunii xe este c a variabilele situate dup a tabloul respectiv se pot plasa la adrese xe si pot accesate direct; dezavantajul este un consum sporit de memorie si o limit a mai mic a a num arului de obiecte ce pot puse n tablou. La transmiterea tablourilor prin conexiuni n ret ea, de regul a num arul de elemente transmise este egal cu num arul de elemente existente n mod real, plus elementul terminator (dac a este adoptat a varianta cu terminator). Nu sunt utilizate tablouri de lungime x a deoarece datele situate dup a tablou oricum nu pot accesate direct. In cazul reprezent arii cu num ar de elemente, receptorul cite ste nt ai num arul de elemente, dup a care aloc a spat iu (sau veric a dac a spat iul alocat este sucient) si cite ste elementele. In cazul reprezent arii cu terminator, receptorul cite ste pe r and ecare elemen si-i veric a valoarea; la nt alnirea terminatorului se opre ste. Inainte de-a citi ecare element, receptorul trebuie s a verice dac a mai are spat iu alocat pentru acesta, iar n caz contrar e s a realoce spat iu pentru tablou si s a copieze n spat iul nou alocat elementele citite, e s a renunt e si s a semnaleze eroare. Exemplul 8.2: Se cere transmiterea unui sir de caractere. Reprezentarea sirului pe conexiune este: un ntreg pe 16 bit i big endian reprezent and lungimea sirului, urmat de sirul propriu-zis (reprezentare diferit a deci de reprezentarea uzual a n memorie, unde sirul se termin a cu un caracter nul). Descriem n continuare emit atorul:
char* s; uint16_t l; ... l=htons(strlen(s)); send(sd, &l, 2, 0); send(sd, s, strlen(s), 0);

si receptorul:

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere char* s; uint16_t l; if(2==recv(sd, &l, 2, MSG_WAITALL) && 0!=(s=new char[l=ntohs(l)+1]) && l==recv(sd, s, l, MSG_WAITALL)){ s[l]=0; // sir citit cu succes } else { // tratare eroare } 255

De remarcat la receptor necesitatea de-a reface terminatorul nul, netransmis prin ret ea. Exemplul 8.3: Se cere transmiterea unui sir de caractere. Reprezentarea pe conexiune va ca un sir de caractere urmat de un caracter nul (adic a reprezentare identic a celei din memorie, dar pe lungime variabil a, egal a cu minimul necesar). Emit atorul este:
char* s; ... send(sd, s, strlen(s)+1, 0);

Receptorul:
char s[500]; int dim_alloc=500, pos, ret; while(pos<dim_alloc-1 && 1==(ret=recv(sd, s+pos, 1, 0)) && s[pos++]!=0) {} if(ret==1 && s[pos-1]==0){ // sir citit cu succes } else { // tratare eroare }

8.2.1.3. Variabile compuse (struct-uri) La prima vedere, variabilele compuse (struct-urile) sunt reprezentate la fel si n memorie si pe conexiune se reprezint a c ampurile unul dup a altul si ca urmare transmiterea lor nu ridic a probleme deosebite. Din p acate ns a, reprezentarea unei structuri n memorie depinde de platform a (arhitectura calculatorului si compilator), datorit a problemelor

c 2008, Radu-Lucian Lup sa


256 8.2. Formatarea datelor

privind alinierea ntregilor. Din considerente legate de arhitectura magistralei de date a calculatorului (detalii ce ies din cadrul cursului de fat a), accesarea de c atre procesor a unei variabile de tip ntreg sau real a c arui adres a n memorie nu este multiplu de un anumit num ar de octet i este pentru unele procesoare imposibil a iar pentru celelalte inecient a. Num arul ce trebuie s a divid a adresa se nume ste aliniere si este de obicei minimul dintre dimensiunea variabilei si l a timea magistralei. Astfel, dac a magistrala de date este de 4 octet i, ntregii de 2 octat i trebuie s a e plasat i la adrese pare, iar ntregii de 4 sau 8 octet i trebuie s a e la adrese multiplu de 4; nu exist a restrict ii cu privire la caractere ( ntregi pe 1 octet). Compilatorul, mpreun a cu funct iile de alocare dinamic a a memoriei, asigur a alinierea recurg and la urm atoarele metode: adaug a octet i nefolosit i ntre variabile, adaug a octet i nefolosit i ntre c ampurile unei structuri, adaug a octet i nefolosit i la nalul unei structuri ce face parte dintr-un tablou, aloc a variabilele de tip structur a la adrese multiplu de o l a timea magistralei. Ca urmare, reprezentarea n memorie a unei strcturi depinde de platform a si, n consecint a, un fragment de cod de forma:
struct Msg { char c; uint32_t i; }; Msg m; ... m.i=htonl(m.i); send(sd, &m, sizeof(m), 0);

este neportabil. In funct ie de l a timea magistralei, de faptul c a alinierea incorect a duce la imposibilitatea acces arii variabilei sau doar la inecient a si, n acest din urm a caz, de opt iunile de compilare, dimensiunea structurii Msg de mai sus poate 5, 6 sau 8 octet i, ntre cele dou a c ampuri ind respectiv 0, 1 sau 3 octet i neutilizat i. Rezolvarea problemei de portabilitate se face transmit and separat ecare c amp:
struct Msg { char c; uint32_t i;

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere }; Msg m; ... m.i=htonl(m.i); send(sd, &m.c, 1, 0); send(sd, &m.i, 4, 0); 257

8.2.1.4. Pointeri Deoarece un pointer este o adres a n cadrul unui proces, transmiterea unui pointer c atre un alt proces este complet inutil a.

8.2.2. Formate text


Intr-un format text, ecare c amp este n esent a un sir de caractere terminat cu spat iu, tab, newline sau un alt caracter specicat prin standard. Metodele descrise pentru transmiterea si recept ionarea unui sir de caractere se aplic a si la obiectele transmise n formate de tip text.

8.2.3. Probleme de robustet e si securitate


Orice apel de funct ie sistem poate e sua din multe motive; ca urmare, la ecare apel send() sau recv() programul trebuie s a verice valoarea returnat a. Un receptor robust trebuie s a se comporte rezonabil la orice fel de date trimise de partenerul de comunicat ie, inclusiv n cazul nc alc arii de c atre acesta a standardului de reprezentare a datelor si inclusiv n cazul n care emit atorul nchide conexiunea n mijlocul transmiterii unei variabile. Validitatea datelor trebuie vericat a ntotdeauna dup a citire. Dac a receptorul a steapt a un ntreg pozitiv, este necesar s a se verice prin program c a num arul primit este ntr-adev ar pozitiv. Este de asemenea necesar s a se stabileasc a si s a se impun a explicit ni ste limite maxime. Astfel, s a presupunem c a programul receptor prime ste un sir de ntregi reprezentat prin lungimea, ca num ar de elemente, pe 32 de bit i, urmat a de elementele propriu-zise. Chiar dac a de principiu num arul de elemente ne a stept am s a e ntre 1 si c ateva sute, trebuie s a ne asigur am c a programul se comport a rezonabil dac a num arul de elemente anunt at de emit ator este 0, 2147483647 (adic a 231 1) sau alte asemenea valori. Comportament rezonabil nseamn a e s a e capabil s a proceseze corect datele, e s a declare eroare si s a ncheie curat operat iile ncepute. Orice program care nu este robust este un risc de securitate.

c 2008, Radu-Lucian Lup sa


258 8.2. Formatarea datelor

8.2.4. Probleme privind costul apelurilor sistem


Apelul funct iilor send() si recv() este scump, n termeni de timp de procesor, deoarece, ind funct ii sistem, necesit a o comutare de drepturi n procesor, salvarea si restaurarea contextului apelului si o serie de veric ari din partea nucleului sistemului de operare; n total, echivalentul c atorva sute de instruct iuni. Acest cost este independent de num arul de octet i transferat i. Este, prin urmare, ecient ca ecare apel send() sau recv() s a transfere c at de mult i octet i se poate. Un program care trimite date este bine s a preg ateasc a datele ntr-o zon a tampon local a si s a trimit a totul printr-un singur apel send(). Un program care prime ste date este bine s a cear a (prin recv()) c ate un bloc mai mare de date si apoi s a analizeze datele primite. Acest mod de lucru se realizeaz a cel mai bine prin intermediul unor funct ii de bibliotec a adecvate. Descriem n continuare funct iile din biblioteca standard C utilizabile n acest scop. Biblioteca poate utilizat a at at pentru emisie si recept ie printr-o conexiune socket stream c at si pentru citire sau scriere ntr-un sier sau pentru comunicare prin pipe sau fo. Elementul principal al bibliotecii este structura FILE, ce cont ine: un identicator de sier deschis, conexiune socket stream, pipe sau fo ; o zon a de memorie tampon, mpreun a cu variabilele necesare gestion arii ei. Funct iile de citire ale bibliotecii sunt fread(), fscanf(), fgets() si fgetc(). Fiecare dintre aceste funct ii extrage datele din zona tampon a structurii FILE dat a ca parametru. Dac a n zona tampon nu sunt sucient i octet i pentru a satisface cererea, aceste funct ii apeleaz a funct ia sistem read() asupra identicatorului de sier din structura FILE pentru a obt ine octet ii urm atori. Fiecare astfel de apel read() ncearc a s a citeasc a c a tiva kilooctet i. Pentru ecare din funct iile de mai sus, dac a datele de returnat aplicat iei se g asesc deja n zona tampon, costul execut iei este de c ateva instruct iuni pentru ecare octet transferat. Ca urmare, la citirea a c ate un caracter o dat a, utilizarea funct iilor de mai sus poate reduce timpul de execut ie de c ateva zeci de ori. Exemplul 8.4: Fie urm atoarele fragmente de cod:
int sd; char s[512]; int i,r; ... while( ((r=recv(sd, s+i, 1, 0))==1 && s[i++]!=0 ) {}

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere 259

si
FILE* f; char s[512]; int i,r; ... while( (r=fgetc(f))!=EOF && (s[i++]=r)!=0) {}

Ambele fragmente de cod citesc de pe un socket stream un sir de octet i terminat cu un caracter nul. Primul fragment de cod apeleaz a recv() o dat a pentru ecare caracter al sirului. Al doilea fragment apeleaz a fgetc() o dat a pentru ecare caracter al sirului. La o mic a parte dintre aceste apeluri, functia fgetc() va apela n spate funct ia sistem read() pentru a citi efectiv datele de pe conexiune; la restul apelurilor, fgetc() returneaz a apelantului c ate un caracter aat deja n zona tampon. Ca rezultat global, al doilea fragment de cod se va executa de c ateva zeci de ori mai repede dec at primul. Testarea nchiderii conexiunii si termin arii datelor se poate face apel and funct ia foef(). De notat c a aceast a funct ie poate s a returneze false chiar dac a s-a ajuns la nalul datelor; rezultatul true este garantat doar dup a o tentativ a nereu sit a de-a citi dincolo de nalul datelor transmise. Pentru scriere, funct iile de bibliotec a corespunz atoare sunt fwrite(), fprintf(), fputs() si fputc(). Aceste funct ii scriu datele n zona tampon din structura FILE. Transmiterea efectiv a pe conexiune (sau scrierea n sier) se face automat la umplerea zonei tampon. Dac a este necesar s a ne asigur am c a datele au fost transmise efectiv (sau scrise n sier), funct ia fflush() efectueaz a acest lucru. Funct ia fclose() de asemenea trimite sau scrie ultimele date r amase n zona tampon. Asocierea unei zone tampon unei conexiuni deja deschise se face apel and funct ia fdopen(). Funct ia fdopen() prime ste doi parametri. Primul parametru este identicatorul de socket c aruia trebuie s a-i asocieze zona tampon (identicatorul returnat de funct ia socket() sau accept()). Al doilea parametru specic a funct iei fdopen() dac a trebuie s a asocieze zona tampon pentru citire sau pentru scriere; este de tip sir de caractere si poate avea valoarea "r" pentru citire sau "w" pentru scriere. Pentru un socket stream, cele dou a sensuri function and complet independent, aceluia si socket i se pot asocia dou a zone tampon (dou a structuri FILE), c ate una pentru ecare sens. Funct ia fclose() scrie informat iile r amase n zona tampon (dac a zona tampon a fost creat a pentru sensul de scriere), elibereaz a memoria alocat a zonei tampon si nchide conexiunea.

c 2008, Radu-Lucian Lup sa


260 8.3. Probleme de concurent a n comunicat ie

8.3. Probleme de concurent a n comunicat ie


O particularitate a majorit a tii programelor ce comunic a n ret ea este aceea c a trebuie s a r aspund a prompt la mesaje provenind din surse diferite si ntr-o ordine necunoscut a dinainte. S a lu am de exemplu un server ssh ( 11.2.1). Serverul poate avea mai mult i client i conectat i simultan. La ecare moment, este imposibil de prezis care dintre client i va trimite primul o comand a. Dac a serverul execut a un apel recv() blocant de pe socket-ul unui client, serverul va pus n a steptare p an a ce acel client va trimite date. Este posibil ca utilizatorul ce comand a acel client s a stea 10 minute s a se g andeasc a. Dac a n acest timp un alt client trimite o comand a, serverul nu o va putea ,,vedea c at timp este blocat n a steptarea datelor de la primul client. Ca urmare, datele de la al doilea client vor a stepta cel put in 10 minute pentru a procesate. Exist a mai multe solut ii la problema de mai sus: Serverul cite ste de la client i, pe r and, prin apeluri recv() neblocante (cu agul MSG_DONTWAIT):
for(i=0 ; true ; i=(i+1)%nr_clienti){ r=recv(sd[i], buf, dim, MSG_DONTWAIT); if(r>=0 || errno!=EAGAIN){ /* prelucreaza mesajul primit sau eroarea aparuta */ } }

In acest fel, serverul nu este pus n a steptare dac a un client nu i-a trimis nimic. Dezavantajul solut iei este acela c a, dac a o perioad a de timp nici un client nu trimite nimic, atunci bucla se execut a n mod repetat, consum and inutil timp de procesor. Pentru evitarea inconvenientului solut iei anterioare, sistemele de operare de tip UNIX ofer a o funct ie sistem, numit a select(), care prime ste o list a de identicatori de socket si, opt ional, o durat a, si pune procesul n a steptare p an a c and e exist a date disponibile pe vreunul din socket -ii dat i, e expir a durata de timp specicat a. O abordare complet diferit a este aceea de-a crea mai multe procese sau, n sistemele de operare moderne, mai multe re de execut ie (thread-uri) n cardul procesului server ecare proces sau r de execut ie urm arind un singur client. In acest caz, procesul sau rul de execut ie poate executa recv() blocant asupra socket-ului corespunz ator clientului s au. In lipsa

c 2008, Radu-Lucian Lup sa


Capitolul 8. Programarea n ret ea introducere 261

activit a tii client ilor, ecare proces sau r de execut ie al serverului este blocat n apelul recv() asupra socket-ului corespunz ator. In momentul n care un client trimite date, nucleul sistemului de operare treze ste procesul sau rul ce executa recv() pe socket-ul corespunz ator; procesul sau rul execut a prelucr arile necesare dup a care probabil execut a un nou recv() blocant. Cazul unui server cu mai mult i client i nu este singura situat ie n care este nevoie de a urm ari simultan evenimente pe mai multe canale. Alte situat ii sunt: un client care trebuie s a urm areasc a simultan act iunile utilizatorului si mesajele sosite de la server; un server care poate trimite date cu debit mai mare dec at capacitatea ret elei sau capacitatea de prelucrare a clientului; n acest caz serverul are de urm arit simultan, pe de o parte noi cereri ale client ilor, iar pe de alt a parte posibilitatea de-a trimite noi date spre client i n urma faptului c a vechile date au fost prelucrate de ace stia. un server care trebuie s a preia mesaje de la client ii conectat i si, n acela si timp, s a poat a accepta client i noi. Un aspect important ce trebuie urm arit n proiectarea unui server concurent este servirea echitabil a a client ilor, independent de comportamentul acestora. Dac a un client trimite cereri mai repede dec at este capabil serverul s a-l serveasc a, serverul trebuie s a execute o parte din cereri, apoi s a serveasc a cereri ale celorlalt i client i, apoi s a revin a la primul si s a mai proceseze o parte din cereri si a sa mai departe. Nu este permis ca un client care inund a serverul cu cereri s a acapareze ntreaga putere de calcul a serverului si ceilalt i client i s a a stepte la innit.

S-ar putea să vă placă și