0% au considerat acest document util (0 voturi)
3 vizualizări57 pagini

04 Python Net Programming

Documentul oferă o prezentare detaliată a rețelelor de calculatoare, inclusiv componentele lor, tipurile de rețele (LAN, WAN, MAN) și topologiile de rețea. De asemenea, se discută despre protocoalele de transport precum TCP și UDP, precum și despre protocoalele de aplicație, cum ar fi HTTP și WebSocket. Avantajele rețelelor, cum ar fi partajarea informațiilor, resurselor și aplicațiilor, sunt de asemenea evidențiate.

Încărcat de

Sparrow Jack
Drepturi de autor
© All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca PDF, TXT sau citiți online pe Scribd
0% au considerat acest document util (0 voturi)
3 vizualizări57 pagini

04 Python Net Programming

Documentul oferă o prezentare detaliată a rețelelor de calculatoare, inclusiv componentele lor, tipurile de rețele (LAN, WAN, MAN) și topologiile de rețea. De asemenea, se discută despre protocoalele de transport precum TCP și UDP, precum și despre protocoalele de aplicație, cum ar fi HTTP și WebSocket. Avantajele rețelelor, cum ar fi partajarea informațiilor, resurselor și aplicațiilor, sunt de asemenea evidențiate.

Încărcat de

Sparrow Jack
Drepturi de autor
© All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca PDF, TXT sau citiți online pe Scribd

Contents

1.1. Rețeaua de calculatoare și straturile ei ........................................................................................ 3


Ce este internetul? .................................................................................................................................... 3
Noduri și pachete ...................................................................................................................................... 5
Topologia de rețea .................................................................................................................................... 6
Straturile rețelei ........................................................................................................................................ 8
1.2. Socketuri şi protocoale de transport .......................................................................................... 10
Porturi şi socketuri ................................................................................................................................. 11
Tipuri de soketuri ................................................................................................................................... 11
Protocoale de transport ......................................................................................................................... 12
Protocolul UDP ........................................................................................................................................ 12
Protocolul TCP ......................................................................................................................................... 13
Tripla strângere de mână (3-way handshake) .................................................................................. 14
Diferențe între TCP și UDP .................................................................................................................... 15
1.3. Protocoale de aplicaţie .................................................................................................................. 16
Protocolul HTTP ...................................................................................................................................... 18
Metode HTTP............................................................................................................................................ 18
Antetul HTTP ............................................................................................................................................ 19
2.1. Protocolul UDP ................................................................................................................................ 22
Clientul UDP ............................................................................................................................................. 22
Serverul UDP ............................................................................................................................................ 24
Inițierea și analiza rezultatelor ............................................................................................................ 25
2.2. Protocolul TCP ................................................................................................................................. 27
Procedura de triplă potrivire (three-way handshake) .................................................................... 27
Clientul TCP .............................................................................................................................................. 27
Serverul TCP ............................................................................................................................................. 29
Inițierea și analiza rezultatelor ............................................................................................................ 30
2.3. Protocolul WebSocket ................................................................................................................... 32
Sondaj HTTP lung/HTTP long polling ................................................................................................. 32
WebSocket ................................................................................................................................................ 33
Avantajele WebSocket-ului ................................................................................................................... 33
Utilizarea WebSocket ............................................................................................................................. 33
Implementarea WebSocket în Python................................................................................................. 34
Client WebSocket..................................................................................................................................... 35
WebSocket server ................................................................................................................................... 37
Inițierea și analiza rezultatelor ............................................................................................................ 38
3.1. Bazele HTTP ..................................................................................................................................... 41
Python ca şi client HTTP ........................................................................................................................ 42
Cererea GET către serverul HTTP ........................................................................................................ 42
Cererea POST către serverul HTTP ...................................................................................................... 46
Descărcarea fișierelor din rețea ........................................................................................................... 47
3.2. Client și server HTTP ...................................................................................................................... 49
Clase de server și clase pentru procesare .......................................................................................... 49
Serverul HTTP.......................................................................................................................................... 50
Clasa pentru procesare (handler class) .............................................................................................. 50
Metoda GET .............................................................................................................................................. 50
Metoda POST ............................................................................................................................................ 52
Clientul HTTP ........................................................................................................................................... 54
Inițierea și analiza rezultatelor ............................................................................................................ 56

Page 2 of 57
1.1. Rețeaua de calculatoare și straturile ei

Rezumat

• Rețelele de calculatoare sunt construite din componente: clienți, servere, cabluri, plăci de rețea,
switch-uri, software de rețea și rețele fără fir.
• În funcție de suprafaţa pe care se întinde rețeaua, rețelele sunt împărțite în LAN (Local Area
Network), WAN (Wide Area Network) și MAN (Metropolitan Area Network).
• Un nod este, de fapt, un dispozitiv dintr-o rețea care poate fi chiar calculatorul nostru.
• Un pachet este, de fapt, mesajul care este trimis prin rețea, de la un nod la altul, care conține
adresa inițială, destinația și conținutul.
• Topologia rețelei, adică modalitățile de configurare a rețelelor, poate fi: topologia magistrală,
topologia stea, topologia inel, topologia plasă (mesh), precum și topologia hibridă (combinarea
unor topologii diferite într-una singură).
• OSI (Open System Interconnection) definește ce aspecte ale funcționalității rețelei pot fi supuse
standardelor de rețea.
• Straturile inferioare (1, 2, 3, 4) se ocupă cu formatarea, codificarea și transmiterea datelor prin
rețea.
• Straturile superioare (5, 6, 7) se ocupă de interacțiunea cu utilizatorul final și de implementarea
aplicațiilor în aceste rețele.

Ce este internetul?

Internetul este o rețea de calculatoare care conectează milioane de dispozitive din întreaga lume. Până
de curând, aceste dispozitive erau în primul rând desktopuri tradiționale (atât sisteme Windows și
macOs, cât și sisteme Linux), dar și servere care stochează și transmit informații precum site-uri web și
adrese de e-mail. Recent, au dobândit întâietate sistemele netradiționale care utilizează internetul, ca
laptopurile, smartphone-urile, tabletele, televizoarele, consolele, camerele, mașinile și diferiți senzori.
Deoarece varietatea de dispozitive care utilizează internetul este mare, termenul de rețea de
calculatoare este puțin depășit. În jargonul internetului, aceste dispozitive se numesc fie sisteme gazdă
(hosts), fie sisteme finale (end systems).

Componentele rețelei de calculatoare

În funcție de dimensiunea rețelei, există un anumit număr de componente de care e nevoie pentru ca
aceasta să fie de fapt o rețea. În cazul rețelelor mai mici, aceste componente sunt plăcile de rețea din
calculatoare, cablurile care conectează aceste plăci și comutatorul care conectează acele cabluri. Rețelele
de calculatoare mai mari constau din mai multe componente:

• Clienți – calculatoare sau dispozitive pe care utilizatorii finali le folosesc pentru a accesa
internetul. Deci, este vorba despre dispozitivele pe care utilizatorul le folosește în fiecare zi:
telefon mobil, calculator desktop, laptop. Calculatoare client se mai numesc și stații de lucru.
• Servere – dispozitive, în principal calculatoare, care asigură resurse precum serviciul de rețea -
trimite e-mailuri și accesează internetul și stochează datele. Sistemele de operare ale
calculatoarelor server sunt în mare parte dedicate, specializate, ceea ce le permite să furnizeze
aceste servicii. Câteva dintre aceste sisteme de operare sunt Windows Server și Linux.
• Carduri de rețea – card conectat la calculator, care îi permite să comunice prin rețea.
Majoritatea calculatoarelor de astăzi vin cu acest card încorporat direct în placa de bază a
calculatorului. Fiecare client și fiecare server trebuie să aibă un astfel de card ca să poată face
parte din rețea.
• Cabluri – calculatoarele în reţea sunt conectate fizic între ele prin cabluri. De-a lungul istoriei
rețelelor de calculatoare, au fost utilizate mai multe tipuri de cabluri, dar inclusiv azi se folosesc

Page 3 of 57
cablurile cu perechi răsucite (twisted pair), cabluri coaxiale și cabluri optice. Cablurile cu perechi
răsucite au fost utilizate inițial în telefonie și abia ulterior au început să fie folosite în rețelele de
calculatoare. Acestea sunt formate din una sau mai multe perechi de fire de cupru împletite cu
scopul de a reduce zgomotul electric. Cablurile coaxiale conțin un singur fir de cupru care este
înconjurat de trei straturi care izolează și protejează. În general, sunt utilizate în televiziunea
prin cablu și permit intrări mai mari decât cablurile telefonice. Cablurile optice sunt alcătuite
dintr-un număr mare de fire de sticlă sau plastic care utilizează lumina pentru a transmite date.
Dintre aceste trei tipuri de cabluri, acestea din urmă permit cel mai rapid și mai mare flux de
date.
• Comutatoare (switches) – Cablurile de rețea nu conectează calculatoarele direct, ci o fac
printr-un dispozitiv numit comutator (switch). Acest dispozitiv conectează un calculator
conectat cu alte calculatoare. Fiecare comutator are un anumit număr de porturi (de obicei 8,
16, dar și mai multe) și, în funcție de aceasta, stabilește câte calculatoare pot fi interconectate.
De asemenea, switch-urile pot fi interconectate pentru a crea rețele mai mari.
• Rețelele fără fir (wireless networks) - În majoritatea rețelelor, conexiunile prin cablu sunt
înlocuite de conexiunile fără fir care permit același tip de comunicare între două calculatoare,
dar prin unde radio. În rețelele fără fir, emițătoarele și receptoarele preiau rolul cablurilor. Cel
mai mare avantaj al rețelelor fără fir este reflectat chiar de numele lor - nu avem nevoie de un
cablu ca să ne conectăm la rețea. Cel mai mare dezavantaj al acestor rețele este faptul că suntem
limitați de raza semnalului emis de transmițător, dar și de faptul că aceste rețele sunt mai puțin
sigure și stabile decât rețelele prin cablu.
• Software de rețea – Deși fără hardware de rețea nu există rețele de calculatoare, software-ul
care utilizează funcționalitatea acestui hardware este de asemenea, necesar. Calculatoare server
folosesc, în general, sisteme de operare dedicate pentru a-și îndeplini rolul, în timp ce
calculatoarele client trebuie să fie configurate corespunzător pentru a putea accesa rețeaua
dorită.

Imaginea 1.1. Un exemplu de conexiune a componentelor unei rețele de calculatoare1

Page 4 of 57
Avantajele rețelelor

Deși aceste componente pot părea complexe și dificil de configurat, deoarece există atât de mulți factori,
rețelele au avantaje mari, cel mai mare avantaj fiind partajarea. Mai exact, rețelele sunt create tocmai
pentru a partaja informații, resurse și aplicații.

• Partajarea informațiilor - Rețelele permit utilizatorilor să partajeze informații în mai multe


moduri, în funcție de ce tip de informație este vorba. Modul obișnuit de partajare a informațiilor
este partajarea de fișiere. Pe lângă aceasta, rețelele sunt folosite și pentru a face schimb de
mesaje, prin e-mailuri sau prin aplicații populare de pe dispozitive mobile ca WhatsApp, Viber
și altele. Și mesajele video se pot partaja prin rețele.
• Partajarea resurselor – Câteva resurse de calculator, cum ar fi de exemplu imprimantele sau
hard diskurile, pot fi partajate prin rețea astfel încât şi alți utilizatori să le poată accesa, ceea ce
reduce costurile. De asemenea, pe lângă aceste resurse, chiar şi conexiunea la internet poate fi
partajată astfel: mai multe calculatoare sunt conectate într-un singur switch (comutator) care
este conectat la internet și, în felul acesta şi calculatoarele respective au acces la internet.
• Partajarea aplicațiilor – Unul dintre motivele comune pentru crearea rețelelor este că
utilizatorii pot să lucreze împreună la aceeași aplicație. De exemplu, un departament dintr-o
companie poate să aibă propria aplicație pe care mai mulți angajați ai acelei companii o pot
accesa în același timp de pe calculatoare diferite.

Dimensiunea reţelei

În funcție de numărul de dispozitive din rețea și al suprafeţei acoperite de acea rețea, rețelele pot fi
împărțite în:

• Local area network (LAN) – rețeaua locală este o rețea în care calculatoarele sunt, în general,
apropiate unele de altele, de exemplu în același birou sau în aceeași clădire. Trebuie să
menţionăm că termenul LAN nu înseamnă neapărat că rețeaua este mică, adică că are puține
dispozitive conectate la ea. O rețea LAN poate avea sute de dispozitive, dar este important ca
aceste dispozitive să fie apropiate unele de altele.
• Wide area network (WAN) – rețeaua regională este o rețea care se extinde în tot orașul, în
toată regiunea sau în întreaga țară. Aceste rețele sunt adesea utilizate pentru a conecta două
sau mai multe rețele LAN. De exemplu, o rețea WAN poate să conecteze birourile unei companii
(care se află în rețeaua LAN) cu birourile aceleiași companii din alt oraș. Din nou, trebuie să
menţionăm că, de fapt, suprafața este cea care determină dacă este o rețea LAN sau WAN.
• Metropolitan area network (MAN) – rețeaua urbană este mai mică decât o rețea WAN și mai
mare decât o rețea LAN. Practic, rețeaua metropolitană combină mai multe rețele locale din
același oraș care nu pot fi interconectate prin cablu sau wireless.

Noduri și pachete

Când vorbim de transferul concret de informații între două dispozitive, trebuie să utilizăm
termenii noduri și pachete:

• Noduri (engl. nodes) – Un nod este, de fapt, un dispozitiv pe o rețea - poate fi chiar
calculatorul nostru. În rețele, accentul se pune pe modul în care nodurile sunt interconectate.
• Pachete (engl. packets)– Un pachet este, de fapt, mesajul care se trimite prin rețea, de la un
nod la altul. Pachetul conține adresa nodului de la care a fost trimis, determinantul nodului la
care ar trebui să ajungă, precum și informațiile care sunt trimise. Dacă aceste informații sunt
mari, pachetul poate fi împărțit în pachete mai mici și trimis treptat prin rețea.

Page 5 of 57
Topologia de rețea

Termenul de topologie în contextul rețelelor se referă la forma de interconectare a calculatoarelelor și a


altor componente de rețea.

• Topologia magistrală (engl. bus topology) - În acest tip, nodurile sunt conectate în linie.
Calculatoarele se conectează des în acest mod la rețelele locale (LAN). Acesta este un tip de
rețea în care toate calculatoarele sunt conectate pe același cablu care se extinde de la un capăt
la altul al rețelei, astfel încât datele să fie transmise doar într-o singură direcție (half-duplex).
o Avantaje: Este potrivită pentru rețele mai mici și ușor de configurat. De aceea, şi costul
configurării unei astfel de rețele este mai mic.
o Dezavantaje: Aceste rețele sunt expuse la căderi și încetiniri. Deoarece este utilizat un
cablu, dacă apare o problemă pe el, întreaga rețea nu va mai funcționa.

Imaginea 1.2. Aspectul topologiei magistrale2

• Topologia stea (engl. star topology) – Topologia stea este o rețea în care fiecare nod este
conectat direct la un singur switch (comutator) central sau router. Într-o rețea configurată astfel,
acel switch central este un server, iar dispozitivele conectate la acesta sunt clienți.
o Avantaje: Avantajul acestor rețele este că întreaga rețea poate fi controlată dintr-o
singură locație centrală. Dacă un nod din rețea nu mai funcționează, celelalte noduri
vor continua să funcționeze fără probleme. Se pot adăuga noduri noi fără a opri
întreaga rețea. De asemenea, aceste rețele necesită mai puține cabluri, deci sunt mai
ușor de configurat atunci când e nevoie să fie conectate mai multe dispozitive (noduri).
o Dezavantaje: Deoarece în aceste rețele există un singur dispozitiv central, care are
sarcina de a controla și gestiona rețeaua, dacă acesta cade, întreaga rețea va înceta să
funcționeze. De asemenea, performanța întregii rețele depinde de acel dispozitiv
central și de configurația lui.

Imaginea 1.3. Aspectul topologiei stea3

Page 6 of 57
• Topologia mesh (plasă) (engl. mesh topology) – Topologia mesh este un sistem de conexiuni
în care toate nodurile sunt interconectate, fără nicio ierarhie. Această structură permite două
tipuri de transmitere a datelor: flooding și rutare. Rutarea în acest context se referă la logica de
a găsi cea mai mică distanță când se trimite un pachet, în timp ce flooding nu necesită nicio
logică atunci când se caută un nod de destinație.
o Avantaje: Rețelele organizate astfel sunt foarte fiabile. Pot să gestioneze niveluri ridicate
de trafic, deoarece fiecare nod poate să transmită pachete în același timp. De asemenea,
aceste rețele sunt caracterizate de o stabilitate ridicată și rezistență la erori și eșecuri,
deoarece rețeaua va continua să funcționeze indiferent de care nod cade.
o Dezavantaje: Datorită conexiunilor foarte numeroase, aceste rețele sunt complexe și
costisitor de configurat și întreținut.

Imaginea 1.4. Aspectul topologiei mesh4

• Topologia inel (engl. ring topology) – Într-o astfel de rețea nodurile sunt conectate într-un
cerc, unde fiecare nod din rețea va avea două noduri adiacente (la stânga și la dreapta unul de
altul). Pentru că rețelele sunt configurate așa, pachetele trebuie să treacă prin toate nodurile din
secvență ca să ajungă la destinație. Aceste rețele sunt mai puțin utilizate azi. Pentru că în aceste
rețele pachetul poate să călătorească doar într-o singură direcție, ele se numesc semi-duplex.
Dacă am vrea să transformăm topologia inel în full-duplex (pachetele să poată merge în ambele
direcții), ar trebui să adăugăm încă o conexiune și astfel obținem o topologie inelară dublă (engl.
- dual ring topology).
o Avantaje: fluxul circular de date ne permite să reducem coliziunea pachetelor și astfel
să obținem fluxuri mai mari. Datorită modului în care sunt instalate, aceste rețele sunt
mai ieftin de configurat și permit eliminarea ușoară a defectelor de pe ele.
o Dezavantaje: o problemă pe un nod poate să provoace o defecțiune a întregii rețele.
De asemenea, extensibilitatea acestei rețele este pusă sub semnul întrebării deoarece
adăugarea de noduri noi poate crea întârzieri în comunicare.

Imaginea 1.5. Aspectul topologiei inel5

Page 7 of 57
• Topologiile hibride sunt adesea întâlnite în practică și reprezintă o combinație de mai multe
tipuri de rețele care au fost descrise în acest capitol.

Imaginea 1.6. Un exemplu de topologie hibridă6

Straturile rețelei

Pentru că există un număr imens de utilizatori care folosesc zilnic rețele de calculatoare în întreaga lume,
este nevoie de un set de standarde care să reglementeze configurația acestor sisteme. Acesta este
scopul modelului ISO-OSI.

Modelul OSI (Open System Interconnection, 1984), creat de ISO (International Organization for
Standardization), împarte diferitele aspecte ale rețelelor de calculatoare în șapte straturi diferite. OSI
definește ce aspecte ale funcționalității rețelei pot fi supuse standardelor de rețea. Aceste șapte straturi
ale rețelei de calculatoare sunt (de la cel mai de jos la cel mai înalt):

Imaginea 1.7. Structura modelului OSI (de la cel mai înalt la cel mai de jos) 7

Page 8 of 57
Stratul fizic - se referă la caracteristicile fizice ale rețelei, cum ar fi tipurile de cablu și conector, tensiunea,
frecvența și altele asemenea. De asemenea, stratul fizic influenţează şi tipul de arhitectură care va fi
utilizată.

Stratul de legătură/date (engl. data link layer) - pe acest strat se stabilește dimensiunea și numărul
de pachete care trebuie trimise. Pe acest nivel, se vede şi ce adresă MAC (Media Access Control) are
nodul. Această adresă este, de fapt, imprimată fizic pe fiecare dispozitiv de rețea de către producător și
este unică - deci în lume nu există două dispozitive care să aibă aceeași adresă MAC.

Stratul de rețea (engl. network layer) - acest strat permite primirea și trimiterea unui pachet la adresa
specificată în pachet. Destinația se găsește folosind adrese logice – IP (internet protocol). Stabilirea
adreselor de trimitere și primire se face folosind un router.

Notă:

Pentru a verifica adresa MAC și IP, utilizăm comanda ipconfig/all din consolă (Command Prompt).

Stratul de transport (engl. transport layer) - controlează transferul pachetelor și găsește erori. Acest
strat realizează transferul securizat de pachete, verificând ce pachete au fost trimise și le trimite din
nou pe cele care nu au ajuns la destinație. Acest strat include și protocolul TCP (Transmission Control
Protocol) cu ajutorul căruia înainte de a trimite un pachet, trebuie mai întâi să stabilim o conexiune.

Stratul de sesiune (engl. session layer) - controlează comunicarea între diferite noduri. În acest strat,
o sesiune sau conexiune între dispozitive este stabilită și terminată și gestionată (în timpul unei
sesiuni se pot efectua mai multe schimburi de diferite pachete). Acest strat acceptă, de asemenea şi
autentificarea și reconectarea.

Stratul de prezentare (engl. presentation layer) - acest strat „traduce” datele și le salvează pentru
următorul strat de aplicație, conform regulilor sintactice date, de aceea acest strat este uneori denumit
și strat sintactic. Pe acest strat se pot efectua criptarea şi decriptarea datelor, precum
și compresia/decompresia lor.

Stratul de aplicație (engl. application layer) - ultimul (cel mai înalt) strat al modelului OSI. Se referă
la tehnicile și metodele care sunt furnizate aplicațiilor și programelor pentru a fi utilizate în comunicarea
cu rețeaua. Unele dintre protocoalele cunoscute din stratul de aplicație sunt:

• DNS (Domain Name System) – sistem de denumire a domeniului;


• FTP (File Transfer Protocol) – protocol de transferare a fișierelor;
• SMTP (Simple Mail Transfer Protocol) – protocol de trimitere a e-mailurilor;
• Telnet – protocol pentru copierea terminalelor și a liniilor de comandă.

1. [Link]
2. [Link]
3. [Link]
4. [Link]
5. [Link]
6. [Link]
7. [Link]

Page 9 of 57
1.2. Socketuri şi protocoale de transport

Rezumat

• Protocoalele de transport de bază ale stratului de transport sunt UDP și TCP.


• Socketurile ne permit să comunicăm între două procese diferite pe aceeași mașină sau mașini
diferite.
• Un socket este format din două părți: adresa IP și portul (exemplu: [Link]:80).
• Cel mai des utilizate tipuri socketuri sunt stream sockets și datagram sockets.
• Numerele port le putem forma în mod arbitrar, dar se recomandă ca acestea să fie cuprinse în
intervalul de la 49152 până la 65535.
• UDP (User Datagram Protocol) este un protocol care nu necesită o conexiune prealabilă și nu
are raportare de erori încorporată, dar de aceea este mai rapid decât TCP.
• TCP (Transmission Control Protocol) este un protocol care necesită realizarea prealabilă a unei
conexiuni prin tripla strângere de mână și acceptă raportarea și căutarea segmentelor, dar de
aceea este mai lent decât UDP.

Stratul de transport este al patrulea strat din modelul OSI, responsabil cu colectarea datelor de utilizator
din stratul de aplicație și pregătirea lor pentru transportarea ulterioară prin rețea, mai precis, în primul
rând, pentru transportarea prin stratul de rețea. Responsabilitatea stratului de transport se reflectă în
obligația de a transporta datele de utilizator de la sursă la destinație.

Legătura dintre stratul de transport și stratul de rețea se reflectă în faptul că stratul de transport asigură
comunicarea între procesele de pe un anumit dispozitiv (host), în timp ce stratul de rețea asigură
comunicarea între dispozitive (hosts). De asemenea, o parte a stratului de rețea este şi IP (Internet
Protocol) care, de fapt, ne permite această comunicare între dispozitive. De aceea, se poate spune că
protocoalele de transport sunt singurele responsabile pentru comunicarea proces-la-proces.

Notă

Întâlnim adesea termenul de adresă IP. Adresa IP este o valoare numerică atribuită fiecărui dispozitiv conectat
la o rețea de calculatoare care utilizează un IP (Internet Protocol) ca modalitate de comunicare. În prezent, în
circulație sunt două versiuni IP: mai vechi, IPv4 (adresele acestei versiuni sunt scrise în format: [Link])
și mai noi: IPv6 (adresele IP ale acestei versiuni sunt scrise în format 2001: db8: 0:1234:0:567: 8:1). Dacă adresa
IP este vizibilă doar în rețeaua noastră de calculatoare, atunci este o adresă locală și dacă acea adresă este vizibilă
(și poate fi accesată) și în afara rețelei noastre, de pe internet, atunci este o adresă IP publică.

Câteva dintre funcțiile stratului de transport

• Stratul de transport ne oferă un mediu (interfață) pentru a accesa aplicațiile de rețea în rețea.
• Stratul de transport ne oferă un mediu (interfață) pentru a accepta datele din diferite aplicații
pe mașina curentă și pentru a le trimite către mașina de destinație. Același proces are loc şi pe
mașina de destinație.
• Stratul de transport acceptă mecanisme de livrare a datelor prin rețea fără pierderi.
• Stratul de transport oferă mecanisme de detectare a erorilor, controlare a fluxului de transmisie
și retrimitere a datelor ratate (pachete).

Page 10 of 57
Porturi şi socketuri

Socketurile (conexiuni, engl. prize) ne permit să comunicăm între două procese diferite pe aceeași
mașină sau pe mașini diferite. Din punctul de vedere al programatorului, socketurile arată și se comportă
foarte asemănător cu descriptorii de fișiere de nivel inferior, deoarece acceptă concepte ca scrierea și
citirea. Socketul este format din două părți separate prin caracterul două puncte (:) - adresa IP și numărul
portului.

În exemplul [Link]:80:

• [Link] este adresa internet protocol (IP);


• 80 este numărul portului (variind în mod arbitrar de la 0 la 65535).

Numărul de intrare (port number) este un număr de 16 biți care se află situat
în antetul (header) protocolului din cadrul stratului de transport. De asemenea, porturile trebuie să fie
numere unice în cadrul protocolului. De exemplu, pot să existe două porturi identice în UDP și TCP,
dar nu pot să existe două numere identice în protocolul TCP.
Numărul portului poate să ocupe maximum 16 biţi, ceea ce ne oferă 65535 de numere disponibile.

Porturile sunt împărțite în zone:

• numerele de la 0 la 1023 - porturi deja cunoscute, alocate de organizația IANA (Internet


Assigned Numbers Authority), atribuite serverelor spre utilizare;
• numerele de la 1024 la 49151 - porturi înregistrate; aceste numere nu sunt alocate de IANA, dar
pot fi înregistrate; deci cu adresa IP căutăm dispozitivul dorit în rețea, iar numărul portului ne
permite să comunicăm cu procesul dorit pe dispozitivul găsit anterior.
• numerele de la 49152 până la 65535 - aceste numere sunt porturi dinamice și nu sunt alocate,
controlate sau înregistrate. Sunt folosite pentru porturi temporare sau private.

Lista porturilor poate fi găsită pe lista oficială la link IANA.

Tipuri de soketuri

Există patru tipuri de socketuri care ne sunt disponibile în general (și toate cele patru tipuri ne sunt
disponibile și în Python). Dintre aceste patru tipuri de socketuri, primele două sunt utilizate cel mai des,
iar celelalte două mai rar. Se recomandă ca procesele să comunice între socketuri de același tip, dar
aceasta nu este o regulă.

• Datagram sockets – Livrarea datelor trimise nu este garantată. Aceste socketuri


sunt connectionless - nu au nevoie de o conexiune directă. Deci, nu avem nevoie de o conexiune
deja stabilită pentru a trimite datele - pachetul este generat cu informațiile despre destinație și
este trimis ca atare. Aceste socketuri utilizează protocolul UDP, despre care vom discuta mai
târziu în lecție.
• Stream sockets – Livrarea datelor trimise este garantată. Dacă trimitem datele în ordinea 1,2,3,
ele vor ajunge în aceeași ordine. Aceste socketuri pentru comunicații utilizează protocolul TCP.
În cazul în care livrarea nu este posibilă din anumite motive, expeditorul trimite destinatarului
o eroare.
• Raw sockets – Acestea oferă acces utilizatorului la toate protocoalele de nivel inferior. Nu sunt
destinate utilizatorilor în general, ci servesc pentru dezvoltarea unor protocoale noi. Sunt
orientate pe datagrame.

Page 11 of 57
• Sequenced packet sockets – Sunt similare cu stream sockets, deci trebuie în primul rând să
stabilească o conexiune. Aceste socketuri permit în continuare lucrul cu Sequence Packet
Protocol (SPP) sau Internet Datagram Protocol (IDP).

Protocoale de transport

Dintre protocoalele principale ale stratului de transport enumerăm TCP (Transmission Control Protocol)
și UDP (User Datagram Protocol). În cadrul acestora au fost implementate alte protocoale (care fac parte
din straturile superioare ale modelului OSI), cum ar fi HTTP, HTTPS, VoIP și altele. Deoarece IP ca atare
nu este fiabil (singurul lucru pe care îl garantează IP este realizarea unei conexiuni; nu garantează accesul
sigur și ordonat al pachetelor), rolul UDP și TCP este de a extinde funcționalitatea IP. Atât UDP cât și
TCP asigură verificarea integrității datelor primite. TCP și UDP utilizează porturi pentru a stabili cărui
proces de pe mașina curentă să-i trimită datele necesare.

Curiozitate

Datagrama (monedă al cărei nume a apărut din combinarea cuvintelor data și telegram) este o unitate de măsură,
la fel ca pachetul, dar diferă de acesta prin faptul că datagrama nu necesită confirmarea că a fost primită.

Protocolul UDP

UDP (User Datagram Protocol) este un protocol care nu necesită o conexiune prealabilă. Nu are control
de erori încorporat (nu raportează erori, dar poate verifica dacă există) și nu va retrimite pachetele
pierdute. Din cauza acestor dezavantaje, deși acest protocol este mult mai rapid decât TCP, este
considerat nesigur. Este potrivit în situațiile în care trebuie să primim date constant și rapid, cum ar fi de
exemplu un apel video sau vizionarea unui videoclip live pe internet. La fel ca toate celelalte protocoale,
şi UDP conține un antet care constă din anumite câmpuri, predefinite, precum și o secțiune pentru
transferul de date (payload). Antetul protocolului UDP este format din:

• source port (16 bits) – numărul portului de la care sunt trimise datele (pachetul);
• destination port (16 bits) – numărul portului unde ar trebui să fie livrat pachetul;
• length (16bits) – lungimea întregii datagrame UDP, inclusiv antetul și datele care fac parte din
pachetul curent;
• checksum (16bits) – suma de control a întregii datagrame (inclusiv antetul și datele care fac
parte din pachetul curent); este opțional și se utilizează pentru a verifica integritatea datelor și
a antetului primit; de asemenea, această parte a antetului conține alte câteva date suplimentare,
mai exact 12 octeţi ai așa-numitului pseudo-header, moștenit de la datagrama IP, care sunt
utilizați în cea mai mare parte pentru calcularea sumei de control. Acești 12 octeți din datagrama
IP sunt:
o IP source address (4 byți) – adresa IP a dispozitivului de la care este trimis pachetul;
o IP destination address (4 byți) – adresa IP a dispozitivului către care este trimis pachetul;
o UDP Length (2 byți) – lungimea datagramei UDP moștenită din antetul IP;
o protocol (1 byte) – câmpul din antetul IP rezervat protocolului (pentru UDP este 17);
o zero (1 octet) – câmp care conține doar zero.

Page 12 of 57
Imaginea 2.1. Tabel cu antetul UDP 1

Protocolul TCP

Pentru ca TCP (Transmission Control Protocol) să funcționeze, trebuie mai întâi să stabilim o conexiune.
Suportă căutarea şi raportarea erorilor (troubleshooting).

Câteva din protocoalele de aplicație bazate pe TCP sunt HTTP și HTTPS, care sunt rezervate pentru site-
uri, SMTP și POP, care sunt rezervate pentru trimiterea de e-mailuri, precum și FTP - pentru trimiterea
fișierelor.

Cele trei principii de bază ale protocolului TCP sunt:

• asigurarea livrării sigure a segmentelor într-o ordine dată;


• asigurarea controlului fluxului astfel încât destinatarul să poată primi toate segmentele;
• verificarea corectitudinii segmentelor primite (gestionarea erorilor) și solicitarea retrimiterii
segmentelor deteriorate.

TCP descompune datele pe care le primește din stratul de aplicație în părți mai mici - segmente. Aceste
segmente sunt numerotate înainte de a fi trimise IP-ului, care le încapsulează în continuare în pachete.
TCP monitorizează numărul de pachete trimise; dacă nu primește confirmarea sosirii acestor pachete
într-o anumită perioadă de timp, va presupune că pachetele s-au pierdut și le va trimite din nou. De
asemenea, TCP verifică fiecare segment pentru eventuale modificări (gestionarea erorilor), opțiune
activată de câmpul checksum din antet. La fel ca UDP și alte protocoale, TCP folosește socketuri pentru
a indica care proces și care mașină ar trebui să livreze pachetul. Cum TCP poate să primească un număr
mare de segmente care trebuie verificate individual, trebuie controlată cantitatea de segmente care pot
fi primite. Acest lucru este definit de expeditor, prin câmpul window size, în care se stabilește numărul
de segmente pe care îl poate primi destinatarul până când trimite o confirmare.

Lista câmpurilor din antet este următoarea:

• source & destination port (2 x 16 biți) – portul TCP al expeditorului și al receptorului;


• sequence number (32 biți) – numărul primului octet de date din partea payload a segmentului;
• acknowledgment number (32 biți) – următorul octet așteptat, ceea ce înseamnă că destinatarul
a primit toți octeții trimși către el;
• flow control window (16 biți) – dimensiunea ferestrei de control definește câte segmente poate
să primească receptorul până când trimite o confirmare de primire;
• data offset (4 biți) – utilizat pentru a localiza începutul și sfârșitul părții payload a segmentului;
trebuie avut în vedere faptul că TCP nu trebuie să conțină alte date decât anteturile;
• urgent pointer (16 biți) – dacă există date urgente, acest câmp indică locația octeților lor; cum
terminalele funcționează similar cu socketurile (cel puțin atunci când este vorba de citire și

Page 13 of 57
scriere), se poate lua exemplul cazului în care utilizatorul trimite comanda Ctrl+C terminalului;
cu această combinație de taste dorim să întrerupem și să încheiem procesul curent în terminal
- în acest caz, Ctrl+C ar fi date urgente;
• flags (6 biți) – acest câmp este format din șase indicatori de un bit:
o urgent pointer (URG) – dacă valoarea acestui indicator este 0, câmpul urgent
pointer este ignorat, în caz contrar, indică existența acestuia;
o acknowledgment valid (ACK) – indică dacă câmpul acknowledgment number (număr de
confirmare) este valid; singurul caz în care acesta nu este valid este în timpul „triplei
strângeri de mână” (triple handshake) care are loc la începutul conexiunii;
o rest (RST) – indicator utilizat pentru deconectarea rapidă; scopul este de a semnala
erori;
o push (PSH) – dacă acest indicator este accesibil, acesta este un indiciu că destinatarul
trebuie să predea imediat segmentele curente stratului de deasupra;
o synchronization (SYN) – folosit pentru a iniția o conexiune nouă;
o finish (FIN) – folosit pentru deconectare.

Imaginea 2.2. Tabel cu antetul TCP 2

Tripla strângere de mână (3-way handshake)

Acest mod de a realiza o conexiune este inerent protocolului TCP. Să presupunem că A este un client,
iar B este un server - atunci această „strângere de mână” arată aşa:

• Clientul A trimite serverului B un segment cu indicatorul SYN setat (SYN=1). Indicatorul ACK
este încă 0 (ACK = 0).
• Serverul B răspunde cu propriul pachet, care are, de asemenea, setat un indicator SYN. Ca
răspuns, indicatorul ACK este setat la 1.
• Clientul A corespunde indicatorului SYN al serverului cu propriul indicator ACK (clientul ACK
este acum setat la 1).

Page 14 of 57
Imaginea 2.3. Diagrama triplei strângeri de mână

Acest mod de comunicare se realizează doar la început și fără el, transferul de date nu poate să înceapă.

Diferențe între TCP și UDP

Diferențele dintre aceste două protocoale sunt prezentate în tabel:

Categoria TCP UDP


Nume Transmission Control Protocol User Datagram Protocol
Protocol este necesară stabilirea unei conexiuni nu este necesară stabilirea unei conexiuni
Verifică erorile doar cu checksum, dar nu le
Securitate Raportează şi caută erori cu checksum
raportează
Trimiterea
lent rapid
datelor
Aplicare Email, FTP VoIP

Tabelul 2.1. Tabel cu diferenţele între UDP şi TCP

O analiză mai detaliată a performanței acestor protocoale vedeți la acest link.

1. [Link]
2. [Link]

Page 15 of 57
1.3. Protocoale de aplicaţie

Rezumat

• Stratul de aplicație este cel mai înalt strat al modelului OSI.


• Cel mai utilizat protocol al stratului de aplicație este HTTP, care se bazează pe protocolul de
transport TCP.
• Protocolul de aplicație definește tipul mesajului, sintaxa mesajului, semnificaţia câmpului şi
termenii și condițiile pentru comunicare.
• Uniform Resource Locator URL (Locație unică a adresei HTTP) - conține cele mai importante
informații într-o solicitare GET - o combinație a locului în care se află site-ul, schema utilizată
pentru a căuta pagina de pe acel site și calea către numele fișierului care conține pagina
solicitată.
• HTTP funcționează pe bază principiului cerere-răspuns.
• Seturile antetului HTTP sunt selectate în funcție de trimiterea unei cereri sau de primirea unui
răspuns de la server.

Stratul de aplicație este al șaptelea, adică cel mai înalt strat al modelului OSI. Toată manipularea directă
a datelor și a informațiilor se desfăşoară pe acest nivel și, astfel, se permite accesul la software și utilizator.
Unele dintre serviciile furnizate de acest strat sunt: e-mail, trimiterea și primirea de fișiere, resurse de
rețea, distribuirea datelor către utilizator etc.

Acest strat constă din diferite tipuri de protocoale. Unul dintre acestea este larg răpânditul HTTP
(HyperText Transfer Protocol), care stă la baza World Wide Web-ului. Ori de câte ori deschidem o pagină
în browser, acesta trimite numele paginii dorite și locația acesteia către server, care utilizează HTTP, iar
serverul o trimite înapoi.

Notă

World Wide Web și HTTP sunt strâns legate. Şi anume, World Wide Web reprezintă un sistem informațional în
care documentele și alte resurse se adresează cu ajutorul adreselor URL (Uniform Resource Locators) și sunt
obţinute și trimise prin HTTP. Deci, World Wide Web nu este internetul în sine, ci doar o parte a lui, deși cei
doi termeni sunt adesea echivalați.

Alte protocoale de aplicaţie sunt:

• FTP (File Transfer Protocol) – îi permite utilizatorului să trimită fișiere și să tragă fișiere de pe o
mașină la distanță. Folosește protocolul TCP. Atunci când se face conectarea la un dispozitiv la
distanță, se realizează două conexiuni: o conexiune de date și o conexiune de control.
Conexiunea de date utilizează portul 20, iar conexiunea de control utilizează portul 21. O
conexiune (de date) este utilizată doar pentru transferul de date, în timp ce cealaltă (de control)
este utilizată pentru a trimite comenzi și a obține răspunsuri.
• SMTP (Simple Mail Transfer Protocol) – îi permite utilizatorului să trimită și să primească e-
mailuri. Folosește protocolul TCP. Protocolul SMTP are două părţi - cea de client și cea de server
și folosește portul 25.
• TELNET (remote login) – acces de la distanță care îi permite utilizatorului să trimită comenzi de
la o mașină la alta - prin intermediul unui terminal. Folosește protocolul TCP. Face acest lucru
folosind sistemul NVT (Network Virtual Terminal) care codifică comenzile trimise pe partea de
client și le decodează pe partea de server. Portul pe care îl folosește este 23.
• DNS (Domain Name System) – acest sistem ne permite maparea adresei IP cu numele site-urilor
pe care se prezintă acele adrese. DNS este împărțit în trei categorii:

Page 16 of 57
o Domeniu generic - acest domeniu se referă la „comportamentul” site-ului - indiferent
dacă este vorba de site-ul guvernului de stat (.gov), dacă este site-ul unei organizații
(.org) sau al unei instituții de învățământ (.edu). În acest domeniu sunt utilizate trei
caractere.
o Domeniul țării - acest domeniu este o abreviere din două litere a țării căreia îi aparține
site-ul (.me, .rs, .us). În acest domeniu sunt utilizate două caractere.
o Domeniu invers - acest domeniu este utilizat pentru a se mapa adresa IP la numele site-
ului.

Câteva dintre aceste protocoale utilizează UDP și câteva utilizează TCP ca protocol de transport.
Împărțirea protocoalelor de aplicație în funcție de care sunt utilizate protocoalele de transport poate fi
văzută în imaginea următoare:

Imaginea 3.1. Împărțirea protocoalelor de aplicație în funcție de protocoalele de transport pe care se


bazează

Funcțiile stratului de aplicație

• e-mail - acest strat oferă baza pentru trimiterea, primirea și stocarea e-mailurilor;
• terminale virtuale de rețea - îi permit utilizatorului să se conecteze la un dispozitiv la distanță;
aplicația emulează un terminal pe acel dispozitiv la distanță şi, astfel, se realizează comunicarea
între utilizator și acel dispozitiv final prin intermediul terminalului;
• transfer de fișiere - utilizatorul are posibilitatea de a accesa și gestiona un fișier la distanță și îl
poate şi adăuga;
• procesele de la ambele capete (dispozitive inițiale și de destinație) sunt ultimele din lanțul de
comunicații.

Un protocol de aplicație definește modul în care procesele de pe un anumit dispozitiv (sau două
dispozitive) își transferă informațiile între ele. Astfel, protocolul stratului de aplicație definește:

• tipul mesajului - dacă este vorba de mesaje ca solicitări sau mesaje ca răspunsuri;
• sintaxa mesajului - ce câmpuri de mesaj conține și cum sunt delimitate acele câmpuri din mesaj;
• semnificaţia câmpului - semnificația și selecția informațiilor pe care le va conține câmpul;
• termeni și condiții - cum și când va trimite procesul mesaje, respectiv va răspunde la mesaje.

Page 17 of 57
Protocolul HTTP

HyperTextTransfer Protocol, sau HTTP, este un protocol care permite o conexiune între un server web și
un client. HTTP este un protocol de aplicație pentru distribuirea informațiilor pe web. Utilizează portul
80. De asemenea, clientul HTTP poate fi un browser, cum ar fi Firefox, Google Chorme și altele. În World
Wide Web, informațiile sunt difuzate folosind fișiere HyperText Markup Language (HTML), în timp ce
locația acelor informații este definită de un URL (Uniform Resource Locator).
HTTP a fost definit pentru prima dată în 1990 și a trecut cinci revizuiri:

• HTTP 0.9 – implementarea inițială a protocolului, care a permis doar obţinerea paginii;
• HTTP 1.0 – implementat în 1996; în această versiune, pentru prima dată sunt văzute anteturile -
mai precis, câmpuri suplimentare la cerere;
• HTTP 1.1 – implementat și ratificat în 1997; una dintre cele mai importante modificări a fost o
conexiune permanentă care nu s-a încheiat după prima cerere - deci, au fost activate mai multe
solicitări pe o conexiune;
• HTTP 2 – ratificat în 2015; cu această versiune, solicitările în cadrul unei conexiuni au devenit
paralele, în timp ce anteturile erau comprimate;
• HTTP 3 – prezentat ca succesor al HTTP 2, pentru prima dată apare în 2019; cea mai mare
diferență este că, în acest caz, HTTP folosește protocolul UDP.

Obţinerea paginii

O pagină web este alcătuită din multe elemente și obiecte diferite - de la un șablon HTML care
alcătuiește o pagină până la imagini și clipuri video. Procesul de obţinere a tuturor acestor elemente
arată așa:

• clientul trimite o cerere pentru serverul străin dorit;


• serverul analizează cererea și trimite înapoi confirmarea către client împreună cu codul HTML,
care alcătuiește pagina;
• clientul va începe să analizeze acel cod HTML și să creeze pagina (afișând-o);
• în continuare, în următoarele solicitări, clientul va obţine şi toate celelalte obiecte, precum
imagini, clipuri video și alte surse multimedia.

După ce au fost procurate toate elementele, browserul web va termina de afișat pagina utilizatorului.

Metode HTTP

Pe lângă posibilitatea de a obţine date de pe server, HTTP permite şi trimiterea datelor către server.
Astfel de mecanisme de trimitere și primire de date în HTTP sunt prezentate ca metode. Lista metodelor
acceptate de HTTP este prezentată în următorul tabel:
METODA DESCRIERE
GET metoda prin care obţinem datele dorite (procurarea paginii)
HEAD metodă similară cu solicitarea GET, dar serverul trebuie să returneze doar antetul HTTP
POST metoda prin care clientul trimite informații către server (trimiterea unui formular de logare)
PUT metoda prin care clientul lasă date pe server în locația specificată
DELETE metoda prin care clientul poate șterge date de pe server
TRACE metodă care permite clientului să vadă cererea trimisă de server
OPTIONS metodă care îi arată clientului opțiunile disponibile pentru comunicarea cu serverul

Page 18 of 57
Tabelul 3.1. Aspectul tabelar al metodei HTTP

Când este vorba de căutarea pe internet, cele mai utilizate metode sunt GET și POST. Metoda GET este
utilizată pentru a obține obiecte și elemente de pe pagină (și pagina în sine), în timp ce metoda POST
este utilizată în situațiile în care sunt solicitate anumite informații de la client (utilizator) - logare,
cumpărături online etc.

Locație unică a adresei HTTP (URL URL)

Uniform Resource Locator URL (Locație unică a adresei HTTP) - conține cele mai importante informații
într-o solicitare GET - o combinație a locului în care se află site-ul, schema utilizată pentru a căuta
pagina de pe acel site și calea către numele fișierului care conține pagina solicitată. Opțional, adresa
URL poate conține informații despre numărul portului sau parametrii de interogare.
De asemenea, există şi URI (Uniform Resource Identifier), care identifică pagina după locație, nume sau
ambele criterii împreună.

• [Link] – URL;
• [Link] – URI;
• [Link] – URI.

Antetul HTTP

Deoarece există două tipuri de mesaje în HTTP - solicitări și răspunsuri, deci sunt utilizate două seturi
de anteturi diferite.

Cererea HTTP (HTTP request)

Un exemplu în care vom vedea câteva dintre câmpurile antet obişnuite care sunt utilizate la trimiterea
unei cereri, arată astfel:

Exemplu

1GET /[Link] HTTP/1.1


2
3Connection: close
4
5User-agent: Chrome/42.0.2311.135
6
7Accept: text/html, image/jpeg
8
9Accept-language:en

• GET /[Link] HTTP/1.1 – cu această linie în mesaj indicăm ce metodă HTTP folosim
atunci când solicităm (GET), ce pagină de pe server dorim (/[Link]), precum și versiunea
protocolului HTTP pe care o folosim (HTTP / 1.1).
• Connection: close – cu această linie îi spunem serverului că, după cererea curentă, nu vrem
să mai facem alte cereri și, astfel, să încheiem conexiunea. Dacă dorim să facem mai multe
solicitări într-o singură conexiune, vom utiliza valoarea Keep-Alive a conexiunii antetului
Connection al cererii.

Page 19 of 57
• User-agent: Chrome/42.0.2311.135 – cu antetul User-agent indicăm cărui agent
utilizator îi vom afișa pagina (tipul de browser pe care îl folosim). În acest caz, este Chrome.
Acest câmp este foarte util, deoarece îi permite serverului să personalizeze elementele de pe
pagină în funcție de ce suportă acel browser.
• Accept: text/html, image/jpeg – cu acest antet îi spunem serverului ce tip de date
dorim să acceptăm. În acest caz, este vorba de date text - pagini HTML, precum și imagini, mai
exact extensia *.jpeg.
• Accept-language:en – Cu acest antet sugerăm că motorul nostru de căutare preferă limba
engleză și, ca atare, vrea obiecte adaptate la limba respectivă. Dacă obiectele din limba
respectivă nu există pe server, serverul va trimite obiecte în limba implicită.

Cererea HTTP (HTTP response)

Un exemplu în care vom vedea câteva dintre câmpurile comune de antet care sunt utilizate la obţinerea
unui răspuns arată astfel:

Exemplu

1HTTP/1.1 200 OK
2
3Connection: close
4
5Date: Thu, 20 Aug 2020 12:00:00 GMT
6
7Server: Apache/2.4.1 (Unix)
8
9Last-Modified: Mon, 22 Jun 2020 09:21:42 GMT
10
11Content-Length: 6821
12
13Content-Type: text/html
14
15’data …’

Răspunsul serverului poate fi împărțit în trei secțiuni: stare, linii de antet și corpul mesajului - respectiv
datele dorite (linia: ’data…’).

• HTTP/1.1 200 OK – acest câmp din antet reprezintă mesajul de stare al cererii. Este compus
din trei câmpuri, versiunea protocolului (HTTP/1.1), codul de stare (200) și mesajul de stare
indicat de acel cod (OK). Există mai multe mesaje de stare; pot fi citite aici.
• Connection: close – indică posibilitatea de a închide conexiunea imediat sau, dacă
parametrul Keep-Alive este trecut - lăsăm posibilitatea de a trimite cereri suplimentare către
server, toate în aceeași conexiune.
• Date: Thu, 20 Aug 2020 12:00:00 GMT – acest câmp indică data și ora (ore, minute și
secunde) când a fost generat și trimis acest răspuns.
• Server: Apache/2.4.1 (Unix) – acest câmp antet indică tipul și versiunea serverului care
a generat răspunsul. Acest câmp este, de asemenea, analog cu câmpul de antet de răspuns
HTTP al User-Agent, care indică numele browserului care trimite cererea.
• Last-Modified: Mon, 22 Jun 2020 09:21:42 GMT – indică data și ora la care obiectul
(pentru care browserul a trimis solicitarea) a fost modificat sau creat ultima dată. Acest câmp
este foarte important atunci când este vorba de stocarea în cache a datelor locale și de pe server.

Page 20 of 57
• Content-Length: 6821 – Indică dimensiunea răspunsului generat în octeţi.
• Content-Type: text/html – Acest câmp indică tipul de corp al mesajului care se trimite în
răspuns.

Notă
Memorarea în cache este o tehnică care stochează o copie a unei resurse date (obiectul de pe pagină, pagina în
sine) pentru a fi utilizată din nou. În cazul nostru, dacă am avut o pagină cache pe maşina noastră, adică pagina
pe care am solicitat-o anterior, în loc să trimită o cerere către server, browserul va folosi exact acea copie salvată.
Acest lucru ușurează serverele și accelerează doar căutarea online. De asemenea, această funcționalitate este
posibilă şi pe server, dar pentru a fi utilizată complet, trebuie configurată corect.

Page 21 of 57
2.1. Protocolul UDP

Rezumat

• Pentru a lucra cu socketuri folosim biblioteca încorporată de socketuri Python.


• Cu metoda socket() creăm un obiect socket și îi transmitem două argumente: AF_INET -
versiunea protocolului internet și SOCK_DGRAM - tipul de socket (pentru protocolul UDP,
acesta este datagram).
• Pentru a trimite concret un mesaj, folosim metoda sendto(), căreia îi trecem doi parametri -
mesajul de tip bytes și n-tork cu două elemente: adresa și portul.
• Pentru a primi un mesaj de la cealaltă parte, folosim metoda recvfrom().
• Clientul și serverul trebuie să utilizeze aceeași adresă și același port atunci când comunică.

Cu ocazia proiectării unei aplicații care va utiliza practic protocolul internet, se pune întrebarea dacă
comunicarea din rețea va utiliza sau nu o serie organizată și fiabilă de pachete și, în funcție de aceasta,
alegerea între protocoalele UDP și TCP. Deși protocolul TCP este mai popular, protocolul UDP, datorită
simplității sale, ne oferă o perspectivă mai rapidă și mai clară asupra comportamentului pachetelor în
timpul comunicării, de care vom avea nevoie în lecţia despre protocolul TCP. De asemenea, ne permite
să cunoaștem mai repede mediul de rețea Python.

În loc să dezvoltăm propriile medii de aplicații în Python pentru lucrul cu socketuri, vom folosi soluții
încorporate. Şi anume, Python ne permite să lucrăm cu socketuri folosind biblioteca de
socketuri încorporată, care oferă un mediu orientat pe obiect pentru lucrul cu o rețea de calculatoare.
Deci, această bibliotecă ne oferă acces la o linie de comunicare - cum ar fi porturile UDP sau TCP, pe
care le putem folosi ca obiecte în Python. După definirea unui astfel de obiect, este necesar să-l
conectați cu socketul dorit. Socketurile în Python sunt destul de asemănătoare cu descriptorii de
fișiere, dar în loc să se afle pe disc, sunt online și acceptă operațiuni, cum sunt .read(). şi .write(), care
ne permit scrierea și citirea cu ușurință din el. Familiarizarea cu principiile de bază ale protocolului UDP
le vom arăta pe exemplul de comunicare între client și serverul al cărui flux de comunicare arată aşa:

• Clientul trimite tipul de date string specificat în variabila ’input_s’.


• Serverul primește aceste date și împarte stringul pe baza caracterului virgulă (,) ca delimitator.
• Stringul astfel modificat este trimis de server înapoi clientului.
• Clientul primește șirul modificat și îl imprimă pe ecran.

Fișierul pentru serverul UDP va fi numit udp_local_server.py, în timp ce fișierul pentru clientul UDP se va
numi udp_local_client.py. Atât serverul, cât și clientul vor porni de pe serverul local (localhost) și vor fi
lansate din fereastra de comandă (Command Prompt) cu comenzile python udp_local_server.py și
python udp_local_client.py.

Clientul UDP

Înainte de crearea serverului și a clientului, trebuie importată biblioteca socket cu comanda import
socket, care este responsabilă de elementele de bază ale comunicării în rețea în Python și cu care putem
crea socketuri. După aceea, vom defini portul prin care se va desfăşura comunicarea cu linia: server_port
= 21060. În acest moment, totul este pregătit pentru a crea un obiect socket. Facem acest lucru cu linia:

1client_socket = [Link](socket.AF_INET, socket.SOCK_DGRAM)<br><br>


Deci, am definit variabila client_socket ca un obiect de tip socket. Parametrii trecuți ai acestui socket
sunt:

Page 22 of 57
• AF_INET – Cu acest parametru indicăm versiunea protocolului internet prin care se va desfăşura
comunicarea. În acest caz, este IPv4.
• SOCK_DGRAM – Cu acest parametru definim ce tip de soket va fi. În acest caz, deoarece este
vorba de un protocol UDP, tipul de soket este datagram UDP (SOCK_DGRAM).

În acest pas, creăm un obiect socket; încă nu-i direcționăm adresa la care ar trebui trimis, nici portul
către care ar trebui să ajungă. Vom face acest lucru după ce definim mesajul pe care îl vom
trimite: input_s = "Hello, server!". Cu următoarea comandă:

1client_socket.sendto(bytes(input_s, encoding='utf8'), ('[Link]', server_port))

folosim metoda obiectului socket - sendto, cu care îi trimitem un mesaj serverului și căruia îi setăm
următorii parametri:

• obiectul bytes - mesajul pe care dorim să-l trimitem de tip octet, în ordinea codului UTF-8;
• n-tork cu două elemente, care conține adresa la care trimitem, precum și portul de proces de
pe mașina care se află în spatele acelei adrese. În cazul nostru, adresa este [Link]. Această
adresă este specifică și reprezintă localhost - serverul web local. Pe fiecare aparat, această
adresă este rezervată tocmai pentru serverul local. De asemenea, aceasta poate fi accesată doar
din rețeaua locală (rețea la nivelul dispozitivului în sine), utilizând adresa 0.0.1 în browser sau,
tastând simplu, localhost.

În acest moment, în cod am trimis un pachet care conține mesajul, adresa și portul unde ar trebui să
sosească mesajul. După procesarea serverului, când primim pachetul de returnare, vom accepta aceste
informații cu linia:

1input_s_modified, address = client_socket.recvfrom(65535)


Cu această comandă încărcăm tot ce ne vine de la client. Mai exact, în variabila address avem adresa și
portul serverului, iar în variabila input_s_modified se află răspunsul serverului. Metoda recvfrom
(prescurtare de la: receive from) acceptă doi parametri, dintre care doar unul este utilizat în acest caz:

• buffer – este o memorie auxiliară și, în acest caz, această memorie este utilizată pentru a stoca
răspunsurile serverului. Valoarea pe care am trecut-o (65535) nu este întâmplătoare și reprezintă
dimensiunea bufferului exprimată în octeți. De asemenea, conform specificațiilor protocolului,
această cifră reprezintă limita teoretică a dimensiunii mesajului care se poate transmite de UDP.
Dacă încercăm să trimitem un mesaj mai mare decât limita, va apărea o eroare OSError:
[WinError 10040], deoarece încercăm să trimitem un mesaj mai mare decât limita, caz în care
mesajul ar trebui să fie împărțit înainte de trimitere;
• flags (indicatori de stare) - reprezintă modul în care mesajul va fi primit; valoarea acelui
parametru este transmisă ca tip int. Puteți afla mai multe despre acest parametru pe această
pagină.

Pe lângă metoda recvfrom, există și metoda recv, care primește un singur parametru de intrare, respectiv
valoarea buffer care este de tip int. Diferența dintre metodele recv și recvfrom se află în valoarea
returnată: metoda recv returnează valoarea ca un string, iar recvfrom returnează n-tork care este format
din două elemente, unde primul element este feedbackul unui server de tip string și al doilea element
este adresa serverului de la care a sosit răspunsul.

Odată ce am primit mesajul, îl putem procesa mai departe. Deși Python va închide conexiunea în sine
când se va ajunge la sfârșitul programului, este o bună practică să închidem socketul manual folosind

Page 23 of 57
metoda .close(). Aspectul părții de client a UDP se află în fișierul udp_local_client.py, care în final arată
aşa:

udp_local_client.py

import socket
1server_port = 21060
2client_socket = [Link](socket.AF_INET, socket.SOCK_DGRAM)
3input_s = 'Hello, server!'
4client_socket.sendto(bytes(input_s, encoding='utf8'), ('[Link]', server_port))
5input_s_modified, address = client_socket.recvfrom(65535)
6print ('[CLIENT] Response from server {}, is: "{}"'.format(address,
7str(input_s_modified.decode('utf8'))))
8client_socket.close()

Notă

La trimiterea unui mesaj către server, trimitem tipul bytes; de asemenea, atunci când primim un răspuns, primim
date de tip bytes. Pentru a converti tipul bytes într-un string și un format lizibil pentru noi, folosim metoda
decode(), căreia îi trecem ordinea codului. Cea mai cunoscută metodă de codificare este UTF-8. În felul acesta,
obținem tipul str al obiectului. Și dacă vrem să convertim obiectul str în bytes, folosim metoda string encode(),
căreia, de asemenea, îi trecem și dispunerea codului de caractere. Mai multe informații despre lista codului de
dispunere a caracterelor se pot citi aici.

Serverul UDP

Primele câteva linii de început pentru implementările serverului UDP sunt aceleași ca și pentru client. În
ambele cazuri, importăm biblioteca sockets, specificăm numărul de port pe care va asculta serverul,
respectiv stabilim la ce port va trimite clientul mesajul (porturile care sunt specificate pe partea de server
și pe partea de client trebuie să fie aceleași), precum și tipul de socket pe care îl creăm (în ambele cazuri
este datagram socket prin protocolul IPv4):

1import socket
2
3server_port = 21060
4
5server_socket = [Link](socket.AF_INET, socket.SOCK_DGRAM)
Prima diferență următoare în ceea ce privește codul clientului este în metoda socket .bind(), prin care îi
atribuim serverului nostru local, mai precis procesului aplicației noastre, un număr de port pe care va
asculta.

1server_socket.bind(('[Link]', server_port))

După utilizarea metodei bind, cu comanda print vom imprima că a fost creat un socket, iar cu comanda
getsockname() putem imprima adresa și portul socketului.

Pentru ca serverul să recunoască la momentul potrivit primirea unui mesaj, acesta trebuie să verifice în
fiecare moment dacă mesajul a sosit. Această problemă, printre altele, poate fi rezolvată folosind o buclă
simplă while infinită. Deci, în fiecare iterație a buclei while, se va verifica dacă a sosit un mesaj nou.
Această verificare se face în același mod ca la client:

Page 24 of 57
1message, address = server_socket.recvfrom(65535)

La fel ca și în cazul clientului, în acest caz primim mesajul și adresa folosind metoda .recvfrom(). Spre
deosebire de client, unde variabila address nu joacă un rol important în feedback, această variabilă este
foarte importantă pentru server, pentru a extrage adresa și numărul de port la care trebuie trimis
răspunsul.

Trebuie menţionat faptul că această comandă în sine va opri programul pe această linie și va aștepta
mesajul, dar fără o buclă infinită; odată ce a sosit mesajul, scriptul s-ar încheia și nu vom mai putea
asculta mesaje noi.

După această linie, ajungem şi la logica principală a scriptului, mai exact funcționalitatea pe care ar
trebui să o implementeze serverul nostru UDP - să împărțim mesajul textual primit folosind un
delimitator (,) și să returnăm valoarea numerică - câte părți au fost create cu acea diviziune. Această
problemă se rezolvă folosind metoda string .split(). Este important de menţionat că variabila message,
care vine din rețea, utilizează metoda .recvfrom() de tip octet și trebuie mutată într-un obiect string
pentru a se putea utiliza metoda .split().

După această procesare, în răspunsul clientului sunt plasate mesajul în sine, adresa și portul. Metoda de
trimitere a mesajului de la server către client este identică şi invers - folosind metoda .sendto(), căreia îi
atribuim un mesaj sub forma unui obiect octet împreună cu adresa și portul. La acest server UDP, acesta
se află în fișierul udp_local_server.py:

udp_local_server.py

import socket
1server_port = 21060
2server_socket = [Link](socket.AF_INET, socket.SOCK_DGRAM)
3server_socket.bind(('[Link]', server_port))
4print ('[SERVER] Listening at: {}'.format(server_socket.getsockname()))
5while True:
6 message, address = server_socket.recvfrom(65535)
7 modified_message = str(len(str(message).split(",")))
8 print ('[SERVER] The client at {}, originally sent: {}'.format(address,
9repr(message)))
10 server_socket.sendto(bytes('Server is \
11sending back: "{}".'.format(modified_message), encoding='utf8'), address)
12server_socket.close()

Inițierea și analiza rezultatelor

Pentru a verifica funcționalitatea clientului și a serverului, trebuie să rulăm ambele fișiere în același timp.
Cel mai simplu mod de a face acest lucru este utilizând Command Prompt (linia de comandă). Deci, mai
întâi se deschide o fereastră a liniei de comandă și în ea se iniţiază udp_local_server.py, apoi se porneşte
şi cea de a doua fereastră a liniei de comandă și în ea se iniţiază udp_local_client.py. Înainte de a
iniţia aceste comenzi, este necesar să le poziționăm în directorul în care se află aceste scripturi (folosind
comanda liniei de comandă cd). Un astfel de rezultat de iniţiere arată astfel:

Page 25 of 57
Imaginea 4.1. Afișarea iniţierii clientului și a serverului prin linia de comandă

În fereastra de sus a liniei de comandă este iniţiat serverul UDP, iar în fereastra inferioară este inițiat
clientul UDP. Imediat după iniţierea serverului, se imprimă un string care afirmă că serverul este gata să
primească date la adresa și portul dat. În acel moment, rulăm fișierul client. După iniţierea clientului,
către server este trimis mesajul „Hello, server!”, pe care serverul aplică metoda .split(), numără câte
elemente au fost create de această diviziune și trimite rezultatul înapoi la client cu metoda .sendto(), pe
care clientul o primește și imprimă rezultatul pe ecran.

Page 26 of 57
2.2. Protocolul TCP

Rezumat

• Pentru implementarea serverului și a clientului TCP, se utilizează o bibliotecă socket Python


încorporată, care are deja funcționalitatea încorporată de a face singură tripla potrivire.
• Ca tip socket, folosim SOCK_STREAM.
• Pentru a trimite mesaje, folosim metoda sendall(), căreia îi transmitem octeți ca tip al obiectului.
• Folosim metoda listen() pentru a asculta conexiunile primite de pe partea de server.
• Folosim metoda accept() pe partea de server și aceasta returnează un nou obiect de tip socket,
și doar cu acesta putem face trimiterea și primirea mesajelor. Acest obiect socket nou trebuie
închis după folosire şi înainte de folosirea metodei accept().

Spre deosebire de protocolul UDP, TCP este un protocol care se bazează în principal pe crearea unei
conexiuni între client și server și, doar după realizarea ei, urmează trimiterea datelor. Acest tip de
comunicare se realizează printr-o procedură de triplă potrivire (three-way handshake). Când vrem să
creăm o conexiune TCP, îi atribuim un socket. Deoarece facem conexiunea printr-un socket, atunci
când o parte (ori un client, ori un server) dorește să trimită date printr-o conexiune realizată anterior -
acele date se stochează în socketul său.

Procedura de triplă potrivire (three-way handshake)

Pentru a putea răspunde la cererea clientului, serverul trebuie să fie pregătit, ceea ce înseamnă două
lucruri:

• la fel ca UDP, şi serverul TCP trebuie să fie pornit în prealabil;


• serverul trebuie să aibă un anume socket pentru acceptarea cererii inițiale de conectare de către
client.

Deoarece avem un server pornit, clientul poate iniția o conexiune TCP folosind un socket TCP. Clientul
TCP creează un socket în care specifică adresa socketului serverului.

În timpul triplei potriviri, clientul îi spune serverului că dorește o conexiune. După ce serverul
înregistrează aceea solicitare, deschide un socket nou, special, care îi este destinat doar clientului
respectiv și comunicării cu el, apoi trimite o confirmare de primire. După sosirea acelui mesaj, clientul
este conectat cu succes la socket, comunicarea este activată și ambele părți sunt gata pentru trimiterea
datelor.

La fel ca în exemplul cu comunicarea UDP, şi în comunicarea TCP vom realiza o structură client-server
(tcp_local_client.py și tcp_local_server.py) care va împărți mesajul primit pe partea de server pe baza
caracterului virgulă (,) și îi va returna clientului numărul elementelor obținute după acea împărțire.

Clientul TCP

Când se analizează primele câteva linii ale clientului TCP, se văd destule asemănări cu clientul UDP:

1import socket
2
3PORT = 35789
4
5client_socket = [Link](socket.AF_INET, socket.SOCK_STREAM)

Page 27 of 57
Metoda socket() este apelată la ambii clienți utilizând un alt tip de socket. În cazul UDP, acesta
este SOCK_DGRAM – datagram socket, iar la crearea unui socket TCP folosim tipul de
socket SOCK_STREAM, care este destinat comunicării TCP. Primul parametru este același ca pentru UDP
și indică versiunea protocolului IP utilizat.

În următoarea linie se văd deja diferențele dintre acești doi clienți. Și anume, deoarece nu este posibil
transferul de date prin TCP înainte de a face o conexiune reciprocă, este necesar să se facă această
conexiune. Inițializarea conexiunii o efectuăm folosind metoda connect():

1client_socket.connect(('[Link]', PORT))

Metoda connect() primește un parametru n-tork cu două elemente:

• adresa la care serverul așteaptă o conexiune;


• numărul portului prin care se va desfăşura conexiunea.

După efectuarea acestei conexiuni, realizăm o sincronizare triplă completă cu serverul și comunicarea
poate începe. Când se implementează server TCP, este bine să se codifice gestionarea erorilor în acest
punct, deoarece dacă procesul de potrivire cu serverul eșuează, pe partea de client se va ajunge la o
eroare ConnectionRefusedError: [WinError 10061] – și acest lucru este diferit de
implementarea clientului UDP, unde programul se va opri pur și simplu și va aștepta ca acesta să se
conecteze la server la un moment dat.

Deoarece TCP menține ordinea pachetelor, se poate trimite mai întâi doar o parte a pachetului, apoi cea
de-a doua parte. Trimiterea mesajului de la client la server - mai exact, mai întâi convertirea mesajului
în pachete, apoi trimiterea acestuia la server - se face folosind două metode: send() şi sendall().
Dacă folosim metoda send(), trebuie să avem în vedere faptul că poate nu va fi trimis tot mesajul, în
funcție de completarea rândului de trimitere a pachetelor la nivelul plăcii de rețea. Partea bună este că
metoda send() are ca valoare de returnare numărul de octeţi efectiv trimiși, deci pe această bază
putem urmări manual câte din mesajele dorite ar trebui trimise. Pe de altă parte,
metoda sendall() rezolvă această problemă pentru noi. Trimiterea mesajului arată aşa:

1client_socket.sendall(bytes('Hello, there, server!', encoding = 'utf8'))


Răspunsul serverului se obţine folosind metoda recv(), la fel ca în cazul clientului
UDP: input_s_modified = client_socket.recv(1024)

La sfârşit, închidem socketul cu metoda close(): client_socket.close()


Exemplul TCP client este stocat în fişierul tcp_local_client.py şi arată aşa:

tcp_local_client.py

1import socket
2PORT = 35780
3client_socket = [Link](socket.AF_INET, socket.SOCK_STREAM)
4client_socket.connect(('[Link]', PORT))
5print ("[CLIENT] Client's has received from server \
6its dedicated socket: {}".format(client_socket.getsockname()))
7client_socket.sendall(bytes('Hello, there, server!', encoding = 'utf8'))
8reply = client_socket.recv(1024)
9print ('[CLIENT] Response from the server: "{}"'.format([Link]('utf8')))
10client_socket.close()

Page 28 of 57
Serverul TCP

Când este vorba despre compararea serverelor TCP și UDP, aici există deja destule diferențe. În primul
rând, deoarece pe partea de server este, de fapt, necesară crearea a două socketuri diferite pentru ca
serverul TCP să fie implementat corect.

Ca și în cazul clientului TCP, este necesar să se definească o variabilă de tip socket cu


tipul SOCK_STREAM în versiunea 4 a protocolului internet:

1server_socket = [Link](socket.AF_INET, socket.SOCK_STREAM)

Vom conecta socketul la adresa locală și la portul dispozitivului nostru cu comanda bind():

1server_socket.bind(('[Link]', PORT))

Cu această linie, am creat socketul iniţial care va servi la stabilirea comunicării cu clientul. Este important
de menționat că în acest moment, inclusiv acea linie, încă nu știe dacă serverul dorește să comunice
printr-o conexiune de intrare. Acest moment vine după utilizarea metodei listen() – metoda prin
care ascultăm conexiunile primite. Când folosim această metodă, obiectul nostru socket anterior nu mai
poate fi utilizat pentru recepția convențională și trimiterea datelor, ci singura modalitate prin care acest
socket poate primi comunicații este utilizarea metodei accept(), neutilizate până acum. Această
metodă este unică pentru protocolul TCP și returnează un obiect socket nou (noul obiect socket și
adresa lui) - pe care îl vom numi în cod conn_socket, unic doar pentru clientul care a stabilit conexiunea.
Metoda listen() primește un parametru de tip int cu o valoare minimă 1, unde acel număr reprezintă
numărul de conexiuni permise acestui socket. Această parte de cod arată astfel:

1server_socket.listen(1)
2
3conn_socket, conn_sockname = server_socket.accept()

După metoda accept() este finalizat procesul de triplă potrivire (three-way handshake) şi trimiterea
datelor între client și server poate începe.

Deoarece este activată comunicarea client-server, urmează implementarea logicii necesare, care a fost
şi sarcina acestui exemplu:

1modified_message = str(len(str(message).split(",")))
2
3conn_socket.sendall(bytes(modified_message, encoding = 'utf8'))

Şi în realizarea părţii serverului folosim, de asemenea, în loc de metoda send(), metoda mult mai
fiabilă sendall().

De asemenea, se poate implementa serverul TCP folosind bucla infinită while, așa cum este cazul cu
implementarea serverului UDP; aceasta se setează după metoda listen().

Dacă folosim bucla while, la sfârșitul fiecărei iterații, cu metoda close() trebuie să închidem obiectul
socket creat doar în scopuri de comunicare pentru un anumit client (conn_socket). La ieșirea din buclă,
închideți socketul serverului principal (server_socket).

Page 29 of 57
Un exemplu de implementare a socketului TCP folosind bucla while infinită plasat în fișierul
tcp_local_server.py arată aşa:

tcp_local_server.py

import socket, sys


PORT = 35780
server_socket = [Link](socket.AF_INET, socket.SOCK_STREAM)
1server_socket.bind(('[Link]', PORT))
2server_socket.listen(1)
3while True:
4 print ('[TCP_SERVER] Listening at: {}'.format(server_socket.getsockname()))
5 conn_socket, conn_sockname = server_socket.accept()
6 print ('[TCP_SERVER] Connection is accepted from:
7{}'.format(conn_sockname))
8 print ('[TCP_SERVER] Socket connects: server_socket: {} and conn_socket:
9{}'.format(
10 conn_socket.getsockname(), conn_socket.getpeername()))
11 message = conn_socket.recv(1024)
12 print ('[TCP_SERVER] The client_socket at {}, originally
13sent "{}"'.format(conn_socket.getpeername(), [Link]('utf8')))
14 modified_message = str(len(str(message).split(",")))
15 conn_socket.sendall(bytes(modified_message, encoding = 'utf8'))
16 conn_socket.close()
17 print ("[TCP_SERVER] Reply sent, closing client's socket.")
18server_socket.close()

Inițierea și analiza rezultatelor

Pentru a monitoriza mai atent în timpul funcționării programului adresa și numărul de socketuri în
comunicarea dintre client și server, ne sunt disponibile două metode utilizate frecvent:

• getsockname – cu această metodă obţinem adresa exactă și portul socketului dat; se poate
utiliza atât pe partea de server, cât și pe cea de client;
• getpeername – cu această metodă obținem la ce adresă de la distanță și port socket este
conectat.

Pentru a verifica funcționalitatea clientului și a serverului TCP, trebuie să rulăm ambele fișiere, ceea ce
vom face cel mai ușor folosind linia de comandă. Deci, mai întâi, într-o fereastră separată de linia de
comandă trebuie rulat tcp_local_server.py, apoi se deschide o fereastră nouă de linie de comandă și din
ea pornim fișierul tcp_local_clinet.py.

Page 30 of 57
Imaginea 5.1. Afișajul pornirii clientului și a serverului prin linia de comandă

După ce pornim partea de server, fereastra de comandă va imprima linia: [TCP_SERVER] Listening
at: ('[Link]', 35780)’ și se va opri acolo. În acel moment, serverul se află în stare de
așteptare și ascultă la adresa și portul dat. După ce executăm codul clientului, serverul va imprima
următoarele linii:

• [TCP_SERVER] Connection is accepted from: ('[Link]', 2842) – Clientul


și serverul au trecut printr-o triplă potrivire (three-way handshake) și conexiunea a fost realizată
(metoda accept). A fost creat un socket nou, numit conn_socket, care va fi responsabil cu
comunicarea ulterioară. Solicitarea a sosit de la client de pe adresa [Link] și portul 2842.
• [TCP_SERVER] Socket connects: server_socket: ('[Link]', 35780) and
conn_socket: ('[Link]', 2842) – Pe această linie vedem diferența dintre server,
socketul principal, care este folosit pentru a asculta conexiunile primite, și socketul care este
creat atunci când se stabilește comunicarea.

La stabilirea comunicării, clientul trece imediat la linia sendall(), prin care trimite mesajul, în timp ce
socketul serverului intră în starea de așteptare pentru a primi un mesaj nou folosind metoda recv().
După primirea acestui mesaj, se imprimă linia:[TCP_SERVER] The client_socket at
('[Link]', 35780), originally sent: "Hello, there, server!". Cu aceasta
imprimăm doar mesajul primit și adresa de la care vine (adresa și socketul clientului).

În continuare, se face împărțirea stringului folosind virgula ca semn de separare și rezultatul îi este trimis
clientului prin metoda sendall(), în timp ce clientul va primi acel mesaj folosind metoda recv().

Page 31 of 57
2.3. Protocolul WebSocket

Rezumat

• Sondajul serverului (server polling) este o metodă prin care clientul întreabă în mod constant
serverul dacă au sosit informații pentru el.
• WebSocket-urile ne permit să stabilim o conexiune TCP între client și server, care permite
comunicarea bidirecțională (full-duplex).
• Cererea pentru conexiunea WebSocket este trimisă serverului de pe partea de client printr-un
proces cunoscut ca potrivire WebSocket.
• Solicitarea de potrivire începe ca o solicitare simplă HTTP către server și conține şi un câmp de
antet Upgrade, care îi indică serverului că clientul încearcă să realizeze o conexiune WebSocket.
• Biblioteca socketio, care ne permite lucrul cu WebSocket-uri, se bazează pe biblioteca originală
scrisă în JavaScript.
• Vom instanția obiectul client WebSocket ca [Link]().
• Vom instanția obiectul server WebSocket ca [Link] ().
• Pentru a trimite evenimente și mesaje cu acestea, folosim metoda emit().
• Pentru fiecare eveniment pe care dorim să-l folosim, indiferent dacă este vorba de partea de
client sau de server, trebuie să definim şi funcția adecvată și să o decorăm.

WebSocket este o tehnologie care permite deschiderea unei sesiuni de comunicare interactivă între un
client și un server. Se prezintă şi ca o actualizare pentru protocolul HTTP. În felul acesta, utilizatorul
(clientul) poate trimite mesaje către server și poate primi răspunsuri bazate pe evenimente (event-driven
programming), fără a fi nevoie ca să trimită clientul constant cereri către server cu un mesaj dacă a sosit
ceva nou (sondaj lung, engl. long polling). Există multe astfel de scenarii, iar cel mai ușor se observă în
aplicațiile de chat atunci când este foarte important să se primească și să se trimită mesaje noi în timp
real.

Sondaj HTTP lung/HTTP long polling

Din punct de vedere istoric, înainte de WebSocket s-a folosit principiul sondajului HTTP lung (HTTP long
polling). Cu această metodă, clientul trimitea în mod constant cereri către server cu întrebarea dacă
există informații noi pentru mine. Serverul păstra acea cerere deschisă până când apărea o informație
nouă pentru client (sau până când se ajungea la o eroare request timed out (408) care indica faptul că
solicitarea a expirat). Esența este ca utilizatorul să examineze/sondeze (poll) constant serverul cu privire
la informații dacă a sosit o informație nouă pentru el. Când clientul primeşte, în cele din urmă, un
răspuns la cererea sa, el va trimite imediat următoarea cerere și va aștepta. Acest proces s-ar repeta în
mod constant, ceea ce ne duce la dezavantajele acestei abordări.

Odată cu creșterea bazei de date pe server și cu numărul de clienți noi, această abordare (HTTP long
polling) necesită o utilizare tot mai mare a CPU, a traficului pe internet și a stocării. Acest lucru devine
foarte repede un sistem foarte complicat, cu prea multe date și costuri. Numărul de solicitări inutile este,
de asemenea, în creștere, deoarece pentru fiecare cerere trebuie realizată o conexiune nouă.

Notă

Pe lângă long polling, unde serverul, după primirea unei cereri, nu trimite un răspuns până când nu primește
datele solicitate (sau până când expiră durata cererii), există și un sondaj scurt (short polling). Sondajul scurt are
loc atunci când un client trimite periodic un semnal de test, la fiecare x milisecunde, ceea ce înseamnă că la
fiecare x secunde vom obține date. Această abordare nu este eficientă, deoarece supraîncarcă serverul cu cereri,
iar răspunsurile primite vor fi în majoritatea cazurilor goale.

Page 32 of 57
WebSocket

WebSocket ne permite să stabilim o conexiune TCP între client și server, care permite comunicarea
bidirecțională (full-duplex). Cererea de conexiune WebSocket este trimisă serverului de pe partea de
client printr-un proces cunoscut sub numele de potrivire WebSocket. Această solicitare începe ca o
simplă cerere HTTP către server și partea lui include şi un câmp de antet Upgrade, care îi indică serverului
că clientul încearcă să realizeze o conexiune WebSocket.
Exemplu de antet cerere

1GET /[Link] HTTP/1.1


2
3Upgrade: WebSocket
4
5Connection: Upgrade

Exemplu de antet răspuns

1HTTP/1.1. 101 WebSocket Protocol Handshake


2
3Upgrade: WebSocket
4
5Connection: Upgrade

Dacă serverul acceptă protocolul WebSocket și acceptă cererea, conexiunea HTTP inițială se schimbă
într-o conexiune WebSocket, utilizând același protocol TCP, și se menține pe tot parcursul sesiunii.
Această conexiune ne permite să trimitem date în timp real către client după cum este nevoie. În felul
acesta, eliminăm nevoia unei anchete lungi a serverului, reducem încărcarea lui și reducem numărul de
cereri trimise.
Deci, cum HTTP se bazează pe un protocol cerere-răspuns și, ca atare, nu acceptă transmiterea
simultană a datelor (conexiuni full-duplex), WebSocket tocmai rezolvă această problemă.

Avantajele WebSocket-ului

Deoarece WebSocket-urile sunt actualizate pe HTTP, avantajele WebSocket-urilor sunt:

• o conexiune constantă, neîntreruptă, între client și server, unde datele sunt trimise clientului,
indiferent dacă acesta a trimis sau nu o cerere;
• fluxul de comunicare poate circula în ambele direcții (client-server, server-client) în timpul
conexiunii (full-duplex);
• întârziere mică - viteza de primire și trimitere a mesajelor este mai mare.

Utilizarea WebSocket

WebSocket este folosit atunci când este importantă viteza de comunicare. Se utilizează în principal
pentru:

• aplicații de chat;
• urmărirea scorurilor live;
• actualizarea fluxurilor de date pe rețelele sociale în timp real;
• jocuri multiplayer.

Page 33 of 57
Implementarea WebSocket în Python

Vom implementa WebSocket în Python folosind două biblioteci care nu fac parte din lista de biblioteci
încorporate, ci trebuie instalate ulterior. Aceste biblioteci sunt:

• socketio – biblioteca care ne permite să lucrăm cu WebSocket-uri, pe baza bibliotecii originale


scrise în JavaScript. Puteți afla mai multe despre aceasta pe pagina oficială;
• eventlet – biblioteca care oferă o gestionare mai ușoară și competitivă a rețelei.

Instalarea se face folosind un program de instalare a pachetelor - PIP, care vine ca o parte integrată a
instalării Python. Se iniţiază prin linia de comandă tastând comanda pip. Comanda install pip este
utilizată pentru a instala pachetul, urmată de numele exact al pachetului. Este important de menţionat
că numele pachetelor, uneori, nu sunt aceleași cu numele pe care le importăm în programul Python cu
comanda import și că numele exact al pachetului poate fi văzut pe pagina sa oficială. Se întâmplă adesea
ca pe pagina oficială a pachetului să existe şi o instrucțiune detaliată despre cum se instalează acel
pachet. Comenzile pentru instalarea celor două biblioteci menționate mai sus sunt: pip install eventlet
și pip install python-socketio.

Pentru a șterge pachetele instalate, este utilizată comanda pip uninstall packet-name.

Imaginea 6.1. Prezentarea instalării cu succes a acestor biblioteci

Cu ajutorul acestei biblioteci, vom implementa protocolul WebSocket pe exemplul unei aplicații de
chat simple (server: websockets_local_server.py) care va putea primi mai mulți clienți
(websockets_local_client.py) și va putea afișa mesaje între ei. Partea de client va fi organizată astfel
încât utilizatorul să introducă mai întâi numele său cu funcția input(), iar apoi biblioteca socketio preia

Page 34 of 57
crearea conexiunii, precum și trimiterea și primirea mesajelor. Acest nume de pe partea clientului ne
este important, deoarece ne va ajuta să identificăm clienții conectați pe partea de server.

Client WebSocket

Deși am menționat două biblioteci de care vom avea nevoie, pentru client avem nevoie doar de una, și
anume socketio. Numele utilizatorului care se va conecta la serverul nostru WebSocket este acceptat
utilizând funcția integrată input() și stocat în variabila username.

Vom instanția obiectul client WebSocket cu un apel al [Link] () și vom plasa acel obiect în
variabila sio_client. În felul acesta, am creat doar un obiect client. Conectarea la server o vom face
folosind metoda sio_client.connect(), căreia îi transmitem socketul la care ne conectăm (ip address:port).

Notă

WebSocket-urile pot utiliza și porturi rezervate pentru protocolul HTTP (8080 și 80).

După aceste comenzi, dacă conexiunea are succes, putem continua cu implementarea clientului.
Deoarece WebSocket este bazat pe evenimente, trebuie să definim funcțiile care vor corespunde acestor
evenimente. Aceste evenimente pot fi conectarea, deconectarea sau trimiterea unui mesaj (numele
evenimentelor rezervate conform bibliotecii socketio sunt connect, disconnect și message). Definirea
funcțiilor care corespund acestor evenimente de bază (și care trebuie denumite după ele) se face prin
decorarea funcțiilor noastre cu funcția sio_client.event. De asemenea, cum protocolul WebSocket
acceptă şi implementarea propriilor evenimente, şi pentru ele folosim aceeași metodă sio_client.event,
cu care le vom decora. În felul acesta, am „înregistrat” funcția pentru un anumit eveniment.

De exemplu, în cazul în care conexiunea este reuşită, se va executa evenimentul connect, ceea ce, în
contextul Python, înseamnă că se va executa funcția def connect().

Exemplu de decorare a unui eveniment de bază numit connect

1@sio_client.event
2
3def connect():
4
5 print('connection established')
6
7Exemplu de decorare a propriului eveniment numit server_response:
8
9@sio_client.event
10
11def server_response(data):
12
13 print('this is server response', data)

Pentru exemplul nostru, este suficient să definim funcțiile care corespund evenimentului connect şi
disconnect, precum și o funcție separată send_msg(), care va primi mesajul introdus de client și i-l va
trimite serverului.

Logica aplicației noastre de chat este de așa natură, încât imediat după conectare, dorim să începem să
trimitem mesaje către server. De asemenea, vrem să-i permitem clientului să trimită mesaje constant

Page 35 of 57
până când întrerupe conexiunea. Acest lucru poate fi implementat folosind o singură buclă while infinită.
Corpul acestei bucle ar consta din:

• funcția input() - funcția prin care utilizatorul își va introduce mesajul pe care îl vom plasa în
variabila msg;
• sio_client.emit() – funcția prin care trimitem (emitem) un eveniment cu un mesaj către server. În
această funcție ne sunt importanți doi parametri. Primul dintre ei, de tip string, reprezintă
numele evenimentului pe care îl trimitem. Pentru numele evenimentului de pe partea de server
ar trebui să existe o funcție cu același nume care să manipuleze acel eveniment. Al doilea
parametru este de tip dicționar (în exemplul nostru este de tip dicționar, dar poate fi str, bytes,
list sau dict); putem trimite datele dorite prin intermediul lui - în acest caz, vrem să
trimitem mesajul utilizatorului, precum și numele acestuia, de fiecare dată când se trimite. Deci,
dacă emitem un eveniment numit my_message, pe partea de server trebuie să avem funcția def
my_message(): pass.

În acest fel, am implementat clientul WebSocket folosind biblioteca socketio. Este important de
menţionat că, în practică, clientul se întâlneşte mai des ca un browser prin interfața căruia utilizatorul
comunică cu serverul.

websockets_local_client.py

1import socketio
2
3username = input("Your username is:")
4
5sio_client = [Link]()
6
7def send_msg():
8
9 while True:
10
11 msg = input("Enter message: ")
12
13 sio_client.emit('my_message', {'msg': msg, 'name':username})
14
15sio_client.sleep(1) # wait for 1 sec and ask user again for message
16
17
18@sio_client.event
19
20def connect():
21
22 print('connection established')
23
24send_msg()
25
26sio_client.connect('[Link]
27
28sio_client.wait() # Wait until server ends the connection

Page 36 of 57
WebSocket server

Implementarea serverului WebSocket se bazează, de asemenea, pe definirea funcțiilor care vor gestiona
evenimentele dorite și sunt decorate cu metodele obiectului server WebSocket.

Pentru implementarea acestui server, importăm bibliotecile eventlet și socketio. Biblioteca socketio ne
permite să creăm, cu comanda [Link](), un obiect server pe care îl vom numi sio_server. Ceea
ce este diferit de partea clientului este că trebuie să punem în funcțiune acest obiect. Mai exact, avem
nevoie de o modalitate de a plasa codul serverului nostru pe server. Acest lucru se face folosind un
mediu special (așa-numitul software proxy) numit WSGI, care este specific lui Python. WSGI (Web Server
Gateway Interface) intermediază atunci când este vorba de cererile clientului către server și permite
conectarea cererii utilizatorului la server cu funcționalitatea corespunzătoare într-un script Python sau
aplicație Python.

Imaginea 6.2. Prezentarea simplă a funcționării WSGI1

Din aceste motive, trebuie să transmitem obiectul nostru server mediului WSGI, astfel încât obiectul
nostru server să poată primi cererile și evenimentele clienților. Facem acest lucru cu comanda:
[Link] (sio_server), pe care o plasăm în variabila app. Acum, cu această variabilă putem pune
serverul în funcțiune.

Folosind biblioteca eventlet, vom pune serverul în starea de ascultare cu comanda:

[Link]([Link](('localhost', 5000)), app)<br><br>


Trebuie avut în vedere faptul că trebuie utilizate aceleași numere de port atât pe partea de client, cât și
pe cea de server.

După ultima comandă, serverul nostru este pregătit pentru solicitările primite. Dar, înainte de a porni
serverul, trebuie să definim un scenariu despre ce se va întâmpla când sosește cererea de conectare de
la client și ce se va întâmpla când va sosi evenimentul my_message pe care l-am definit pe partea de
client.
Pe partea de server, definirea funcțiilor care corespund evenimentelor WebSocket pe care le așteptăm,
se face într-un mod similar cu cea de pe partea de client, fiind necesară definirea acestor funcții cu cel

Page 37 of 57
puțin un parametru (dacă clientul, atunci când trimite evenimentul, nu trimite niciun fel de date) care
afișează numărul de identificare al clientului. Singura excepție de la această regulă este
funcția connect, care este definită întotdeauna cu doi parametri, dintre care unul reprezintă numărul
de identificare al clientului, iar celălalt reprezintă antetul cererii inițiale a clientului de tip dicţionar.
Deci, atunci când clientul informează serverul WebSocket că trimite o cerere connect, va fi apelată
funcția server connect (sid, headers). Aşa cum decorăm funcțiile pe partea de client, la fel facem și pe
partea de server, cu funcția sio_server.event.
Pe partea de server, pentru a defini funcția care va gestiona evenimentul my_message(), care va fi
utilizat pentru a imprima mesajul clientului, vom defini funcția cu același nume, defmy_message.
Argumentele acestei funcții vor fi numărul de identificare implicit al clientului, dar și argumentul data,
care este de tip dicționar și care reprezintă corpul întregului mesaj trimis de client. Din codul client
vedem că este un dicționar cu două câmpuri: name, care indică numele utilizatorului, și mesajul
introdus, care este trecut prin cheia msg.
websockets_local_server.py

1import eventlet
2
3import socketio
4
5sio_server = [Link]()
6
7app = [Link](sio_server)
8
9@sio_server.event
10
11def connect(sid, headers):
12
13 print('Client connected: ', sid)
14
15@sio_server.event
16
17def my_message(sid, data):
18
19 print('User "{}", send message:{}'.format(data['name'], data['msg']))
20
[Link]([Link](('localhost', 5000)), app)

Inițierea și analiza rezultatelor

Pentru a testa clientul și serverul, trebuie să porniți două ferestre de comandă diferite și să vă poziționați
în directorul în care se află fișierele websockets_local_client.py și websockets_local_server.py. Mai întâi,
este pornită partea de server, apoi partea de client.

Page 38 of 57
Imaginea 6.3. Afișarea pornirii clientului și a serverului prin linia de comandă

Primul lucru pe care îl observăm când pornim partea de server este că nimic nu se întâmplă de fapt, cu
excepția faptului că serverul ascultă cererile primite pe un anumit socket (print (25512) wsgi starting up
on [Link] La pornirea fișierului client, mai întâi introducem numele cu care dorim să ne
identifice serverul și apoi are loc conexiunea dintre client și server. Când conexiunea este realizată,
serverul imprimă (25512) accepted (’[Link]’, 9672), ceea ce înseamnă că în acel moment i-a atribuit
conexiunii client un socket nou (conexiune HTTP realizată). Apoi, clientul trimite o cerere de modificare
a protocolului WebSocket, iar, în acel moment, serverul imprimă linia:

[Link] - - [28/Jun/2020 05:19:29] "GET


/[Link]/?transport=polling&EIO=3&t=1593314367.2755895 HTTP/1.1" 200 349
10.000983

cu care afișează acea solicitare GET client, urmată de un mesaj de acceptare a acelei solicitări: (25512)
accepted (’[Link]’, 9680). Pe măsură ce cererea este acceptată, pe partea de client se imprimă
mesajul connection established și poate începe trimiterea.
În acest moment, clientul poate începe să trimită mesaje, iar serverul se află în starea de așteptare și
ascultă evenimentul my_message, prin care ajunge mesajul clientului.
De asemenea, este posibil să se conecteze doi sau mai mulți clienți la serverul nostru (fiecare într-o
fereastră separată de linie de comandă). Un astfel de exemplu arată aşa:

Page 39 of 57
Imaginea 6.4. Afişaj al pornirii unui server și doi clienți prin linia de comandă

În această lecţie am implementat protocolul WebSocket pe baza unei aplicații simple de chat. Însă,
WebSocket reprezintă o tehnologie puternică care poate fi utilizată şi pentru aplicații web mult mai
serioase.

1. [Link]

Page 40 of 57
3.1. Bazele HTTP

Rezumat

• Cele mai populare două biblioteci Python pentru trimiterea cererilor către un server HTTP sunt
[Link] și requests.
• Pentru a trimite cereri GET, biblioteca [Link] folosește metoda urlopen(), căreia îi trecem
linkul de server de la care dorim să obținem datele, în timp ce biblioteca requests utilizează
metoda get(), căreia i se redirecționează, de asemenea, și linkul.
• Dacă dorim să folosim metoda GET pentru a trimite şi anumite argumente pe lângă link şi
folosim biblioteca [Link], trebuie ca, mai întâi, să facem un dicționar al acestor
argumente, să îl codificăm folosind comanda [Link]() și apoi să trecem un astfel
de obiect ca un argument suplimentar al metodei urlopen(). Când este vorba de biblioteca
requests, de asemenea, convertim argumentele într-un dicționar, dar nu le codăm, ci împreună
cu linkul le redirecționăm ca argumente ale funcției get().
• Pentru a trimite o cerere POST, biblioteca [Link] codifică din nou argumentele în același
mod ca în cazul cererilor GET și apoi folosește noua metodă Request() pentru a crea un obiect
de tip cerere care i se transmite, împreună cu linkul, metodei urlopen(). Când este vorba de
biblioteca requests, trimitem cererea POST folosind metoda post(), căreia îi redirecționăm linkul,
precum și data de tip dicționar care conține argumentele pe care dorim să le trimitem la server.
• Descărcarea fișierelor din rețea, când se utilizează biblioteca [Link], se face cu metoda
urlretrieve(), căreia îi redirecționăm linkul către fișier și numele în care dorim să îl salvăm, iar
când este vorba de biblioteca requests - aceasta se face cu metoda get(), cu condiția că trebuie
să scriem singuri în fișier.

HTTP (HyperText Transfer Protocol) este utilizat mai întâi pentru transferul HTML (HyperText Markup
Language). Se bazează pe modelul cerere-răspuns, ceea ce înseamnă că, înainte ca serverul să ne poată
trimite ceva, noi, ca şi client, trebuie să trimitem cererea către server. Cererile sunt standardizate sub
formă de metode. Pe baza acțiunii pe care clientul dorește să o efectueze, cererile pot fi împărțite în
cele care obţin doar date de la server și cele care schimbă datele de pe server. Solicitările HTTP sunt:

• GET
• POST
• PUT
• HEAD
• DELETE
• PATCH
• OPTIONS

După fiecare cerere trimisă serverului, acesta returnează un răspuns care, printre altele, conține şi un
cod de stare, care explică mai detaliat ce s-a întâmplat cu acea cerere și care corespunde mesajului de
stare corespunzător, despre care am discutat deja. Cele mai comune coduri de stare sunt:

• 200 – OK
• 204 – No Content
• 304 – Not Modified
• 400 – Bad Request
• 403 – Forbidden
• 404 – Not Found
• 405 – Not Allowed
• 500 – Internal Server Error

Page 41 of 57
Python ca şi client HTTP

Deși browserele sunt cel mai comun tip de client HTTP, un client poate fi orice program care are acces
la rețea și care poate trimite cereri și primi răspunsuri de la server. În Python, trimiterea cererilor se face
folosind biblioteci deja construite. Cele mai populare biblioteci pentru trimiterea cererilor către un server
HTTP sunt [Link] și requests. Prima bibliotecă este încorporată, iar a doua trebuie instalată
utilizând comanda pip install requests din fereastra de comandă. În acest sens, biblioteca requests este
mult mai puternică și mai ușor de gestionat decât biblioteca [Link]. Exemplele de trimitere a
cererilor folosind codul Python se vor baza pe trimiterea cererilor la adresa [Link] Acest
site, opera persoanei care este responsabilă și pentru crearea bibliotecii requests, ne permite o testare
simplă a bibliotecilor HTTP.

Documentația pentru [Link] poate fi găsită pe pagina care face parte din documentația oficială
Python, în timp ce documentația pentru biblioteca requests se poate găsi pe această pagină.

Prin exemple, vom arăta cum cererile și funcționalitățile serverului HTTP sunt implementate în
bibliotecile [Link] și requests.

Site-ul în care testăm aceste biblioteci pentru fiecare metodă HTTP oferă un punct final (link) care îi
corespunde numelui metodei pe care o testăm. Pentru GET, punctul final ar arăta aşa:
[Link]
în timp ce, de exemplu, pentru metoda POST ar arăta aşa:
[Link]

Cererea GET către serverul HTTP

Biblioteca [Link]

Pentru a trimite o cerere GET, mai întâi trebuie să introducem biblioteca [Link]. Pentru crearea
unei cereri GET utilizând această bibliotecă, folosim metoda urlopen(), căreia îi trimitem linkul paginii
pe care dorim să o obţinem.

[Link]('[Link]

Rezultatul metodei urlopen() (valoare returnată) este răspunsul pe care îl primim de la server. Dacă îi
atribuim acest răspuns unei variabile, de exemplu, un răspuns numit, putem accesa detaliile acelui
răspuns. Valoarea returnată a acestei funcții este de tip [Link], care acceptă
atributele și metodele din care sunt adesea utilizate: url, cod, info(), read(), precum și readlines().

Exemplu de trimitere a unei cereri GET utilizând biblioteca [Link]

1import [Link]
2
3response = [Link]('[Link]
4
5print("URL: ",[Link])
6
7print("Code: ",[Link])
8
9print("Info: ", [Link]())
10
11print("Read: ", [Link]())

Page 42 of 57
12
13print("Readlines: ",[Link]())

URL: [Link]

Code: 200

Info: Date: Mon, 29 Jun 2020 01:04:01 GMT

Content-Type: application/json

Content-Length: 275

Connection: close

Server: gunicorn/19.9.0

Access-Control-Allow-Origin: *

Access-Control-Allow-Credentials: true

Read: b'{\n "args": {}, \n "headers": {\n "Accept-Encoding": "identity",


\n "Host": "[Link]", \n "User-Agent": "Python-urllib/3.7", \n "X-Amzn-Trace-
Id": "Root=1-5ef93e01-65bee1260e0ae9a84c7c55ba"\n }, \n "origin":
"[Link]", \n "url": "[Link]

Readlines: []

În această imprimare vedem care este scopul apelului:

• url – primim linkul exact al cererii noastre, care îi este adresată serverului;
• code – primim codul de stare al cererii noastre ca răspuns de la server;
• info() – primim informații despre antet;
• read() – citim răspunsul serverului, care este de tip string; valoarea acestui atribut poate fi
utilizată pentru a înregistra conținutul paginii HTML obţinute într-un fișier de pe discul nostru;
• readlines() – citim răspunsul serverului, dar linie după linie. Valoarea returnată este de tip list,
unde fiecare element este o linie a răspunsului respectiv. Este important de menţionat că, la fel
ca în cazul citirii fișierelor, dacă folosim o dată read() pe un răspuns, indicatorul va fi la sfârșitul
acelui răspuns. Dacă cu ocazia recitirii acelui răspuns, nu setăm acel indicator la începutul
răspunsului - vom obține un string gol în cazul metodei read() și o listă goală în cazul metodei
readlines(), ceea ce se poate vedea în exemplul nostru. Trecerea locației de unde dorim să
începem să citim răspunsul se face printr-un argument de tip int, pe care i-l trecem metodei
read(), respectiv metodei readlines().

Site-ul [Link] este configurat astfel încât, apelând [Link] să primim


răspunsul care este, de fapt, cererea noastră cu toți parametrii pe care i-am redirecționat. Cum cererea
noastră conține doar un antet – acest antet al nostru, transformat într-un răspuns, asta am şi primit.

Redirecționarea parametrilor utilizând [Link] este ceva mai complicată și necesită codificarea
inițială a acelor parametri într-un format compatibil cu serverul și combinarea parametrului respectiv cu
linkul site-ului către care trimitem cererea GET.

Page 43 of 57
Codificarea parametrilor se face folosind biblioteca [Link], pe care trebuie mai întâi să o
introducem.

Notă

Pe internet se pot găsi o mulțime de rezultate despre biblioteca urllib2, care este, de fapt, o bibliotecă pentru
Python2.x. În Python 3 nu există o bibliotecă cu un astfel de nume. Mai exact, urllib2 este împărțită în bibliotecile
[Link], [Link], [Link] și [Link]. Există, de asemenea, o bibliotecă urllib3 dezvoltată de
comunitate, care este instalată utilizând comanda pip.

Pentru codificare se utilizează metoda urlencode() a bibliotecii [Link]. Parametrul care se transmite
este de tip dicționar de date - unde cheile sunt numele variabilelor pe care le va căuta serverul, iar
valorile acelor variabile sunt valorile pe care vrem să i le cerem serverului. De exemplu, dacă dorim să-i
trimitem o cerere serverului prin care să ne trimită toți utilizatorii care se numesc John, pe lângă adresa
URL de bază către care trimitem cererea, vom adăuga şi first_name=John, astfel încât o astfel de adresă
URL ar putea arăta aşa: [Link] Caracterul (?) îi spune serverului că din
acel moment încep parametrii din URL prin care dorim să fie executată cererea. Dacă dorim mai mulți
parametri, îi vom separa cu semnul (&):

1[Link]

Aşa cum putem vedea, dacă știm în prealabil ce argumente dorim să trimitem, putem crea un astfel de
link şi fără codificare.

Odată ce am codificat parametrii săi cu linia:

parsed_data = [Link]({'first_name':'John',
1'last_name':'Johnson'})
putem face şi o cerere GET asociindu-i linkului variabila parsed_data care este de tip string, dar având
grijă să avem caracterul ? între URL-ul de bază și valoarea variabilei parsed_data:

Exemplu de trimitere a unei cereri GET cu argumente

import [Link]

1import [Link]
2
3parsed_data = [Link]({'first_name':'John',
4'last_name':'Johnson'})
5
6response =
[Link]('[Link]

Și în acest caz, variabila response este de tip [Link], deci metodele și atributele din
exemplul anterior pot fi aplicate și aici.

Biblioteca requests

Trimiterea cererilor GET este foarte simplă atunci când se utilizează această bibliotecă și, înainte de a o
trimite, trebuie să o importăm cu comanda import requests. Pentru a trimite o cerere GET, folosim
metoda get() a acestui modul, căruia îi redirecționăm linkul către care dorim să trimitem cererea.

Page 44 of 57
Valoarea returnată a metodei get() este un obiect Request. Asta este tot ce trebuie să facem pentru a
trimite cererea GET folosind această bibliotecă:

1import requests
2
3r = [Link]('[Link]
4
5print("URL: ",[Link])
6
7print("Headers: ",[Link])
8
9print("Status code: ", r.status_code)
10
11print("Text: ", [Link])
12
13print("OK: ", [Link])
URL: [Link]

Headers: {'Date': 'Mon, 29 Jun 2020 01:56:42 GMT', 'Content-Type': 'application/json', 'Content-Length':
'307', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin':

'*', 'Access-Control-Allow-Credentials': 'true'}

Status code: 200

Text: {

"args": {},

"headers": {

"Accept": "*/*",

"Accept-Encoding": "gzip, deflate",

"Host": "[Link]",

"User-Agent": "python-requests/2.21.0",

"X-Amzn-Trace-Id": "Root=1-5ef94a5a-3cc7cd380419f946d60cd559"

},

"origin": "[Link]",

"url": "[Link]

OK: True

Page 45 of 57
La fel ca [Link], și biblioteca requests ne oferă anumite metode și atribute atunci când este vorba
de obiectul Response. Am folosit câteva dintre ele în exemplul nostru, acestea fiind:

• atributul url – imprimă linkul cererii noastre trimise către server, de tip string;
• atributul headers – obținem informații despre header, de tip dict;
• status_code atribut – imprimă codul de stare al cererii noastre, de tip int;
• atributul text – imprimă textul răspunsului serverului, de tip string. Cum este și în cazul unei
cereri făcute prin metoda [Link], deoarece nu am transmis niciun argument, răspunsul
este doar antetul cererii noastre transferat în răspuns. De asemenea, pe lângă atributul text,
există și un atribut content, care returnează un obiect bytes/octeți. Putem folosi valoarea acestui
atribut pentru a înregistra conținutul paginii HTML obţinute într-un fișier pe discul nostru.
• atributul ok – un atribut foarte util, care este de tip bool și imprimă True sau False, în funcție de
faptul dacă codul de stare este sub sau peste 400. Dacă codul de stare este 4xx sau 5xx,
înseamnă că este vorba de o eroare de client, respectiv o eroare a serverului.

Pentru a trimite argumente la cererea noastră GET, trebuie modificat puţin apelul [Link]() anterior.
Așa cum este și cazul cu modulul [Link] de a trimite cereri GET cu argumente, și aici trecem
aceste argumente ca un tip de dicționar, fără a fi necesară o codificare suplimentară, deoarece acest
lucru îl va face biblioteca în locul nostru. Trecem argumentele funcției get() folosind argumentul params:

[Link]('[Link]
1'last_name':'Johnson'})

Cererea POST către serverul HTTP

Biblioteca [Link]

Din exemplul anterior, am văzut că pentru trimiterea unei cereri GET folosind biblioteca [Link]
este necesar un apel al funcției urlopen(). Cu toate acestea, un pas în plus este utilizat pentru a trimite
o cerere POST. De asemenea, cu metoda POST, trimitem întotdeauna câteva argumente, ceea ce nu este
cazul metodei GET, cu care solicităm date de la server. Pe site-ul nostru de testare, punctul final
responsabil de o cerere POST poate fi găsit la adresa: [Link]

Deoarece trebuie să trimitem și argumente, toate instrucțiunile anterioare privind codificarea folosind
[Link] se aplică și aici. După ce creăm un obiect codat care conține argumentele noastre, i-l trecem
metodei Request(), care returnează un obiect de tip Request. Această metodă este o parte din biblioteca
[Link] și cu ea creăm o cerere tip POST, dar nu o trimitem încă. Îi redirecționăm linkul către care
trimitem cererea, precum și rezultatul metodei Request(), mai exact un obiect de tip Request:

1import [Link]
2
3import [Link]
4
5parsed_data = [Link]({'first_name':'John',
6'last_name':'Johnson'})
7
8req = [Link]('[Link]
9encoding = 'utf8'))
10

Page 46 of 57
11resp = [Link](req)

print([Link]())
b'{\n "args": {}, \n "data": "", \n "files": {}, \n "form": {\n "first_name": "John", \n "last_name":
"Johnson"\n }, \n "headers": {\n "Accept-Encoding": "identity", \n "Content-Length": "33", \n "Content-
Type": "application/x-www-form-urlencoded", \n "Host": "[Link]", \n "User-Agent": "Python-
urllib/3.7", \n "X-Amzn-Trace-Id": "Root=1-5ef95370-13173b845319291e1fa65ac7"\n }, \n "json": null,
\n "origin": "[Link]", \n "url": "[Link]

Dacă examinăm în detaliu răspunsul serverului, vom vedea antetul acestuia și argumentele pe care le-
am trimis. De asemenea, atributele și metodele pe care le-am folosit pe obiectul
[Link] cu ocazia trimiterii cererii GET, se pot folosi și în acest caz.

Biblioteca requests

Trimiterea cererilor POST utilizând această bibliotecă este destul de similară cu trimiterea cererilor GET,
doar cu o mică diferență - în loc de metoda get() folosim metoda post(), căreia ca argument params îi
trecem tipul dicționar:

r = [Link]('[Link] params={'q':'wordpress',
1'type':'title'})

Atributele pe care le-am aplicat pe obiectul Request atunci când trimitem o cerere GET se aplică și aici.

Notă

În ambele cazuri, este important de menționat că trebuie ca punctul final, mai exact linkul către care trimitem
cererea, să susțină primirea acelei cereri (de exemplu, dacă serverul, pe linkul [Link]/get, nu acceptă
(nu așteaptă) cererea GET - vom primi o eroare).

Descărcarea fișierelor din rețea

De asemenea, în lucrul cu protocolul HTTP, prin scripturi vom ajunge deseori la situația în care trebuie
să descărcăm un anumit fișier - imagine, document, video etc. Ambele biblioteci acceptă această
funcționalitate.

Descărcarea fișierului utilizând biblioteca [Link]

Descărcarea unui fișier cu ajutorul acestei biblioteci se face prin simpla apelare a metodei urlretrie(),
căreia îi transmitem două argumente: linkul final la care se află fișierul și numele fișierului (extensia nu
este necesară, dar este de dorit) în care vrem să plasăm fișierul descărcat:

[Link]('[Link]
1logo@[Link]', 'python_logo.png')
2
3
În felul acesta, am salvat imaginea într-un fișier numit python_logo.png, care se află în directorul în care
este şi scriptul pe care îl rulează metoda urlretrieve(). De asemenea, este posibil să specificăm întreaga
cale la care dorim să plasăm fișierul:

Page 47 of 57
[Link]('[Link]
1logo@[Link]', 'C:\\Users\\user\\Documents\\python_logo.png')

În cadrul celui de-al doilea parametru al metodei urlretrieve, dacă specificăm adresa locală unde dorim
să salvăm fișierul, este necesar să scriem două backslash-uri în loc de unul, deoarece primul backslash
poate reprezenta un caracter escape.

Descărcarea fișierului utilizând biblioteca requests

Descărcarea fișierelor folosind această bibliotecă este la fel de simplă ca utilizarea bibliotecii
[Link]. Tot ce trebuie să facem este să apelăm metoda get(), căreia îi atribuim un link către fișier.
După aceasta, trebuie să deschidem manual fișierul cu funcția open() încorporată și în el să plasăm
datele:

1r = [Link]('[Link]
2
3with open('python_logo.png', 'wb') as f:
4
[Link]([Link])

Cu această abordare, este important a menţiona că fișierul trebuie deschis în modul binar destinat
scrierii - wb, și că în loc de [Link] vom folosi [Link], care ne returnează obiectul bytes/octeți.

Notă

În ambele abordări, dacă la locația în care dorim să înregistrăm fișierul, pe disc există deja un fișier cu același
nume - acesta va fi înlocuit cu cel nou.

Page 48 of 57
3.2. Client și server HTTP

Rezumat

• Pentru implementarea simplă a serverului HTTP, utilizăm biblioteca încorporată [Link].


• Obținem un obiect HTTP cu clasa HTTPServer, care face parte din biblioteca [Link].
• Constructorul clasei HTTPServer acceptă următoarele argumente: n-tork cu două elemente
(adresă și port) și clasa de procesare (clasa handler).
• Punerea în funcțiune a obiectului server se face folosind metoda server_forever ().
• Clasa de procesare este clasa pe care o definim și o implementăm conform cerințelor noastre
și care, de fapt, moștenește clasa BaseHTTPRequestHandler, care face parte din biblioteca
[Link].
• Clasa BaseHTTPRequestHandler conține deja mecanisme stabilite pentru gestionarea
metodelor HTTP, pe care doar le extindem.
• Definirea metodelor de clasă care corespund metodelor protocolului HTTP, se face conform
șablonului: do_NUME_METODE.
• Atât metoda de clasă GET, cât și metoda de clasă POST, trebuie să genereze un răspuns în
următoarea ordine:
o self.send_response()
o self.send_header()
o self.end_headers()
o [Link]()
• Pentru a citi o cerere care a venit printr-un string de interogare, folosim comanda [Link].
• Pentru a crea o cerere client, folosim biblioteca requests, care nu face parte din bibliotecile
Python încorporate. Cu ajutorul acestei biblioteci, folosim comanda [Link] (url, params)
pentru a trimite cereri GET, în timp ce pentru a trimite cereri POST, folosim comanda
[Link] (url, params). În ambele cazuri, argumentul URL este de tip string, iar argumentul
params este de tip dicționar.

Pentru implementarea serverului HTTP în Python, vom folosi modulul încorporat [Link], în timp ce
pentru partea client, în loc să folosim browserul ca și client, vom folosi modulul încorporat urllib3.
Deoarece aceste module sunt scrise în Python orientat pe obiecte, ele oferă o abordare orientată pe
obiecte a implementării.

Clase de server și clase pentru procesare

Serverele HTTP create în Python, care utilizează modulul [Link] constau din două părți:

• Clasa HTTPServer – este încorporată în modulul în sine și este aceeași pentru fiecare server bazat
pe acest modul; ne permite să ascultăm pe un anumit port, precum și să acceptăm cereri de la
client;
• clasa handler (handler class) – după ce clasa HTTPServer primește cererea, transmite aceste
informații clasei handler pe care o implementăm.

Vom prezenta implementările clientului HTTP (http_local_client.py) și a serverului (http_local_server.py)


în exemplul în care serverul conține un dicționar de nume și prenume, numit names_dict, iar clientul
încearcă fie să obţină prenumele pentru numele dat (GET), fie să adauge valori noi la dicționarul
respectiv (POST) folosind stringuri de interogare.

Page 49 of 57
Serverul HTTP

Deoarece pentru a implementa serverul HTTP folosim clasa HTTPServer, care face parte din modulul
[Link], îl vom importa folosind sintaxa from – import. De asemenea, clasa noastră pentru procesare
va moșteni clasa BaseHTTPRequestHandler, care face parte, de asemenea, din acest modul, așa că o
vom introduce și pe ea: from [Link] import HTTPServer, BaseHTTPRequestHandler.

Pentru instanţierea unui obiect HTTPServer (obiectul care ne va servi ca server în sine), avem nevoie de
două lucruri ca argumente:

• n-tork, care conține două elemente – adresa IP pe care se află serverul și portul (conform
specificațiilor protocolului, porturile implicite din protocolul HTTP sunt 8080 și 80);
• numele clasei pentru procesare, pe care o vom defini mai târziu.

Instanţierea obiectului server se face cu linia http_server = HTTPServer((’[Link]’,


8080), RequestHandler). În exemplul nostru, am numit această clasă responsabilă cu procesarea
pe care o vom implementa, RequestHandler. Deoarece am creat un obiect server http, îl vom rula
folosind metoda HTTPServer serve_forever().

În acest moment, serverul nostru HTTP este rulat pe adresa IP locală ([Link]) și portul 8080 și poate
fi accesat pe adresa și portul respectiv. Înainte de a rula acest fișier, trebuie să definim și clasa pentru
procesare.

Clasa pentru procesare (handler class)

Clasa pentru procesare care moștenește clasa BaseHTTPRequestHandler poate fi definită cu


class RequestHandler(BaseHTTPRequestHandler). Ceea ce ne permite moștenirea clasei
BaseHTTPRequestHandler este că, de îndată ce primim cererea de la client - vom direcționa acea
cerere către metoda noastră corespunzătoare. De aceea, numele metodelor acestei clase au un şablon
exact după care trebuie numite: do_GET, do_POST, do_PUT etc. Deoarece știm că atunci când clientul
dorește să obțină o anumită resursă sau informații de la un server HTTP, folosește metoda HTTP GET,
mai întâi o vom implementa. Deci, în clasa noastră RequestHandler, metoda de procesare a cererilor
GET se va numi def do_GET(self).

Metoda GET

La trimiterea unui răspuns la solicitarea din exemplul nostru, serverul HTTP va trimite trei parametri:

• codul de stare;
• antetul;
• datele solicitate (corpul mesajului (body)).

De asemenea, trebuie să trimită acești parametri HTTP în această ordine. Înainte de a trimite răspunsul,
serverul nostru HTTP trebuie ca mai întâi să analizeze și să descompună stringul de interogare (query
string) care a venit împreună cu cererea GET (trimisă de client). Acest string face parte din adresa URL
în sine și arată aşa: [Link] În acest exemplu URL, [Link]
este adresa la care se află serverul, în timp ce partea name = michael este, de fapt, stringul de interogare
care este format din două părți:

• variabila pe care o căutăm - în cazul nostru este name (nume);


• valoarea variabilei - în cazul nostru este michael.

Page 50 of 57
Caracterul ? este un indicator care indică faptul că de la acea parte începe stringul de interogare.

O adresă URL, mai precis o cerere GET, poate conține mai multe astfel de perechi variabilă-valoare
pentru stringuri interogative separate printr-un ampersand (&) sub forma:

1[Link]

În acest caz, am trecut două variabile: name și last_name, cu valorile lor.

Pentru analiza stringurilor de interogare din Python, există deja o funcționalitate încorporată care face
parte din biblioteca [Link], mai exact funcția parse_qs, căreia îi este trecut un astfel de string și care
returnează dicționarul:

Exemplu de analiză folosind funcţia parse_qs()

1>>> from [Link] import parse_qs


2>>> print(parse_qs('name=peter&last_name=peterson'))
3{'name': ['peter'], 'last_name': ['peterson']}

Deci, primul lucru pe care trebuie să îl determine serverul este codul de stare al cererii (dacă este o
cerere reușită, o eroare client sau server sau altceva). Lista tuturor codurilor de stare HTTP și a mesajelor
pe care le afișează pot fi găsite aici. Trimitem codul de stare folosind metoda moștenită de la
BaseHTTPRequestHandler din clasa send_response(), care primește ca parametru numărul codului de
stare. Deci, dacă solicitarea are succes, va fi trimisă la 200.

Următorul lucru pe care trebuie să îl împacheteze serverul HTTP în răspuns este antetul. Antetul se
trimite cu metoda clasei BaseHTTPRequestHandler moștenită, send_header (), care pentru parametri
primește două elemente: numele câmpului antet și valoarea lui - un câmp antet pe apelul acestei
metode. De asemenea, după ce stabilim ce câmpuri de antet trimitem, folosim metoda end_headers ()
pentru a indica unde este sfârșitul întregului antet și de unde, opțional, începe partea cu datele care se
trimit. Astfel, dacă dorim să trimitem două câmpuri de antet, o vom face în felul următor:

1self.send_header(’Content-type’, ’text/plain’)
2
3self.send_header(’Date’, ’ Date: Thu, 20 Aug 2020 12:00:00 GMT’)
4
5self.end_headers()

În felul acesta, trimitem două câmpuri în antet: Content-type, care indică faptul că tipul de date care
este trimis în acest răspuns este de tip text/plain și câmpul Date, care arată ora și data acestui răspuns.
Pentru a extrage numele persoanei din stringul de interogare pentru care se căută numele de familie în
dicționarul names_dict, trebuie să analizăm stringul de interogare folosind funcția parse_qs a modulului
încorporat [Link]. Mai întâi, pentru a ajunge la stringul de interogare al cererii GET trimis de client,
vom folosi câmpul path al clasei moștenite BaseHTTPRequestHandler, care returnează obiectul
string. Pentru că ne returnează întregul string de interogare (/?name=michael) și nu vrem să ne intre
caracterele / și ? în cheia dicționarului, vom folosi un segment al acelui string:

1parse_qs([Link][2:])

Page 51 of 57
Valoarea returnată a funcției parse_qs este dicționar, deci putem accesa numele cu comanda:

1name = parse_qs([Link]([2:]))[’name’][0]

În final, folosim un index nul, deoarece parse_qs returnează valoarea de tip listă pentru cheia dată. După
această procedură, avem numele căutat de utilizator și putem obține numele de familie corespunzător
care îi aparține din dicționarul names_dict prin simpla căutare a cheii.

Ca rezultat, împachetarea datelor în răspuns se face utilizând [Link](), comanda clasei


BaseHTTPRequestHandler moștenite. Python, la fel ca alte limbaje de programare, folosește o analogie
între conexiunile de rețea și deschiderea fișierelor. În unele obiecte de fișiere putem doar scrie, iar pe
altele le putem doar citi. Astfel, wfile reprezintă o conexiune între client și server în care se poate doar
scrie. Orice date introduse în această conexiune folosind metoda write() sunt trimise imediat ca răspuns
la cererea clientului. Parametrul metodei write() este de tip octeți, deci dacă trimitem stringuri, trebuie
să folosim metoda string encode(), care va converti obiectul str în octeți. O astfel de comandă de
trimitere a răspunsului, adaptată exemplului nostru, arată aşa:

[Link](str(names_dict[name]).encode())

Acest proces de implementare a cererii GET se referă la cazul în care cererea este reuşită şi, de aceea,
este necesară şi implementarea scenariilor atunci când cererea eşuează. Mai exact, în exemplul nostru,
cererea eşuează atunci când cerem un nume care nu există în dicționar și la o astfel de cerere ar trebui
returnat codul de stare 404. Verificarea reuşitei cererii poate fi codificată folosind blocul de cod try-
except, unde, în cazul obţinerii numelui de familie pe baza prenumelui care a fost introdus de client,
verifică dacă acel nume există în dicționar cu comanda if name in names_dict.keys() și, în funcție de
valoarea acestei comenzi, îi trimite clientului un nume de familie pentru numele dat (cod de stare 200)
sau returnează Name not found (cod de stare 404) în cazul în care nu există. Aceeași verificare se aplică
și la apelul funcției parse_qs(). În funcție de apariția sau nu a unei erori cu ocazia parsării, trebuie trimis
codul de stare 404.

Astfel, am implementat procesarea metodei/cererii GET.

Metoda POST

Metoda POST este utilizată atunci când un client trimite informații către server. În exemplul nostru,
clientul va trimite o pereche de nume și prenume noi către server pentru a adăuga perechea în
dicționarul nostru. Începem implementarea metodei POST pe serverul nostru, prin definirea funcției
def do_POST(self). Înainte de a adăuga o pereche de cheie-valoare în dicționarul nostru, la fel ca în
cazul metodei GET, trebuie să analizăm stringul de interogare folosind metoda parse_qs și citind
stringul din comanda [Link][2:]. Deoarece este vorba de o cerere POST, clientul trimite un string de
interogare, care este o parte din URL, al cărui format arată aşa:
[Link] Dacă analiza este reuşită, noua pereche de
nume familie-prenume o vom adăuga cu comanda:

1names_dict[data['name'][0]] = data['last_name'][0]

În cazul în care clientul a trimis un câmp diferit în stringul de interogare - de exemplu, dacă adresa URL
arată aşa: [Link] comanda din linia anterioară va
arăta o eroare KeyError. În acest caz, acest scenariu ar trebui codificat astfel încât, dacă apare această
eroare, clientului să i se trimită mesaje 404 cu eroarea Incorrect parameters provided în locul mesajului
200 cu un dicționar nou, modificat. În ambele cazuri, trimiterea răspunsului către client se face la fel ca
și cu metoda GET, apelând la următoarele metode:

Page 52 of 57
• self.send_response()
• self.send_header()
• self.end_headers()
• [Link]()

De asemenea, în timpul acestei proceduri, este important să se acorde atenție şi la ordinea apelurilor
lor, deoarece dacă se apelează mai întâi self.send_header() și apoi self.send_response(), va apărea o
eroare pe partea de server.

Pentru o gestionare mai eficientă a erorilor și pentru a nu repeta liniile de cod, vom adăuga o funcție
auxiliară la clasa noastră RequestHandler: def send_response_to_client(self, status_code, data), care va fi
responsabilă doar cu trimiterea codului de stare, headerul și body-ul mesajului. Acest lucru nu este
necesar, dar facilitează citirea și înțelegerea codului. Întregul cod de server arată aşa:

http_local_server.py

1from [Link] import HTTPServer, BaseHTTPRequestHandler


2from [Link] import parse_qs
3names_dict = {'john':'smith',
4 'david':'jones',
5 'michael':'johnson',
6 'chris':'lee'}
7class RequestHandler(BaseHTTPRequestHandler):
8 def do_GET(self):
9 self.log_message("Incoming GET request...")
10 try:
11 name = parse_qs([Link][2:])['name'][0]
12 except:
13 self.send_response_to_client(404, 'Incorrect parameters provided')
14 self.log_message("Incorrect parameters provided")
15 return
16
17 if name in names_dict.keys():
18 self.send_response_to_client(200, names_dict[name])
19 else:
20 self.send_response_to_client(404, 'Name not found')
21 self.log_message("Name not found")
22
23 def do_POST(self):
24 self.log_message('Incoming POST request...')
25 data = parse_qs([Link][2:])
26 try:
27 names_dict[data['name'][0]] = data['last_name'][0]
28 self.send_response_to_client(200, names_dict)
29 except KeyError:
30 self.send_response_to_client(404, 'Incorrect parameters provided')
31 self.log_message("Incorrect parameters provided")
32
33 def send_response_to_client(self, status_code, data):
34 # Send OK status
35 self.send_response(status_code)

Page 53 of 57
36 # Send headers
37 self.send_header('Content-type', 'text/plain')
38 self.end_headers()
39
40 # Send the response
41 [Link](str(data).encode())
42
43server_address = ('[Link]', 8080)
44http_server = HTTPServer(server_address, RequestHandler)
45http_server.serve_forever()

Notă

În lucrul cu clasa BaseHTTPRequestHandler există alte câteva metode pe care nu le-am menționat şi care pot fi
utile. Aceste metode sunt:

• [Link]() – returnează în program antetul cererii clientului;


• self.log_message() – înregistrarea mesajelor; se utilizează în locul comenzilor print;
• self.address_string() – returnează adresa clientului de la care a sosit cererea;
• [Link] – returnează dicționarul în care cheile int sunt valorile codurilor de stare, iar
valorile acestor chei sunt n-tork cu două elemente în care primul element este mai scurt și al doilea
element este o stare mai lungă de mesaj, care îi aparține codului de stare dat;

• [Link] – returnează tipul metodei (GET sau PUT, sau POST etc.).

Clientul HTTP

Deși clientul HTTP este de obicei un browser, asta nu înseamnă că aceasta este singura opțiune de a
accesa serverul HTTP. Orice lucru care poate face o conexiune TCP la un server poate fi folosit de noi ca
și client HTTP. În exemplul nostru, ne-am decis să folosim o bibliotecă care nu face parte din setul
standard de biblioteci - requests. Deși în Python există o bibliotecă pentru lucrul cu protocolul HTTP -
urllib3, biblioteca requests a fost aleasă datorită simplității ei și ușurinței de utilizare la crearea cererilor
către server. Deoarece această bibliotecă este scrisă de comunitate, deci nu face parte din bibliotecile
încorporate care au venit în timpul instalării Python, trebuie instalată folosind comanda pip din fereastra
de comandă. Și anume, pentru a instala comanda requests în fereastra de comandă deschisă, trebuie
pornită următoarea comandă:

1pip install requests

Page 54 of 57
Imaginea 8.1. Afişajul unei instalării reuşite a bibliotecii requests folosind comanda pip

După instalarea reuşită a acestei biblioteci, este necesar să o importăm în scriptul nostru cu comanda
import requests. Această bibliotecă ne oferă un mod foarte simplu de a crea cereri GET și POST către
server:

• r = [Link](url)
• r = [Link](url)

De asemenea, restul cererilor protocolului HTTP sunt apelate utilizând biblioteca requests, folosind exact
numele acestora, de exemplu: [Link](), [Link](), [Link]() etc. Mai multe informații
despre alte metode ale bibliotecii requests pot fi citite pe pagina oficială de documentație.

În felul acesta, am creat un obiect requests cu care, atunci când sosește răspunsul de la server, putem
citi acel răspuns, codul de stare, anteturile și altele.

Mai exact, în cazul nostru, pe lângă adresa URL, care aici este [Link] trebuie să trecem
și stringurile de interogare. Stringurile de interogare în apelul requests sunt transmise ca tip de dicționar
care i se atribuie argumentului params:

• r = [Link]("[Link] params={"name":'michael'})
• r = [Link]("[Link] params = {'name':'peter',
'last_name':'peterson'})

Acesta este tot ce avem nevoie pentru a testa funcționalitatea GET și POST a serverului nostru. Deci, pe
partea de client (http_local_client.py) arată aşa:

http_local_client.py

import requests
1r = [Link]("[Link] params={"name":'michael'})
2print("Request method: GET, \
3 Response status_code: {}, Response data: {}".format(r.status_code, [Link]))
4r = [Link]("[Link] params = {'name':'peter',
5'last_name':'peterson'})
6print("Request method: POST, \
7 Response status_code: {}, Response data: {}".format(r.status_code, [Link]))

Page 55 of 57
Comenzile print nu sunt necesare, dar au fost adăugate pentru a facilita observarea rezultatelor.

Inițierea și analiza rezultatelor

Pentru a testa clientul și serverul, trebuie rulate două ferestre de comandă diferite și poziționate în
directorul în care se află fișierele http_local_client.py și http_local_server.py. Mai întâi, trebuie pornită
partea de server, apoi scriptul client.

Imaginea 8.2. Afișajul pornirii clientului și a serverului prin linia de comandă

Primul lucru pe care îl observăm când pornim partea de server este că, de fapt, nu se întâmplă nimic în
afară de faptul că serverul ascultă cererile primite pe un socket dat. De îndată ce pornim fișierul client -
serverul va începe procesarea cererilor. Pe partea de client, prima la rând este cererea GET cu stringul
de interogare name=michael. De îndată ce serverul confirmă cererea, imprimă antetul cererii, care în
cazul nostru conține numele metodei (GET), stringul de interogare care face parte din adresa URL,
versiunea protocolului HTTP prin care are loc conexiunea (HTTP/1.1) și codul de stare. După procesarea
acestei cereri, serverul îi trimite clientului răspunsul, care se observă în fereastra de comandă în formatul:
Request method. GET, Response status_code: 200, Response data: johnson. Deci, se poate observa că
serverul a găsit numele de familie pentru numele redirecționat - Johnson - și a returnat acele informații
clientului. În acest moment, conexiunea dintre server și client se întrerupe, dar şi începe una nouă, de
data aceasta folosind metoda POST. Clientul trimite acum cererea POST pentru adăugarea perechii noi
nume-prenume în dicționarul existent. Serverul recunoaște că este vorba de o cerere POST, procesează
cererea și returnează dicționarul nou creat ca dovadă a finalizării cu succes.

Aceasta a fost o prezentare a comunicării reuşite. Dacă schimbăm puțin codul clientului astfel încât, prin
stringul de interogare să trimitem parametrii la care serverul nu se așteaptă, vom obține un rezultat
diferit.

Page 56 of 57
Imaginea 8.3. Afișarea pornirii clientului și a serverului prin linia de comandă cu parametri incorecți

În acest caz, ne bazăm pe sesiunea serverului existentă deja din exemplul anterior și pornim din nou
clientul, dar acum cu parametri modificați. Observăm imediat ce parametri s-au schimbat. Este vorba
despre o cerere GET; mai precis, numele transmis nu a fost găsit în dicționarul predefinit. Serverul a
recunoscut acest lucru și a returnat codul de stare 400 cu mesajul Name not found, care poate fi văzut
și din linia de comandă a clientului.

Notă

Experimentați cu trimiterea de parametri diferiți de către client și analizați aceste rezultate.

În această lecţie, am realizat comunicarea HTTP client-server pe exemplul manipulării simple a


dicționarului. Totuşi, protocolul HTTP oferă mult mai mult decât atât, astfel încât, cu adăugări minore la
programul nostru, serverul nostru îi poate returna clientului un string sub formă de cod HTML. În acest
caz, clientul poate fi şi un script cu o bibliotecă requests, dar și browserul care va afișa codul HTML.

Page 57 of 57

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