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()


asteptare dupa un client stabilirea conexiunii date (cererere)

Client socket() connect () read()

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() recvfrom()
asteptare pana se primesc date de la client date (cererere) procesarea cererii

Client socket() bind() sendto()

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 AF_INET AF_NS AF_IMPLINK AF_IMPLINK Unix internal protocols Internet protocols Xerox NS protocols IMP link layer 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 SOCK_DGRAM SOCK_RAW SOCK_SEQPACKET stream socket datagram socket raw socket sequenced packet socket

Nu toate combinatiile de family si type sunt valide. AF_UNIX SOCK_STREAM Da SOCK_DGRAM Da SOCK_RAW - 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);

AF_INET TCP UDP IP

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; u_short sin_port struct in_addr char }; sin_addr sin_zero[8] /* /* /* /* /* /* AF_INET */ 16-bit port number */ network byte order */ 32-bit netid/hostid */ network byte order */ 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 htonl() ntohl() htons() ntos() Actiune converteste formatul gazda 32 bit in nbo converteste nbo in formatul gazda 32 bit converteste formatul gazda 16 bit in nbo 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 MSG_PEEK trimite si receptioneaza date out-of-band 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 #include #include #include <sys/types.h> <sys/socket.h> <netinet/in.h> <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 #include #include #include #include #include <stdio.h> <sys/types.h> <sys/socket.h> <netinet/in.h> <arpa/inet.h> <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 #include #include #include #include #include <stdio.h> <sys/types.h> <sys/socket.h> <netinet/in.h> <arpa/inet.h> <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 &server_addr, sizeof(server_addr)); if (retcode <= -1) { perror("sendto failed"); return -1; }

sockaddr

*)

printf ("client: sent succesfully\n"); close(sockid); return 0; }

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