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()
socket()
bind()
listen()
accept()

asteptare dupa

un client

bind() listen() accept() asteptare dupa un client read() procesarea cererii write() Client socket() stabilirea

read()

procesarea
procesarea

cererii

accept() asteptare dupa un client read() procesarea cererii write() Client socket() stabilirea conexiunii connect ()

write()

Client

socket()
socket()

stabilirea conexiunii

cererii write() Client socket() stabilirea conexiunii connect () read() write() date (cererere) date (raspuns)

connect ()cererii write() Client socket() stabilirea conexiunii read() write() date (cererere) date (raspuns) Figura 1.

write() Client socket() stabilirea conexiunii connect () read() write() date (cererere) date (raspuns) Figura 1.

read()

Client socket() stabilirea conexiunii connect () read() write() date (cererere) date (raspuns) Figura 1. Apelurile

write()Client socket() stabilirea conexiunii connect () read() date (cererere) date (raspuns) Figura 1. Apelurile sistem

socket() stabilirea conexiunii connect () read() write() date (cererere) date (raspuns) Figura 1. Apelurile sistem

date (cererere)

date (raspuns)

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() recvfrom()
socket()
bind()
recvfrom()

asteptare pana se primesc date de la client

recvfrom() asteptare pana se primesc date de la client date (cererere) procesarea cererii sendto() date

date (cererere)

procesarea

cererii

primesc date de la client date (cererere) procesarea cererii sendto() date (raspuns) Client socket() bind()

sendto()

date (raspuns)

sendto() date (raspuns)
(cererere) procesarea cererii sendto() date (raspuns) Client socket() bind() sendto() recvfrom() Figura 1.

Client

socket() bind()
socket()
bind()
sendto() recvfrom()
sendto()
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 */

struct in_addr sin_addr

/* network byte order */ /* 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:

unc tie

F

A

c tiun e

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;

}