Sunteți pe pagina 1din 11

Programarea retelelor de calculatoare

Am ales pentru partea de programare a retelelor de calculatoare limbajul C, n mediul Unix, ntruct permite lucru la un nivel ct mai scazut, ceea ce ne ofera o privire mai detaliata asupra etapelor care sunt parcurse la comunicarea ntre aplicatiile de retea. Limbajele de nivel nalt ascund programatorului aceste detalii, acesta urmnd a utiliza o clasa predefinita pentru care detaliile de implementare nu i sunt accesibile n mod direct. Pentru a putea realiza comunicarea ntre doua aplicatii de pe masini diferite mai nti trebuie sa cream doua capete de comunicare, cte unul pentru fiecare aplicatie. Aplicatiile pot rula si pe aceeasi masina de calcul, nsa acest caz nu prezinta interes, eventual doar pentru testare. Capetele de comunicare se realizeaza prin intermediul primitive socket(). Aceasta permite obtinerea unui descriptor, similar cu cel utilizat pentru fisiere si pipe-uri. Prin intermediul acestui descriptor vom putea realize diverse operatiuni, pe care le vom prezenta n acest capitol. Prototipul functiei socket() este urmatorul: int socket(int domain, int type, int protocol); Valoarea returnata reprezinta descriptorul asociat resursei (capatul de comunicare) n caz de succes, iar n caz de eroare se va obtine -1, iar variabila errno va fi setata corespunzator erorii survenite. Parametrul domain stabileste un domeniul de comunicare, adica familia de protocoale care va fi utilizata. Aceste domenii sunt definite n fisierul header sys/socket.h si se pot utiliza constantele: PF_UNIX, PF_LOCAL pentru comunicatii locale PF_INET pentru protocoalele utilizate n Internet care folosesc protocolul IPv4. PF_INET6 pentru protocoalele utilizate n Internet care folosesc protocolul IPv6. PF_IPX pentru protocoalele Novell IPX.

PF_NETLINK pentru protocoale de comunicare ntre kernel si spatial proceselor utilizator. PF_X25 pentru protocoale ITU-T X.25 / ISO-8208 PF_AX25 pentru protocoale radio AX.25 PF_ATMPVC pentru protocoale ATM PVC PF_APPLETALK pentru Appletalk. PF_PACKET pentru protocoale de nivel jos, bazate pe pachete.

n programele nostre vom utiliza frecvent domeniu pentru protocoale Internet IPv4, adica PF_INET. Parametru type indica modul de comunicare: flux de octeti, datagrame etc. Si pentru acest parametru avem o serie de constante pe care le putem utiliza.Acestea sunt: SOCK_STREAM pentru comunicare bazata pe flux de octeti (orientata conexiune). SOCK_DGRAM pentru comunicare prin datagrame, cu posibile pierderi de pachete. SOCK_SEQPACKET pentru comunicare bazata pe flux de pachete (orientata conexiune); atunci cnd se va citi va trebui sa se citeasca cate un pachet ntreg. SOCK_RAW pentru comunicare prin protocoale de nivel jos. SOCK_RDM pentru comunicare prin datagrame, fara pierdere de pachete. Cele mai utilizarte n practica sunt comunicarea orientata conexiune prin flux de octeti (SOCK_STREAM) si cea neorientata conexiune prin datagrame (SOCK_DGRAM). Ultimul parametru stabileste protocolul de comunicatie utilizat. Prezentam mai jos cteva constante care pot fi utilizate pentru parametrul protocol:IPPROTO_TCP IPPROTO_UDP IPPROTO_IP IPPROTO_IPV6 IPPROTO_ICMP IPPROTO_ICMPV6 IPPROTO_IGMP

Daca utilizam valoarea zero pentru parametrul protocol, atunci se va alege protocolul care se potriveste cu domeniul si tipul de comunicare. De exemplu, pentru PF_INET si
2

SOCK_STREAM se va alege IPPROTO_TCP, iar pentru PF_INET si SOCK_DGRAM se va alege IPPROTO_UDP. Pentru o mai buna claritate se poate utiliza constantele. Dupa ce am creat capatul de comunicare va trebui sa l atasam unei adrese si unui port. Acest lucru se realizeaza prin intermediul primitivei bind().

Aceasta are urmatoarea signatura: int bind(int sockfd, const struct sockaddr * my_addr, socklen_t addrlen); Parametrul sockfd reprezinta descriptorul de socket care vrem sa-l atasam, my_addr este o structura care contin datele despre adresa si portul la care se leaga socketul, iar addrlen reprezinta dimensiunea structurii utilizata de parametrul al doilea. Structura sockaddr este generica si este definita astfel: Struct sockaddr { /* familia de adrese */ unsigned short sa_family; /* adresa utilizata */ char sa_data[14]; } nsa pentru Internet se utilizeaza o alta structura, sockaddr_in care este definita n acest fel: struct sockaddr_in { /* familia de adrese */ short int sin_family; /* portul */ unsigned short int sin_port; /* adresa */ struct in_addr sin_addr; /* 8 octeti neutilizati, au valoarea zero */ unsigned char sin_zero[8]; } n definitia structurii sockaddr_in mai apre structura in_addr care are urmatoarea structura: struct in_addr {
3

unsigned long int s_addr; } Pentru portabilitate se foloseste constanta INADDR_ANY pentru adresa masinii locale. Daca pentru port se utilizeaza valoarea zero, atunci sistemul va alege un port liber. Daca portul este deja ocupat se va nregistra o eroare (Address already in use), iar errno va avea valoarea EADDRINUSE. Atasarea socketului la o adresa si un port este obligatorie pentru programele server, pentur ca aplicatiile client sa stie unde sa se conecteze. Pentru programele client, acest pas este facultativ, caz n care sistemul de operare va aloca un port liber pentru adresa locala. Descriptorul de socket poate fi nchis cu primitiva close(), la fel ca n cazul descriptorilor de fisier sau de pipe. Aceasta are ca efect nchiderea conexiunii. Primitivele socket() si bind() sunt utilizate att pentru servere (clienti) TCP, ct si pentru UDP.

Servere si clienti TCP


Dupa ce am creat socketul si l-am atasat la un port, va trebui sa pornim activitatea de primire a clientilor. Mai nti se apeleaza primitiva listen() care va avea ca efect stabilirea lungimii cozii de asteptare si asteptarea clientilor. Prototipul functiei listen() este: int listen(int sockfd, int backlog); Parametrul sockfd este descriptorul de socket pentru care se ncepe asteptarea clientilor si pentru care se stabileste lungimea cozii de asteptare. Atunci cnd vine un client acesta este pus n coada de asteptare pna cnd i se accepta conexiunea. De regula, este utilizata valoarea 5. n caz de succes, listen() va returna valoarea zero, iar n caz de eroare va returna valoarea 1. Pentru acceptarea unei conexiuni cu un client se va apela primitive accept(). Aceasta are urmatoarea forma:

int accept(int sockfd, struct sockaddr * addr, socklen_t *addrlen); Parametrul sockfd reprezinta descriptorul de socket al serverului pentru care se accepta o noua conexiune, structura sockaddr este completata cu datele referitoare la client (familia de adrese, adresa clientului si portul utilizat de acesta), iar addrlen este completat cu lungimea structurii sockaddr. Primitiva accept() este blocanta, n sensul ca procesul curent se blocheaza pna cnd apare un client. n caz de eroare se va returna valoarea -1, iar n caz de succes se va obtine un nou descriptor (numit si descriptor de serviciu), prin intermediul caruia se vor trimite si receptiona date de la clientul acceptat. ntruct modul de reprezentare a datelor de pe calculatorul gazda poate diferi de modul de reprezentare utilizat n retea, va trebui sa realizam conversii. n biblioteca netinet/in.h se gasesc cteva functii utile pentru realizarea de conversii: uint32_t htonl(uint32_t hostlong); converteste un ntreg fara semn reprezentat pe 4 octeti din formatul pentru gazda n cel pentru retea (eng., host to network long). uint16_t htons(uint16_t hostshort); converteste un ntreg fara semn reprezentat pe 2 octeti din formatul pentru gazda n cel pentru retea (eng., host to network short). uint32_t ntohl(uint32_t netlong); converteste un ntreg fara semn reprezentat pe 4 octeti din formatul pentru retea n cel pentru gazda (eng., network to host long). uint16_t ntohs(uint16_t netshort); converteste un ntreg fara semn reprezentat pe 2 octeti din formatul pentru retea n cel pentru gazda (eng., network to host short). Prezentam n continuare continutul fisierului server-tcp.c care creeaza un server TCP iterativ. Acesta va primi un mesaj de la client si l va trimite napoi.
5

/* server-tcp.c */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <errno.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> #define PORT 8001 /* descriptorul pentru socketul serverului */ int sd = -1; /* descriptorul pentru client */ int client = -1; /* functia de tratare a semnalelor pentru inchiderea serverului */ void terminare(int semnal) { /* afisam un mesaj */ printf("\nInchidem serverul.\n"); /* inchidem descriptorii daca acestia sunt utilizati */ if (client > 0) close(client); if (sd > 0) close(sd); /* terminam programul */ exit(0); } int main() { /* declaram variabilele utilizate */ struct sockaddr_in server; struct sockaddr_in from; char mesaj[128]; int lungime, citit; /* tratam semnale */ if (signal(SIGINT, terminare) == SIG_ERR) { perror("Eroarea la tratarea semnalului SIGINT"); return errno; } /* crearea socketului pentru server */ sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sd == -1) { perror("Eroare la crearea socketului");
6

return errno; } /* initializam structurile de date */ bzero(&server, sizeof(server)); server.sin_family = AF_INET; server.sin_addr.s_addr = htonl(INADDR_ANY); server.sin_port = htons(PORT); /* atasam socketul */ if (bind(sd, (struct sockaddr *) &server, sizeof(struct sockaddr)) == -1) { perror("Eroare la atasarea socketului"); close(sd); return errno; } /* serverul va incepe ascultarea la portul stabilit */ if (listen(sd, 5) == -1) { perror("Eroare la inceperea ascultarii"); close(sd); return errno; } printf("Asteptam clienti la portul %d\n", PORT); while(1) { bzero(&from, sizeof(from)); lungime = sizeof(from); printf("Asteptam sa vina un client..."); fflush(stdout); /* acceptam un client */ client = accept(sd, (struct sockaddr *) &from, &lungime); 34 printf("\n"); if (client < 0) { perror("Eroare la acceptarea unei noi conexiuni"); continue ; } bzero(mesaj, sizeof(mesaj)); citit = read(client, mesaj, sizeof(mesaj)); if (citit < 0) { perror("Eroare la receptionarea mesajului"); close(client); client = -1;
7

continue ; } if (citit == 0) { printf("Clientul a inchis conexiunea"); close(client); client = -1; continue ; } printf("Am receptionat mesajul '%s'\n", mesaj); /* trimitem mesajul inapoi */ if (write(client, mesaj, citit) <= 0) { perror("Eroare la expedierea mesajului"); close(client); client = -1; continue ; } printf("Am trimis mesajul inapoi\n"); /* inchidem conexiunea cu clientul */ close(client); client = -1; } return 0; }

Compilarea programului se realizeaza cu comanda gcc, astfel: gcc server-tcp.c -o server-tcp Pornirea serverului se face lansnd programul: ./server-tcp ntr-o alta sesiune de lucru vom porni un client generic, cu care putem realiza testarea serverului: telnet 127.0.0.1 8001 O posibila testare poate fi: Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. Salut! Salut!
8

Connection closed by foreign host. Primul mesaj Salut! este scris de utilizator, iar al doilea este cel receptionat de la server. Daca ne uitam la consola serverului vom vedea urmatoarele: Asteptam clienti la portul 8001 Asteptam sa vina un client... Am receptionat mesajul 'Salut!' Am trimis mesajul inapoi Asteptam sa vina un client... Observam ca serverul a receptionat inclusiv caracterul pentru trecerea la rnd nou si ca serverul continua sa astepte clienti. Oprirea acestuia se realizeaza prin apasarea combinatiei de taste CTRL+C. De regula nti se realizeaza aplicatia server care poate fi testata cu acest client generic, dupa care se dezvolta aplicatia client. Pentru conectarea unui client la server se va utiliza primitiva connect() care are urmatoarea forma: int connect(int sockfd, const struct sockaddr * serv_addr, socklen_t addrlen); Primitiva connect() are rolul de initiere a conexiunii. Observam ca parametrii sunt similari cu cei utilizati de bind(), numai ca se realizeaza o conexiune la serverul a carui date sunt specificate n structura sockaddr. Implicit functia connect() este blocanta. Aceasta se blocheaza pna cnd serverul va accepta conexiunea sau pna cnd timpul de asteptare s-a terminat, caz n care variabila errno va lua valoarea ETIMEDOUT. n caz de succes se va returna valoarea zero, iar n caz de eroarea se va obtine valoarea -1.

O aplcatie client pentru serverul de mai sus este:


#include #include #include #include #include #include <sys/types.h> <sys/socket.h> <netinet/in.h> <errno.h> <unistd.h> <stdio.h>
9

#include <stdlib.h> #define SERVER "0" #define PORT 8001 int main() { int sd; struct sockaddr_in server; char mesaj[128]; /* cream socketul pentru comunicare */ sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sd == -1) { perror("Eroare la crearea socketului"); return errno; } /* completam structura cu datele serverului */ bzero(&server, sizeof(server)); server.sin_family = AF_INET; server.sin_addr.s_addr = htonl(inet_addr(SERVER)); server.sin_port = htons(PORT); /* stabilim conexiunea cu serverul */ printf("Ne conetam la server..."); fflush(stdout); if (connect(sd, (struct sockaddr *) &server, sizeof(struct sockaddr)) == -1) { printf("\n"); perror("Eroare la stabilirea conexiunii"); close(sd); return errno; } printf("\n"); /* citim un mesaj de la tastatura */ printf("Introduceti mesajul: "); fflush(stdout); bzero(mesaj, sizeof(mesaj)); fgets(mesaj, sizeof(mesaj), stdin); if (write(sd, mesaj, strlen(mesaj))<=0) { perror("Eroare la expedierea mesajului"); close(sd); return errno; } /* citim raspuinsul de la server */ bzero(mesaj, sizeof(mesaj)); if (read(sd, mesaj, sizeof(mesaj)) < 0) { perror("Eroare la receptionarea mesajului");
10

close(sd); return errno; } printf("Mesajul receptionat este '%s'\n", mesaj); /* inchidem conexiunea cu serverul */ close(sd); return 0; } O executie posibila a aplicatiei client poate fi: Ne conetam la server... Introduceti mesajul: Salutare! Mesajul receptionat este 'Salutare!'

Observam ca si n acest caz este trimis si caracterul pentru trecerea la rnd nou.

11

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