1. 8 Modelul client/server - TCP
In cadrul aplicatiilor in retea modelul cel mai frecvent intalnit este client/server in
care un server ofera anumite servicii anumitor clienti ruland pe aceeasi masina sau la distanta
(pe alte calculatoare conectate la retea).
Serverul poate satisface cererile provenite de la clienti in mod iterativ (la un moment
dat serverul va deservi cereri provenite de la un singur client, secvential pentru toti clientii lui)
sau concurent (mai multe cereri provenite de la clienti multipli vor fi procesate simultan).
8.1 Server TCP iterativ
Modelul general al unui server iterativ respecta ordinea urmatoarelor apeluri de sistem:
• socket() creaza un socket care va trata conexiunile cu clientii;
• pregatirea structurilor de date (continute in sockaddr_in) pentru a atasa socket-ul
la portul folosit de aplicatie;
• bind() ataseaza socket-ul la port;
• listen() pregateste socket-ul pentru ascultarea in vederea stabilirii conexiunii cu
clientii;
• accept() accepta realizarea unei conexiuni cu un anumit client (acest apel blocheaza
programul pana la aparitia unei cereri de conectare din partea unui client);
• procesarea cererilor clientului - schimb de mesaje intre server si client folosindu-se
descriptorul de socket returnat de accept(), prin apelul primitivelor read() si
write();
• close() inchiderea conexiunii cu clientul la terminarea dialogului cu acesta;
• shutdown() inchiderea directionala a conexiunii cu clientul.
12
2. Conexiunea client-server, odata stabilita, ramane valida pana cand, fie clientul, fie
serverul o intrerup in mod explicit sau nu. Socket-ul utilizat pentru aceasta conexiune se
numeste socket orientat conexiune. Un socket poate fi la un moment dat pentru mai multe
conexiuni.
8.1.1 Primitiva bind()
Pentru a putea fi folosit efectiv, un socket va trebui atasat la portul masinii la care
serverul va astepta cereri de conexiune din partea posibililor clienti. Acest lucru se realizeaza
prin intermediul primitivei bind().
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockd, struct sockaddr *addr, socklen_t addrlen);
13
3. Structura sockaddr este o structura folosita pentru stocarea informatiilor de adresa
pentru orice tip de socket-uri.
struct sockaddr{
unsigned short sa_family; /* familia de adrese
(AF_UNIX, AF_INET, ...) */
char sa_data[14]; /* 14 bytes - adresa folosita */
}
Pentru internet se va folosi o structura particulara (derivata din sockaddr) numita
sockaddr_in:
struct sockaddr_in{
short int sin_family; /* familia de adrese (AF_INET) */
unsigned short int sin_port; /* portul (0-65355) */
struct in_addr sin_addr; /* adresa Internet */
unsigned char sin_zero[8]; /* bytes neutilizati (zero) */
}
Adresa Internet este stocata in structura in_addr care are definitia:
struct in_addr{
unsigned long int s_addr /* 4 bytes ai adresei IP */
}
Serverul va putea folosi constanta INADDR_ANY pentru a se utiliza adresa IP a masinii
pe care ruleaza procesul (aceasta asigura independenta codului de adresa IP a calculatorului
pe care se va executa serverul).
La apelul primitivei bind() se poate folosi 0 in loc de un numar de port valid. In
acest caz se va alege in mod automat un port gasit liber si va fi asociat socket-ului in cauza.
Pentru oferirea de servicii nestandard se recomanda folosirea porturilor > 1024. Valorile
de la 1 la 1023 sunt rezervate serviciilor sistem standard:
• 80 - HTTP (HyperText Transfer Protocol - WWW );
• 23 - telnet;
• 21 - FTP;
• 22 - SFTP (Secret File Transfer Protocol);
• 25 - SMTP (Simple Mail Transfer Protocol);
Pentru vizualizarea unei liste a perechilor port-serviciu standard furnizate de catre
sistem se poate accesa fisierul /etc/services.
14
4. 8.2 Primitiva listen()
Dupa atasarea portului, serverul va trebui sa astepte viitoare conexiuni de la diversi
clienti si sa le rezolve cererile. Pentru aceasta se utilizeaza apelul listen() urmat de
accept().
#include <sys/socket.h>
int listen(int sockd, int backlog);
Al doilea parametru va stabili numarul de conexiuni permise in coada de asteptare a
conexiunii cu clientii. Uzual, valoarea sa este 5.
8.3 Primitiva accept()
Acceptarea propriu-zisa a conexiunilor se va realiza cu accept():
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockd, struct sockaddr *addr, socklen_t *addrlen);
Acest apel va returna un descriptor de socket corespunzator clientului a carui conexiune
a fost acceptata. Noul descriptor poate fi folosit pentru a trimite si receptiona date prin
send(), write(), recv() sau read().
8.4 Primitivele send() si recv()
#include <sys/types.h>
#include <sys/socket.h>
int send(int sockd, char *buf, int len, int flags);
int recv(int sockd, char *buf, int len, int flags);
8.5 Primitivele close() si shutdown()
Inchiderea conexiunii se poate face prim apelul primitivelor close() sau shutdown().
In cazul primitivei close() orice incercare de a citi sau scrie folosind un socket inchis va
genera eroare. Pentru a controla modul de inchidere al socket-ului se va folosi primitiva
shutdown().
#include <sys/socket.h>
int shutdown(int sockd, int how);
15
5. 8.6 Client TCP
In cazul unui client TCP, apelul primitivei accept() va fi inlocuit cu primitiva
connect().
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockd, struct sockaddr *addr, socklen_t addrlen);
Structura addr va contine adresa IP si portul serverului la care se va conecta clientul.
8.7 Conversia datelor
Comunicarea dintre server si client se realizeaza in cadrul unei retele eterogene cu
configuratii hardware pe care programatorul nu le poate prevedea. Pentru a se asigura
independenta transferului de date, s-a convenit ca octetii pe retea sa respecte o anumita
ordine de codificare. Ordinea octetilor dintr-un cuvant (word - 2 octeti) poate fi rumatoarea:
• big endian cel mai semnificativ octet este primul (codificare folosita de procesoarele
Motorola);
• little endian cel mai semnificativ octet este al doilea (codificare folosita de procesoarele
Intel).
Pentru ca programele sa fie independente de codificare se folosesc o serie de functii de
conversie de la codificarea de retea la cea a calculatorului gazda (pentru folosirea lor trebuie
inclus fisierul netinet/in.h).
• htons() conversia unui intreg scurt (2 octeti) de la gazda la retea (host to network
short);
• htonl() conversia unui intreg lung (4 octeti) de la gazda la retea (host to network
long);
• ntohs() conversia unui intreg scurt (2 octeti) de la retea la gazda (network to host
short);
• ntohl() conversia unui intreg lung (4 octeti) de la retea la gazda (network to host
long);
Membrii sin_port si sin_addr ai structurii sockaddr_in trebuie sa fie stocati
in codificarea de retea. Pentru a asigura conversia adreselor IP de la formatul intern (patru
bytes) la cel uzual (patru octeti despartiti de punct) sau invers se vor folosi functiile:
16
6. • inet_addr converteste un sir de caractere continand o adresa IP la intreg. In caz de
eroare returneaza INADDR_NONE;
• inte_aton converteste un sir de caractere continand o adresa IP la intreg. In caz de
eroare returneaza 0;
• inte_ntoa converteste o adresa IP de la intreg la sir de caractere.
Bibliografie
[1] Sabin Buraga, Gabriel Ciobanu, Atelier de programare in retele de calculatoare, Polirom,
Iasi, 2001
17