Socket-uri BSD



 Socket-urile sunt o facilitate generalizata a retelelor ce au fost introduse pentru prima oara la 4.1cBSD si imbunatatite ulterior pana la forma curenta in 4.2BSD. Facilitatile socket-urilor sunt prezente in majoritatea sistemelor UNIX folosite in mod curent. (Transport Layer Interface (TLI) este alternativa System V). Socket-urile permit comunicatia dintre doua procese diferite de pe acelasi calculator sau de pe calculatoare diferite. Protocoalele pe internet sunt folosite in mod curent pentru comunicatia dintre calculatoare; alte protocoale precum DECnet pot fi folosite daca sunt prezente.

 Pentru un programator un socket arata si lucreaza mai mult ca un descriptor de nivel scazut. Acest lucru se intampla deoarece comenzi ca read() si write() lucreaza cu socket-uri la fel cum lucreaza cu fisiere si cu pipe-uri. Diferentele dintre socket-uri si descriptori normali de fisiere apar la momentul crearii unui socket si la folosirea unor anumite operatii de control al unui socket. Aceste operatii sunt diferite la socket-rui fata de descriptorii normali de fisiere datorita complexitatii sporite la stabilirea unei conexiuni de retea atunci cand facem o comparatie cu accesul normal la disc.

 Pentru cele mai multe operatii folosind socket-uri, rolurile de client si server trebuiesc atribuite. Un server este un proces care executa niste functii la cererea unui client. Dupa cum vom vedea in aceasta discutie, rolurile nu sunt simetrice si nu pot fi schibate intre ele fara efort.

 Aceasta descriere a folosirii socket-urilor decurge in trei etape:

 Folosirea socket-urilor intr-un mediu fara conexiune sau datagrama intre procesele client si server pe aceeasi statie. In aceasta situatie, clientul nu realizeaza explicit o legatura cu severul. Bineinteles, clientul, trebuie sa cunoasca adresa serverului. In schimb, serverul, pur si simplu asteapta aparitia unui mesaj. Adresa clientului este unul dintre parametrii cererii de primire a mesajului si este folosita de server pentru a raspunde.

 Folosirea socket-urilor intr-un mediu conectat intre client si server pe aceeasi masina. In acest caz, rolurile de client si server sunt stabilite mai departe prin felul in care socket-ul este instalat si folosit. Acest model este denumit de obicei model client-server orientat pe conexiune.

 Folosirea socket-urilor intr-un mediu conectat intre client si server pe statii diferite. Aceasta este extensia la nivelul retelei a stagiului 2 sw mai sus.

 Modul fara conexiune sau datagrama dintre client si server pe host-uri diferite nu este discutat explicit aici. Folosirea lui poate fi dedusa din prezentarile facute la Stagiul 1 si 3.


Crearea socket-urilor folosind socketpair()

#include <sys/types.h>
#include <sys/socket.h>

int socketpair(int af, int type, int protocol, int sv[2])
 socketpair() rezulta in crearea a doua socket-uri conectate. sv[] este stringul in care sunt returnati descriptorii socket-urilor. Fiecare descriptor din sv[] este asociat cu un capat al legaturii de comunicatie. Fiecare descriptor poate fi folosit atat pentru intrare cat si pentru iesire. Acest lucru inseamna ca este posibila comunicatia intre procesul parinte si procesul fiu in ambele sensuri.

 In mod normal, un descriptor este rezervat pentru procesul parinte si celalat descriptor este folosit de procesul fiu. Procesul parinte inchide descriptorul folosit de procesul fiu. Pe de alta parte, procesul fiu inchide descriptorul folosit de procesul parinte. fork() este necesar inca pentru a transmite unul dintre socket-uri unui fiu.

 af reprezinta familia de domenii sau adresa careia ii apartine socket-ul. type este tipul de socket care trebuie creat.

 Domeniile se refera la zona in care exista procesele care comunica. Cele mai uzuale domenii folosite includ:

   

Tipul socket-ului se refera la "stilul" comunicatiilor. Cele mai folosite doua valori includ: 

 

O valoare de 0a protocolului este foarte frecventa. Aceasta permite sistemului sa aleaga primul protocol permis cu o pereche de valori specificate pentru familie si tip.

 Exemplu (imprumutat din Tutorialul 4.3BSD IPC de Stuart Sechrest)

#define DATA1   "sirul de test 1"
#define DATA2   "sirul de test 2"

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>

main()
{
  int sockets[2], child;
  char buf[1024];

  /* Se citeste perechea de socket-uri */
  if (socketpair(AF_UNIX, SOCK_STREAM,
    0, sockets) < 0) {
      printf("error %d on socketpair\n", errno);
      exit(1);
  }

  /* creaza procesul fiu */
  if ((child = fork()) == -1) {
    printf("fork error %d\n", errno);
    exit(1);
  }

  if (child != 0) { /* this is the parent */
    /* inchide capatul socket-ului de la fiu */
    close(sockets[0]);

    /* citeste mesajul de la fiu */
    if (read(sockets[1], buf, sizeof(buf)) < 0) {
      printf("error %d reading socket\n", errno);
      exit(1);
    }
    printf("-->%s\n", buf);

    /* scrie un mesaj catre fiu */
    if (write(sockets[1], DATA1, sizeof(DATA1)) < 0) {
      printf("error %d writing socket\n", errno);
      exit(1);
    } 

    /* terminare */
    close(sockets[1]);

  } else {  /* fiul */

    /* inchide capatul socket-ului de la parinte */
    close(sockets[1]);

    /* trimite mesaj parintelui */
    if (write(sockets[0], DATA2, sizeof(DATA1)) < 0) {
      printf("error %d writing socket\n", errno);
      exit(1);
    } 

    /* citeste mesajul de la parinte */
    if (read(sockets[0], buf, sizeof(buf)) < 0) {
      printf("error %d reading socket\n", errno);
      exit(1);
    }
    printf("-->%s\n", buf);

    /* terminare */
    close(sockets[0]);
  }
}

Crearea de socket-uri folosind socket()

#include <sys/types.h>
#include <sys/socket.h>

int socket(int af, int type, int protocol)
socket() este asemanatoare cu socketpair() cu exceptia faptului ca un singur socket este creat in loc de doua. Acest lucru este cel mai frecvent folosit daca se doreste comunicarea cu un proces care nu este fiu. Campurile af, type si protocol sunt folosite la fel ca la apelul sistem socketpair().

 In caz de succes, este returnat un descriptor de fisier pentru socket. In caz de esec, -1 este returnat si errno descrie problema.


Denumirea unui socket - bind()

#include <sys/types.h>
#include <sys/socket.h>

int bind(int s, struct sockaddr *name, int namelen)
Amintiti-va ca folosind socketpair(), socket-urile puteau fi impartite numai intre procesul parinte si procesul fiu sau intre procesele fiu al aceluiasi proces parinte. Cu un nume atasat socket-ului, orice proces din sistem il poate descrie (si folosi).

 La apelul sistem bind(), s este descriptorul de fisier pentru socket, obtinut prin apelul sistem socket(). name este un pointer la o structura de tip sockaddr. Daca familia adresei este AF_UNIX (cum a fost specificat la crearea socket-ului), structura este definita dupa cum urmeaza:

   

        struct sockaddr {
                u_short sa_family;
                char    sa_data[14];
        };
name.sa_family ar trebui sa fie de tip AF_UNIX. name.sa_data ar trebui sa contina pana la 14 octeti dintr-un nume de fisier care va fi asignat socket-ului. namelen returneaza lungimea reala a lui name, adica, lungimea continutului initializat al structurii de date.

 Valuarea 0 este returnata in caz de reusita. In caz de esec, -1 este returnat cu erno descriind eroare.

 Exemplu:


 

struct sockaddr name;
int s;
name.sa_family = AF_UNIX;
strcpy(name.sa_data, "/tmp/sock");
if((s = socket(AF_UNIX, SOCK_STREAM, 0) < 0)
   {
        printf("socket create failure %d\n", errno);
        exit(0);
   }
if (bind(s, &name, strlen(name.sa_data) +
   sizeof(name.sa_family)) < 0)
       printf("bind failure %d\n", errno);

Specificarea unui socket la distanta - connect()

#include <sys/types.h>
#include <sys/socket.h>

int connect(int s, struct sockaddr *name, int namelen)
Apelul sistem bind() permite numai specificarea unei adrese locale. Pentru a specifica partea de la distanta a unei adrese de conectare apelul sistem connect() este folosit. In apelul sistem de conectare, s este descriptorul de fisier pentru socket. name este un pointer la o structura de tip sockaddr:

   

        struct sockaddr {
                u_short sa_family;
                char    sa_data[14];
        };
La fel ca la apelul sistem bind(), name.sa_family are trebui sa fie de tip AF_UNIX. name.sa_data ar trebui sa contina pana la 14 octeti dintr-un nume de fisier care va fi asignat socket-ului. namelen returneaza lungimea actuala a numelui. Returnarea valorii 0 indica reusita, in timp ce valoarea -1 indica esecul cu errno descriind eroarea.

 Un fragment exemplu de cod:

struct sockaddr name;

name.sa_family = AF_UNIX;
strcpy(name.sa_data, "/tmp/sock");

if (connect(s, &name, strlen(name.sa_data) +
        sizeof(name.sa_family)) < 0) {
                printf("connect failure %d\n", errno);
}

Transmiterea catre un socket numit - sendto()

int sendto(int s, char *msg, int len, int flags,
        struct sockaddr *to, int tolen)
Aceasta functie permite unui mesaj msg de lungime len sa fie trimis printr-un socket cu descriptorul s socket-ului numit de to si tolen, unde tolen esre lungimea actuala a lui to. flags va fi mereu 0 pentru scopurile noastre. Numarul de caractere trimise este valoarea returnata de functie. In caz de eroare, -1 este returnat si errno descrie eroarea.

 Un exemplu:

struct sockaddr to_name;

to_name.sa_family = AF_UNIX;
strcpy(to_name.sa_data, "/tmp/sock");

if (sendto(s, buf, sizeof(buf), 0, &to_name,
        strlen(to_name.sa_data) +
                sizeof(to_name.sa_family)) < 0) {
        printf("send failure\n");
        exit(1);
}

Receptia pe un socket numit - recvfrom()

#include <sys/types.h>
#include <sys/socket.h>

int recvfrom(int s, char *msg, int len, int flags,
        struct sockaddr *from, int *fromlen)
Aceasta functie permite unui mesaj msg de lungime maxima len sa fie citit de la un socket cu descriptorul s din socket-ul denumit de from si fromlen, unde fronlen este lungimea actuala a lui from. Numarul de caractere citite de fapt de la socket este valuarea returnata de functie. In caz de eroare, -1 este returnat si errno descrie eroarea. flags poate fi 0, sau poate specifica lui MSG_PEEK sa examineze un mesaj fara sa-l primeasca cu adevarat din coada.

 Daca nu exista nici nu mesaj de citit, si daca socket-ul nu este setat pe modul fara blocaje (printr-un apel sistem ioctl), procesul va fi suspendat asteptand un mesaj.

 Apelul sistem de intrare/iesire read() poate fi deasemenea folosit pentru a citi date de la un socket.


Inchiderea unui socket

#include <stdio.h>

void close(int s).
Apelul sistem de intrare/iesire close() va inchide descriptorul socket-ului s la fel cum ar inchide orice descriptor de fisier deschis.

 Exemplu - sendti() si recvfrom()

/* destinatar */
#include <sys/types.h>
#include <sys/socket.h>

struct sockaddr myname;
struct sockaddr from_name;
char buf[80];

main()
{
  int   sock;
  int   fromlen, cnt;

  sock = socket(AF_UNIX, SOCK_DGRAM, 0);
  if (sock < 0) {
    printf("socket failure %d\n", errno);
    exit(1);
  }

  myname.sa_family = AF_UNIX;
  strcpy(myname.sa_data, "/tmp/tsck");

  if (bind(sock, &myname, strlen(myname.sa_data) +
        sizeof(name.sa_family)) < 0) {
    printf("bind failure %d\n", errno);
    exit(1);
  }

  cnt = recvfrom(sock, buf, sizeof(buf),
    0, &from_name, &fromlen);
  if (cnt < 0) {
    printf("recvfrom failure %d\n", errno);
    exit(1);
  }

  buf[cnt] = '\0';  /* assure null byte */
  from_name.sa_data[fromlen] = '\0';

  printf("'%s' received from %s\n",
    buf, from_name.sa_data);
}


/* expeditor */
#include <sys/types.h>
#include <sys/socket.h>

char buf[80];
struct sockaddr to_name;

main()
{
  int   sock;

  sock = socket(AF_UNIX, SOCK_DGRAM, 0);
  if (sock < 0) {
    printf("socket failure %d\n", errno);
    exit(1);
  }

  to_name.sa_family = AF_UNIX;
  strcpy(to_name.sa_data, "/tmp/tsck");

  strcpy(buf, "test data line");

  cnt = sendto(sock, buf, strlen(buf), 0, &to_name,
    strlen(to_name.sa_data) + sizeof(to_name.sa_family));
  if (cnt < 0) {
    printf("sendto failure %d\n", errno);
    exit(1);
  }
}

Un rafinament: Conexiune CLient-Server

Socket-urile pot fi folosite pentru a scrie aplicatii client-server folosind o tehnica client-server orientata pe conexiune. Cateva carecteristici ale acestei tehnici includ: Cand este realizata, conexiunea client-server, exista pana cand clientul sau serverul o distrug explicit, la fel ca un apel telefonic fara interferente. Socket-ul folosit pentru aceasta conexiune este numit socket orientat pe conexiune.

 Tipul socket-ului este specificat ca SOCK_STREAM. In consecinta, procesul care primeste un mesaj proceseaza acel mesaj dupa urmatoarele reguli:

Functiile listen() si accept() permit serverului sa astepte cereri de servicii. read() si write() pot fi folosite de client si server pentru a trimite/primi mesaje; send() si recv() pot fi folosite deasemenea.

Realizarea unui socket Listen-only Connection Endpoint - listen()

#include <sys/types.h>
#include <sys/socket.h>

int listen(int s, int backlog)
listen stabileste socket-ul ca un capat pasiv al conexiunii. Nu suspenda executia procesului.

 Nici un mesaj nu poate fi transmis prin acest socket. Pot fi receptionate mesajele primite.

 s este descriptorul de fisier asociat cu socket-ul creat folosind apelul sistem socket(). backlog esre marimea cozii de asteptare a cererilor in timp ce serverul este ocupat cu o cerere de serviciu. Valoarea maxima actuala a lui SYSTEM-IMPOSED este 5.

 0 este returnat in caz de succes, -1 in caz de eroare, errno indicand problema.

Exemplu:

#include <sys/types.h>
#include <sys/socket.h>

int sockfd; /* descriptorul de fisier al socket-ului */

if(listen(sockfd, 5) < 0)
        printf ("listen error %d\n", errno);

Connection Establishment by Server - accept()

#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *name, int *namelen)

Apelul sistem accept() realizeaza o conexiune client-server pe partea de server. (Clientul cere conexiunea folosind apelul sistem connect().) Serverul trebuie sa fi creat socket-ul folosind socket(), sa dea un nume socket-ului folosind bind(), si sa stabileasca o coada de asteptare folosind listen().

 sockfd este descriptorul de fisier al socket-ului din apelul sistem socket(). name este un pointer la o structura de tip sockaddr descrisa deasupra

        struct sockaddr {
                u_short sa_family;
                char    sa_data[14];
        };
In cazul returnarii cu succes din accept(), aceasta structura va contine adresa protocolului socket-ului clientului.

 Zona de date pointata de namelen ar trebui initializara cu marimea actuala a lui name. UPON returnarii cu succes din accept, zona de date pointata de namelen va contine lungimea actuala a adresei protocolului a socket-ului clientului.

 In caz de succes, accept() creaza un nou socket din aceeasi familie, tip, si protocal ca si sockfd. Descriptorul de fisier pentru acest nou socket este valoarea returnata de accept(). Acest nou socket este folosit pentru toate comunicatiile cu clientul.

 Daca nu exista nici o cerere de conexiune din partea clientului, accept() se va bloca pana cand in coada apare o cerere din partea clientului.

 accept() va esua in mare parte daca sockfd nu este un descriptor de fisier pentru un socket sau daca tipul socket-ului nu este SOCK_STREAM. In acest caz, accept() returneaza valoarea -1 si errno descrie problema.


Transferul de date intre socket-urile conectate - send() si recv()

Daca socket-urile sunt conectate, sunt disponibile inca doua librarii de apeluri pentru transferul datelor, numite send() so recv(). Ele corespund foarte mult cu functiile read() si write() folosite pentru intrari/iesiri in cazul descriptorilor oarecare de fisier.
#include <sys/types.h>
#include <sys/socket.h>

int send(int sd, char *buf, int len, int flags)

int recv(int sd, char * buf, int len, int flags)
In ambele cazuri, sd este descriptorul socket-ului.

 Pentru send(), buf pointeaza la un buffer care contine datele ce trebuiesc trimise, len este lungimea datelor si flags va fi 0 de obicei. Valoarea returnata este numarul de octeti trimisi in caz de succes. In caz de nereusita, -1 este returnat si errno descrie eroarea.

 Pentru recv(), buf pointeaza la o zona de date in care sunt copiate datele receptionate, len este marimea acestei zone de date in octeti, si flags este de obicei ori 0 ori setat pe MSG_PEEK daca datele trebuiesc retinute in sistem dupa ce sunt receptionate.Valoarea returnata este numarul de octeti primiti in caz de succes. In caz de nereusita, -1 este returnat si errno descrie eroarea.


Un exemplu orientat pe conexiune - listen(), accept()

/* Structura generica a unui program pentru stabilirea mediului client-server orientat pe conexiune. */
/* programul server */

#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>

struct sockaddr myname;
char buf[80];

main()
{
  int sock, new_sd, adrlen, cnt;

  sock = socket(AF_UNIX, SOCK_STREAM, 0);
  if (sock < 0) {
    printf("server socket failure %d\n", errno);
    perror("server: ");
    exit(1);
  }

  myname.sa_family = AF_UNIX;
  strcpy(myname.sa_data, "/tmp/billb");
  adrlen = strlen(myname.sa_data) +
      sizeof(myname.sa_family);

  unlink("/tmp/billb");  /* programare defensiva */
  if (bind(sock, &myname, adrlen) < 0) {
    printf("server bind failure %d\n", errno);
    perror("server: ");
    exit(1);
  }

  if (listen(sock, 5) < 0 {
    printf("server listen failure %d\n", errno);
    perror("server: ");
    exit(1);
  }

  /* Ignorarea terminarii procesului fiu.    */

  signal (SIGCHLD, SIG_IGN);

  /*  Plaseaza serverul intr-o bucla infinita, asteptand
      sa apara cereri de conexiuni de la clienti.
      In practica, ar trebui sa existe o metoda curata
      de a termina acest proces, dar deocamdata va ramane
      rezident pana cand va fi terminat 
      de terminalul de start sau de super-user.        */

  while (1) {
    if (new_sd = accept(sock, &myname, &adrlen)) < 0 {
      printf("server accept failure %d\n", errno);
      perror("server: ");
      exit(1);
    }

    /* Creaza serverului-fiu procesul. Parintele nu mai
       proceseaza -- se intoarce inapoi pentru a astepta
       o alta cerere de conexiune.                */

    printf("Socket address in server %d is %x, %s\n",
      getpid(), myname.sa_data, myname.sa_data);

    if (fork() == 0) {  /* procesul fiu */
      close (sock);  /* fiul nu are nevoie de el  */
      /*  . . . . .  */

      cnt = read(new_sd, buf, strlen(buf));
      printf ("Server with pid %d got message %s\n",
         getpid(), buf);
      strcpy (buf, "Message to client");
      cnt = write(new_sd, buf, strlen(buf));
      printf("Socket address in server %d is %x, %s\n",
        getpid(), myname.sa_data, myname.sa_data);

      /*  . . . . .  */
      close (new_sd); /* inchiderea dinaintea iesirii  */
      exit(0);
    }   /* inchiderea acoladei pentru if (fork() ... )  */

  }  /* inchiderea acoladei pentru  while (1) ... )    */

}  /* inchiderea acoladei pentru  main procedure   */

/* programul client */

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>

char buf[80];
struct sockaddr myname;

main()
{
  int   sock, adrlen, cnt;

  sock = socket(AF_UNIX, SOCK_STREAM, 0);
  if (sock < 0) {
    printf("client socket failure %d\n", errno);
    perror("client: ");
    exit(1);
  }

  myname.sa_family = AF_UNIX;
  strcpy(myname.sa_data, "/tmp/billb");
  adrlen = strlen(myname.sa_data) +
      sizeof(myname.sa_family);

  if (connect( sock, &myname, adrlen) < 0) {
    printf("client connect failure %d\n", errno);
    perror("client: ");
    exit(1);
  }
  /*  . . . . .  */

  strcpy(buf, "Message sent to server");
  cnt = write(sock, buf, strlen(buf));
  cnt = read(sock, buf, strlen(buf));
  printf("Client with pid %d got message %s\n",
     getpid(), buf);
  printf("Socket address in server %d is %x, %s\n",
     getpid(), myname.sa_data, myname.sa_data);

  /*  . . . . .  */
  exit(0);
}

Conectarea intr-o retea

Trimiterea de date printr-o retea este realizata in mare in acelasi fel ca si trimiterea de date pe aceeasi masina. Realizarea unei conexiuni este mai dificila deoarece un simplu nume de fisier nu este comun pe diferite masini. Din acest motiv exista domenii de retea diferite de AF_UNIX.

 Urmatoarele pagini descriu cateva dintre rutinele disponibile pentru a realiza o conexiune de retea. Toate aceste functii sunt pentru folosirea cu domeniul AF_INET. Conceptul de adresare in retea este discutat mai intai.


Adrese de retea - Adresa IP

Adresa IP de host, sau mai comun doar adresa IP, este folosita pentru a identifica host-urile conectate la Internet. IP inseamna Protocol Internetsi se refera la Layer-ul Internet-ului a intregii arhitecturi de retea a Internet-ului. O adresa IP este o marime de 32-biti interpretata ca numere de 4-8-biti sau octeti. Fiecare adresa IP identifica unic reteaua-utilizator participanta, host-ul din retea, si clasa retelei utilizator.

 O adresa IP este scrisa deobicei sub forma unei notatii punctata-zecimal de forma N1.N2.N3.N4, unde fiecare Ni este un numar zecimal cuprins intre zecimalele 0 si 255 (00 prin ff hexazecimal). Adresele sunt controlate si asignate de catre Centrul de Informatii despre Reteaua Internet al SRI International. In prezent exista definite 5 clase de retele, numite A, B, C, D si E; clasele A, B si C sunt disponibile utilizatorilor. Fiecare interfata de pe un host conectat la Internet trebuie sa aiba propria adresa IP.

Subretele. Pe masura ce numarul de host-uri dintr-o anumita retea a crescut in marime si acestea au ajuns din ce in ce mai separate geografic, consideratiile privind managementul retelei si limitarile stratului fizic precum lungimea cablului au impulsionat cercetarile in posibilele schimbari in instalarea retelelor fara a le afecta in mod drastic. Deasemenea, folosirea retelelor cu strat fizic eterogen ( de exemplu, Ethernet si Token Ring) cu aceeasi adresa IP a crescut.

 Conceptul de subretea a fost introdus pentru a ajuta la rezolvarea acestor probleme. Beneficiile pentru retelele de tip clasa A si clasa B valorifica efortul planificarii si implementarii subretelelor.

 Ideea de baza intr-o subretea este de a partitiona portiunea din adresa IP care identifica host-ul in doua parti:

  1. O adresa de subretea inauntrul adresei de retea ; si
  2. O adresa de host in subretea.
De exemplu, un format curent a adreselor din clasa B este N1.N2.S.H, unde N1.N2 identifica reteaua de clasa B, campul S de 8-biti identifica subreteaua, si campul H de 8-biti identifica host-ul din subretea.

Numele host-urilor din retea

Folosirea adreselor IP pentru a accesa host-urile dintr-o retea este util pentru softul IP. Majoritatea oamenilor sunt invatati cu folosirea numelor, iar procedurile pentru construirea corecta a numelui si a translatiei acestor nume in adrese IP exista de ceva timp. Cel mai adesea folosit este Sistemul de Nume de Domenii (DNS), ocazional dar incorect numit Serviciul de Nume de Domenii. Denumirea in DNS este realizata ierarhic, intr-un format de structura arborescenta, la fel ca sistemul de denumire a fisierelor din UNIX. Cele doua niveluri de deasupra sunt controlate de catre Centrul de Informatii despre Reteaua Internet (NIC) al SRI International.

In capatul domeniului se afla doua litere care desemneaza tara si trei litere (deobicei) care desemneaza o categorie generala din interiorul USA. Cateva exemple:

Urmatorul nivel identifica de obicei institutia. De exemplu:
 
  Combinatia acestora ar trebui sa fie destula pentru a specifica un numele, tipul si locatia entitatii organizationale. De exemplu:
 
 
ukuug.uk -- Grupul de utilizatori UNIX din Marea Britanie
utdallas.edu -- Universitatea Texas din Dallas, USA
ti.com -- Texas Instruments din USA
Ierarhii viitoare pot fi stabilite inauntrul acestor organizatii si sunt controlate local. Cateva exemple de host-uri: 
 
csservr2.utdallas.edu -- host-ul csservr2 de la UT-D
pac1.pac.sc.ti.com --   host-ul pac1 din domeniul pac dinauntrul domeniului pac din domeniul sc de la TI
sunk.ssc.gov -- host-ul sunk de la laboratorul Superconducting SuperCollider
DNS si celelate programe ajuta la intretinerea acestor conversii de nume si la translatia numelor de host-uri in adrese IP si vice versa.

Fisierul /etc/hosts

Corespondenta dintre numele host-urilor si adresele IP este mentinuta intr-un fisier denumit hosts in (nivelul superior) directorul /etc. Pe majoritatea sistemelor, orice utilizator poate citi acest fisier. (Un cuvant de avertizare: pe multe sisteme, printarea acestui fisier poate aduce prejudicii proviziilor locale de hartie)

 Intrarile din acest fisier arata dupa cum urmeaza:

127.0.0.1               localhost
192.217.44.208  snoopy beagle hound metlife
153.110.34.18   bugs wabbit wascal
153.110.34.19   elmer
153.110.34.20   sam
Tineti cont ca mai mult de un nume poate fi asociat cu o adresa IP data. Acest fisier este folosit la convertirea adreselor IP in nume de host-uri si vice versa.

Selectarea unui port de serviciu

Specificatiile de protocol ale protocoalelor folosite in domeniul AF_INET necesita specificarea unui port. Numarul portului este folosit pentru a determina cu care proces de pe masina de la distanta trebuie comunicat.

 Unor servicii de retea le-au fost asignate porturi bine cunoscute. Asignarile de porturi unui serviciilor de retea pot fi gasite in fisierul /etc/services. Selectarea unui port bine cunoscut implica cautarea in acest fisier si este realizata cu urmatoarele functii:

#include <netdb.h>

struct servent *getservbyname(char *name, char *proto)

struct servent *getservbyport(int port, char *proto)
Cele doua optiuni pentru proto din fiecare apel sunt tcp pentru comunicatiile orientate spre siruri, si udp pentru comunicatii orientate spre datagrame. port este (cunoscut) numarul portului atunci cand numele serviciului este necesar, in timp ce name este sirul de caractere care contine numele serviciului cand numarul portului este necesar.

 Valoarea returnata pentru fiecare functie este un pointer la o structura cu urmatoarea forma:

        struct    servent {
        char *s_name;      /* numele oficial al serviciului */
        char **s_aliases;  /* lista de nume a serviciilor alias */
        long s_port;       /* unde se afla portul serviciului */
        char *s_proto;     /* protocolul care trebuie folosit */
     };
Daca un program nu are nevoie sa comunice cu un port bine cunoscut este posibil sa aleaga un port nefolosit pentru a fi folosit de un program. Ilustrarea acestei tehnici se gaseste in programele exemplu de mai jos.

Formarea programului de adresa a unui host

Odata ce sunt cunoscute adresa unui host si numarul portului, adresa completa a procesului trebuie alcatuita intr-o forma care sa poate fi folosita de catre apelurile sistem deja amintite. Structurile setate sa accepte asta urmeaza.
 
 
#include <netinet/in.h>

/*
 * Adresa Internet (o structura pentru motive istorice)
 */
struct in_addr {
    union {
         struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
         struct { u_short s_w1,s_w2; } S_un_w;
         u_long S_addr;
    } S_un;
#define s_addr  S_un.S_addr
        /* poate fi folosit pentru majoritatea codului tcp & ip */
};


/*
 * Adresa socket-ului, in stil internet.
 */
struct sockaddr_in {
      short   sin_family;
      u_short sin_port;
      struct  in_addr sin_addr;
      char    sin_zero[8];
};
Completarea campurilor pentru sockaddr_in va produce o versiune de Internet a unei adrese de socket.

Gasirea adresei unui calculator

Gasirea unei adrese care poate fi folosita pentru conectarea la un calculator de la distanta este realizata cu una dintre urmatoarele comenzi:
 
 
#include <netdb.h>

struct hostent *gethostbyname(char *name)

struct hostent *gethostbyaddr(char *addr, int len, int type)
name contine numele host-ului pentru care adresa IP este necesara. addr pointeaza la o structura de tip in_addr si len este marimea in octeti a acestei structuri. In aceasta discutie tipul este mereu AF_INET din moment ce discutia este limitata la folosirea adreselor IP din Internet.

 Ambele apeluri returneaza un pointer la o structura de intrare a unui host. Aceasta structura are urmatoare forma:

        struct hostent {
                char   *h_name;  /* numele oficial al host-ului */
                char   **h_aliases;  /* lista de alias-uri */
                int    h_addrtype;   /* tipul adresei */
                int    h_length;     /* lungimea adresei */
                char   **h_addr_list;
                        /* lista adreselor de la serverul de nume */
        #define  h_addr h_addr_list[0]
                        /* adresa pentru compatibilitatea inversa */
        };

Ordinea octetilor pentru numere in retea

Nu toate arhitecturile calculatoarelor folosesc aceeasi reprezentare pentru numere. De exemplu, pe sistemele host bazate pe microprocesoarele Intel si arhitectura DEC VAX octetii sunt memorati in locatii de memorie in crestere in ordine inversa sau ordine "little-endian". Toate celelate sisteme memoreaza octetii in locatii de memorie in crestere in ordine "normala" sau "big-endian".

 Lasata asa cum este, comunicatia dintre, spre exemplu, calculatoarele compatibile IBM si statiile Sun Microsystems ar fi dificila in cel mai bun caz. Pentru a depasi asta, toate datele trimise printr-o retea sunt convertite la ordinea de octeti a retelei care este, in contextul discutiei de mai sus, ordinea "big-endian".

 Rutinele pentru convertirea datelor dintre reprezentarea interna a unui host si ordinea de octeti a retelei sunt:

#include <sys/types.h>
#include <netinet/in.h>

u_long htonl(u_long hostlong);

u_short htons(u_short hostshort);

u_long ntohl(u_long netlong);

u_short ntohs(u_short netshort);
Aceste functii sunt macrouri si rezulta in insertia codului sursa de conversie in programul apelant. Pe calculatoarele little-endian codul va schimba valorile la ordinea de octeti a retelei. Pe calculatoarele big-endian nici un cod nu va fi introdus din moment ce nu este nevoie; functiile sunt definite ca null.

Rutinele de conversie a adreselor de retea

O adresa de Internet este scrisa de obicei si specificata in notatia punctata-zecimal descrisa deasupra. Intern ea devine o parte a unei structuri de tip in_addr. Pentru a converti intre aceste doua reprezentari sunt disponibile doua functii.
 
 
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>     /* structura in_addr */

unsigned long inet_addr(char *ptr)

char *inet_ntoa(struct in_addr inaddr)
inet_addr() converteste un sir de caractere in notatie punctata-zecimal intr-o adresa de Internet pe 32-BIT. Valoarea returnata nu este consistenta, din pacate. Valoarea returnata corect ar trebui sa fie un pointer la o structura de tip in_addr dar multe calculatoare, respectand o conventie mai veche, returneaza numai reprezentarea interna a notatiei punctate-zecimal. Paginile de manual vor clarifica situatia pentru calculatorul host pe care este folosita functia.

 inet_ntoa() primeste o structura de tip in_addr ca parametru (notati ca structura in sine este folosita, nu un pointer) si returneaza un pointer la un sir de caractere continand reprezentarea punctata-zecimal a adresei de Internet.


Exemplu de conexiune client-server orientata pe Internet

Urmatoarele doua sectiuni contin exemple de programe pentru server si pentru client atunci cand comunicatiile orientate pe conexiune din Internet (folosind TCP) sunt necesare. Programele server si client sunt numite vcserver, si respectiv vcclient. Odata construite, programele au cateva restrictii: 
  Daca vcclient este pornit pe acelasi host pe care ruleaza si vcserver, linia de comanda pentru pornirea lui vcclient este
 
 
        vcclient localhost server-port-number
Daca vcclient este pornit pe un host diferit de cel pe care ruleaza vcserver, linia de comanda pentru a porni vcclient este
 
 
        vcclient server-host-name server-port-number
vcserver va ramane rezident pana cand utilizatorul in inchide. Ce-a mai curata metoda de a face asta este
 
 
        kill -15 pid
unde pid este id-ul procesului vcserver.

Lista sursei serverului - vcserver.c

/*  vcserver.c -- server TCP de retea (circuit virtual)  */

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>    /* structura sockaddr_in */

/* Aceasta intrare permite programului sa caute numele
   host-ului si orice nume alias asociat cu el. */

#include <netdb.h>          /* intrarile din tabel /etc/hosts  */

main (int argc, char *argv[])

{
   int rc,            /* codul returnat de apelul sistem */
       new_sd, sock,  /* descriptorii socket-ului server/listen */
       adrlen,        /* lungimea lui sockaddr */
       cnt;           /* numarul de octeti de intrare/iesire */

   struct sockaddr_in myname;  /* numele socket-ului Internet */
   struct sockaddr_in *nptr;   /* pointer pentru a citi numarul portului */
   struct sockaddr    addr;    /* numele generic al socket-ului */

   char buf[80];   /* buffer-ul de intrare/iesire, cam mic  */

   /* Pentru cautarea in fisierul /etc/hosts .   */

   struct hostent *hp, *gethostbyaddr();

   /* Identificarea procesului server. */

   printf("\nThis is the network server with pid %d\n",
        getpid() );

   /* Se creaza un socket de "ascultare", ca in cazul socket-urilor de domenii din UNIX */

   if (( sock = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
      printf("network server socket failure %d\n", errno);
      perror("network server");
      exit(1);
   /* Initializeaza campurile din structura numelui socket-ului Internet.  */

   myname.sin_family = AF_INET;  /* Adresa Internet */
   myname.sin_port = 0;  /* Sistemul va asigna portul #  */
   myname.sin_addr.s_addr = INADDR_ANY;  /* "Wildcard" */

   /* Leaga adresa Internet la socket-ul Internet */

   if (bind(sock, &myname, sizeof(myname) ) < 0 ) {
      close(sock);  /* programare defensiva  */
      printf("network server bind failure %d\n", errno);
      perror("network server");
      exit(2);
   }

   /*   Citeste numarul portului asignat socket-ului Internet.
                        getsockname() obtine numarul portului asociat
                        cu socketul legat si il returneaza  ca o parte a
                        informatiei din structura sockaddr addr.  A se observa
                        ca, din moment ce numarul portului nu este trecut direct
                        de catre acest program oricarui client, singura cale de a-i
                        face "reclama" este de a-l afisa, aceasta insemnand, trimiterea lui
                        la iesirea stdout a utilizatorului.  Diferit de aceasta afisare, acest
                        cod nu este intrinsec procesului care se conecteaza
       */

   adrlen = sizeof(addr); /* este necesar tipul int pentru valoarea returnata */
   if ( ( rc = getsockname( sock, &addr, &adrlen ) ) < 0 )
        {
      printf("setwork server getsockname failure %d\n",
                                errno);
      perror("network server");
      close (sock);
      exit(3);
   }

   /*   CODUL DE VERIFICARE: adresa general "addr" este folosita pentru
                        a returna valoarea socket-ului obtinuta de la apelul sistem
                        getsockname().  Afiseaza aceste informatii.  In
                        definitia generala a structurii, tot in afara de familia
                        de adrese este definit ca sir de caractere.  Dupa acest apel,
                        structura generala de adrese addr este folosita pentru a
                        pastra informatii despre procesul client. */

   printf("\nAfter getsockname():");
   printf(" server listen socket data\n");
   printf("\taddr.sa_family field value is: %d\n",
        addr.sa_family);
   printf("\taddr.sa_data string is %d bytes long;\n",
        sizeof ( addr.sa_data ) );
   printf("\taddr.sa_data string is:");
   for ( cnt = 0: cnt < sizeof (addr.sa_data); cnt++)
        printf(" %x", addr.sa_data[cnt]);
   printf("\n");

   /*   Acum "fa reclama" la numarul portului asignat
                        socket-ului.  In acest exemplu, acest numar al portului trebuie sa fie
                        folosit ca al doilea parametru din linia de comanda
                        care porneste procesul client.  */

   /*   A se observa folosirea pointerului nptr, cu o mapare
                        diferita a memoriei alocate, pentru a pointa la
                        strctura general de adrese. */

   nptr = (struct sockaddr_in *) &addr;  /* port # */
   printf("\n\tnetwork server: server has port number: %d\n",
      ntohs ( nptr -> sin_port ) );

   /*  Marcheaza socket-ul ca "citire-numai" sau socket pasiv */

   if ( listen ( sock, 5 ) < 0 ) {
      printf("network server bind failure %d\n", errno);
      perror("network server");
      close (sock);
      exit(4);
   }

   /* Iesirea de verificare: informatia continuta in structura myname
      (socket-ul Internet).                                  */

   printf("Server has set up client socket with values:\n");
   printf("\tInternet address is %lx\n", myname.sin_addr.s_addr);
   printf("\tPort number used is %d\n", myname.sin_port);
   printf("\tInternet family ID is %d\n", myname.sin_family);
   printf("\tValues are filled in after connection request ");
   printf("is accepted.");

   /* Seteaza "bucla infinita" pentru a asculta clientii.  Din moment ce
      structura "myname" este legata de socket-ul de ascultare, 
      valorile numelui structurii socket-ului si parametrului lungimii 
      socket-ului sunt omise de la apelul sistem acceptat.  Valorile de granita
      sunt folosite.                                               */

   while (1) {
      if ( ( new_sd = accept ( sock, 0, 0 ) ) < 0 ) {
         printf("network server accept failure %d\n", errno);
         perror("network server");
         close (sock);
         exit(5);
      }

      /* Procesul fiu rezultat din apelul fork() care se ocupa de cererea de serviciu a clientului */

      if ( ( fork() ) == 0 ) {    /* Procesul fiu */

         int pid;

         pid = getpid();   /* PID-ul procesului fiu */
         close (sock); /* Nu este necesara ascultarea socket-ului in procesul fiu. */

         /* Afla cine este clientul.  Observati folosirea
            structurii generale de adrese addr pentru pastrarea informatiilor
            despre clientul (conectat).                    */

         if ((rc = getpeername( new_sd, &addr, &adrlen )) < 0) {
            printf("network server %d getpeername failure %d\n",
                 pid, errno);
            perror("network server");
            close(new_sd);
            exit(6);
         }
         /* Doar pentru zambete, "anunta" clientul.  Observati ca,
            din moment ce pointerul nptr este de tip struct sockaddr_in,
            numele campurilor asa cum au fost definite in modelul
            sockaddr_in pot fi folosite pentru accesarea valorilor din 
            structura generica addr.                               */

         printf("\n\tnetwork server %d:", pid);
         printf(" client socket from host %s\n",
              inet_ntoa ( nptr -> sin_addr ) );
         printf("\t     has port number %d\n",nptr -> sin_port);

         /* Acum gaseste toate numele asociate cu clientul; acesta
            este motivul pentru cautarea in declaratiile din fisierul 
            /etc/hosts.   */

         if (( hp = gethostbyaddr (&nptr -> sin_addr,4,AF_INET))
                  != NULL ) {
            printf ("\tfrom hostname: %s\n\twith aliases: ",
                   hp -> h_name );
            while ( *hp -> h_aliases )
               printf ("\n\t\t\t%s", *hp -> h_aliases++ );
            printf("\n\n");
         }
         else {
            printf("network server %d ", pid);
            printf("gethostbyaddr failure %d\n", h_errno);
            perror("network server");
         }

         /*  Schimba date cu clientul.  Goleste buffer-ul intai. */

         do {
      
            /* Fa alegerea, in functie de dotarile sistemului.
               Functiile lui System V nu au fost testate
               in aceasta editie.                               */

            bzero( buf, sizeof(buf)); /* zero buf, BSD call. */
            /* memset (buf,0,sizeof(buf)); /* zero buf, S5. */

            /* Citeste mesajul de la clientul de la distanta; daca lungimea mesajului
               = 0, iesi. */

            if (( cnt = read (new_sd, buf, sizeof(buf))) < 0 ) {
               printf("network server %d ", pid);
               printf("socket read failure &d\n", errno);
               perror("network server");
               close(new_sd);
               exit(7);
            }
            else
            if (cnt == 0) {
               printf("network server received message");
               printf(" of length %d\n", cnt);
               printf("network server closing");
               printf(" client connection...\n");
               close (new_sd);
               continue; /* iesi din bucla */
            }
            else {

               /*  Afiseaza mesajul primit de la client.  Trimite
                   un mesaj inapoi.        */

               printf("network server %d received message",pid);
               printf(" of length %d\n", cnt);
               printf("network server %d  received", pid));
               printf(" the message %s\n", buf);
               bzero (buf, sizeof(buf)); /* zero buf, BSD. */
               /* memset(buf,0,sizeof(buf)); /* zero buf, S5. */
               strcpy(buf, "Message from server to client");
               write (new_sd, buf, sizeof(buf));
            }  /* sfarsitul ramurei else de la afisarea mesajului */

         }  /* sfarsitul buclei do */

         while (cnt != 0);  /* conditia buclei do */
         exit(0);  /* Iesirea procesului fiu */

      } /* Terminarea conditiei adevarate a procesului if-child */

      else      /* Nu e procesul fiu; trebuie sa fie procesul parinte */

         close (new_sd); /* Parintele nu are nevoie de socket-ul de lucru. */

   }  /* sfarsitul lui while (1) */

}  /* sfarsitul procedurii principale */

Listarea sursei clientului - vcclient.c

/*  vcclient.c -- Clientul retelei TCP ( circuit virtual)   */

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>    /* structura sockaddr_in */

/* Aceasta intrare permite programului sa caute numele
   host-ului si a oricarui alias asociat cu el.    */

#include <netdb.h>          /* intrarile din tabelul /etc/hosts */

main (argc, argv)
int argc;
char *argv[];

/* Parametrii din linia de comanda asteptati:

   argv[0] -- numele executabilului
   argv[1] -- numele host-ului la care se doreste conexiunea
   argv[2] -- numarul portului care va fi folosit de client: 
              valoarea este numarul portului asignat serverului
              de catre sistemul host-ului server.

   Nu este elegant, dar este foarte folositor pentru corectarea
   codului de conectare.  De exemplu, daca numele host-ului ( argv[1] )
   ar fi fost specificat ca "localhost" si clientul si serverul
   ar putea sa ruleze pe acelashi sistem dar codul conectivitatii retelei
   ar fi fost exersat in intregime.         */

{
   int sock,          /* descriptorul socket-ului */
       val,           /* variabila scratch */
       cnt;           /* numarul de octeti de intrare/iesire */

   struct sockaddr_in myname;  /* numele socket-ului Internet (addr) */
   struct sockaddr_in *nptr;   /* pointer pentru a obtine numarul portului */

   char buf[80];   /* bufferul de intrare/iesire, cam mic  */

   /* Pentru cautarea in fisierul /etc/hosts .   */

   struct hostent *hp, *gethostbyaddr();

   /* Verifica daca utilizatorul a dat toti parametrii
      la linia de comanda.  Daca da, converteste argv[2] la intreg; copie-l
      in campul sin_port al structurii myname.  Foloseste
      functia htons pentru a se asigura ca valoarea este memorata
      in ordinea de octeti a retelei.                           */

   if ( argc < 3 ) {
      printf("network client failure: required parameters");
      printf(" missing from the command line\n");
      printf("network client: usage");
      printf("[executable-name] [host name] [port number]\n");
      exit(1);
   }

   /* Ca si la domeniul UNIX, creaza un socket client care sa ceara
      servicii          */

   if (( sock = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
      printf("network client socket failure %d\n", errno);
      perror("network client");
      exit(2);
   }

   /* Converteste numarul portului dat de utilizator in intreg.  
      Nu exista nici un test al consistentei la aceasta setare.        */

   myname.sin_port = htons( atoi(argv[2]) ); /* Portul serverului # */

   myname.sin_family = AF_INET;    /* Domeniul Internet */

   /* Numai in scopuri de afisare, afiseaza numele host-ului
      si numarul convertit al portului.                      */

   printf("network client %s will try to connect to host %s\n",
          argv[0], argv[1]);
   printf("network client %s will use port number %d\n",
          argv[0], ntohs ( myname.sin_port ) );

   /* Obtine informatii despre host-ul serverului.  */

   hp = gethostbyname ( argv[1] );

   /* Acesta este codul principal de corectare; daca devine necesar
      sa fie inserat (sau daca e mai comod sa fie la locul lui!!!
      Nu-l scoate-ti cand totul merge bine.
      Mai degraba, lasati-l la loc ca un comentariu.      */

   if ( hp == NULL ) {
      printf("network client gethostbyname failure %d\n",
           errno);
      perror("network client");
      close ( sock );
      exit(3);
   }
   else {
      printf("\nServer information obtained via");
      printf(" gethostbyname:\n");
      printf("\tThe official host name is %s\n", hp -> h_name);
      printf("\tThe address type is %d\n", hp -> h_addrtype);
      printf("\tThe length of the address is %d bytes\n",
            hp -> h_length);
      printf("\tThe first host address is %lx\n",
             ntohl ( * (int * ) hp -> h_addr_list[0] ) );
      printf("\tAlias names for the host are:\n");
      while ( *hp -> h_aliases )
         printf( "\t\t%s\n", *hp -> h_aliases++ );
   }

   /* Foloseste fie memcpy sau bcopy, in functie de nevoie
      (System V vs BSD). */

   bcopy ( hp -> h_addr_list[0], &myname.sin_addr.s_addr,
           hp -> h_length );
   /* memcpy ( &myname.sin_addr.s_addr, hp -> h_addr_list[0],
           hp -> h_length ); */
      
   /* Mai mult cod de corectat: Verifica continutul structurii myname
      inainte de a incerca conectarea la serverul (de la distanta).    */

   printf("\nInformation provided in client's");
   printf(" connect request\n");
   printf("\tRemote host address is %lx\n",
        ntohl ( myname.sin_addr.s_addr ) );
   printf("\tPort number supplied is %d\n",
        ntohs ( myname.sin_port ) );
   printf("\tInternet family ID is %d\n", myname.sin_family);
   printf("\tsin_zero character string is: %s\n",
        myname.sin_zero);

   /* Realizeaza conexiunea socket-ului cu serverul (de la distanta).  */

   if ( ( connect ( sock, &myname, sizeof(myname) ) ) < 0 ) {
         printf("network client %s connect failure %d\n",
              argv[0], errno);
         perror("network client");
         close (sock);
         exit(4);
      }

   /*  Schimba date cu clientul.  Goleste octetii din buffer intai. */

   /* Alege, in functie de dotarile sistemului. Functiile
      System V nu au fost inca testate. */

   bzero  ( buf,    sizeof( buf) );  /* zero buffer, BSD. */
   /* memset ( buf, 0, sizeof( buf) ); /* zero buffer S5. */

   strcpy ( buf, "Message from client to server" );
   write ( sock, buf, sizeof(buf) );

   /* Acum citeste mesajul trimis inapoi de server.    */

   if ( ( cnt = read (sock, buf, sizeof(buf) ) ) < 0 ) {
      printf("network client socket read failure &d\n", errno);
      perror("network client");
      close(sock);
      exit(5);
   }
   else
   printf("network client received the message %s\n", buf);

   /* Acum trimite un mesaj cu 0 octeti. */

   bzero  ( buf,    sizeof( buf) );  /* zero buffer, BSD. */
   /* memset ( buf, 0, sizeof( buf) ); /* zero buffer S5. */
   write ( sock, buf, 0 );
   close (sock);
   exit(0);

}  /* sfarsitul procedurii principale */