Sunteți pe pagina 1din 11

Programare client-server

1. Generalitati

Modelul standard pentru aplicatii in retea este client-server. Serverul este un


proces care asteapta ca clientii sa il contacteze. In principiu, modul de
functionare pentru un astfel de sistem, este: procesul server este pornit iar
acesta intra intr-o stare de asteptare pana la conectarea unui posibil client.
Procesul client este pornit se va conecta la server si va face cereri pentru
anumite servicii (servicii pe care le ofera serverul). Clientul poate fi pornit pe
acelasi sistem ca si serverul sau pe un altul. Urmeaza o conversatie intre
client si server care se termina in momentul in care clientul a obtinut rezultatul
dorit de la server. In acel moment serverul revine la starea de asteptare dupa
potentiali clienti.

Serverul poate fi de doua tipuri:


 iterativ – acel server care poate servii clientii intr-un timp dat (de obicei
foarte scurt), clientul fiind servit chiar de procesul server.
 concurent – tipul pentru a procesa o cerere de la client este
necunoscut, in consecinta, clientii vor trebui sa fie serviti intr-o
maniera concurenta. Aceasta inseamna ca respectivul proces
server va clona un nou proces, identic cu procesul original. Noul
proces va prelua respectivul client, procesul server original revenind
in starea de asteptare dupa alti clienti.

2. Socket-uri

Cele mai folosite metode de comunicare client-server, din punct de vedere al


programatorilor (API – Aplication Program Interface) sunt: socket-uri Berkeley
si TLI (System V Transport Layer Protocol). In continuare se va discuta
exclusiv despre socket-uri.

Socket-urile au aparut pentru prima data in jurua anului 1982 in BSD 4.1.
Socket-urile sunt create explicit, utilizate si puse in functiune de catre aplicatii.

Exista doua tipuri de servicii de transport pentru socket:

 legaturi orientate pe conexiune


Se mai numesc si sigure, in cazul lor se garanteaza livrarea datelor. In
figura 1. este prezentata schema logica a unei astfel de conexiuni.
Clientul trebuie sa se conecteze in mod explicit la server înainte de a
transmite sau primi date. Clientul nu va accepta connect() pana cind
serverul nu accepta clientul. Serverul trebuie sa astepte in mod explicit
clientul inainte de a trimite/primii date. Serverul va astepta cu
accept() pana la connect() din partea clientului.
Server
socket()

bind()

listen()

accept()
Client
socket()
asteptare dupa
un client
stabilirea conexiunii connect ()

read() date (cererere) read()

procesarea
cererii

write() date (raspuns) write()


Figura 1. Apelurile sistem pentru legaturi orientate pe conexiune

 legaturi fara conexiune (nesigure) – nu se garanteaza livrarea


Se mai numesc si nesigure, in cazul lor nu se garanteaza livrarea
datelor. In figura 2. este prezentata schema logica a unei astfel de
conexiuni. Nu exista o identificare explicita a serverului sau a clientului.
Daca se initializeaza contactul cu cealalta parte trebuie cunoscute
adresa de IP si numarul portului sau procesul care asteapta sa fie
contactat. Se lucreaza cu datagrame.
Server
socket()

bind()
Client
socket()
recvfrom()

bind()
asteptare pana se
primesc date de la client
date (cererere) sendto()

procesarea
cererii

sendto() date (raspuns) recvfrom()


Figura 1. Apelurile sistem pentru legaturi fara conexiune

In continuare se vor descrie apelurile de functii elementare necesare


comunicarii intre client si server.

Functia socket()

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

int socket(int family, int type, int protocol);

unde:

- family este familia de protocoale de transport. Poate fi una dintre:

AF_UNIX Unix internal protocols


AF_INET Internet protocols
AF_NS Xerox NS protocols
AF_IMPLINK IMP link layer
AF_IMPLINK DEC DNA protocols

AF_ este o abreviere de la “adress family”. Se mai poate folosi si PF_ care
este o abreviere pentru “protocol family”. Cele doua notatii sunt echivalente.

- type este unul dintre:

SOCK_STREAM stream socket


SOCK_DGRAM datagram socket
SOCK_RAW raw socket
SOCK_SEQPACKET sequenced packet socket

Nu toate combinatiile de family si type sunt valide.

AF_UNIX AF_INET
SOCK_STREAM Da TCP
SOCK_DGRAM Da UDP
SOCK_RAW IP

- protocol este de obicei 0

Functia returneaza un descriptor de socket (int), similar cu descriptorul de


fisier in cazul functiei fopen(). In caz de eroare se returneaza -1.

Functia bind()

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

int bind(int sockfd, struct sockaddr *myaddr, int addrlen);

unde:
- sockfd este descriptorul de socket returnat de functia soket()
- myaddr este un pointer catre o structura sockaddr_in ce pastreaza
adresa locala (specifica pentru un anumit protocol)
- addrlen este dimensiunea structurii myaddr

Functia returneaza 0 pentru succes sau -1 in cazul unei erori, caz in care
errno este setat corespunzator.

Structura sockaddr_in va fi de forma:

struct sockaddr_in {
short syn_family; /* AF_INET */
u_short sin_port /* 16-bit port number */
/* network byte order */
struct in_addr sin_addr /* 32-bit netid/hostid */
/* network byte order */
char sin_zero[8] /* unused */
};

iar structura in_addr de forma:

struct in_addr{
u_long s_addr; /* 32-bit netid/hostid */
/* network byte order */
};

Deoarece exista multe arhitecturi de calculatoare ce pot stoca numere intregi


pe 16/32 biti in diferite moduri de ordonare in memorie, este nevoie de niste
rutine de conversie a acestora intr-un format comun NBO – Network Byte
Order.

Acestea sunt:

Functie Actiune
htonl() converteste formatul gazda 32 bit in nbo
ntohl() converteste nbo in formatul gazda 32 bit
htons() converteste formatul gazda 16 bit in nbo
ntos() converteste nbo in formatul gazda 16 bit

Daca dorim ca procesul server sa ascule pe toate interfetele de retea


existente pe sistem se poate folosi ca parametru pentru s_addr constanta
INADDR_ANY.

#define MY_PORT_ID 6666

struct sockaddr_in ssock_addr;

bzero((char *) &ssock_addr, sizeof(ssock_addr));

ssock_addr.sin_family = AF_INET;
ssock_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ssock_addr.sin_port = htons(MY_PORT_ID);
Functia listen()

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

int listen(int sockfd, int backlog);

unde:

- sockfd este descriptorul de socket returnat de functia soket()


- backlog specifica cate conexiuni pot fi puse intr-o coada de asteptarede
catre sistem in timp ce serverul ruleaza apelul system accept()

Functia este folosita pe serverele cu conexiune orientata pentru a semnala ca


doreste sa accepta conexiuni.

Functia accept()

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

int accept(int sockfd, struct sockaddr *fromaddr, int addrlen);

unde:

- sockfd este descriptorul de socket returnat de functia soket()


- fromaddr este un pointer catre o structura ce pastreaza adresa remote
(specifica pentru un anumit protocol) – adresa socket-ului ce transmite date
- addrlen este dimensiunea structurii fromaddr

Functia accept() preia primul request de la client si creeaza un nou socket


cu aceleasi proprietati ca si sockfd prin care va trimite si primi date serverul.

Functia returneaza -1 pentru in cazul unei erori, sau descriptorul de socket


pentru socketul ce va accepta datele de la client.

Functia connect()

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

int connect(int sockfd, struct sockaddr *toaddr, int addrlen);

unde:

- sockfd este descriptorul de socket returnat de functia soket()


- toaddr este un pointer catre o structura ce pastreaza adresa remote
(specifica pentru un anumit protocol) – adresa serverului
- addrlen este dimensiunea structurii toaddr

Functia returneaza 0 pentru succes sau -1 in cazul unei erori, caz in care
errno este setat corespunzator.

Functiile read() si write()


int accept(int sockfd, struct sockaddr * fromaddr, int addrlen);

Apelurile sistem read(), write() sunt apeluri normale ca in cazul citirii dintr-un
fisier.

Functiile send(), sendto(), recv() si recvfrom()

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

int send(int sockfd, char *buff, int nbytes int flags);


int sendto(int sockfd, char *buff, int nbytes int flags,
struct sockaddr *to, int addrlen);
int recv(int sockfd, char *buff, int nbytes int flags);
int recvfrom(int sockfd, char *buff, int nbytes int flags,
struct sockaddr *from, int addrlen);

unde:

- sockfd, buff si nbytes pentru toate cele patru functii, sunt similari cu
parametri functiilor read() si write().

- flags este 0 sau un OR logic intre urmatoarele constante:

MSG_OOB trimite si receptioneaza date “out-of-band”


MSG_PEEK lasa apelantul sa vada daca sunt date care sa
fie citite fara ca sistemul sa renunte la date
dupa apelul lui recv() sau recvfrom()
MSG_DONTROUTE bypass route ( send() si sendto() )

Toate functiile returneaza lungimea datelor scrise sau citite sau -1 in caz de
eroare. In cazul folosirii lui recvfrom() cu o legatura fara conexiune
valoarea returnata este lungimea datagramei primite.

Functia close()

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

int close(int sockfd);

unde:

- sockfd este descriptorul de socket returnat de functia soket()

Functia inchide un descriptor de socket deschis de catre socket().


Returneaza in caz de succes sau -1 in caz de eroare.

Exemplu legaturi orientate pe conexiune:

server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <errno.h>
#define MY_PORT_ID 6666 /* a number > 5000 */

int main()
{
int sockid, newsockid, i,j;
struct sockaddr_in ssock_addr;
char msg[255];

if ((sockid = socket (AF_INET, SOCK_STREAM, 0)) < 0)


{
perror("Error creating socket");
return -1;
}

bzero((char *) &ssock_addr, sizeof(ssock_addr));


ssock_addr.sin_family = AF_INET;
ssock_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ssock_addr.sin_port = htons(MY_PORT_ID);

if (( bind(sockid, (struct sockaddr *) &ssock_addr,


sizeof(ssock_addr)) < 0))
{
perror("Error binding socket");
return -1;
}

if ( listen(sockid, 5) < 0)
{
perror("Error listening");
return -1;
}

while (1)
{
newsockid = accept(sockid, (struct sockaddr *)0, (int *)0);
if (newsockid < 0)
{
perror("Error accepting socket");
return -1;
}

if ((read(newsockid, &msg, sizeof(msg))) < 0)


{
perror("Error reading new socket\n");
return -1;
}

printf("Client say: %s\n", msg);

sprintf(msg, "==> mesaj de la server <==");

write(newsockid, &msg, sizeof(msg));


close(newsockid);
}
close(sockid);

return 0;
}

client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MY_PORT_ID 6666 /* numar > 5000 */


#define SERV_HOST_ADDR "10.6.14.190"

int main()
{
int sockid;
struct sockaddr_in ssock_addr;
char msg[255];

if ((sockid = socket(AF_INET, SOCK_STREAM, 0)) < 0)


{
perror("Error creating client socket");
return -1;
}

bzero((char *) &ssock_addr, sizeof(ssock_addr));

ssock_addr.sin_family = AF_INET;
ssock_addr.sin_addr.s_addr = inet_addr(SERV_HOST_ADDR);
ssock_addr.sin_port = htons(MY_PORT_ID);

if (connect(sockid, (struct sockaddr *) &ssock_addr,


sizeof(ssock_addr)) < 0)
{
perror("Error connecting to server");
return -1;
}

sprintf(msg, "==> mesaj de la client <==");

if ( (write(sockid, &msg, sizeof(msg))) < 0)


{
perror("Error writing to socket");
return -1;
}

bzero((char *) &msg, sizeof(msg));


if ( (read(sockid, &msg, sizeof(msg))) < 0)
{
perror("Error reading new socket");
return -1;
}
printf("Server say: %s\n", msg);

close(sockid);

return 0;
}
Exemplu legatura fara conexiune:

server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define MY_PORT_ID 6666 /* a number > 5000 */

int main()
{
int sockid, nread, addrlen;
struct sockaddr_in my_addr, client_addr;
char msg[50];

printf("Server: creating socket\n");

if ((sockid = socket (AF_INET, SOCK_DGRAM, 0)) < 0)


{
perror("socket error");
return -1;
}
printf("Server: binding my local socket\n");

bzero((char *) &my_addr, sizeof(my_addr));

my_addr.sin_family = AF_INET;
my_addr.sin_addr.s_addr = htons(INADDR_ANY);
my_addr.sin_port = htons(MY_PORT_ID);

if ((bind(sockid, (struct sockaddr *) &my_addr,


sizeof(my_addr)) < 0))
{
perror("bind fail");
return -1;
}

printf("Server: starting blocking message read\n");

nread = recvfrom(sockid,msg,11,0,
(struct sockaddr *) &client_addr, &addrlen);

if (nread >0)
printf("Server: message is: %.11s\n",msg);

close(sockid);

return 0;
}

client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define MY_PORT_ID 6089


#define SERVER_PORT_ID 6666
#define SERV_HOST_ADDR "10.6.14.190"

int main()
{
int sockid, retcode;
struct sockaddr_in my_addr, server_addr;
char msg[12];

printf("Client: creating socket\n");


if ((sockid = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("socket failed");
return -1;
}

printf("Client: binding my local socket\n");

bzero((char *) &my_addr, sizeof(my_addr));

my_addr.sin_family = AF_INET;
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
my_addr.sin_port = htons(MY_PORT_ID);

if (( bind(sockid, (struct sockaddr *) &my_addr,


sizeof(my_addr)) < 0) )
{
perror("bind fail");
return -1;
}

printf("Client: creating addr structure for server\n");

bzero((char *) &server_addr, sizeof(server_addr));

server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(SERV_HOST_ADDR);
server_addr.sin_port = htons(SERVER_PORT_ID);

printf("Client: initializing message and sending\n");


sprintf(msg, "Hello world");

printf("client: send \"Hello world\"\n");


retcode = sendto(sockid,msg,12,0, (struct sockaddr *)
&server_addr,
sizeof(server_addr));

if (retcode <= -1)


{
perror("sendto failed");
return -1;
}

printf ("client: sent succesfully\n");

close(sockid);

return 0;
}

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