Programare socket în Windows

      Vom încerca, în acest articol, să scriem o mică aplicaţie utilizând socket-uri. Mai exact vom scrie două programe: unul server şi celălalt client. Programul client va trimite către server un pachet (nu au importanţă datele pe care le conţine). În momentul în care serverul recepţionează mesajul va trimite, înapoi către client, un alt pachet. Clientul înregistrează momentul transmisiei primului pachet şi momentul recepţionării răspunsului de la server. Va face diferenţa şi va afişa timpul necesar celor două pachete să străbată reţeaua. Această modalitate, de a determina timpul de răspuns al unui host din reţea, nu este nouă. Ea este folosită în aplicaţii tip "ping" şi poate fi catalogată drept cea mai simplă metodă de a vedea dacă un calculator din reţea este pornit.

Puţină istorie

      În stadiul iniţial al proiectării socket-urilor (în sistemul de operare UNIX) s-a plecat de la ideea implementării lor ca operaţii de I/O (Input/Output) obişnuite. Se ştie că, in Sistemele tip UNIX, toate operaţiile I/O cu diverse obiecte (fişiere de pe harddisk, imprimante, unităţi de bandă, etc.) au la bază acelaşi principiu: deschide-citeşte-scrie-închide. Ca urmare, pentru a comunica cu o reţea TCP/IP, programul deschide mai întâi o conexiune cu reţeaua, apoi citeşte şi scrie date prin conexiune. La sfârşit, programul închide conexiunea.
      Dezvoltarea funcţiilor API pentru socket-uri, aşa cum sunt ele în forma actuală, a fost făcută de un grup de cercetători de la Berkeley. Pe măsură ce proiectarea avansa, se "descopereau" noi probleme şi în cele din urmă s-a constatat că operaţiile I/O de reţea erau mult mai complexe decât alte tipuri de I/O. De exemplu: pentru scrierea unui program client a fost uşor de adaptat funcţiile API existente pentru fişiere. Problemele au apărut când s-a trecut la scrierea unor funcţii care să definească un program server. Serverul trebuie să aştepte, în mod pasiv, să fie contactat de programe client. Ori în UNIX funcţiile I/O normale nu încorporează prea multe facilităţi legate de operaţii I/O pasive. De aceea s-au creat noi funcţii sistem pentru a gestiona operaţiile pasive.
      O altă problemă apărută a fost aceea că prin funcţiile sistem existente se puteau crea socket-uri orientate spre conexiune dar pentru socket-uri fără conexiune nu existau funcţii sistem echivalente.
      Interfaţa Berkeley este doar una dintre implementările unui API pe baza modelului socket . Windows Socket (denumită şi Winsock) este o altă API bazată pe paradigma socket, inspirată, totuşi, din interfaţa Berkeley. Chiar dacă sistemele de operare Windows (Windows 9x, Windows NT) sunt proprietate a firmei Microsoft, la dezvoltarea Winsock au contribuit mulţi alţi cercetători şi programatori de la alte firme.
      Specificaţiile Winsock organizează biblioteca API în trei grupuri:

  • funcţii Berkeley-sockets incluse in Winsock API,
  • funcţii de baze de date - permit programelor să interogheze reţeaua Internet pentru informaţii despre nume de domenii, servicii de comunicaţii şi protocoale,
  • extensii ale rutinelor Berkeley-sockets specifice Windows.

      Aceste funcţii sunt cuprinse in biblioteca cu legare dinamică WINSOCK.DLL.

Aspecte teoretice

      Pentru a folosi o interfaţă socket programele trebuie să urmeze un proces simplu, compus din patru etape:
  1. Crearea socket-ului,
  2. Configurarea socket-ului pentru a putea fi folosit (conectarea la un host distant sau legarea la un port de protocol local),
  3. Transmiterea şi/sau recepţionarea datelor prin socket,
  4. Închiderea socket-ului.
      Creearea socket-ului. Pentru aceasta se apelează funcţia: socket() socket_handle = socket(protocol_family, socket_type, protocol) în care:
  • socket_handle - handle de socket. Specificaţiile Winsock stabileşte ca valori valide numere întregi pozitive, mai mici decât 0XFFFF (cel mai mare întreg fără semn).
  • protocol_family - identifică o familie sau o colecţie de protocoale înrudite. Valorile pe care le poate lua acest parametru sunt definite in fişierul WINSOCK.H . În total sunt definite (pentru versiunea 1.1 a winsock) 18 valori, printre care:
Tabelul 1. Familii de protocoale. AF_UNSPEC 0 AF_UNIX 1 AF_INET 2 AF_APPLETALK 16 AF_NETBIOS 17
  • socket_type - indică tipul de socket folosit şi poate lua valorile SOCK_DGRAM pentru datagrame sau SOCK_STREAM pentru fluxuri de octeţi. De asemenea, în winsock.h sunt definite şi alte valori, dar ele pot fi folosite doar cu anumite familii de protocoale,
  • protocol - specifică protocolul folosit. Valorile posibile (definite în winsock.h) încep cu prefixul IPPROTO_ (IPPROTO_TCP, IPPROTO_UDP).
      Când programul apelează funcţia socket(), winsock alocă memorie pentru structura de date internă, care stochează informaţii despre socket. Simpla apelare a funcţiei nu completează câmpurile structurii de date. Acest lucru îl fac alte funcţii care vor fi prezentate ulterior.
      Configurarea socket-ului. Modul de configurare depinde de tipul legăturii pe care o stabileşte programul în reţea (orientată pe conexiune sau fără conexiune) şi de scopul programului (proces client sau server). Fiecare socket necesită cinci elemente:
  • adresa IP a host-ului local,
  • portul de protocol pentru procesul local,
  • adresa IP a hostului de la distanţă,
  • portul de protocol pentru procesul de la distanţă,
  • un protocol.
      Funcţiile winsock API utilizate, de programe, pentru configurarea unui socket sunt prezentate în tabelul de mai jos. Ele completează primele patru câmpuri din structura de date, ultimul, protocolul, fiind completat de apelul funcţiei socket()..

Tabelul 2. Funcţiile socket API utilizate pentru configurarea unui socket.
Utilizare socket Informaţie locală Informaţie la distanţă
Client orientat pe conexiune connect connect
Server orientat pe conexiune bind listen, accept
Client fără conexiune bind sendto
Server fără conexiune bind recvfrom

      Deci, în cazul programelor client orientate pe conexiune, se foloseşte instrucţiunea connect() cu următorii parametri: connect (socket_handle, remote_socket_address, address_length). în care:
  • socket_handle - valoarea returnată de funcţia socket(),
  • remore_socket_address - un pointer la o structură specială de adresă socket:
struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; };       Structura de adresă conţine o familie de adrese, un port de protocol şi adresa host-ului pe care rulează programul server. Înainte de apelarea funcţiei connect(), programul trebuie să memoreze în structură datele necesare. Prezentăm mai jos secvenţa de instrucţiuni utilizată de programul nostru (client) pentru a realiza conectarea: memset((char *) &addr_Dest, 0, sizeof(addr_Dest)); addr_Dest.sin_family = AF_INET; addr_Dest.sin_addr.s_addr = inet_addr(szDestIP); addr_Dest.sin_port = htons(destport); if (connect(sock_Send, (sockaddr*) &addr_Dest, sizeof(addr_Dest)) < 0) AfxMessageBox("Eroare: functia connect() ."); unde addr_Dest este o variabilă de tipul sockaddr_in.
      Se observă că în cazul unui client orientat pe conexiune nu se specifică un port de protocol local, unde clientul va recepţiona răspunsul trimis de server. Acest lucru este "lăsat în seama" sistemului de operare, mai precis a modulului winsock. Cu alte cuvinte winsock selectează portul de protocol pentru programul client şi-l anunţă când sosesc date la acel port; nu este necesar ca programul să ştie ce port foloseşte winsock.
      În celelalte trei cazuri (servere şi client fără conexiune) programele trebuie să specifice un port de protocol.
      Transmiterea şi/sau recepţionarea datelor. Interfaţa Berkeley socket include zece funcţii (cinci pentru transmiterea datelor şi cinci pentru recepţionarea datelor). Winsock dispune doar de patru funcţii (două de transmitere şi două de recepţie), prezentate pe scurt în tabelul 3.

Tabelul 3. Funcţii winsock API utilizate în lucrul cu socket-uri
Funcţia winsock API Descriere
Send Transmite date printr-un socket conectat, folosind flag-uri speciale pentru a controla comportarea socket-ului.
Sendto Transmite date la o adresă de host specificată în structura de adresă socket, folosind un buffer de mesaj simplu.
Recv Recepţionează date de la un socket conectat, folosind flag-uri speciale pentru a controla comportarea socket-ului.
Recvfrom Recepţionează date de la un socket şi înregistrează (opţional) adresa de reţea a host-ului emiţător, folosind un buffer de mesaj simplu.

      Programele cu socket-uri fără conexiune pot utiliza doar funcţiile sendto() şi recvfrom(). Aceasta deoarece ele pot specifica adrese de reţea. Programele ce utilizează socket-uri cu conexiune pot utiliza toate cele patru funcţii dar sunt recomandate funcţiile send() şi recv(). În cazul utilizării funcţiilor sendto() şi recvfrom(), winsock ignoră parametrii de adresă incluşi în apelul lor.
      Există cazuri când se impune scrierea unor programe care să utilizeze ambele tipuri de transfer de date (cu şi fără conexiune). În acest caz se pot scrie funcţii generice pentru executarea acestor operaţii, funcţii ce vor utiliza sendto() şi recvfrom(). În acest mod se pot utiliza funcţiile scrise pentru a transmite şi/sau recepţiona date indiferent de tipul socket-ului pe care programul îl pasează funcţiei.
      Un apel tipic al funcţiei send() are următoarea sintaxă: result = send(socket_handle, message_buffer, buffer_length, special_flags)       Când programele apelează funcţia send(), winsock preia informaţia legată de destinaţie (adresa IP şi port de protocol) din structura de date socket internă identificată de socket_handle. Apoi, funcţia transmite datele din buffer-ul de mesaj (message_buffer). Parametrul buffer_length reprezintă lungimea buffer-ului de mesaj care poate fi determinată printr-un apel al funcţiei sizeof(). Parametrul special_flags poate lua următoarele valori:
  • MSG_DONTROUTE - indică faptul că nu se vor folosi tabele de routare,
  • MSG_OOB - Out-Of-Band. Datele Out-Of-Band sunt date urgente pe care programul trebuie să le prelucreze imediat.
      Funcţia recv() are următoarea sintaxă: result = recv(socket_handle, message_buffer, buffer_length, special_flags) şi preia datele de la un port de protocol local, memorându-le în buffer-ul de mesaj (message_buffer). Când se apelează recv() winsock foloseşte structura de date internă (socket_handle) pentru a comunica funcţiei la ce port de protocol se află datele. Parametrul special_flags poate lua valorile:
  • MSG_OOB - datele trebuie prelucrate imediat
  • MSG_PEEK - permite programului să analizeze datele de intrare înainte de a stabili cum să le prelucreze.
      Se observă că cele două funcţii nu oferă informaţii despre host-ul distant. Deci, programul trebuie, mai întâi, să conecteze socket-ul la host-ul distant, apoi să-l utilizeze. Celelalte două funcţii winsock specifică, în apelul lor, informaţii despre host-ul distant: result = sendto(socket_handle, message_buffer, buffer_length, special_flags, socket_address_structure, address_structure_length)       Primii patru parametri sunt identici cu cei ai funcţiei send(). Parametrul socket_address_structure este de tipul sockaddr: struct sockaddr { u_short sa_family; char sa_data[14]; };       Când programul utilizează funcţia sendto() cu un socket conectat, winsock ignoră ultimii doi parametri iar nivelul transport va formata datele din buffer-ul de mesaje ca un segment TCP - nu ca datagrama UDP.
      Următoarea linie prezintă un apel tipic al funcţiei recvfrom(): result = recvfrom(socket_handle, message_buffer, buffer_length, special_flags, socket_address_structure, address_structure_length)       Parametrii funcţiei au aceeaşi semnificaţie cu ai funcţiei sendto().
      Un program server care utilizează funcţia recvfrom() va trebui întotdeauna să extragă adresa emiţătorului pentru a şti unde să trimită răspunsul. În majoritatea cazurilor programele client nu au nevoie de adresa serverului. Dar sunt unele excepţii:
  • clientul trebuie să confirme primirea datelor,
  • clientul trebuie să verifice dacă datele extrase provin de la hostul dorit.


Închiderea socket-ului

      Această ultimă operaţie a unui program se face prin apelul funcţiei: result = closesocket(sock_Send);       Facem precizarea că variabila result (valoare returnată de funcţiile de mai sus) este de tipul int.

Exemplu practic

Ambele programe, la lansare, vor determina numele şi adresa IP a host-ului local. Numele calculatorului se determină cu ajutorul funcţiei: gethostname (name, name_length ) unde:
  • name - este un pointer la un buffer sir de caractere, în care funcţia va "pune" numele calculatorului,
  • name_length - lungimea buffer-ului.
Adresa IP este determinată cu funcţia: gethostbyname ( name ) unde:
  • name - este un pointer la un buffer (determinat anterior cu funcţia gwthostname() ).
      Cele două programe sunt setate să comunice pe portul 4000, port care apare într-o fereastră EDIT.

Programul server. Puteţi face download de la adresa: http://www.epress.ro/TCPServer.exe
      Programul server este astfel conceput, că la simpla lui lansare în execuţie, nu se execută şi secvenţa de instrucţiuni care-l face să "asculte" portul 4000. Acest lucru se realizează apăsând butonul "Listen".

Programul client. Puteţi face download de la adresa: http://www.epress.ro/TCPClient.exe
      Pentru a comunica cu programul server trebuie completată adresa IP a calculatorului pe care rulează programul server. Programul mai are şi o fereastră EDIT care vă permite să schimbaţi conţinutul mesajului transmis. Comunicaţia propriu-zisă se realizează apăsând butonul "Send".
      După punerea server-ului în modul "Listen", completarea şi trimiterea mesajului cu clientul, va apare pe ecranul host-ului client o fereastră care va indica numărul de secunde până la primirea răspunsului de la server.
      Dacă una din funcţiile, apelate de cele două programe pentru a realiza comunicarea, eşuează, va apare o fereastră modală, care afişează funcţia care nu a fost executată cu succes.


Reţele III

Topologia reţelei

      Topologia este un model în care cablurile medii sunt folosite pentru a interconecta diverse calculatoare care formează reţeaua. Topologia este legată direct de protocolul de conexiune. Mecanismul prin care protocolul de legătură pasează datele de la calculator pe reţea impune multe restricţii. Cantitatea de atenuare esenţială mediului, viteza semnalului, şi lungimea segmentelor de cablu sunt factori de care trebuie ţinut seama de protocolul de legătură.

Topologia Bus

      Sunt destule categorii generale de topologii fizice LAN care au diferite metode de a lega cablul de la o staţie la alta. Cea mai simpla, prima topologie LAN adevărată, este topologia Bus (vezi figura), care este formată dintr-un singur cablu care interconectează capetele. Pe parcursul acestui cablu sunt ataşate calculatoarele (numite şi noduri). În topologia Bus este foarte folosit cablul coaxial, cu toate ca alte interfeţe care utilizează componentele legate în serie (cum ar fi SCSI) sunt numite uneori Bus.
      Depinzând de grosimea cablului, bus-ul se poate extinde direct de la un calculator la alt calculator, ca în thin Ethernet, sau poate fi legat în apropierea calculatorului şi un alt cablu scurt este folosit pentru a-l ataşa la bus, ca în thick Ethernet. Cele două capete ale bus-ului trebuiesc 'terminate'; altfel, semnalele care ating capătul unui cablu pot să se reflecte înapoi şi să perturbe transmisia.
      Ca orice alt circuit legat în serie, topologia bus nu este fiabilă pentru că orice întrerupere pe cablu va determina disfuncţia reţelei respective.

Topologia Stea

      Dezvoltată mai târziu, topologia stea a devenit cea mai populară topologie în domeniul reţelelor datorită numărului redus de probleme care pot apărea. În topologia stea, fiecare maşina din reţea are o singură conexiune direct legată la un hub sau un concentrator. Un hub este un dispozitiv, instalat într-o locaţie centrală, care funcţionează ca un nod central (nexus) pentru reţea. Serverele şi calculatoarele client sunt de asemenea ataşate acestui hub, după cum se observă în figură. Dacă un cablu particular se defectează, numai calculatorul conectat la acel segment este afectat. Celelalte calculatoare vor continua să funcţioneze normal. Topologia stea este folosită în general cu sistemul de cablare twised-pair.
      Singurul impediment al acestui tip de reţea este costul hub-urilor. De obicei acest fapt este contrabalansat de uşurinţa prin care se instalează cablul twisted-pair şi preţul scăzut al acestui tip de cablu.

Topologia Token-Ring

      În general, o topologie bus în care cele două capete sunt conectate, formează o topologie de tip ring (inel). Reţeaua obişnuită Token-Ring este de fapt cablată folosind o topologie stea, dar acolo poate fi o topologie logica care diferă de cea fizică. Topologia logica descrie modalitatea în care circulă semnalul nu cum arată cablurile. În cazul Token-Ring, sunt folosite hub-uri speciale pentru a crea o cale de date care pasează semnalele de la o staţie la alta într-un proces care se termină la staţia de la care a pornit. În concluzie, topologia acestei reţele este aceea a unei stele fizice dar un din punct de vedere logic este un inel.

Topologia Mesh

      Topologia Mesh (plasa) este un exemplu de sistem de legaturi care nu este folosit aproape deloc. Descriind o reţea în care fiecare calculator conţine o conexiune dedicata la fiecare alt calculator ne lovim de o altă construcţie teoretică care pune o problema în timp ce încercăm să rezolvăm alta. Una dintre problemele fundamentale ale reţelelor la nivelul de date al modelului OSI este metoda prin care semnalele provenite de la calculatoare diferite sunt transmise pe o reţea medie tip share fără interferenţe. Fiecare calculator are propria lui legătură la fiecare dintre calculatoarele prezente, având dreptul de a transmite liber la orice destinaţie (în afară de momentul în care primeşte date). Problemele apar pe măsură ce numărul de calculatoare creşte. Chiar şi o reţea modesta cu 10 noduri ar necesita 100 NIC (network interface card) şi 100 de cabluri. Nu este cazul să vorbesc de calculatoare care lucrează concomitent cu 10 NIC.

Topologiile hibride

      În multe cazuri, topologiile descrise mai sus sunt combinate şi astfel iau naştere hibrizii. De exemplu, mai multe hub-uri, fiecare centru de stea, sunt de multe ori conectate între ele folosind un segment de tip bus. În alte cazuri, hub-urile sunt adăugate la capătul cablurilor până la alt hub, formând topologie de tip tree (copac).

Standarde pentru Data-Link

      Multe standarde pentru legătura de date sunt în folosinţă astăzi. În orice protocol pentru legătura de date, o cantitate de date este împachetată într-un pachet, care conţine adresa, informaţii de routing, control plus alte informaţii care sunt adăugate astfel încât celelalte staţii din reţea să recunoască dacă datele sunt pentru ele.

Ethernet

      A fost dezvoltat original de Digital Equipment, Intel şi Xerox spre sfârşitul anilor 1970, fiind adoptat ca standard internaţional şi a devenit cel mai predominant protocol de tip Data-Link de astăzi. Iniţial a fost specificat cu o rată a semnalului de 10 Mbps. Foloseşte o metodă de acces cunoscut ca CSMA/CD (Carrier Sense Multiple Access/Collision Detection). În loc să se paseze rândul de la staţie la staţie pentru a da acces la transmisia pe reţea, orice staţie Ethernet are permisiunea de a transmite pachete în orice moment, cât timp reţeaua nu este ocupată cu transmisia de la alte staţii. În momentul în care două sau mai multe staţii încep să transmită în acelaşi timp apar coliziunile. Staţiile implicate de obicei detectează coliziunile şi opresc transmisia. După un timp aleator (câteva milisecunde) fiecare dintre staţii încearcă să transmită din nou, după ce în prealabil, au ascultat să vadă dacă linia este liberă. Numărul mic de coliziuni şi alte erori sunt normale într-o reţea de tip Ethernet.
      Pentru că este imposibil de determinat cu precizie momentul în care un nod va fi următorul care va transmite şi că este aproape imposibil să se garanteze daca transmisia va avea succes, Ethernet este numit şi reţea probabilistică. Există o mică posibilitate ca un anumit pachet sa nu poată să fie transmis cu succes în timpul necesar datorită posibilităţii ca la fiecare încercare să apară o coliziune. Cu cât creste cantitatea de trafic pe o reţea Ethernet, numărul coliziunilor şi erorilor creşte, şi şansa ca un anumit pachet să fie comunicat cu succes descreşte. Voi continua în numărul următor.