Aplicatie Bazata pe Tehnologia Java

Capitolul I. Arhitectura rețelelor de calculatoare.

Modelul ISO/OSI. Protocolul TCP/IP

1.1 Arhitectura rețelelor de calculatoare

În rețelele moderne de calculatoare, comunicația între utilizatori se realizează printr-o succesiune de activități organizate de o manieră ierarhizată. În cadrul fiecărui dispozitiv ce face parte din sistemul rețelei de calculatoare se delimitează un număr de n diviziuni ierarhice – numite niveluri – fiecare realizat pe baza celui inferior și interacționând doar cu straturile adiacente. Rolul oricărui nivel este de a oferi nivelului superior anumite funcții – numite servicii – ce concură la buna funcționare a întregii rețele și pe care se bazează următorul nivel pentru a realiza serviciile pe care le oferă la rândul lui. Această ierarhizare oferă avantajele modularizării.

Elementul activ ce generează o funcție în cadrul unui nivel se numește entitate sau modul. Acesta poate fi de tip software (un anumit program) sau de tip hardware (de exemplu un chip inteligent de intrare-ieșire).

Conform organizării pe niveluri ierarhice a comunicației de rețea, nivelul (de rang) n al unui dispozitiv comunică doar cu nivelul de același rang n al oricărui alt dispozitiv.

Algoritmul distribuit (la mai multe sau la toate dispozitivele între care se face comunicația în rețea) și care traduce regulile și convențiile stabilite (cunoscute și utilizate de toate dispozitivele participante) pentru realizarea unei comunicații (sesiuni) între entitățile pereche de la nivelul n formează un protocol de nivel n.

Convențiile într-un protocol sunt:

semantice (de semnificație), care privesc semnificația semnalelor vehiculate: semnale informaționale (date), semnale de stare (de supraveghere) și semnale de control;

de sintaxă (de asamblare a semnalelor), ce specifică dimensiunile, amplasamentul și rolul elementelor constitutive ale semnalelor.

Regulile unui protocol (formând succesiunea de desfășurare a protocolului) rezultă din combinarea convențiilor susmenționate și reprezintă secvența de semnale elementare, numite unități de date de protocol (Protocol Data Unit [PDU]), schimbate între ele de entitățile pereche pentru realizarea unei comunicații.

Mulțimea nivelurilor ierarhice și a protocoalelor aferente formează arhitectura rețelei.

Cu excepția primului nivel (cel inferior), la care se realizează efectiv o conexiune fizică între entitățile pereche, la toate celelalte niveluri comunicația între entitățile pereche este virtuală, căci informațiile nu sunt transferate direct între nivelurile de rang n ale dispozitivelor comunicante ci, în cadrul fiecărui dispozitiv emițător mesajele informaționale (împreună cu niște informații de control) sunt trecute succesiv nivelurilor imediat inferioare, până la nivelul 1 (unde se face transmisia efectivă a semnalelor între dispozitivele comunicante) și apoi la dispozitivul receptor, trecute de la fiecare nivel la succesorul lui, până ajung la nivelul n.

Fig.1.1 Niveluri, protocoale și interfețe

Prin interfețe s înțelege un set de convenții și reguli utilizate între entități nesimilare dintr-un același dispozitiv, unități ce interacționează printr-o conexiune fizică.

Între oricare două niveluri adiacente dintr-un dispozitiv comunicant al rețelei există o interfață care definește ce servicii oferă nivelul inferior celui superior.

Proiectarea interfețelor trebuie să răspundă la două cerințe:

minimizarea cantității de informație care se vehiculează între niveluri;

insensibilizarea serviciilor pe care le oferă un nivel față de modul de implementare al acestuia.

Problematica proiectării (protocoalelor) nivelurilor ierarhice ale unei rețele de calculatoare trebuie să aibă în vedere următoarele:

prevederea unei modalități de stabilire a conexiunilor la fiecare nivel, în particular a unui mod de specificare a adreselor interlocutorilor;

prevederea unei modalități de întrerupere a conexiunilor la terminarea comunicației;

stabilirea regulilor privind transferul informațiilor și anume:

comunicație simplex, comunicație semi-duplex sau comunicație duplex

numărul de canale logice care corespund unei conexiuni și prioritățile lor

asigurarea protecției la erori (error control), adică prevenirea, depistarea și eventual corectarea erorilor introduse de perturbațiile care afectează canalul fizic, prin:

utilizarea unor coduri detectoare și chiar corectoare de erori;

prevederea unor posibilități ca receptorul de mesaje să poată confirma dispozitivului emițător dacă mesajele ce i-au fost adresate au fost corect recepționate sau nu;

posibilitatea refacerii ordinii fragmentelor de mesaj la recepție, atunci când modul de transmisie nu păstrează succesiunea de la emisia lor.

evitarea – la fiecare nivel ierarhic – a „înecării” cu mesaje a unui receptor lent de către un emițător rapid, utilizând o reacție care să informeze dispozitivul emițător despre starea curentă a receptorului, stopându-i, la nevoie, temporar emisia;

tratarea lungimilor inacceptabile de mesaje în cazurile:

unor mesaje prea lungi în raport cu durata proceselor ce comunică între ele – prin dezasamblarea mesajelor în fragmente, transmiterea lor (succesivă sau simultană, pe aceeași cale sau pe trasee diferite – după cum permit canalele și metoda de comutație folosită) și reasamblarea lor la recepție;

unor mesaje neeconomic de scurte și având același destinatar – prin concatenarea lor la emisie și descompunerea la recepție a mesajului rezultat în componentele sale;

posibilitatea utilizării unei aceleiași conexiuni de la un anumit nivel pentru transmisia mai multor mesaje necorelate de la nivelurile superioare – prin operații de multiplexare – demultiplexare

alegerea traseului dintre sursă și destinație, atunci când există – la oricare nivel – mai multe posibilități.

1.2. Modele de referință pentru arhitectura rețelelor de calculatoare

La apariția rețelelor de calculatoare, fiecare producător de echipamente de calcul avea propriile sale protocoale de comunicație, ceea ce făcea imposibilă interconectarea calculatoarelor de proveniențe diferite. Pe de altă parte subrețelele de comunicație care își ofereau serviciile pentru WAN erau și ele destul de diferite, de la companii private de telecomunicații publice – ca, de pildă, American Telephone & Telegraph (AT&T) și Bell Communications Research (Bellcore) din S.U.A. – și până la societățile de stat pentru poștă, telegraf, telefon, adesea și pentru radio și televiziune.

Pentru ca utilizatorii să poată să-și conecteze între ele calculatoarele de orice producție, prin intermediul oricărui serviciu public de telecomunicație, a rezultat necesitatea coordonării activităților de proiectare, realizare și exploatare a mijloacelor de comunicație, precum și a unei standardizări a acestora ca și a metodelor de transmisie a informațiilor.

1.2.1 Modelul de referință OSI

Modelul OSI se bazează pe o propunere dezvoltată de către Organizația Internațională de Standardizare (International Standards Organization – OSI) ca un prim pas către standardizarea internațională a protocoalelor folosite pe diferite niveluri (Day și Zimmermann, 1983). Modelul se numește ISO OSI (Open Systems Interconnection – Interconectarea sistemelor deschise), pentru că el se ocupă de conectarea sistemelor deschise – adică de sisteme deschise comunicării cu alte sisteme.

Modelul OSI cuprinde șapte niveluri. Principiile aplicate pentru a se ajunge la cele șapte niveluri sunt următoarele:

Un nivel trebuie creat atunci când este nevoie de un nivel de abstractizare diferit.

Fiecare nivel trebuie să îndeplinească un rol bine definit.

3. Funcția fiecărui nivel trebuie aleasă acordându-se atenție definirii de protocoale standardizate pe plan internațional.

4. Delimitarea nivelurilor trebuie făcută astfel încât să se minimizeze fluxul de informații prin interfețe.

5. Numărul de niveluri trebuie să fie suficient de mare pentru a nu fi nevoie să se introducă în același nivel funcții diferite și suficient de mic pentru ca arhitectura să rămână funcțională.

Modelul de referință OSI (ISO) este reprezentat în figura următoare:

Fig. 1.2 Modelul ISO/OSI

În cele ce urmează, prezentăm succint rolul fiecărui nivel ierarhic al modelului de referință OSI (ISO).

Modelul OSI nu reprezintă în sine o arhitectură de rețea, pentru că nu specifică serviciile și protocoalele utilizate la fiecare nivel. Modelul spune numai ceea ce ar trebui să facă fiecare nivel. ISO a produs de asemenea standarde pentru fiecare nivel, însă aceste standarde nu fac parte din modelul de referință propriu-zis. Fiecare din standardele respective a fost publicat ca un standard internațional separat.

Nivelul fizic se ocupă de transmiterea biților printr-un canal de comunicație. Proiectarea trebuie să garanteze că atunci când unul din capete trimite un bit 1, acesta e receptat în cealaltă parte ca, un bit 1, nu ca un bit 0. Problemele tipice se referă la câți volți trebuie utilizați pentru a reprezenta un 1 și câți pentru un 0, dacă transmisia poate avea loc simultan în ambele sensuri, cum este stabilită conexiunea inițială și cum este întreruptă când au terminat de comunicat ambele părți, câți pini are conectorul de rețea și la ce folosește fiecare pin. Aceste aspecte de proiectare au o legătură strânsă cu interfețele mecanice, electrice, funcționale și procedurale, ca și cu mediul de transmisie situat sub nivelul fizic.

Sarcina principală a nivelului legătură de date este de a transforma un mijloc oarecare de transmisie într-o linie care să fie disponibilă nivelului rețea fără erori de transmisie nedetectate. Nivelul legătură de date realizează această sarcină obligând emițătorul să descompună datele de intrare în cadre de date (în mod tipic câteva sute sau câteva mii de octeți), să transmită cadrele secvențial și să prelucreze cadrele de confirmare trimise înapoi de receptor. Deoarece nivelul fizic nu face decât să accepte și să transmită un flux de biți, fără să se preocupe de semnificația sau de structura lor, responsabilitatea pentru marcarea și recunoașterea delimitatorilor între cadre îi revine nivelului legătură de date. Aceasta se poate realiza prin atașarea unor șabloane speciale de biți la începutul și la sfârșitul cadrului. În cazul în care șabloanele respective de biți pot apărea accidental în datele propriu-zise, trebuie luate măsuri speciale de precauție pentru ca aceste șabloane să nu fie incorect interpretate ca delimitatori de cadre.

Un zgomot apărut pe linie poate distruge un cadru în întregime. În acest caz, programele nivelului legătură de date de pe mașina sursă pot să retransmită cadrul. Transmiterile multiple ale aceluiași cadru introduc posibilitatea cadrelor duplicate. Un cadru duplicat poate apărea la transmisie în situația în care s-au pierdut cadrele de confirmare trimise de la receptor înapoi spre emițător. Rezolvarea problemelor datorate cadrelor deteriorate, pierdute sau duplicate cade în sarcina nivelului legătură de date. Acesta poate oferi nivelului rețea câteva clase de servicii diferite, fiecare de o calitate și un preț diferit.

O altă problemă care apare la nivelul legătură de date (și, de asemenea, la majoritatea nivelurilor superioare) este evitarea inundării unui receptor lent cu date provenite de la un emițător rapid. În acest scop sunt necesare mecanisme de reglare a traficului care să permită emițătorului să afle cât spațiu tampon deține receptorul la momentul curent. Controlul traficului și tratarea erorilor sunt deseori integrate.

Nivelul rețea se ocupă de controlul funcționării subrețelei. O problemă cheie în proiectare este determinarea modului în care pachetele sunt dirijate de la sursă la destinație. Dirijarea se poate baza pe tabele statistice care sunt "cablate" intern în rețea și care sunt schimbate rar. Traseele pot fi de asemenea stabilite la începutul fiecărei conversații, de exemplu la începutul unei sesiuni la terminal. In sfârșit, dirijarea poate fi foarte dinamică, traseele determinându-se pentru fiecare pachet în concordarate, pierdute sau duplicate cade în sarcina nivelului legătură de date. Acesta poate oferi nivelului rețea câteva clase de servicii diferite, fiecare de o calitate și un preț diferit.

O altă problemă care apare la nivelul legătură de date (și, de asemenea, la majoritatea nivelurilor superioare) este evitarea inundării unui receptor lent cu date provenite de la un emițător rapid. În acest scop sunt necesare mecanisme de reglare a traficului care să permită emițătorului să afle cât spațiu tampon deține receptorul la momentul curent. Controlul traficului și tratarea erorilor sunt deseori integrate.

Nivelul rețea se ocupă de controlul funcționării subrețelei. O problemă cheie în proiectare este determinarea modului în care pachetele sunt dirijate de la sursă la destinație. Dirijarea se poate baza pe tabele statistice care sunt "cablate" intern în rețea și care sunt schimbate rar. Traseele pot fi de asemenea stabilite la începutul fiecărei conversații, de exemplu la începutul unei sesiuni la terminal. In sfârșit, dirijarea poate fi foarte dinamică, traseele determinându-se pentru fiecare pachet în concordanță cu traficul curent din rețea.

Dacă în subrețea există prea multe pachete simultan, ele vor intra unul pe traseul celuilalt și astfel se vor produce gâtuiri. Controlul unor astfel de congestii îi revine tot nivelului rețea.

Multe probleme pot apărea când un pachet trebuie să călătorească dintr-o rețea în alta ca să ajungă la destinație. Modul de adresare folosit de a doua rețea poate să difere de cel pentru prima. A doua rețea poate chiar să nu accepte deloc pachetul pentru că este prea mare. Rezolvarea acestor probleme în vederea interconectării rețelelor eterogene este sarcina nivelului rețea.

Rolul principal al nivelului transport este să accepte date de la nivelul sesiune, să le descompună, dacă este cazul, în unități mai mici, să transfere aceste unități nivelului rețea și să se asigure că toate fragmentele sosesc corect la celălalt capăt. În plus, toate acestea trebuie făcute eficient si într-un mod care izolează nivelurile de mai sus de inevitabilele modificări în tehnologia echipamentelor.

În condiții normale nivelul transport creează o conexiune de rețea distinctă pentru fiecare conexiune de transport cerută de nivelul sesiune. În cazul în care conexiunea de transport necesită o productivitate mare, nivelul transport poate totuși să creeze conexiuni de rețea multiple și să dividă datele prin conexiunile de rețea astfel încât productivitatea să crească. Pe de altă parte, dacă crearea și întreținerea unei conexiuni de rețea este costisitoare, nivelul transport ar putea reduce costul prin multiplexarea câtorva conexiuni de transport pe aceeași conexiune de rețea. În oricare dintre cazuri, nivelului transport i se cere să facă mutiplexarea transparentă pentru nivelul sesiune.

Nivelul sesiune permite utilizatorilor de pe mașini diferite să stabilească între ei sesiuni. Ca și nivelul transport, o sesiune permite transportul obișnuit de date, dar furnizează totodată și servicii îmbunătățite, utile în anumite aplicații. O sesiune poate fi utilizată pentru a permite unui utilizator să se conecteze la distanță pe un sistem cu divizarea timpului sau să transfere un fișier între două mașini.

Unul dintre serviciile nivelului sesiune se referă la controlul dialogului. Sesiunile pot permite să se realizeze trafic în ambele sensuri simultan, sau numai într-un sens odată. Dacă este permis traficul într-un singur sens odată (analog drumurilor cu sens unic), nivelul sesiune poate ajuta să se țină evidența emițătorilor cărora le vine rândul să transmită.

Nivelul prezentare îndeplinește câteva funcții care sunt solicitate suficient de des pentru ca, în loc să fie lăsat fiecare utilizator să rezolve problemele, să se justifice găsirea unei soluții generale. În particular, spre deosebire de toate nivelurile inferioare, care se ocupă numai de transferul sigur al biților dintr-un loc în altul, nivelul prezentare se ocupă de sintaxa și semantica informațiilor transmise.

Nivelul aplicație conține o varietate de protocoale frecvent utilizate. De exemplu, în lume există sute de tipuri de terminale incompatibile. Gândiți-vă la impasul în care se află un editor în mod ecran care trebuie să lucreze într-o rețea cu multe tipuri diferite de terminale, fiecare cu un aspect diferit al ecranului și cu secvențe ESCAPE diferite pentru introducerea și ștergerea textului, mutarea cursorului etc.

O modalitate de a rezolva problema este să se definească un terminal virtual de rețea abstract și să se scrie editoare și alte programe care știu să lucreze cu acesta. Pentru a putea lucra cu orice tip de terminal, este necesar un program care să pună în corespondență funcțiile terminalului virtual de rețea și terminalul real. De exemplu, atunci când editorul mută cursorul din terminalul virtual în colțul stânga-sus al ecranului, programul trebuie să aplice secvența potrivită de comenzi pentru terminalul real, astfel încât să se mute și cursorul acestuia. Toate programele terminalului virtual se află pe nivelul aplicație.

Un alt rol al nivelului aplicație este transferul fișierelor. Sisteme de fișiere diferite au convenții de nume diferite, moduri diferite de a reprezenta liniile de text, și așa mai departe. Transferul unui fișier între două sisteme de fișiere diferite presupune rezolvarea acestor incompatibilități și a altora de același gen. Și acest lucru cade tot în seama nivelului aplicație, la fel ca și poșta electronică, introducerea lucrărilor la distanță, examinarea cataloagelor și diverse alte facilități cu scop general sau particular.

1.2.2 Modelul de referință TCP/IP

ARPANET, strămoșul Internet-ului, a fost o rețea de cercetare sponsorizată de către DoD (U.S. Department of Defense – Departamentul de Apărare al Statelor Unite). În cele din urmă, rețeaua a ajuns să conecteze între ele, utilizând linii telefonice închiriate, sute de rețele universitare și guvernamentale. Atunci când au fost adăugate, mai târziu, rețele prin satelit și radio, interconectarea acestora cu protocoalele existente a pus diferite probleme. Era nevoie de o nouă arhitectură de referință. De aceea, posibilitatea de a interconecta fără probleme mai multe tipuri de rețele a reprezentat de la bun început un obiectiv de proiectare major. Această arhitectură a devenit cunoscută mai târziu sub denumirea de modelul de referință TCP/IP, dată după numele celor două protocoale fundamentale utilizate. Arhitectura respectivă a fost definită prima dată în 1974 (Cerf și Kahn), o perspectivă ulterioară fiind prezentată în 1985 (Leiner). Filozofia de proiectare din spatele modelului este discutată în 1988 (Clark).

Dată fiind îngrijorarea Departamentului de Apărare că o parte din prețioasele sale gazde, rutere și porți de interconectare ar putea fi distruse dintr-un moment în altul, un alt obiectiv major a fost ca rețeaua să poată supraviețui pierderii echipamentelor din subrețea fără a fi întrerupte conversațiile existente. Cu alte cuvinte, DoD dorea ca atâta timp cât funcționau mașina sursă și mașina destinație, conexiunile să rămână intacte, chiar dacă o parte din mașini sau din liniile de transmisie erau brusc scoase din funcțiune. Mai mult, era nevoie de o arhitectură flexibilă, deoarece se aveau în vedere aplicații cu cerințe divergente, mergând de la transferul de fișiere până la transmiterea vorbirii în timp real.

Toate aceste cerințe au condus la alegerea unei rețele cu comutare de pachete bazată pe un nivel inter-rețea fără conexiuni. Acest nivel, numit nivelul internet, este axul pe care se centrează întreaga arhitectură. Rolul său este de a permite gazdelor să emită pachete în orice rețea și a face ca pachetele să circule independent până la destinație (fiind posibil ca aceasta să se găsească pe o altă rețea). Pachetele pot chiar să sosească într-o ordine diferită față de cea în care au fost trimise, caz în care – dacă se dorește furnizarea lor ordonată rearanjarea cade în sarcina nivelurilor de mai sus. De observat că "internet" este folosit aici într-un sens generic, chiar dacă acest nivel este prezent și în Internet.

Aici, analogia este cu sistemul de poștă (clasică). O persoană dintr-o anumită țară poate depune într-o cutie poștală mai multe scrisori internaționale și, cu puțin noroc, majoritatea scrisorilor vor ajunge la adresa corectă din țara de destinație. Probabil că scrisorile vor trece pe drum prin mai multe oficii de cartare, dar acest lucru se face transparent pentru utilizatori. Mai mult, faptul că fiecare țară (adică fiecare rețea) are propriile timbre, propriile mărimi favorite de plicuri și propriile reguli de livrare este ascuns beneficiarilor.

Nivelul internet definește oficial un format de pachet și un protocol numit IP (Internet Protocol – protocol Internet). Sarcina nivelului internet este să furnizeze pachete IP către destinație. Problemele majore se referă la dirijarea pachetelor și evitarea congestiei. În consecință, este rezonabil să spunem că nivelul internet din TCP/IP funcționează asemănător cu nivelul rețea din OSI. Figura 1.3 arată această corespondență.

OSI TCP/IP

1.

2.

3. Nu există

4. în acest model

5.

6.

7.

fig 1.3 Modelul de referință TCP/IP.

Nivelul situat deasupra nivelul internet din modelul TCP/IP este frecvent numit nivelul transport. Acesta este proiectat astfel încât să permită conversații între entitățile pereche din gazdele sursă și, respectiv, destinație, la fel ca în nivelul transport OSI. În acest sens au fost definite două protocoale capăt-la-capăt. Primul din ele, TCP (Transmission Control Protocol – protocolul de control al transmisiei), este un protocol sigur orientat pe conexiuni care permite ca un flux de octeți trimiși de pe o mașină să ajungă fără erori pe orice altă mașină din inter-rețea. Acest protocol fragmentează fluxul de octeți în mesaje discrete și pasează fiecare mesaj nivelului internet. La destinație procesul TCP receptor reasamblează mesajele primite în flux de ieșire. TCP tratează totodată controlul fluxului pentru a se asigura că un emițător rapid nu inundă un receptor lent cu mai multe mesaje decât poate acesta să prelucreze.

Al doilea protocol din nivel, UDP (User Datagram Protocol – protocolul datagramelor utilizator), este un protocol nesigur, fără conexiuni, destinat aplicațiilor care doresc să utilizeze propria lor secvențiere și control al fluxului, și nu pe cele asigurate de TCP. Protocolul UDP este de asemenea mult folosit pentru interogări întrebare-răspuns dintr-un foc, client-server, și pentru aplicații în care comunicarea promptă este mai importantă decât comunicarea cu acuratețe, așa cum sunt aplicațiile de transmisie a vorbirii și a imaginilor video. Relația dintre IP, TCP și UDP este prezentată în fig. 1.4. De când a fost dezvoltat acest model, IP a fost implementat pe multe alte rețele.

Fiecare proces care dorește să comunice cu un alt proces (așa cum este SMTP, FTP, TELNET etc.) se identifică față de suita de protocoale TCP/IP prin unul sau mai multe porturi. Un port, în acest context este un ID de 16 biți folosit de către protocolul gazdă-gazdă pentru a identifica la care protocoale de nivel înalt sau programe de aplicație trebuie livrate mesajele recepționate.

Un proces TCP/IP este identificat de :

adresa IP a echipamentului pe care se execută;

numărul portului prin care comunică cu TCP/IP.

Aceste două ID-uri, care împreună identifică în mod unic fiecare proces sunt denumite socket (adresă IP,număr port).

IP este un protocol de nivel rețea neorientat pe conexiune, care face tot posibilul pentru transmiterea cu succes a datelor. TCP este un protocol orientat pe conexiune cap-la-cap, fiabil, putând oferi conexiuni logice între perechi de procese. Fiecare proces dintr-un mediu TCP/IP este identificat în mod unic de un socket. Porturile sunt utilizate pentru demultiplexarea datelor ce sunt recepționate pentru procesele adecvate de la nivelele superioare, așa cum a fost cazul și la UDP. În TCP, o conexiune este în mod unic definită de o pereche de socket-uri. Cu alte cuvinte, TCP este definit de o pereche de procese, pe un același sistem, sau pe un sistem diferit, ce schimbă informații între ele.

Scopul primar al TCP este de a oferi conexiuni logice fiabile (sigure) între perechi de procese.

Modelul TCP/IP nu conține niveluri sesiune sau prezentare. Acestea nu au fost incluse pentru că nu s-a simțit nevoia lor. Experiența modelului OSI a dovedit că această viziune a fost corectă: în majoritatea aplicațiilor, nivelurile respective nu sunt de mare folos.

Nivelul

Aplicație

Protocoale Transport

Rețea

Rețele Fizic+

Legătură de date

Fig. 1.4. Protocoalele și rețelele din modelul TCP/IP inițial

Deasupra nivelului transport se află nivelul aplicație. Acesta conține toate protocoalele de nivel mai înalt. Așa cum se vede din fig. 1.4, primele protocoale de acest gen includeau terminalul virtual (TELNET), transferul de fișiere (FTP) și poșta electronică (SMTP). Protocolul de terminal virtual permite unui utilizator de pe o mașină să se conecteze și să lucreze pe o mașină aflată la distanță. Protocolul de transfer de fișiere pune la dispoziție o modalitate de a muta eficient date de pe o mașină pe alta. Poșta electronică a fost la origine doar un tip de transfer de fișiere, dar ulterior a fost dezvoltat un protocol specializat pentru acest serviciu. Pe parcursul anilor, la aceste protocoale s-au adăugat multe altele, așa cum sunt Serviciul Numelor de Domenii (Domain Name Service – DNS) pentru stabilirea corespondenței dintre numele gazdelor și adresele rețelelor, NNTP, protocolul utilizat pentru a transfera articole de știri, HTTP, folosit pentru aducerea paginilor de pe Web, și multe altele.

Sub nivelul internet se află necunoscutul (nivelul gazdă rețea). Modelul de referință TCP/IP nu spune mare lucru despre ce se întâmplă acolo, însă menționează că gazda trebuie să se lege la rețea pentru a putea trimite pachete IP, folosind un anumit protocol. Acest protocol nu este definit și variază de la gazdă la gazdă și de la rețea la rețea .

Dintre facilitățile protocolului TCP amintim doar câteva, cele mai importante:

Transfer de date. Din punct de vedere al aplicației TCP transferă un șir continuu de octeți prin rețeaua interconectată. Aplicația nu trebuie să segmenteze datele în blocuri sau pachete deoarece TCP face acest lucru prin gruparea octeților în segmente TCP, care sunt apoi pasate IP-ului pentru transmisie către destinație. TCP determină cum să segmenteze datele și le transmite în modul cel mai convenabil pentru el.

Fiabilitate. TCP asignează un număr de secvență fiecărui segment TCP transmis și așteaptă o achitare (ACK) de la perechea receptoare TCP. Dacă ACK nu este recepționat într-un interval de timp prestabilit, segmentul este retransmis. TCP-ul care recepționează utilizează numerele de secvență pentru a rearanja segmentele dacă ele sosesc în dezordine, și pentru a elimina eventualele segmente duplicat.

Controlul fluxului. TCP-ul ce recepționează semnalează transmițătorului numărul de octeți pe care poate să-i recepționeze dincolo de ultimul segment TCP recepționat, fără a cauza o depășire a capacității buffer-elor interne. Această indicație este transmisă în ACK în forma celui mai mare număr de secvență pe care poate s-o recepționeze fără probleme (această abordare este cunoscută ca mecanism de fereastra – window mechanism).

Multiplexare. Este realizată prin utilizarea mecanismului de porturi.

Conexiuni logice. Pentru a obține fiabilitate și control al fluxului de date, TCP trebuie să mențină o anumită informație de stare corespunzătoare fiecărei legături de date. Combinația de stare, socket-uri, numere de secvență și dimensiuni de ferestre de transmisie, este denumită o conexiune logică (cunoscută și ca circuit virtual).

Comunicație duplex. TCP asigură suport pentru legături de date concurente în ambele direcții.

1.2.3 Critici asupra modelului de referință TCP/IP

Modelul și protocoalele TCP/IP au și ele problemele lor. Mai întâi, modelul nu face o distincție clară între conceptele de serviciu, interfață și protocol. O practică recomandabilă în ingineria programării este să se facă diferența între specificație și implementare, ceea ce OSI face cu multă atenție, pe când TCP/IP nu face. De aceea, modelul TCP/IP nu este un ghid prea bun de proiectare a rețelelor noi folosind tehnologii noi.

În al doilea rând, modelul TCP/IP nu este de loc general și nu este aproape deloc potrivit pentru descrierea altor stive de protocoale în afara celei TCP/IP. De exemplu, descrierea SNA-ului folosind modelul TCP/IP ar fi aproape imposibilă.

În al treilea rând, nivelul gazdă-rețea nu este deloc un nivel – în sensul normal în care este folosit termenul în contextul protocoalelor organizate pe niveluri – ci este o interfață (între nivelurile rețea și legătură de date). Distincția între o interfață si un nivel este crucială și de aceea trebuie să i se acorde atenția cuvenită.

În al patrulea rând, modelul TCP/IP nu distinge (și nici măcar nu menționează) nivelurile fizic și legătură de date. Acestea sunt complet diferite. Nivelul fizic are de-a face cu caracteristicile de transmisie ale comunicațiilor prin cablu de cupru, fibre optice sau radio. Rolul nivelului legătură de date este să delimiteze începutul și sfârșitul cadrelor și să le transporte dintr-o parte în alta cu gradul de siguranță dorit. Un model corect ar trebui să includă ambele niveluri ca niveluri separate. Modelul TCP/IP nu face acest lucru.

În sfârșit, deși protocoalele IP și TCP au fost atent gândite și bine implementate, multe din celelalte protocoale erau ad-hoc, fiind în general opera câtorva absolvenți care tot "meștereau" la ele până oboseau. Implementările protocoalelor erau apoi distribuite gratuit; ca urmare, ele erau larg utilizate, fără să li se asigure suportul necesar, fiind de aceea dificil de înlocuit. Unele protocoale au ajuns acum să fie mai mult o pacoste. Protocolul de terminal virtual, TELNET, de exemplu, a fost proiectat pentru un terminal tele-imprimator mecanic de zece caractere pe secundă. Cu toate acestea, 25 de ani mai târziu, protocolul este încă foarte utilizat.

Pentru a rezuma, în pofida acestor probleme, modelul OSI (mai puțin nivelurile sesiune și prezentare) s-a dovedit a fi excepțional de util pentru a discuta rețelele de calculatoare.

1.2.4 Comparație între modelele de referință OSI și TCP

Modelele de referință OSI și TCP/IP au multe lucruri în comun. Amândouă se bazează pe conceptul unei stive de protocoale independente. De asemenea, funcționalitatea nivelurilor este în linii mari similară. De exemplu, în ambele modele, nivelurile până 1a nivelul transport inclusiv sunt necesare pentru a pune la dispoziția proceselor care doresc să comunice un serviciu de transport capăt-la-capăt independent de rețea. Nivelurile respective formează furnizorul de transport. Din nou, în ambele modele, nivelurile de deasupra transportului sunt beneficiari orientați pe aplicații ai serviciului de transport.

În pofida acestor similitudini fundamentale, între cele două modele există și multe deosebiri.

Trei concepte sunt esențiale pentru modelul OSI:

Servicii

Interfețe

Protocoale

Probabil că cea mai mare contribuție a modelului OSI este că a făcut explicită diferența între aceste trei concepte. Fiecare nivel realizează niște servicii pentru nivelul situat deasupra sa. Definiția serviciului spune ce face nivelul, nu cum îl folosesc entitățile de deasupra sa sau cum funcționează nivelul.

Interfața unui nivel spune proceselor aflate deasupra sa cum să facă accesul. Interfața precizează ce reprezintă parametrii și ce rezultat se obține. Nici interfața nu spune nimic despre funcționarea internă a nivelului.

În sfârșit, protocoalele pereche folosite într-un nivel reprezintă treaba personală a nivelului. Nivelul poate folosi orice protocol dorește, cu condiția ca acesta să funcționeze (adică să îndeplinească serviciul oferit). Nivelul poate de asemenea să schimbe protocoalele după cum vrea, fără ca acest lucru să afecteze programele din nivelurile superioare.

Aceste idei se potrivesc foarte bine cu ideile moderne referitoare la programarea orientată pe obiect. Un obiect, ca și un nivel, posedă un set de metode (operații) care pot fi invocate de către procese din afara obiectului. Semanticele acestor metode definesc mulțimea de servicii pe care le oferă obiectul. Parametrii și rezultatele metodelor formează interfața obiectului. Codul intern al obiectului reprezintă protocolul său și nu este vizibil sau de vreo importanță în afara obiectului.

Deși lumea a încercat ulterior sa îl readapteze pentru a fi mai asemănător modelului OSI, modelul TCP/IP nu a făcut inițial distincție clară între serviciul, interfață și protocol. De exemplu, singurele servicii veritabile oferite de nivelul internet sunt SEND IP PACKET și RECEIVE IP PACKET.

În consecință, protocoalele din modelul OSI sunt mai bine ascunse decât în modelul TCP/IP și pot fi înlocuite relativ ușor pe măsură ce se schimbă tehnologia. Capacitatea de a face asemenea modificări reprezintă unul din scopurile principale ale organizării protocoalelor pe niveluri în modelul OSI.

Modelul de referință OSI a fost conceput înainte să fie inventate protocoalele. Ordinea respectivă semnifică faptul că modelul nu a fost orientat către un set specific de protocoale, fiind prin urmare destul de general. Reversul este că proiectanții nu au avut multă experiență în ceea ce privește acest subiect și nu au avut o idee coerentă despre împărțirea funcțiilor pe niveluri.

De exemplu, nivelul legătură de date se ocupa inițial cu rețelele punct-la-punct. Atunci când au apărut rețelele cu difuzare, a trebuit să fie introdus în model un subnivel nou. Când lumea a început să construiască rețele reale utilizând modelul OSI și protocoalele existente, s-a descoperit că acestea nu se potriveau cu specificațiile serviciului cerut, astfel că a trebuit introdusă în model convergența subnivelurilor, ca să existe un loc pentru a glosa pe marginea diferențelor. În sfârșit, comitetul se aștepta inițial ca fiecare țară să aibă câte o rețea care să fie în custodia guvernului și să folosească protocoalele OSI, așa că nu s-a dat nici o atenție interconectării.

În ceea ce privește TCP/IP, lucrurile stau exact pe dos: mai întâi au apărut protocoalele, iar modelul a fost de fapt doar o descriere a protocoalelor existente. Cu protocoalele respective nu era nici o problemă : ele se potriveau perfect cu modelul. Singurul necaz era că modelul nu se potrivea cu nici o altă stivă de protocoale. Prin urmare, modelul nu a fost prea util pentru a descrie alte rețele non-TCP/IP.

O diferență evidentă între cele două modele se referă la numărul de niveluri: modelul OSI are șapte niveluri, iar TCP/IP are patru. Ambele modele au niveluri (inter)- rețea, transport și aplicație, iar restul nivelurilor sunt diferite.

O altă deosebire privește subiectul comunicație fără conexiuni versus comunicație orientată pe conexiuni. Modelul OSI suportă ambele tipuri de comunicații la nivelul rețea, dar numai comunicații orientate pe conexiuni în nivelul transport, unde acest fapt are importanță (pentru că serviciul de transport este vizibil utilizatorilor). Modelul TCP/IP are numai un mod (fără conexiuni) la nivelul rețea, dar suportă ambele moduri la nivelul transport, ceea ce lasă utilizatorilor posibilitatea alegerii. Această alegere este importantă în mod special pentru protocoale întrebare-răspuns simple.

Capitolul II. Programarea orientată pe obiecte

Programarea cu obiecte (sau POO -programarea orientată pe obiecte) s-a impus atenției prin limbajul C++ (în 1987 apare cartea lui Bjarne Stroustroup) deși limbajul pur obiectual Smalltalk fusese inventat și folosit încă înainte de 1980, iar limbajul cu clase Simula (din care s-a inspirat si Stroustroup) este chiar și mai vechi (în jurul lui 1970).

2.1 Specificul programării cu obiecte

În programarea procedurală, accentul cade pe fluxul operațiilor de prelucrare a datelor, pe succesiunea lor în timp. Proiectarea unui program C (Pascal, Fortran) înseamnă în principal stabilirea funcțiilor (subprogramelor) din componenta programului și a relațiilor dintre aceste funcții (cine pe cine apelează și în ce ordine). Datele prelucrate se transmit acestor funcții ca argumente (parametri) sau (mai rar) se definesc ca variabile externe, globale. Progresele realizate de la programarea cu instrucțiuni de salt (Fortran 66) la programarea structurată (Pascal, C, Fortran 90) au fost mai ales în sensul exprimării mai clare și mai sigure a succesiunii operațiilor de prelucrare (blocuri de instrucțiuni, decizii si cicluri).

În programarea cu obiecte accentul se mută pe obiectele de date care intervin într-o aplicație, iar prelucrările sunt în general asociate cu diferite obiecte (sub formă de "metode" de tratare a obiectelor). Obiectele pot fi mai simple (de exemplu, numere sau șiruri de caractere) sau mai complexe (o structură de date, un document, etc. ). Obiectele pot corespunde unor noțiuni din universul aplicației sau unor concepte abstracte. Proiectarea unui program Java înseamnă stabilirea claselor (obiectelor) ce contribuie la realizarea aplicației, a relațiilor dintre ele și a modului în care aceste obiecte interacționează (comunică între ele prin mesaje).

Așa cum nu există o soluție unică pentru definirea subprogramelor dintr-un program procedural, tot așa nu există o singură soluție pentru definirea și alegerea claselor dintr-un program orientat pe obiecte.

Ca orice nouă tehnologie, programarea cu clase prezintă avantaje și dezavantaje față de abordările anterioare, iar raportul dintre plusuri și minusuri depinde mult de specificul problemelor rezolvate.

Astfel, inginerii, matematicienii sau fizicienii care programează aplicații numerice vor fi greu de convins să renunțe la un limbaj simplu cum este limbajul Fortran pentru limbaje mai complexe ca Java sau C++, deoarece problemele numerice nu beneficiază semnificativ de abordarea cu obiecte, care poate părea doar o complicație inutilă, care conduce la programe mai lungi și mai puțin "naturale" (mai depărtate de formularea matematică a problemei).

Pe de altă parte, programatorii care realizează aplicații Windows sau alte aplicații în care interfața grafică a programului cu operatorul uman are o pondere importantă, apreciază simplificarea adusă prin utilizarea unor biblioteci de clase care încapsulează structuri de date si funcții Windows.

Pentru un număr mare de aplicații, de dimensiuni relativ mici, nici una din cele două abordări (procedurală sau obiectuală) nu este în mod evident superioară, dacă aplicația respectivă este privită separat de altele și nu se consideră aspectul reutilizării claselor în mai multe aplicații.

Trebuie avut în vedere și faptul că însușirea unui nou mod de abordare a programării necesită un efort inițial de adaptare, studiul unor aplicații existente și acumularea unei experiențe în definirea și utilizarea de obiecte.

2.2 Avantajele programării orientate pe obiecte

Programarea orientată pe obiecte este o tehnică de programare capabilă să reducă efortul de realizare a unor aplicații complexe, cu interfață grafică, cu baze de date și cu structuri de date nebanale.

Principala cale de stăpânire a complexității programelor este modularizarea lor, combinată cu reutilizarea unor module în mai multe programe. Modulele program pot fi scrise și verificate separat înainte de a fi asamblate într-o aplicație unică.

Mult timp, noțiunea de modul (funcțional) a însemnat un subprogram (funcție, procedură, subrutină). În multe limbaje, inclusiv în C, mai există și module de compilare (unități de traducere), care sunt de fapt fișiere sursă ce pot conține una sau mai multe funcții.

Aplicațiile mari pot conține zeci și sute de funcții, iar numărul funcțiilor apelate este mult mai mare: funcții standard ale limbajului, funcții din biblioteci, funcții pentru interfața grafică, ș.a. S-a mai observat că o parte din aceste funcții realizează operații asociate unor structuri de date folosite de aplicații sau operații legate de dialogul cu utilizatorii și sunt relativ independente de logica aplicației. Această observație sugerează că este posibil și un alt nivel de modularizare al programelor, cu module mai complexe decât funcțiile.

O clasă poate fi privită ca un modul de program mai cuprinzător deoarece grupează mai multe funcții în jurul unor date (sau chiar fără date comune). Avantajul utilizării unor module de program mai mari este însoțit, în cadrul POO, de posibilitatea reutilizării acestor module în diverse aplicații.

Un alt avantaj care rezultă din gruparea funcțiilor și datelor este reducerea numărului de argumente al funcțiilor (și, mai ales, al argumentelor modificabile, de tip pointer sau referință), deoarece metodele unei clase se pot referi la datele clasei ca și cum ar fi variabile globale (ele nu mai trebuie transmise ca argumente). De exemplu, metodele "push" și "pop", care pun sau scot ceva dintr-o stivă nu trebuie să mai aibă ca argumente variabila ce reprezintă stiva (un vector, de exemplu) și nici indicele vârfului stivei ("stack pointer").

Principala provocare a POO este chiar definirea de clase reutilizabile în cât mai multe aplicații. Deja există biblioteci de clase (de componente) reutilizabile pentru anumite domenii, cum sunt cel al realizării de interfețe grafice cu utilizatorii (GUI-Graphical User Interface), al structurilor de date, al accesului la bazele de date, ș.a.

POO a adus și alte metode de adaptare a modulelor reutilizate la specificul aplicațiilor: crearea de clase derivate sau doar modificarea proprietăților unor componente. O componentă (software) este o clasă utilizabilă ca atare (fără crearea de subclase) și care poate fi cuplată cu alte componente într-o aplicație fără programare sau cu un minim de programare, folosind un mediu vizual (un editor vizual de componente).

Derivarea de subclase dintr-o clasă de bază (superclasă) este o metodă mai sigură de a face ajustări importante în funcționalitatea unui modul, față de modificarea unor subprograme existente, deoarece nu se fac modificări în clasele preluate iar metodele moștenite nu mai trebuie testate.

Un alt avantaj al POO poate fi considerat și posibilitatea de definire a unor noi tipuri de date, de orice complexitate dar cu o utilizare similară tipurilor predefinite. De exemplu, o clasă de tip "stivă" va conține un vector (sau o listă înlănțuită) dar și operațiile asociate stivei: pune o valoare în stivă, scoate valoarea din vârful stivei, test dacă stiva este goală și inițializarea stivei. După ce s-a definit clasa "stivă" se pot declara variabile, parametri și chiar funcții de tip stivă.

Clasele (obiectele) din programe pot corespunde unor obiecte fizice, concrete din aplicație (cărți, automobile, piese mecanice, componente electronice, persoane umane, facturi, etc.) sau unor noțiuni abstracte, mai generale (om, animal, dată calendaristică, interval de timp, activitate).

Unii autori recomandă chiar extragerea părților de vorbire din descrierea aplicației și transformarea substantivelor în clase, adjectivelor în datele clasei și transformarea verbelor în funcții ale claselor (metodele clasei). De exemplu, clasa ce corespunde unei cărți dintr-o aplicație destinată unei biblioteci poate avea ca date titlul cărții, numele autorilor, anul apariției, prețul, etc., și ca operații asociate: împrumutarea la un cititor, restituirea la bibliotecă, ș.a.

Aceasta corespondență dintre componentele programului și noțiunile specifice aplicației este văzută ca un avantaj al POO, comparativ cu programarea tradițională, care înlocuiește universul problemei cu numere, șiruri de caractere, liste și operații cu variabile de aceste tipuri.

Avantajele principale ale POO pot fi rezumate astfel:

Utilizarea (și reutilizarea) unor module de program mai cuprinzătoare (clasele), organizate de multe ori ierarhic în familii (ierarhii) de clase; de aici, reducerea numărului de linii sursă care trebuie scrise pentru o nouă aplicație. Utilizarea unei clase C se face nu numai prin preluarea ei ca atare ci, mai ales, prin derivarea altor clase din C, cu adăugarea de funcții (și /sau date) necesare aplicației respective.

Reducerea posibilităților de eroare (programare mai sigură) datorită pe de-o parte faptului că toate clasele folosite au fost testate în prealabil și, pe de altă parte, verificărilor realizate de compilator asupra operațiilor cu obiecte (sunt permise numai operațiile prevăzute la definirea clasei respective).

Înțelegerea și întreținerea programelor mari este mai simplă datorită dimensiunii lor mai reduse și apropierii dintre elementele programului și noțiunile aplicației (problemei de rezolvat).

Pentru aprecierea acestor argumente este suficient să comparăm o aplicație Windows realizată cu clase ( de exemplu cu biblioteca MFC) cu aceeași aplicație realizată cu apeluri directe de funcții Windows (funcții API).

POO nu înseamnă doar folosirea altor construcții și tehnici în programare, ci presupune un alt mod de gândire și de abordare a programării (o proiectare orientată pe obiecte). În POO rezolvarea unei probleme se concentrează pe identificarea obiectelor, proprietăților și comportamentului lor și nu pe determinarea succesiunii operațiilor cu numere sau șiruri de caractere care conduc la soluția problemei. Ca și în cazul programării clasice, dificultatea cea mai mare o constituie analiza problemei și proiectarea (concepția) programelor și nu exprimarea proiectului în termenii unui anumit limbaj de programare.

2.3 Limbaje pentru programarea cu obiecte

Conceptele de bază și principiile programării orientate pe obiecte sunt aceleași pentru toate limbajele de programare "obiectuale", dar exprimarea acestor noțiuni abstracte în programe concrete trebuie să țină seama de particularitățile limbajului folosit.

În prezent, cele mai utilizate limbaje orientate pe obiecte sunt C++ și Java. Limbajul C++ a permis tranziția gradual de la programarea procedurală, structurată la programarea orientată pe obiecte. Mai mult, într-o aceeași aplicație C++ se pot folosi tehnici de programare aparținând ambelor stiluri.

Ulterior, s-a considerat că programele hibride (parțial procedurale și parțial obiectuale) nu sunt de dorit, astfel că limbajul Java impune o abordare exclusiv orientată pe obiecte a tuturor programelor ("abordare pur obiectuală").

Principalele avantaje ale limbajului C++ sunt:

Eficiența programelor executabile, ca lungime și ca timp de execuție.

Includerea limbajului C deci posibilitatea de a folosi cele mai adecvate tehnici de programare în diverse părți din aplicație și accesul nelimitat la facilitățile sistemului de operare.

Concizia exprimării, deci lungimea programelor sursă.

Principalele avantaje ale limbajului Java sunt:

Reunirea celor mai bune facilități din limbajele obiectuale anterioare și eliminarea unor aspecte mai nesigure sau discutabile din C++.

Posibilitatea utilizării ca limbaj de programare în Internet.

Existența unui mare număr de clase predefinite, standardizate și imediat utilizabile.

Un avantaj suplimentar pe care îl oferă Java unui începător în POO este și disponibilitatea surselor pentru clasele standard (JDK), care pot constitui bune exemple de definire și utilizare a unor clase, rezultat al muncii multor programatori.

Java este un limbaj în evoluție, pentru care există versiuni succesive, dar încă nu există un standard oficial.

2.4 Noțiuni de bază în POO

Unitatea elementară a programelor orientate pe obiecte este obiectul. Limbajele care respectă conceptele programării orientate pe obiecte descriu relațiile dintre acestea. Toate obiectele au o stare și un comportament.

Starea unui obiect se referă la elementele de date conținute de obiect și la valorile asociate acestora. Tot ceea ce cunoaște obiectul despre aceste elemente și despre valorile lor formează starea obiectului. Elementele de date asociate obiectelor se numesc variabile de instanță. Comportamentul unui obiect depinde de acțiunile pe care obiectul poate să le execute asupra variabilelor de instanță definite în cadrul său. În programarea procedurală, o astfel de construcție se numește funcție. În terminologia specifică programării orientate pe obiecte, această construcție se numește metodă. O metodă aparține clasei a cărei membră este.

Astfel, starea unui obiect depinde de lucrurile pe care le cunoaște obiectul, iar comportamentul lui depinde de acțiunile pe care le poate executa. În cazul în care creăm un obiect software care reprezintă modelul unui televizor, obiectul va avea variabile care descriu starea curentă a televizorului, cum ar fi o variabilă care specifică dacă televizorul este pornit, precum și alte variabile care stochează canalul selectat, nivelul de volum și faptul că telecomanda nu transmite nimic pentru moment. De asemenea, obiectul va poseda metode care descriu acțiunile permise, cum ar fi pornirea și oprirea televizorului, schimbarea canalului, modificarea volumului și acceptarea comenzilor de la telecomandă.

Obiectele încapsulează variabile de instanță și metode înrudite într-o singură unitate identificabilă. Ca urmare, obiectele sunt ușor de refolosit, de actualizat și de întreținut. Un obiect poate apela una sau mai multe metode pentru executarea unei operațiuni. Metodele sunt inițiate prin transmiterea unui mesaj către obiectul respectiv. Un mesaj trebuie să conțină numele obiectului către care este transmis, numele metodelor care urmează să fie apelate și valorile necesare metodelor respective. Obiectul care recepționează mesajul folosește informațiile primite pentru a apela metodele corespunzătoare cu valorile specificate.

Avantajul încapsulării variabilelor de instanță și a metodelor constă în faptul că putem trimite mesaje unui obiect, fără să cunoaștem modul de lucru al obiectului respectiv.

Clasele încapsulează obiecte. O singură clasă poate fi folosită pentru instanțierea mai multor obiecte. Aceasta înseamnă că putem avea mai multe obiecte active sau mai multe instanțe ale aceleiași clase.

Noțiunea de "clasă" poate fi privită ca o generalizare și o abstractizare a unui grup de obiecte care au în comun anumite proprietăți (atribute) ai acțiunii (operații). Un obiect este, în acest caz, o "instanțiere" a unei clase, deci un caz particular concret de clasă.

De exemplu, se poate defini un nou tip de date, cum ar fi tipul "complex" sub forma unei clase care reunește cele două componente ale unui număr complex cu operațiile uzuale asupra numerelor complexe: operații aritmetice, operații de citire /scriere, etc. Un obiect din clasa "Complex" va avea valori numerice pentru partea reală și pentru partea imaginară și va putea fi prelucrat prin metodele specifice clasei "Complex".

Situația descrisă este cea mai des întâlnită în POO și corespunde claselor ce pot fi instanțiate, deci care pot genera obiecte.

În C++ și în alte limbaje de programare, o colecție de clase sau de funcții înrudite formează o bibliotecă. Java pune o amprentă proprie asupra bibliotecilor, folosind termenul de pachet pentru descrierea unei colecții de clase înrudite. Așa cum clasele încapsulează obiecte, pachetele încapsulează clase.

O metodă a unei clase instanțiabile este o funcție apelată pentru un anumit obiect, deci este întotdeauna asociată unui obiect concret.

În Java, toate funcțiile sunt metode ale unor clase, deci o metodă poate fi apelată numai dintr-o altă metodă. Atunci când o metodă a unei clase A apelează o metodă a unei clase B se mai spune că un obiect din clasa A transmite un mesaj unui obiect din clasa B, prin care i se cere o anumită operație.

De exemplu, o clasă de obiecte grafice conține metode de afișare pe ecran, de schimbare a poziției sau dimensiunilor; o altă clasă poate deci transmite unui obiect grafic mesaje de tipul "afișează-te" sau "mută-te în altă poziție" sau "modifică-ți dimensiunile".

Moștenirea este o caracteristică puternică a limbajelor de programare orientate pe obiecte, care permite să refolosim codul și să extindem funcționalitatea claselor existente. Folosind acest aspect al programării orientate pe obiecte, putem crea o nouă clasă care moștenește funcționalitatea unei clase existente. Putem apoi să extindem funcțiile vechii clase, astfel încât să corespundă necesităților curente.

O (sub)clasă derivată dintr-o clasă instanțiabilă moștenește de la aceasta date și metode (operații), pe care le poate modifica sau la care poate adăuga alte date și /sau metode proprii.

În terminologia Java, o subclasă "extinde" o altă clasă (superclasa ei), în sensul că adaugă superclasei noi funcții, deci o extinde ca operații posibile. În același timp însă, o subclasă este mai specializată, mai puțin generală decât superclasa ei. O subclasă D este un anumit caz particular de superclasă C. Se mai spune că D este un fel de C.

De exemplu, o clasă "fereastră" (Window) este o clasă mai generală, dar din ea pot fi specializate diferite tipuri de ferestre: buton, fereastră în care se afișează un text nemodificabil, fereastră de dialog, fereastră cu o listă din care se poate selecta un element, etc.

Există însă și clase abstracte, care nu pot fi instanțiate, dar care pot fi folosite pentru obținerea prin derivare a unor clase instanțiabile. O clasă abstractă este o clasă cu sau fără date, dar pentru care o parte din metode (sau toate) sunt metode abstracte. O metodă abstractă este declarată (anunțată) ca rol, tip și parametri, dar nu este definită printr-o secvență de instrucțiuni. Pentru o metodă abstractă se știe ce trebuie să facă dar nu se știe (încă) cum trebuie să facă. Subclasele derivate dintr-o clasă abstractă vor trebui să precizeze metodele abstracte moștenite.

O subclasă a unei clase abstracte moștenește doar obligația de a implementa metodele abstracte.

De exemplu, clasa "Applet" din Java este o clasă abstractă, din care se vor deriva toate clasele ce corespund unor aplicații particulare, numite "applet-uri" și care pot fi executate numai pornind de la un fișier HTML.

În Java se mai întâlnesc și clase care nu sunt abstracte dar nici nu sunt instanțiabile; acestea sunt clase care conțin numai metode statice (și, mai rar, date statice) și care au rolul de a reuni mai multe funcții, care nu operează însă asupra unor date comune. Aceste clase corespund unor biblioteci de funcții cu caracter general, a căror utilizare nu este condiționată de existența unor anumite obiecte.

De exemplu, clasa "Math" din Java reunește funcții matematice uzuale ce operează cu date de tipul primitiv "double" și a căror folosire nu este legată de existența unor obiecte din alte clase.

Capitolul III. Prezentare Java.

Rădăcinile limbajului Java se află într-un proiect de cercetare (proiectul “Green”) al firmei Sun. Coordonator de proiect era James Gosling, unul din veteranii designului software-ului de rețea.

Java nu este numai un limbaj de programare, Java este un mediu de programare ce oferă utilizatorului cadrul necesar și uneltele necesare dezvoltării aplicațiilor Java. Java este o tehnologie care oferă suport dezvoltării de aplicații distribuite, independente de platformă. Programele Java pot rula pe diferite tipuri de platformă, cu condiția să existe instalată o mașina virtuală Java deasupra platformei respective .

Cea mai mare parte a sintaxei de programare Java este moștenită din C++, dar multe din conceptele de programare obiectuală prezentate în Java își au rădăcinile în SmallTalk, Lisp etc. Arhitecții de la Sun au inclus în acest limbaj cele mai noi concepte de programare făcându-l o unealtă puternică și ușor de manevrat.

3.1 De ce Java?

Limbajul Java are câteva caracteristici care l-au făcut și-l fac un limbaj de succes pe piața actuală de software , piață în care tehnologia apare și dispare de la o zi la alta.

Un prim argument în favoarea folosirii limbajului Java este simplitatea lui, conceptele fundamentale ale limbajului fiind foarte simple. Fără a pierde din puterea limbajului, designerii lui au renunțat la părțile redundante ale unui limbaj, păstrând doar părțile strict necesare . Tot în ideea simplității, tehnologia Java conține așa numitul Garbage Colector, care eliberează programatorul de grija dezalocării zonelor de memorie alocate . Pentru cei familiari cu C++ , acest lucru înseamnă că nu mai e nevoie de delete după new . Garbage colectorul este supraveghetorul din umbră care face curățenie după noi, scăpându-ne de grija eliberării memoriei, de date care nu ne mai trebuie.

Faptul că respectă o mare parte a gramaticii, sintaxei de programare C/C++ face să poată fi ușor de învățat de cei care au lucrat în limbajul C/C++ .

Un alt argument este faptul că spre deosebire de C++ limbajul java este în întregime bazat pe obiecte. Tehnologia programării orientate pe obiecte (OOP-Object Oriented Programming) este singura care satisface cerințele actuale de dezvoltare software. Gradul de intercomunicare între echipele de programatori se mărește considerabil, depanarea, modificarea codului se face într-un timp mult mai scurt rezultând o eficiență mult crescută a dezvoltării sistemelor software, deci în ultimă instanță un preț mai scăzut.

Java mărește gradul de siguranță al codului. Există doua nivele de verificare: unul la compilare (prezent în marea majoritate a limbajelor) și unul la rulare. Ca urmare, un program este mai puțin supus erorilor. Comparativ cu C++, multe din trăsăturile acestuia care de foarte multe ori erau surse de erori, au fost eliminate (pointerii de exemplu au fost eliminați). Accesul la tablourile Java este verificat și la rulare, eliminând astfel posibilitatea accesului accidental sau malițios în afara domeniului tabloului. Conversiile între tipurile de date sunt limitate, evitându-se astfel scrierea nepermisă a unor zone de memorie.

Intr-o lume în care calculatoarele nu mai pot exista ca entități solitare, fără a fi conectate în rețea, problema securității este una din cele mai stringente. Se pune problema existenței unui nivel de securitate în cadrul limbajului. Gradul de securitate mărit este unul din principalele avantaje ale limbajului Java care l-au făcut atât de popular. Programele Java sunt făcute să ruleze în sisteme distribuite, calculatoarele pe care ele lucrează nu pot fi sigure de proveniența programelor. Ca urmare, trebuie oprit accesul acestor programe la unele resurse locale pentru a preveni eventualele acțiuni malițioase. În vederea acestui lucru există mai multe proceduri de verificare. Pe lângă verificările de la compilare există verificările de la execuție. Programele Java sunt verificate pas cu pas în timpul rulării prevenind accesul în zonele nepermise. Acest lucru însă nu este fezabil dacă nu era rezolvată problema accesului în afara tablourilor sau problema conversiilor nepermise. Ca urmare există mai multe nivele de securitate în Java.

Un alt argument ar fi acela că Java este un limbaj dinamic prin faptul că multe decizii privind evoluția programului se iau în momentul rulării, la runtime. Dat fiind faptul că multe din aplicațiile Java sunt preluate de pe Internet sub formă de applet-uri chiar în momentul execuției lor, deci în rețea, aceste programe pot fi actualizate să facă față noilor cerințe, utilizatorul dispunând în orice moment de cea mai nouă variantă.

Unul din marile avantaje ale limbajului Java este faptul că este independent de platformă. Într-o lume în care fiecare mare companie de software încearcă să monopolizeze piața forțând o dependență de sistemele hardware, de sistemele de operare, de propriile standarde, Java aduce un aer proaspăt de cooperare, de limbaj viabil pentru toate platformele. Această independență de platformă se impunea, ținând cont de ideea de lucru în sisteme distribuite. De fapt un program Java lucrează pe o singură mașină: mașina virtuală Java (Java Virtual Machine-JVM). Aceasta este o platformă virtuală transpusă în realitate prin intermediul interpretorului, mai exact a emulatorului Java. Este un emulator pentru că se emulează execuția unui program în cadrul unei mașini Java, și nu se transformă codul Java pur și simplu în cod mașină ca in cazul unui interpretor. Programele executabile Java, numite și bytecodes sunt rezultatul compilării unui program text. Pentru a putea fi executate pe o anumită platformă (Windows, Unix) acestea au nevoie de un emulator Java Virtual Machine specific respectivei platforme. Emulatorul convertește bytecodul java în cod executabil pe mașina reală pas cu pas, instrucțiune cu instrucțiune. Ca urmare a utilizării emulatorului un program Java poate rula pe orice platformă pentru care există un emulator Java. Problema utilizării emulatorului este însă cu două tăișuri. Partea negativă este că folosirea emulatorului duce la mărirea timpului de execuție. Soluția este compilarea just-in-time (JIT) care transformă întregul program Java în program mașină înainte de execuția lui. Compilatoarele just-in-time lucrează ca și interpretoarele doar că conversia nu se face la nivel de instrucțiune ci doar la nivel de program, crescând considerabil viteza de execuție a programului Java.

Java are si suport pentru multithreading ceea ce constituie un avantaj. Multithreading-ul este cel care permite ca un program să execute mai multe sarcini aparent în același timp, utilizând mai multe fire de execuție (thread-uri). Java oferă suport pentru multithreading la nivel de limbaj deci la cel mai jos nivel (clasa Thread) oferindu-i astfel utilizatorului posibilitatea de a crea un nou fir de execuție ca și cum ar crea oricare alt obiect. Mai mult, Java permite comunicarea între fire de execuție precum și sincronizarea lor.

Unul din principalele avantaje care a făcut limbajul Java așa de popular este interconexiunea cu browser-ele www. Multe din firmele care dezvoltă browser-ele www au implementat mașina virtuală Java în interiorul acestor browsere. Un browser compatibil Java permite ca o categorie specială de aplicații, numite applet-uri să fie transformate de la server-ul www pentru a fi executate la client, adică pe calculatorul unde se execută browser-ul www. Conexiunea browser-ului cu applet-urile se face prin intermediul etichetelor html, html fiind limbajul în care se scrie o pagină web.

Având în vedere aceste caracteristici, Java se conturează ca un limbaj performant, cu șanse mari de a se impune tot mai mult în lumea informaticii.

3.2. Arhitectura sistemului Java

Mediul de execuție al limbajului Java, numit și Java Runtime Environment (JRE), reprezintă cadrul de execuție al unui program Java, al unui bytecode Java. După cum se vede în figura 3.1, pentru a executa un program Java se trece prin mai multe etape. În primul rând este nevoie de un program sursă (cu extensia java). Acesta este compilat (utilizând un compilator java, de exemplu javac) obținându-se un fișier executabil Java, un bytecode Java. Acesta poate fi executat doar în contextul unui mediu de execuție Java, un JRE, prin apelul unui emulator-interpretor (exemplu: programul java din mediul jdk).

Fig.3.1. Crearea și execuția unui program Java

Mediul de execuție Java, JRE, conține două componente importante (figura 3.2), fără de care un program Java nu poate să se execute. Aceste două componente sunt pe de-o parte librăriile (package-urile) Java și pe de altă parte mașina virtuală Java (JVM).

Fig. 3.2. Ierarhia mediului de execuție Java

Structura mașinii virtuale Java (Java Virtual Machine – JVM) este prezentată în figura următoare:

Fig. 3.3. Structura mașinii virtuale Java

Legat de această structură prezentăm pe scurt componentele mașinii virtuale Java:

Class Loader – Este utilizat pentru a aduce fișierele bytecode (.class) necesare execuției programului. Aceste fișiere se pot găsi pe orice calculator dintr-o rețea.

Bytecode Verifier – Este componenta JVM care se ocupă de verificarea fișierelor bytecode pentru ca acestea să respecte setul de reguli Java. Dacă un fișier nu se conformează acestor reguli este respins.

Odată verificat, programul Java este gata de execuție. Execuția se poate face prin intermediul unui interpretor-emulator sau prin intermediul unui compilator Just In Time (JIT). Prin emulator execuția fiecărei instrucțiuni Java este emulată într-o JVM, ceea ce impune un nivel suplimentar deasupra platformei de lucru. Acest lucru se întâmplă în momentul execuției programului ceea ce duce la un consum mare de timp de procesare. În cazul utilizării compilatorului JIT, acesta transformă, din primul moment, întreg programul Java în instrucțiuni procesor ceea ce duce la mărirea vitezei de execuție a programului.

Garbage Colectarul – Este componenta ce se ocupă de eliberarea zonelor de memorie alocate și care nu mai sunt utilizate de program. Acest lucru ia de pe capul programatorului grija dezalocărilor de memorie. Specificațiile mașinii virtuale Java nu impun însă un anumit algoritm de dezalocare, lăsând la latitudinea celui ce implementează JVM stabilirea lui.

Security Manager – Este componenta ce asigură respectarea unor restricții de securitate impuse de lucrul în sistemele deschise. Lucrează împreună cu mai toate celelalte componente ale JVM.

3.3. Applet-uri și aplicații

3.3.1 Dezvoltarea și execuția applet-urilor și aplicațiilor

Termenul de aplicație Java se referă la un program care poate fi folosit în mod independent. Aplicațiile Java nu necesită un program de vizualizare extern și se folosește direct interpretorul Java.

Procesul de creare și lansare în execuție a unei aplicații Java implică mai multe operații (fig.3.4): mai întâi trebuie să scriem codul și să-l salvăm într-un fișier cu extensia “.java” , apoi trebuie să compilăm programul editat prin comanda “javac <nume_fișier>.java” obținându-se un nou fișier “<nume_fișier>.class”,fișier ce conține programul executabil (bytecode) Java și în ultimul rând trebuie să lansăm în execuție programul prin comanda “java <nume_fișier>.class” .

O aplicație Java conține o singură clasă (publică), care trebuie obligatoriu să conțină metoda main care este apelată la lansarea în execuție a programului. Metoda main este de fapt o funcție (similară cu funcțiile C) care primește ca parametrii un vector de șiruri de caractere reprezentând argumentele din linia de comandă cu care a fost lansat programul. În Java șirurile de caractere sunt (ca toate datele de altfel) obiecte aparținând clasei String. Un vector de astfel de obiecte este definit ca String[]. Această funcție trebuie să fie accesibilă din exteriorul obiectului pentru ca interpretorul să poată cere execuția acesteia, să fie publică, motiv pentru care este declarată de tip public. Codul acestei funcții este comun tuturor instanțelor clasei, metoda fiind declarată din acest motiv static. De asemenea main nu întoarce nimic fiind deci declarată void. În general, numele unei metode statice trebuie precedate de numele clasei din care face parte (separate prin punct), dar care nu este apelată dintr-o metodă din aceeași clasă.

Operația Editare Compilare Interpretare

Program

Unealta

Rezultatul

Fig. 3.4. Etapele dezvoltării și execuției unei aplicații Java stand-alone

Termenul de applet Java (miniaplicație) se referă, în general, la un mic program Java, creat pentru a fi folosit în sistemul World Wide Web. Miniaplicațiile Java necesită un program de vizualizare extern, așa încât, pentru a putea folosi o miniaplicație, avem nevoie de un browser Web sau de un program specializat de vizualizare (applet viewer). În vederea acestei execuții în interiorul unui browser, se creează un fișier html care se conectează prin intermediul unui tag html de programul Java obținut după compilare.

Procesul de creare și lansare în execuție este asemănător cu cel de la aplicație cu deosebirea că în etapele de dezvoltare ale unui applet se adaugă scrierea fișierului html. Procesul dezvoltării și execuției unui applet (fig.3.5) începe cu editarea unui applet java. Prima condiție ce trebuie impusă este ca programul să conțină o clasă publică, cu numele identic cu numele fișierului, clasă care trebuie derivată din clasa Applet (derivarea se face prin cuvântul cheie extends). Pentru a putea utiliza clasa Applet în derivare trebuie să specificăm utilizarea ei sau întregului pachet din care face parte (java.applet). În mod obligatoriu un applet trebuie să conțină una din metodele paint(), init(), sau start(). Una din aceste metode este apelată de browser pentru afișarea applet-ului în pagina html.

Fig. 3.5. Procesul dezvoltării și execuției unui applet Java

Următorul pas este compilarea applet-ului Java care se face la fel ca la o aplicație stand-alone, prin utilitarul “javac <nume_fișier>.java”. Apoi trebuie să creăm o pagina html care să conțină legătura cu programul Java, legătură ce se face prin intermediul tag-ului APPLET de forma “<APPLET…>…</APPLET>”. Acest tag trebuie să conțină în mod obligatoriu atributele CODE=”numeApplet.class”, atribut ce anunță browser-ul că trebuie să pornească JVM executând programul în care se află fișierul “numeApplet.class” din directorul în care se află fișierul html curent, WIDTH și HEIGHT atribute ce specifică dimensiunea applet-ului. În ultima fază trebuie să lansăm în execuție applet-ul prin comanda “appletviewer <nume_pagină>.html” sau încărcând pagina html utilizând un browser www compatibil. Teoretic un applet are asociat un flux de intrare și unul de ieșire, dar este foarte probabil ca acestea să fie ascunse utilizatorului de către programul de navigare, deci applet-ul nu trebuie să le folosească.

În figura 3.6 sunt prezentate etapele și cadrul necesar execuției unui applet Java disponibil undeva pe Internet.

Într-o primă fază browser-ul, clientul www, la indicația utilizatorului se va conecta la un server www de unde va cere o pagină html. Comunicația se face pe baza protocolului http(Hyper Text Transfer Protocol) într-un nivel http aflat deasupra nivelelor TCP/IP. Odată pagina html primită de browser acesta o va afișa într-un anume format. În cazul în care pagina conține un tag de tip APPLET se pornește mașina virtuală Java-JVM. Cunoscând locația fișierului .class care conține programul applet, din atributul CODE, JVM cere server-ului www clasa applet-ului. Acest din urmă transfer se face tot pe baza unui protocol http. După ce a primit clasa applet de la server, JVM pornește execuția ei. În cazul în care în execuția programului e nevoie și de alte clase disponibile doar la server (altele decât cele din Java Core API de care JVM dispune la client) JVM va cere aducerea lor. Această cerere se face doar în momentul în care e nevoie de ele.

Internet

Request HtmlPage

Protocol http/TCP/IP

Send HtmlPage

Request AppletClass

Protocol http/TCP/IP

Send OtherClass

Fig.3.6. Procedura de execuție a unui applet în Internet

3.3.2 Restricții de securitate pentru applet-uri

Având în vedere că applet-urile reprezintă cod străin (adus din rețea și care urmează să se execute pe calculatorul local), ele sunt supuse unui întreg set de restricții . Applet-urile se execută într-o așa numită “cutie cu nisip”(sand box), reprezentată de mașina virtuală din programul de navigare. Pentru fiecare clasă mașina virtuală memorează sursa ei de proveniență (încărcătorul de clase).

În Java, există mai multe nivele la care se implementează securitatea applet-urilor (și a aplicațiilor în general). Astfel, înainte de a fi executat codul unei clase, el este trecut printr-un verificator de cod (bytecode verifier). O clasă trebuie să treacă prin câteva verificări la încărcarea codului cum ar fi:

se verifică codul astfel încât acesta să fie conform cu formatul fișierului .class și să nu apară violări ale regulilor limbajului sau ale convențiilor de nume;

se verifică dacă nu sunt violate regulile legate de tipuri (nu se fac conversii explicite ilegale între diferite tipuri de date);

se verifică dacă se respectă condiția de predictabilitate a stivei, respectiv pentru fiecare obiect de pe stivă se cunoaște tipul său;

sunt verificate erori comune de management al memoriei (de exemplu depășirea superioară sau inferioară a stivei);

Dacă astfel de condiții nu sunt îndeplinite, un applet ar putea corupe sistemul de securitate sau ar putea înlocui clase sistem sau porțiuni ale acestora cu propriul cod. Verificatorul de cod este independent de compilatorul care a generat codul și nu este obligatoriu ca respectivul cod să fi fost generat de un compilator Java. Orice cod care nu este încărcat de încărcătorul de clase sistem (nu numai codul pentru applet-uri) este trecut automat prin verificatorul de cod.

Tot timpul se va încerca încărcarea claselor de pe sistemul local, deoarece acestea sunt considerate sigure. Astfel clasele sistem, din biblotecile standard, vor întotdeauna încărcate local, fiind astfel posibil implementarea unei politici de securitate la momentul execuției.

Soluția Java se bazează pe existența unui obiect dintr-o clasă specială numit gesionar de securitate (Security Manager). Acest obiect va fi consultat de către clasele sistem sau de către alte clase cu privire la ce acțiuni sunt permise și ce acțiuni nu sunt permise pentru codul clasei în curs de execuție. De exemplu, se poate verifica dacă este permis accesul la unele resurse: fișiere, rețea, ecran grafic, dacă se pot apela anumite metode sau clase din anumite biblioteci.

Gestionarul de securitate aparține unei clase speciale care extinde clasa java.lang.SecurityManager.

În Java a fost abordată ca politică așa numita “securitate prin deschidere nu prin ascundere”. În vreme ce alte produse program implementează mecanisme de securitate pentru care specificațiile nu sunt disponibile, în Java specificația mașinii virtuale și a verificatorului de cod, precum și clasele sistem și codul, sunt disponibile. Astfel erorile și scăpările legate de securitate pot fi descoperite de oricine și, odată cunoscute, pot fi remediate.

Politica de securitate pentru applet-uri este realizată pe baza unui gestionar de securitate, care este instalat de către programul de navigare înainte ca vreun applet să fie încărcat și executat. Fiecare browser implementează o serie de politici de securitate pentru a împiedica compromiterea sistemului. Aceste politici pot însă să difere de la un browser la altul.

În majoritatea cazurilor însă se impun unui applet următoarele restricții:

nu poate citi sau scrie fișiere de pe mașina pe care se execută;

nu poate șterge sau modifica fișiere de pe mașina pe care se execută;

nu poate citi anumite proprietăți de sistem;

nu poate porni un program pe mașina pe care se execută;

nu poate deschide o conexiune de rețea cu mașina de pe care a fost adus;

nu poate încărca biblioteci sau metode native.

Gestionarul de securitate nu este folosit numai de către programele de navigare. Un astfel de gestionar este instanțiat, de exemplu, și în cazul în care se utilizează obiecte transferate ca parametru al unui apel RMI (RMISecurityManager) . În general, orice aplicație își poate instala propriul gestionar de securitate. La un moment dat, într-o aplicație poate exista un singur gestionar de securitate și acesta, odată instalat, nu va putea fi înlocuit de altul. Dacă un gestionar de securitate este instalat, clasele sistem îl vor consulta de fiecare dată când un cod nesigur apelează o metodă a unei clase sistem cu efecte care ar putea pune în pericol siguranța sistemului (de exemplu deschiderea unui fișier, lansarea în execuție a unei aplicații). Pentru mașina virtuală toate clasele încărcate de un încărcător de clase, altul decât cel sistem, sunt nesigure.

3.4 Aplicații de rețea. Socluri (Sockets)

Java a câștigat teren în fața competitorilor prin capacitatea de adaptare la mediul eterogen de procesare oferit de o rețea de calculatoare. Această capacitate nu este realizată numai prin faptul că este limbaj independent de arhitectura hardware pe care se execută, ci și prin facilitățile speciale pe care le oferă pentru dezvoltarea unor aplicații distribuite, care folosesc ca mediu de comunicare Internet-ul.

Java este proiectat ca un limbaj de programare pentru rețele. Din acest motiv, suportul pentru aplicațiile care folosesc rețeaua pentru a comunica este de foarte bună calitate. În aceeași idee a păstrării unei legături cu programele deja dezvoltate și compatibilității cu standardele existente (oficiale sau de facto), Java oferă în cadrul modulului java.net clase care prezintă o nouă haină interfeței mai vechi de programare cunoscută în lumea rețelelor IP ca interfața bazată pe socluri (sockets). Aceste clase, numite clase soclu, creează o interfață elegantă și flexibilă, făcând programarea aplicațiilor pentru rețelele IP la fel de ușoară ca lucrul cu fișiere.

Trebuie remarcat că interfața de programare cu socluri oferită de Java permite comunicarea cu alte programe care folosesc o interfață de tip soclu, scrise de exemplu în limbajul C. Aceasta este, de altfel, una din modalitățile prin care Java poate să comunice cu aplicații deja existente, scrise în alte limbaje de programare.

Soclurile sunt un nivel de abstractizare dezvoltat la Universitatea Berkeley din California, SUA, pentru a permite o programare ușoară a rețelelor de calculatoare. Teoretic, această interfață este independentă de protocoalele de comunicație folosite, dar în practică este legată de protocoalele suitei TCP/IP. Implementări ale acestei interfețe se pot găsi nu numai în sistemele de operare UNIX, dar și în orice sistem de operare care permite conectivitate TCP/IP. De exemplu, în sistemele de operare Windows există o versiune a acestei interfețe numită WinSock. Existența acestei interfețe în majoritatea sistemelor de operare a făcut posibilă portarea ușoară a programelor care folosesc rețelele IP, ducând la o largă răspândire a acestor programe.

Înainte de a vorbi despre aplicațiile client-server și implicit despre socket-uri vom vorbi despre utilizarea obiectelor adresă IP și despre comunicarea prin protocoale orientate pe conexiune.

Utilizarea obiectelor adresă IP

Orice calculator gazdă conectat la Internet trebuie să aibă o adresă pentru a fi identificat. Aceste adrese se numesc adrese IP, după numele protocolului care folosește astfel de adrese. Adresele IP sunt formate din numere compuse din 4 octeți, iar reprezentarea lor se face separând cu punct cei patru octeți care compun adresa (un exemplu de adresă IP este 127.0.0.1). Deoarece acest mod este greu de folosit, s-a introdus suplimentar un sistem simbolic de adresare a gazdelor. Deoarece programele IP folosesc numai adrese numerice, iar utilizatorii, de obicei, numai adrese simbolice, trebuie să existe un mecanism care să permită realizarea conversiei între cele două sisteme de adresare. În Java, acest mecanism este realizat prin intermediul unui obiect din clasa InetAddress. Aceste obiecte reprezintă adrese IP și oferă posibilitatea realizării translației între cele două tipuri de adrese. Metoda statică getLocalHost() are ca rezultat un obiect care instanțiază clasa InetAddress, obiect care este instanțiat cu adresa stației curente. Extragerea corespondentului simbolic al adresei se face prin apelul metodei getHostName(), al cărei rezultat este un șir de caractere identic cu numele simbolic al stației.

În Internet există calculatoare care au mai multe adrese IP. Aceasta este rezultatul unei reguli care spune că același calculator trebuie să aibă câte o adresă IP pentru fiecare rețea în care este conectat.

Obținerea unui obiect din clasa InetAddress nu se face prin apelul unui constructor, ci prin intermediul unor metode statice (getLocalHost(), getByName(), getAllByName()), care apelează unul din constructorii privați ai obiectului și au ca rezultat un obiect (sau un vector de obiecte) din clasa InetAddress.

Existența unui constructor public este necesară în special în cazul acțiunii de moștenire, atunci când obiectul este nevoit să-și inițializeze superclasa. În versiune 1.1 a pachetului de dezvoltare Java, clasa InetAddress este declarată final. Deoarece obiectele acestei clase nu pot fi extinse prin moștenire, proiectanții au preferat să folosească inițializarea prin metode statice , deoarece funcțiile cu nume asemănător sunt de mult folosite în cadrul bibliotecii standard C de programare a aplicațiilor de rețea.

Cele mai multe aplicații care folosesc comunicația prin rețea au nevoie de stabilirea unui canal sigur de comunicație, astfel încât să aibă certitudinea că mesajele pe care le trimit ajung corect și în aceeași ordine la partener. Majoritatea protocoalelor care oferă acest serviciu folosesc conexiuni pentru stabilirea acestui canal sigur, de unde și numele de protocoale orientate pe conexiuni. Odată stabilită conexiunea, aplicațiile pot folosi canalul de comunicație prin intermediul abstractizării oferite de fluxurile de date (streams). Astfel, un canal implementează două fluxuri de date unidirecționale, fiecare flux fiind folosit pentru comunicarea într-un singur sens. Este treaba programelor de sistem care gestionează comunicația în rețea (și nu a programatorului de aplicații) să asigure siguranța în funcționare specifică conexiunilor. La fiecare capăt al acestui canal există un soclu care permite trimiterea datelor prin acesta și recepția informațiilor trimise prin către și de la stația parteneră. Orice conexiune este unic determinată de cele două socluri plasate la cele două capete ale conexiune. Pentru a putea transmite date în siguranță, programatorul trebuie doar să specifice adresa aplicației partener, cu care dorește să comunice, programele de sistem asigurând stabilirea conexiunii și gestiunea acesteia.

Aplicațiile de rețea comunică după schema client – server. Aplicația server oferă servicii aplicațiilor client. Pentru a obține un serviciu de la o aplicație server, aplicația client trebuie să se conecteze la aplicația server, între cele două stabilindu-se un canal virtual de comunicație. Aplicația server așteaptă cereri de conectare de la aplicațiile client “ascultând” un port TCP prestabilit n. În momentul în care apare o cerere de conectare din partea unei aplicații pe portul n al calculatorului pe care rulează aplicația server, aceasta recepționează cererea și poate să o accepte deschizând un canal de comunicație duplex pe portul n. Odată creat acest canal, aplicația client poate să-i transmită serverului mesaje-cereri de servicii. Serverul răspunde aplicației client rezultatul procesărilor solicitate prin mesajele-cerere. La terminarea servirii, serverul sau clientul pot închide canalul de comunicație virtual.

Elementul central în crearea și gestionarea unui canal virtual de comunicație între două aplicații este soclul (socket). Acesta este asemănător unui specificator (handler) de fișier fiind un număr întreg pozitiv care identifică unul din cele două puncte terminale al unui canal de comunicație virtuale. Datele transmise pe acest canal sunt buffer-izate atât de aplicația server care transmite datele cât și de aplicația client care le recepționează. Soclul nu este tot una cu portul TCP. Mai curând este un specificator de acces la date care include atât adresa de IP cât și portul prin care se realizează comunicația, caracterizând canalul de comunicație virtuală.

Comunicare prin protocoale orientate pe conexiune

Un protocol este o combinație de reguli de comunicație și formate de mesaje care trebuie respectate de calculatoarele legate în rețea pentru a schimba date. Scopul primar al protocoalelor este de a permite comunicația între calculatoare, indiferent de rețea sau de hardware-ul calculatoarelor legate în rețea.

O conexiune reprezintă un canal sigur de comunicație în rețea, stabilit de către un protocol pentru transmiterea corectă și în ordine a mesajelor între calculatoare. O conexiune este formată din două fluxuri de date unidirecționale folosite pentru comunicație precum și din două socluri (socket) care permit trimiterea, respectiv recepția datelor. Orice conexiune este unic determinată de cele două socluri plasate la cele două capete ale conexiunii.

Orice conexiune trece prin trei faze de-a lungul vieții sale, prima fază este crearea soclurilor de la capetele conexiunii, rămânând în sarcina programelor de sistem stabilirea conexiunii între cele două socluri. Dacă operația de creare a soclurilor a reușit, programatorul poate, din acel moment, să obțină referințe către două fluxuri de date (trimitere și recepție), fluxuri asociate capătului său de conexiune. Cele două fluxuri de date vor fi folosite pentru comunicarea cu aplicația partener. După încheierea comunicației prin canal, fiecare partener trebuie să închidă soclul său, realizându-se astfel distrugerea conexiunii.

În cadrul implementării standard Java, protocolul folosit pentru implementarea comunicației sigure pe bază de conexiuni este TCP (Transmission Control Protocol), unul din protocoalele de bază ale familiei TCP/IP. Acest protocol asigură o comunicare sigură, cu prețul stabilirii de conexiuni înainte de transmisie și al unei rate de transfer mai mici datorată mesajelor de confirmare.

Modul de lucru cu soclurile se încadrează în modelul client /server de scriere de aplicații. În cadrul acestui model, aplicațiile se împart în două categorii :

programe client – cele care inițiază conversația

programe server – cele care oferă servicii programelor client

Există un singur server pentru mai mulți clienți, deci serverul trebuie să aibă un mod de a face diferența între clienții pe care-i servește la un moment dat. Clientul este un program care se bazează în funcționarea sa pe serviciile pe care le oferă un program server.

Trebuie remarcat că soclurile de la capetele unei conexiuni sunt identice, dar sunt create în moduri diferite. Asimetria este dată de modul de stabilire a unei conexiuni: un program, numit client, creează un soclu căruia îi specifică adresa programului partener. În cadru inițializării soclului, sistemul trimite o cerere de conexiune către programul cu care clientul dorește să comunice, program numit server. Programul server este pregătit să primească cereri de conexiune, iar la acceptarea unei cereri de conexiune programele de sistem creează soclul pereche, soclu pe care programatorul aplicației partener îl primește ca rezultat al acceptării conexiunii. În același timp, sistemul de pe calculatorul server trimite sistemul client un mesaj de confirmare a stabilirii conexiunii, moment în care inițializarea soclului client se încheie. Așadar, în programul client, soclul este creat explicit, iar în programul server acesta este creat implicit de către biblioteca de funcții.

Pentru a realiza comunicarea cu un server, clientul are nevoie de două informații care compun împreună adresa serverului: numele gazdei pe care rulează serverul și numărul de port pe care serverul ascultă cereri de conexiuni.

Orice conexiune între două programe care comunică prin rețea este determinată unic de 4 elemente :

adresa gazdei pe care rulează aplicația client;

numărul de port al aplicației client;

adresa gazdei pe care rulează aplicația server;

numărul de port pe care serverul primește cererile;

Un soclu Java poate lucra în două moduri :

direct – implicit (suficient pentru majoritatea aplicațiilor)

cu facilități speciale pe soclu, folosit de aplicațiile care necesită comunicarea peste un zid de protecție (firewall) sau prin intermediul unui server proxy.

3.4.3 Funcționarea soclurilor de tip conexiune

Comportamentul soclurilor de tip conexiune poate fi modificat prin intermediul unor opțiuni. Unele din aceste opțiuni sunt preluate dintre cele standard pentru soclurile clasice (de exemplu opțiunile SO_LINGER și TCP_NODELAY, definite prin API-ul socket), iar altele sunt introduse pentru a ușura munca programatorilor (cazul opțiunii SO_TIMEOUT). Deși valorile implicite sunt bine alese pentru majoritatea aplicațiilor, o bună înțelegere a semnificației opțiunilor poate ajuta programatorul în obținerea de performanțe.

Pentru toate cele trei opțiuni definite în JDK 1.1 sunt furnizate două metode: una pentru aflarea valorii curente a opțiunii (metodă formată din numele opțiunii și prefixul get) și o alta pentru stabilirea unei noi valori a opțiunii (numele opțiunii prefixat de set).

Cea mai folosită opțiune este SO_TIMEOUT, opțiune care asigură deblocarea operațiilor recepție (în cazul soclurilor pentru comunicare – Socket) sau acceptare de conexiuni (în cazul soclurilor pentru acceptarea conexiunilor – ServerSocket). În mod implicit, aceste operații se blochează infinit, până când operația se termină normal sau până când apare o eroare. Deși există mecanisme pentru a împiedica blocarea infinită, chiar în absența opțiunii SO_TIMEOUT, existența unui mecanism la nivelul soclurilor face programarea mai ușoară și mai eficientă. Valoarea curentă poate fi aflată prin apelul metodei getSoTimeout(), iar modificarea prin apelul metodei setSoTimeout(). Valoarea implicită a acestei opțiuni este 0, valoare care inhibă deblocarea automată. Intervalul de timp ce poate fi stabilit, măsurat în milisecunde, este reprezentat de o valoare pozitivă reprezentată pe un întreg, ceea ce înseamnă că deblocarea poate fi planificată după trecerea unui interval de timp cuprins între 1ms și aproape 25 zile. În cazul în care deblocarea unei operații s-a produs ca urmare a expirării intervalului de timp stabilit se semnalează o excepție de tip java.io.InterruptedIOException. Captarea acestei excepții asigură programatorului mijlocul de detectare a modului în care s-a terminat operația analizată. Pentru a proteja o operație contra blocării infinite, programatorul trebuie să activeze opțiunea SO_TIMEOUT înainte de operația care poate produce blocare. Opțiunea nu este dezactivată de operațiile blocante, ceea ce înseamnă că, odată stabilită, opțiunea este luată în considerare de toate metodele ulterioare care se pot bloca.

O altă utilizare a opțiunii SO_TIMEOUT este implementarea unui mecanism de multiplexare a mai multor socluri. Să considerăm cazul unei aplicații în care programatorul trebuie să monitorizeze mai multe socluri de ascultare conexiuni, create pe porturi diferite. În cazul în care resursele nu permit crearea de fire de execuție care să se ocupe individual, de fiecare soclu în parte, putem folosi opțiunea SO_TIMEOUT pentru a analiza starea fiecărui soclu, pentru un interval de timp foarte scurt. De exemplu, dacă avem de monitorizat 7 socluri de ascultare conexiuni și dacă dorim ca fiecare soclu să fie analizat cel puțin odată la 2 secunde, stabilim opțiunea SO_TIMEOUT la o valoare cel mult egală cu 285 milisecunde, fiind deblocați după trecerea acestui interval de timp (sau la stabilirea unei conexiuni), moment în care putem trece la analiza următorului soclu de ascultare.

Mai avem o problema cu pachetele care au fost trimise înainte de închiderea unui soclu de comunicare, dar pe care sistemul nu a apucat să le trimită înainte de execuția metodei de închidere a conexiunii. Opțiunea SO_LINGER guvernează modul în care sunt tratate aceste pachete. Ea este implicit dezactivată, iar pentru schimbarea opțiunii se folosește setSoLinger(). Metoda primește ca parametrii o valoare logică, care stabilește dacă opțiunea este activată sau nu, și un întreg care stabilește un interval de timp în secunde, interval care va fi folosit pentru a aștepta trimiterea datelor rămase. Deși nu există o valoare care să specifice o așteptare blocantă până când s-au transmis toate datele rămase, utilizatorul poate implementa o așteptare blocantă prin folosirea unui interval de timp suficient de mare.

Informarea asupra stării curente a opțiunii SO_LINGER se face prin metoda getSoLinger(), care are ca rezultat valoarea intervalului de timp de așteptare, în secunde, sau –1 în cazul în care opțiunea este dezactivată.

Una din optimizările frecvent folosite pentru creșterea performanțelor comunicației folosind TCP este strângerea mai multor pachete mici care trebuie transmise la momente de timp apropiate în cadrul unui singur pachet mai mare. Algoritmul care realizează această conglomerare a pachetelor este cunoscut sub numele de algoritmul lui Nagle, iar opțiunea care activează sau nu folosirea acestui algoritm este TCP_NODELAY. În cazul în care aplicația care folosește comunicația prin TCP nu este sensibilă la mici întârzieri ale pachetelor, se recomandă activarea opțiunii prin folosirea metodei setTcpDelay(false), mai ales în cazul în care comunicația între client și server nu se desfășoară după modelul întrebare-răspuns (caz în care este bine ca pachetele să plece cât mai repede din nodul care le-a emis). Starea implicită a acestei opțiuni se află cu metoda getTcpDelay().

3.4.4 Restricții de securitate ale soclurilor

Soclurile Java prezintă un mecanism de comunicare foarte puternic și ușor de folosit. Datorită acestor posibilități de comunicare, soclurile au primit o atenție specială din partea proiectanților mecanismelor de securitate ale limbajului Java. Dar ele impun și unele restricții de securitate.

Problema principală este că un applet adus din Internet poate foarte ușor să obțină informații referitoare la mediul de lucru, la utilizatorul care rulează applet-ul sau la gazda pe care se rulează applet-ul. În plus, folosind comunicația prin rețea, un applet este capabil să transmită aceste informații unor utilizatori răuvoitori.

Programele de navigare impun restricții destul de puternice applet-urilor aduse din Internet. Aceste restricții nu sunt impuse și applet-urilor care sunt încărcate de pe gazda locală, cu toate că programele de navigare pot impune restricții și applet-urilor locale. Ca exemple de restricții, applet-urile nu au voie să creeze socluri de ascultare, pentru a nu instala noi servicii dorite de autorul applet-ului, iar soclurile de tip conexiune nu pot fi create decât dacă realizează o conexiune cu gazda de pe care au fost încărcate.

Toate restricțiile sunt stabilite de gestionarul de securitate. Acest obiect implementează metode pentru verificarea acțiunilor care pot provoca daune calculatorului pe care se rulează applet-ul. Orice aplicație Java (deci și un applet) își poate instala propriul gestionar de securitate prin derivarea clasei SecurityManager. Cu toate acestea, programele de navigare preferă să nu permită instalarea unui nou gestionar de securitate, applet-urile folosind gestionarul de securitate impus de programul de navigare. Astfel se asigură protecția informațiilor locale contra acțiunii răuvoitoare ale unor applet-uri răuvoitoare transferate din Internet.

Pentru toate soclurile de comunicare (orientate pe conexiune, datagrame, trimitere multiplă) pot fi introduse restricții sub forma unor perechi (adresă IP, port) cu care aplicația să poată stabili conexiuni. Aceasta se face prin instalarea unui gestionar de securitate care redefinește metodele void checkConnect (String host, int port) și void checkConnect (String host, int port, Object context). Deși operația de conectare este definită doar pentru socluri de tip conexiune, metoda checkConnect() este folosită și pentru a verifica trimiterea unei datagrame către o adresă sau încercarea de a obține adresa IP-ului pentru o anumită gazdă. Prin intermediul acestei metode, gestionarul de securitate al programelor de navigare permite accesul doar la calculatorul de pe care a fost adus applet-ul, pentru a întâmpina atacuri sau furtul de date.

Pentru soclurile pentru ascultarea de conexiuni (ServerSocket) sunt posibile două verificări. Mai întâi, este verificată permisiunea de creare de astfel de socluri prin controlul accesului la operația de ascultare de conexiuni (metoda void CheckListen (int port) a gestionarului de securitate). Chiar dacă un applet a trecut de această verificare, i se pot impune restricții de perechi (adresă IP, port) de la care se pot accepta conexiuni (metoda void checkAccept (String host,int port) a gestionarului de securitate).

Astfel, se poate impune, ca o conexiune să fie făcută doar de la un port privilegiat (cu număr mai mic de 1024 pe sistemele UNIX), pentru a crește nivelul de încredere în conexiunea realizată (și a muta sarcina asigurării securității pe administratorul calculatorului de la care se acceptă conexiuni).

În plus față de controlul efectuat la celelalte tipuri de socluri, pentru soclurile de trimitere multiplă se poate controla la ce adrese de trimitere multiplă poate să adere aplicația (metoda void checkMulticast (InetAddress madir) a gestionarului de securitate).

Pentru toate tipurile de socluri, operația de schimbare a comportamentului este controlată, astfel încât nu orice soclu își poate schimba funcționarea. Această verificare este utilă pentru a nu permite unui applet să comunice în cazul în care comunicarea are particularități (de exemplu trecerea peste un zid de protecție).

Ultimele dezvoltări din cadrul mediului Java permit semnarea digitală a unei clase, deci implicit a unui applet. Acest lucru permite ridicarea unora din restricțiile enumerate mai sus, selectiv, în funcție de proveniența applet-urilor, verificabilă prin semnătura digitală. Programele de navigare pot verifica autenticitatea semnăturii digitale și pot permite applet-urilor să efectueze acțiuni mai sensibile în cazul în care au încredere în cel care a semnat clasele Java.

Aplicații client-server

Clasa Socket este obiectul de bază în comunicația Internet. Instanțele ei sunt cele care permit unui program client să inițieze o conexiune (prin intermediul constructorului) și să implementeze comunicația prin rețea între programe. Obiectele clasei Socket pot avea un comportament modificat față de cel standard, modificare realizându-se prin modificarea setului de metode descrise de clasa abstractă SocketImpl. Comportamentul standard al soclurilor le permite comunicarea în Internet fără restricții impuse de ziduri de protecție sau mecanisme criptografice. Orice comportament diferit de cel standard trebuie scris explicit de programator prin intermediul unei noi clase derivate din SocketImpl.

Clasa Socket abstractizează noțiunea de soclu client (numit în general soclu) și este responsabilă de majoritatea operațiilor necesare comunicației în rețea. Prin instanțierea unui obiect de tip Socket, programul client poate să inițieze o conexiune (prin constructor) și să implementeze comunicația prin rețea.

Constructorul clasei Socket este cel care implementează funcția de stabilire a comunicației cu aplicația partener. Există mai multe variante de constructori. Pentru identificarea calculatorului gazdă a aplicației cu care se realizează conexiunea se folosește fie un șir de caractere (identic cu numele simbolic al gazdei aplicației), fie un obiect corespunzător din clasa InetAddress. Numărul de port pe care ascultă aplicația server este specificat ca al doilea parametru. Acest număr specifică numărul de port de pe calculatorul partener (numărul de port care va fi atribuit local soclului creat, ce poate fi aflat prin apelul metodei getLocalPort()).

La crearea unui obiect din clasa Socket se deschide un canal virtual de comunicație duplex între aplicația client de pe calculatorul local și aplicația server de pe calculatorul corespondent. Clasa Socket definește metode care permit scrierea /citirea în /din canalul virtual de comunicație asociat. Canalul virtual de comunicație este constituit din două streamuri – unul de intrare și altul de ieșire. Metodele getInputStream() și getOutputStream() întorc referințele la aceste streamuri.

Operația de închidere a comunicației se face prin apelul metodei close (). La închidere se poate asigura transmisia mesajelor care mai sunt pe drum, precum și eliberarea resurselor folosite de soclu. Dacă nu este realizată explicit, operația de închidere a soclului este făcută automat, de către sistemul de operare. Se recomandă închiderea soclurilor de către programator în momentul în care nu mai sunt necesare pentru comunicație, deoarece închiderea implicită realizată de sistem se face numai la colectarea memoriei disponibile.

Serverul este programul care ascultă cererile venite de la clienți și le oferă serviciul pentru care a fost creat. Identificarea serverelor se face prin intermediul portului pe care serverul așteaptă cererile de conexiune venite din partea clienților. Majoritatea serviciilor foarte des folosite în Internet au asociate porturi standardizate, pentru a permite clienților să opereze cu diferite servere în vederea obținerii unui serviciu.

Primul lucru pe care trebui să-l facă un program server este să se asocieze portului stabilit la momentul proiectării aplicației și să inițieze ascultarea cererilor de servicii venite de la clienți. Aceste lucruri se realizează prin constructorul clasei ServerSocket, clasa care implementează soclul programului server.

Un lucru esențial în proiectarea unei aplicații server este faptul că aceasta trebuie construită în așa fel încât să poată prelucra în paralel cererile clienților. Acest lucru se realizează prin folosirea mai multor fire de execuție, fiecare fir de execuție ocupându-se cu un singur client. De asemenea este prevăzut un mecanism pentru a limita numărul de cereri care pot fi prelucrate în paralel și deci și numărul firelor de execuție care rulează la un moment dat. Implicit acest număr este 50, dar poate fi specificat ca argument al constructorului. Orice cerere nouă va fi respinsă dacă numărul de cereri prelucrate are valoarea maximă specificată.

Acțiunile de creare a unui canal de comunicație virtual pot fi grupate în două categorii:

1. Acțiuni executate de aplicația client:

creează un soclu

legarea sa la un anumit port TCP și adresa IP a calculatorului client

se conectează la server

transmite /primește date prin soclu

închide soclul-conexiune.

2. Acțiuni executate de aplicația server:

creează un soclu – server

leagă soclul la adresa IP și portul sau

așteptă un apel de la client

după conectarea clientului, serverul acceptă conectarea creând un nou soclu pentru servirea clientului

Soclul server revine la așteptarea unei alte conectări

Soclul nou creat execută operațiile de primire /trimitere a datelor cu clientul după care se închide.

Operațiile de mai sus și stările soclului sunt reprezentate în graful din figura 3.7.

Fig.3.7 Operațiile de conectare prin socluri ale aplicațiilor client-server

Funcțiile reprezentate în acest graf sunt proceduri ale WinSock API care pot fi apelate de către aplicațiile client-server pentru realizarea comunicației într-o rețea IP.

Similar Posts