Date post: | 22-May-2015 |
Category: |
Education |
Upload: | xavier-sala-pujolar |
View: | 245 times |
Download: | 6 times |
Programació de sockets amb C++
Xavier Sala PujolarDesenvolupament de Funcions en el Sistema InformàticIES Cendrassos
Desenvolupament de Funcions en el Sistema Informàtic
Introducció
● L'objectiu és comunicar dos programes a través dels mecanismes de xarxa
● No ens ha d'importar si els dos programes estan en la mateixa màquina o no
● Nosaltres ens concentrarem en TCP/IP amb la versió 4 de IP
Desenvolupament de Funcions en el Sistema Informàtic
TCP/IP
Desenvolupament de Funcions en el Sistema Informàtic
Adreces de xarxa● En IP v4 cada un dels ordinadors s'identificarà
a partir d'una adreça única de 32 bits
● En IP v6 cada un dels ordinadors s'identificarà a partir d'una adreça única de 128 bits
● A més cada ordinador té un grup d'adreces en les que podrà fer connexions: ports
– Es representen amb un número
– Només el root pot fer servir els ports <1024
– Només 1 connexió per port
192.168.0.10
2001:0db8:85a3:08d3:1319:8a2e:0370:7334
Desenvolupament de Funcions en el Sistema Informàtic
Adreces de xarxa● Cada parell IP:port – IP:port permeten establir
un enllaç de comunicacions
Desenvolupament de Funcions en el Sistema Informàtic
Arquitectura client/servidor● Consisteix en que un client fa peticions a un
programa que li donarà resposta
● D'aquesta forma es reparteix la capacitat de procés
● És el client qui sol iniciar el procés
Desenvolupament de Funcions en el Sistema Informàtic
Programació en xarxes● Té una dificultat extra ja que s'han de controlar
a través de missatges el comportament de dos programes independents
● S'ha de tenir en compte que pot passar qualsevol cosa
– Els paquets tarden en arribar
– Els servidors sobrecarregats tarden més en respondre
– etc...
● Hi ha moltes suposicions que fem en programació normal que aquí no les podem fer.
Desenvolupament de Funcions en el Sistema Informàtic
sockets● Els sockets són simplement una forma de
comunicar amb altres programes a través de descriptors de fitxers de Unix
● Obtenim un descriptor de fitxer que en realitat és una connexió de xarxa
● Hi ha molts tipus de sockets– Sockets d'Internet
– Sockets locals (sockets Unix)
– Sockets X.25
– etc...
Desenvolupament de Funcions en el Sistema Informàtic
Sockets d'Internet● Ens concentrarem només en els sockets
d'Internet● Farem servir sobretot IPv4 (el que fem servir a
Internet) tot i que es comentarà alguna cosa de IPv6
● Hi ha diversos tipus de sockets d'Internet però els més importants són:
– Sockets de flux (Stream sockets)
– Sockets de datagrames (Datagram sockets)
– També hi ha els sockets purs (raw sockets) que permeten un control més gran sobre les dades enviades
Desenvolupament de Funcions en el Sistema Informàtic
Sockets de flux● En el nostre codi estaran referits com
SOCK_STREAM ● Defineixen connexions:
– En els dos sentits
– Fiables (control d'errors, flux i confirmació)
– Amb connexió
● Generalment es fan servir quan necessitem mantenir la connexió amb el servidor
● El protocol que es fa servir és TCP● El fan servir HTTP, SMTP, ...
Desenvolupament de Funcions en el Sistema Informàtic
Sockets de datagrames● En el nostre codi estaran referits com
SOCK_DGRAM ● Defineixen connexions:
– En els dos sentits
– No Fiables (pot arribar o no, en ordre o no...)
– Sense connexió
● Generalment es fan servir quan necessitem enviar informació puntual
● El protocol que es fa servir és UDP● El fan servir: tftp, bootp
Programació de sockets en Windows
Desenvolupament de Funcions en el Sistema Informàtic
Programar sockets en Windows● Podem programar amb Windows però hi ha
algunes diferències● Els includes de Windows són diferents dels de
Linux. Normalment es redueixen a:
● O bé el més recomanat:
● Algunes funcions noves no estan disponibles si no s'inclou <ws2tcpip.h>
Sempre s'ha de posar ws2tcpip.h després de winsock2.h
#include <winsock.h>
#include <winsock2.h>
#include <winsock2.h>#include <ws2tcpip.h>
Desenvolupament de Funcions en el Sistema Informàtic
Programar sockets en Windows● Abans de cridar qualsevol funció de sockets en
Windows s'ha d'iniciar la estructura WSA:
● Al acabar de treballar hem de netejar el WSA
● Microsoft té funcions per treballar amb sockets amb el nom WSA*. (WSAAccept, WSAConnect...) Nosaltres no les farem servir perquè ens concentrarem en fer programes portables
WSACleanup();
WSADATA wsaData; if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) { fprintf(stderr, "WSAStartup failed.\n"); exit(1);}
Desenvolupament de Funcions en el Sistema Informàtic
Sockets en Windows● En Windows no es fa sevir la funció close() per
tancar els sockets sinó closesocket()
● Hem d'assegurar-nos d'enllaçar el programa amb la llibreria wsock32.lib, winsock32.lib o WS2_32.lib
● Si estem treballant amb “Visual Studio” podem afegir la llibreria al nostre projecte amb la línia:
– Microsoft recomana afegir la línia anterior en un arxiu .h i no en el codi font .cpp per evitar tenir problemes amb els manifestos
#pragma comment(lib,"WS2_32.lib")
int closesocket(int socket);
Clients de flux
Desenvolupament de Funcions en el Sistema Informàtic
Esquema d'un client de flux
Desenvolupament de Funcions en el Sistema Informàtic
Funció socket()● Crea un descriptor per accedir als ordinadors
de la xarxa#include <sys/socket.h>#include <resolv.h>
int socket(int domain, int type, int protocol);
DOMAIN
PF_INET TCP/IP v4
PF_INET6 TCP/IP v6
PF_IPX IPX
PF_APPLETALK APPLETALK
PF_LOCAL Canals amb noms locals
TYPE
SOCK_STREAM Fiable, flux de dades seqüencial (TCP)
SOCK_DGRAM No fiable, dades en paquets datagrames (UDP)
SOCK_RDM Fiable, dades en paquets
SOCK_RAW No fiable, dades en paquets de baix nivellPROTOCOL
Número de 32 bits, normalment sempre serà 0
Desenvolupament de Funcions en el Sistema Informàtic
Funció socket()● El resultat de la funció ha de donar un valor
positiu o ens està indicant que s'ha produït un error
● Si hi ha un error el posarà a errno.● Els errors més corrents són:
– EPROTONOSUPPORT: Protocol no suportat
– EACCESS: No tenim permís
– EINVAL: Hem col·locat un valor invàlid
Desenvolupament de Funcions en el Sistema Informàtic
Funció socket()● Podem fer servir protocols de TCP/IP de
diferents capes fent servir socket()
Aplicació (HTTP,...)
Transport (TCP) socket(PF_INET,SOCK_STREAM,0);
Transport (UDP) socket(PF_INET,SOCK_DGRAM,0);
Internet (ICMP) socket(PF_INET,SOCK_RAW, IPPROTO_ICMP);
Internet (IP) socket(PF_INET,SOCK_RAW,protocol);
Accés a la xarxa socket(PF_INET,SOCK_PACKET,filter);
Desenvolupament de Funcions en el Sistema Informàtic
Funció connect()
● Fem servir el valor obtingut de la funció socket() que hem cridat abans
● Cal especificar on ens hem de connectar amb la estructura sockaddr conté l'adreça i el port de destí on connectarem
● Només podem connectar amb algú que estigui esperant connexions: esquema client/servidor
#include <sys/socket.h>#include <resolv.h>
int connect(int socket, struct sockaddr *server, int mida);
Desenvolupament de Funcions en el Sistema Informàtic
Funció connect()● Com que podem fer servir diferents protocols la
mida de l'adreça no sempre és igual ● En TCP el que fa connect és el “3 way
handshake”
● Un cop connect() ha funcionat ja podem començar a enviar dades a través d'un port temporal si no hem fet servir bind()
Desenvolupament de Funcions en el Sistema Informàtic
Adreces: struct sockaddr
● La estructura és prou genèrica com per permetre tenir qualsevol adreça. Per exemple per IPv4 tenim:
● El sin_zero es fa servir per igualar la mida o sigui que sockaddr i sockaddr_in són iguals!
struct sockaddr { unsigned shord int sa_family; unsigned char sa_data[14];}
struct sockaddr_in { sa_family_t sin_family; unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero[8];}
Desenvolupament de Funcions en el Sistema Informàtic
Adreces IP
● L'adreça de in_addr és simplement un número de 32 bits.
● Per tant puc posar una adreça (en format numèric de xarxa) directament a l'estructura.
● Puc convertir l'adreça amb inet_addr()
struct in_addr { unsigned long s_addr;}
struct sockaddr_in servidor;servidor.sin_family = PF_INET;servidor.sin_port = htons(80);servidor.sin_addr.s_addr = inet_addr(“192.168.0.1”);memset(&(servidor.sin_zero),'\0',8);
Desenvolupament de Funcions en el Sistema Informàtic
Valors de xarxa● No tots els processadors desen la informació
de la mateixa forma (little endian/big endian) per tant cal un estàndard de xarxa
● Aquestes funcions s'han de fer servir sempre per garantir la portabilitat entre sistemes
#include <arpa/inet.h>
Host to network long uint32_t htonl(uint32_t hostlong);
Host to network short uint16_t htons(uint16_t hostshort);
Network to host long uint32_t ntohl(uint32_t netlong);
Network to Host short uint16_t ntohs(uint16_t netlong);
Desenvolupament de Funcions en el Sistema Informàtic
Treball amb adreces IP● Es recomana fer servir inet_aton en comptes
de inet_addr
● El mateix codi d'abans es pot escriure:
● inet_aton torna 0 quan falla
#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>int inet_aton(const char *cp, struct in_addr *inp);
struct sockaddr_in servidor;servidor.sin_family = AF_INET;servidor.sin_port = htons(MYPORT);inet_aton("10.12.110.57", &(servidor.sin_addr));memset(&(servidor.sin_zero), '\0', 8);
Desenvolupament de Funcions en el Sistema Informàtic
Funció inet_pton()● Però a més hi ha una funció que ens permetrà
treballar amb IPv4 i IPv6 indistintament
● Per IPv4
● Per IP v6
#include <arpa/inet.h>int inet_pton(int af, const char *src, void *dst);
struct sockaddr_in sa; inet_pton(AF_INET, "192.168.0.1", &(sa.sin_addr));
struct sockaddr_in6 sa6; inet_pton(AF_INET6, "2001:db8:63b3:1::3490", &(sa6.sin6_addr));
Desenvolupament de Funcions en el Sistema Informàtic
Treball amb adreces IP● Per fer la conversió al revés també tenim:
● O el més modern (permet v4 i v6 d'IP):
#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>char *inet_ntoa(struct in_addr in);
char ip4[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(sa.sin_addr), ip4, INET_ADDRSTRLEN);printf("L'adreça és is: %s\n", ip4);
#include <arpa/inet.h>const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
Desenvolupament de Funcions en el Sistema Informàtic
Informació sobre connexions● Amb qui ens hem connectat?
● Qui sóc jo? Obtenir el meu nom de host
#include <sys/socket.h>int getpeername(int soc, struct sockaddr *addr, int *addrlen);
#include <unistd.h>int gethostname(char *hostname, size_t size);
Desenvolupament de Funcions en el Sistema Informàtic
Rebre informació● Podem fer servir la biblioteca estàndard d'E/S
per rebre informació pel socket● Per exemple fent servir la funció de lectura
read()
● La funció read() retorna el número de bytes llegits
● Tot i això disposem d'una funció específica anomenada recv()
#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);
Desenvolupament de Funcions en el Sistema Informàtic
La funció recv()● Però també ho podem fer amb la funció
específica recv()
● Li hem de passar la quantitat màxima de bytes que acceptarem
● Ens retornarà:– Quants bytes hem rebut
– Si torna 0 és que s'ha tallat la connexió!
– Si torna -1 és que s'ha produït un error
● Només funciona amb canals de flux (SOCK_STREAM)
int recv(int sockfd, void *buf, int len, unsigned int flags);
Desenvolupament de Funcions en el Sistema Informàtic
La funció recv(): flags● Els flags de recv() els farem servir per modificar
el comportament de recv()● Els més corrents són (n'hi ha més):
MSG_OOB Es processen les dades fora de banda. Alguns protocols permeten definir dades de prioritat normal i alta. Això prioritza els de prioritat alta
MSG_PEEK Es llegeix sense buidar el buffer. Quan tornem a llegir tornarem a rebre les mateixes dades
MSG_WAITALL No retorna dades fins que el buffer estigui totalment ple. És perillós perquè pot fer que esperem indefinidament
MSG_DONTWAIT Es fa servir per evitar que el programa es bloquegi si no hi ha dades pendents de lectura. Tornarà error EWOULDBLOCK (en Linux no està suportat)
Desenvolupament de Funcions en el Sistema Informàtic
Errors de lectura● EAGAIN: Tenim una E/S no bloquejada i no hi ha
dades a rebre. S'ha de tornar a llegir
● EBADFD: El socket no és un descriptor vàlid o no està obert per lectura (s'ha tancat?)
● EINVAL: S'ha fet servir un objecte invàlid de lectura
● ENOTCONN: només recv()
● ENOTSOCK: només recv()
Desenvolupament de Funcions en el Sistema Informàtic
Enviar dades● Per enviar dades també podem fer servir la
biblioteca estàndard d'E/S
● Però també podem fer servir la funció específica:
● El funcionament de send() és el mateix que el de write() però ens permet treballar amb flags
● Només funciona amb canals de flux (SOCK_STREAM)
#include <unistd.h>ssize_t write(int fd, void *buf, size_t count);
int send(int s, const void *msg, int len, unsigned int flags);
Desenvolupament de Funcions en el Sistema Informàtic
La funció send(): flags● Els flags de send() ens permeten modificar-ne
el comportament de la mateixa forma que amb recv()
MSG_OOB Alguns protocols permeten definir dades de prioritat normal i alta. Això permet enviar les dades amb alta prioritat
MSG_DONTROUTE No permet l'encaminament del paquet. Obliga a intentar contactar directament amb el receptor. Si no pot torna un error ENETUNREACH
MSG_NOSIGNAL No emet cap senyal SIGPIPE a l'altre costat
MSG_DONTWAIT No espera a que el send() hagi acabat
Desenvolupament de Funcions en el Sistema Informàtic
Errors d'escriptura● EBADFD: El socket no és un descriptor vàlid o no
està obert per enviament (s'ha tancat?)
● EINVAL: S'ha fet servir un objecte invàlid d'escriptura
● EFAULT: El buffer de dades no és vàlid
● EPIPE: S'han enviat dades per un canal que s'ha tancat
● EMSGSIZE: Mida insuficient per enviar
● ENOTCONN: només send()
● ENOTSOCK: només send()
Desenvolupament de Funcions en el Sistema Informàtic
Convertir a FILE*● Es poden transformar els descriptors de
dispositiu a FILE*
● Ara podem treballar amb: fread, fscanf, fgets... ● Només es poden transformar els sockets de
fluxe (SOCK_STREAM)● Això permet tenir recursos d'anàlisi gramatical i
recerca
FILE* fp;int sock = socket(PF_INET,SOCK_STREAM,0);... Connectif ((fp = fdopen(sock,”rw”))==NULL){ perror(“Conversió a FILE*”);}
Desenvolupament de Funcions en el Sistema Informàtic
Fer servir FILE*● S'ha de tenir en compte que enviar les dades
amb un sol missatge no garanteix que les dades arribin amb un sol missatge (poden ser diversos)
– No podem controlar quin és el buffer que fa servir el sistema operatiu
– Per tant convertir a FILE* farà que el nostre sistema faci servir el buffer de FILE i per tant si que s'esperi a tenir totes les dades
– Però això pot portar a problemes al final de la transmissió... (que no acabi mai si les dades no ens quadren)
Desenvolupament de Funcions en el Sistema Informàtic
Funció close()● Un cop s'ha acabat l'enviament de dades s'ha
de tallar la connexió
● En Windows la funció té un nom diferent:
● Només pot fallar si no tenim un socket obert EBADFD
● Però també es pot tallar la connexió d'una forma més refinada:
#include <unistd.h>int close(int sockfd);
int closesocket(int sockfd);
Desenvolupament de Funcions en el Sistema Informàtic
Funció shutdown()● També es pot tancar la connexió d'una forma
més refinada: només en un sentit!
● Els valors sobre com tancar poden ser:– 0: No es permetrà rebre més dades
– 1: No es permetrà enviar més dades
– 2: Es tanca la connexió com amb close()
#include <sys/socket.h>int shutdown(int sockfd, int how);
Desenvolupament de Funcions en el Sistema Informàtic
Client d'exemple
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <errno.h>#include <string.h>#include <netdb.h>#include <sys/types.h>#include <netinet/in.h>#include <sys/socket.h>
● Includes en Linux:
● En Windows la cosa seria més “light”#include <stdio.h>#include <stdlib.h>#include <winsock.h>#include <errno.h>#include <string.h>
Desenvolupament de Funcions en el Sistema Informàtic
Client d'exemple 2
#define PORT 15000#define MIDADADES 100
int main(int argc, char *argv[]){ int sock, numbytes; char buf[MIDADADES]; struct sockaddr_in servidor; if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); }
● Definim les dades bàsiques i creem el socket
● És important comprovar que el socket ha funcionat correctament
Desenvolupament de Funcions en el Sistema Informàtic
Client d'exemple 3● Connectem amb el servidor
● Sempre comprovant els errors. Estem treballant en xarxa...
● És important el 'cast' de sockaddr_in a sockaddr
servidor.sin_family = AF_INET; servidor.sin_port = htons(MYPORT); inet_aton("192.168.0.2", &(servidor.sin_addr)); memset(&(servidor.sin_zero), '\0', 8); if (connect(sock, (struct sockaddr *)&servidor, sizeof(struct sockaddr)) == -1) { perror("connect"); exit(1); }
Desenvolupament de Funcions en el Sistema Informàtic
Client d'exemple 4● Si tot ha anat bé ja podem començar a enviar i
rebre dades
● És molt important comprovar quantes dades s'han rebut
– No s'han de fer mai suposicions sobre les dades que es rebran.
– No sabem com serà el buffer del SO ni per on passarà el missatge
if ((numbytes=recv(sock, buf, MIDADADES-1, 0)) == -1) { perror("recv"); exit(1); } buf[numbytes] = '\0'; printf("Rebut: %s\n",buf);
Desenvolupament de Funcions en el Sistema Informàtic
Client d'exemple 5● Per acabar tanquem el socket:
● Un cop tinguem això ja s'hauria de poder compilar i executar-lo contra un servidor que escolti el port 15000
● Podem fer servir netcat per simular un servidor i poder provar el client
close(sock);}
$ echo “Hola” | nc -l -p 8800
Servidors de flux
Desenvolupament de Funcions en el Sistema Informàtic
Programació de servidors● Essencialment la programació d'un servidor és
semblant a un client però:– Hem d'establir el port en el que escoltarà amb la
funció bind()
– Hem de definir una cua de clients en espera amb listen()
– Hem d'esperar que se'ns hi connecti algú amb accept()
● No cal que ens limitem a un sol client podem interactuar amb tants clients com pugui la CPU creant processos nous
Desenvolupament de Funcions en el Sistema Informàtic
Programació de servidors● A l'hora de programar un servidor hi ha una
sèrie de coses que s'han de tenir molt clares– Qui parla primer? Si tant client com servidor
esperen rebre tindrem els programes penjats
– Com funcionarà el protocol de comunicacions?● Quines comandes s'accepten i quines no, quan
tallar la comunicació?
– Quin tipus de dades farem servir?● Missatges de mida fixa?, dades binàries o no ...
– Quin és el nivell de seguretat requerit?● Fa falta SSL?, sincronització horària?
– etc..
Desenvolupament de Funcions en el Sistema Informàtic
Esquema d'un servidor de flux
Desenvolupament de Funcions en el Sistema Informàtic
Funció bind()● L'objectiu de bind() és associar-se amb un port
de la màquina on estem.● Es pot fer servir tant en clients com en
servidors però és més normal en servidors
● No podem fer servir ports que ja estiguin en ús● Només el root pot fer servir els ports més petits
que 1024
#include <sys/socket.h>#include <resolv.h>
int bind(int sockfd, struct sockaddr *addr, int addrlen);
Desenvolupament de Funcions en el Sistema Informàtic
Funció bind() 2● A l'hora d'emplenar l'adreça IP podem posar-hi
– l'adreça que escoltarà el servidor
– INADDR_ANY per escoltar totes les targetes de xarxa de l'ordinador
● bind() retorna -1 en cas d'error
jo.sin_family = AF_INET;jo.sin_port = htons(15000);jo.sin_addr.s_addr = INADDR_ANY;memset(&(jo.sin_zero), '\0', 8); if (bind(sockfd, (struct sockaddr *)&jo, sizeof(struct sockaddr))<0){ perror(bind);}
Desenvolupament de Funcions en el Sistema Informàtic
Funció bind() 3● Al programar el servidor ens podem trobar que
no ens deixa fer servir de nou el port "Address already in use"
– El nucli està bloquejant el port perquè no es va tancar bé.
– Només podem esperar
● Podem evitar el bloqueig amb les opcions del socket:
int yes=1;if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR, &yes,sizeof(int)) == -1) { perror("setsockopt"); exit(1);}
Desenvolupament de Funcions en el Sistema Informàtic
Funció listen()● Si volem connectar amb un servidor que ja està
atenent algú no podrem● Podem definir cues d'espera al nostre
programa
● En el camp backlog especifiquem quina mida tindrà la cua d'entrada
– Si arriba una petició de connexió i estem ocupats la posarà en cua
– Si no queda espai la rebutjarà
● No es recomanen cues més grans de 20
int listen(int sockfd, int backlog);
Desenvolupament de Funcions en el Sistema Informàtic
Funció accept()● Fa que el programa s'esperi a rebre un
connect() d'un client remot
● Al cridar accept() el socket original ja no pot ni rebre ni enviar dades
● Al establir la connexió accept() ens tornarà un descriptor de socket que serà el que farem servir per comunicar amb el client
● Amb els paràmetres podem saber qui s'està comunicant amb el nostre servidor
● Hem d'assegurar-nos de que addr tingui espai
#include <sys/socket.h>int accept(int sockfd, void *addr, int *addrlen);
Desenvolupament de Funcions en el Sistema Informàtic
Funció accept()● accept() dóna -1 en cas d'error
● Per cada accept tenim un socket nou i per tant quan acabem de parlar amb el client l'hem de tancar
● Aquest socket és independent de l'original
sockaddr_in ell;int mida;...if ((nou_sock = accept(sock,(struct sockaddr *)&ell, &mida))<0){ perror(“accept”);}
close(nou_sock);
Desenvolupament de Funcions en el Sistema Informàtic
Exemple de servidor● No faig el control d'errors ni poso els includes
per simplificarint main(){ char *missatge = "Hola!"; int sockfd, new_fd; struct sockaddr_in jo, ell; int mida, Enviats; sockfd = socket(AF_INET, SOCK_STREAM, 0); jo.sin_family = AF_INET; jo.sin_port = htons(MYPORT); jo.sin_addr.s_addr = INADDR_ANY; memset(&(jo.sin_zero), '\0', 8); bind(sockfd, (struct sockaddr *)&jo, sizeof(struct sockaddr)); listen(sockfd, BACKLOG); mida = sizeof(struct sockaddr_in);
Desenvolupament de Funcions en el Sistema Informàtic
Exemple de servidor 2● Ara el processat de missatges
● En aquest exemple enviem “Hola!” a qui es connecta i tanquem la connexió.
● El servidor estarà permanentment esperant connexions
● Hauria de fer totes les comprovacions d'errors i veure que realment he enviat les dades comprovant la variable “Enviats”
while(true) { new_fd = accept(sockfd, (struct sockaddr *)&ell, &mida); Enviats = send(new_fd,missatge,strlen(missatge),0); close(new_fd); } close(sockfd);
Client i servidor de Datagrames
Desenvolupament de Funcions en el Sistema Informàtic
Datagrames● Fem servir SOCK_DGRAM al crear el socket● Recordar que:
– Poc fiable
– Sense connexió (el nostre missatge serà el primer que li arribarà!)
– No hi ha garantia d'arribada del missatge
● Podem enviar missatges a llocs diferents sense tancar el socket
● Podem fer servir connect amb datagrames però això no vol dir que es mantingui la connexió
Desenvolupament de Funcions en el Sistema Informàtic
Datagrames● Quan fer servir datagrames?
– Les peticions són consultes independents
– L'ordre d'arribada de les peticions no importa
– No cal tenir informació sobre què ha fet anteriorment un client
– Es pot perdre algun paquet sense que això afecti al funcionament del programa
● Cada datagrama UDP s'envia en un sol datagrama IP (encara que aquest pot ser fragmentat posteriorment)
Desenvolupament de Funcions en el Sistema Informàtic
Client de datagrames
Desenvolupament de Funcions en el Sistema Informàtic
Servidor de datagrames
Desenvolupament de Funcions en el Sistema Informàtic
Funció recvfrom()● Donat que els sockets amb datagrames no
estan connectats a la màquina remota a més de les dades a rebre ens pot interessar saber qui ens les envia.
● Com amb recv() ens retornarà el número de caràcters rebuts
● Ens emplenarà els valors de sockaddr amb el que ens ha enviat les dades
int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
Desenvolupament de Funcions en el Sistema Informàtic
Funció sendto()● De la mateixa forma tenim una funció per
enviar datagrames a una adreça determinada
● Hi afegim a on les volem enviar perquè els sockets amb datagrames no estan connectats al servidor
● Per poder enviar/rebre amb datagrames no cal fer connect.
– Si ho fem podem fer servir send() i recv() per enviar dades
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);
Resolució de noms
Desenvolupament de Funcions en el Sistema Informàtic
Consultes al DNS● Disposem d'una funció que ens permet fer
consultes als DNS
● Li passem el nom del host i ens emplenarà una estructura amb els diferents valors consultats al DNS
#include <netdb.h>struct hostent *gethostbyname(const char *name);
struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; char **h_addr_list;};#define h_addr h_addr_list[0]
Desenvolupament de Funcions en el Sistema Informàtic
Consultes al DNS● gethostbyname() no emplena errno o sigui que
per saber quin error ha donat hem de cridar herror()
● Actualment és millor fer servir getaddrinfo()
hostent he;if ((he = gethostbyname(“www.google.com”)) == NULL) { herror("gethostbyname"); return 2;}
printf("Nom principal: %s\n", he->h_name);printf(" adreces IP: ");addr_list = (struct in_addr **)he->h_addr_list;for(i = 0; addr_list[i] != NULL; i++) { printf("%s ", inet_ntoa(*addr_list[i]));}printf("\n");
Desenvolupament de Funcions en el Sistema Informàtic
struct addrinfo● Hi ha una estructura per agrupar tant
l'assignació d'adreces com la resolució:
● La idea és preparar dades per utilitzar-les posteriorment i resoldre noms i serveis
● Ara és del primer que cridarem
struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; size_t ai_addrlen; struct sockaddr *ai_addr; char *ai_canonname; struct addrinfo *ai_next; };
Desenvolupament de Funcions en el Sistema Informàtic
Funció getaddrinfo()● La estructura s'emplena amb getaddrinfo
● Abans hem d'emplenar com a mínim les dades de hints:
– ai_family: AF_INET, AF_INET6, AF_UNSPEC
– ai_socktype: SOCK_STREAM, SOCK_DGRAM
– ai_protocol: 0 vol dir que qualsevol
– ai_flags: AI_PASSIVE, AI_CANONNAME
#include <sys/types.h>#include <sys/socket.h>#include <netdb.h>
int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
Desenvolupament de Funcions en el Sistema Informàtic
Funció getaddrinfo()● En el primers paràmetres podem posar tant
noms de host i serveis com el seu valor numèric
● Això evita moltes comprovacions
int status;struct addrinfo hints, *servinfo; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC;hints.ai_socktype = SOCK_STREAM; if ((status = getaddrinfo(“www.google.com”, "http", &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status)); exit(1);}...
Desenvolupament de Funcions en el Sistema Informàtic
Funció getaddrinfo()● Un cop està inicialitzada podem fer servir les
dades obtingudes en les funcions en les funcions de connexió:
getaddrinfo("www.google.com", "http", &hints, &res);
s = socket(res->ai_family, res->ai_socktype, res>ai_protocol);connect(sockfd, res->ai_addr, res->ai_addrlen);
Multitasca
Desenvolupament de Funcions en el Sistema Informàtic
Multitasca● No entrem gaire en detalls perquè ho veurem
en un altre tema● Ens permetrà atendre a més d'un client alhora ● En Unix podem crear un procés per cada
connexió: (no comprovo errors)
while(1) { new_sock = accept(sockfd, (struct sockaddr *)&ell, &mida); if (fork()==0) { send(new_sock, "Hello, world!", 13, 0); close(new_sock); exit(0); } close(new_fd); }
Desenvolupament de Funcions en el Sistema Informàtic
Multitasca● Evidentment no hi ha res que ens impedeixi fer
servir pthreads en comptes de fork()
● S'ha de recordar que s'ha de compilar amb la llibreria
● La idea és la mateixa però el fill executarà una funció
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
$ gcc -o executable -lpthread codi.cpp
Desenvolupament de Funcions en el Sistema Informàtic
Multitasca en Windows● En Windows els processos no s'executen o
sigui que hem de fer servir threads:
● I definim la funció de procés 'client'
while(1) { new_sock = accept(sockfd, (struct sockaddr *)&ell, &mida); DWORD nThreadID; CreateThread(0, 0, client, (void*)new_socket, 0, &nThreadID);}
DWORD WINAPI client(void* new_) { int nRetval = 0; SOCKET sd = (SOCKET)new_; ... closesocket(sd); return nRetval;}
Sockets sense bloqueig
Desenvolupament de Funcions en el Sistema Informàtic
Comunicació entre clients● Què passa si vull connectar dos o més clients
entre ells i poden enviar dades en qualsevol ordre?
Desenvolupament de Funcions en el Sistema Informàtic
Bloqueig● Problemes:
– Les funcions recv() i accept() bloquegen el programa fins que arriba alguna dada o una nova connexió
– Això és un problema si volem connectar diferents clients que no segueixen cap ordre a l'hora d'enviar (ex. Chat)
● Cal alguna forma per poder treballar amb molts clients que estiguin enviant aleatòriament
● Podem evitar el bloqueig amb crides a funcions fcntl() però això és excessivament rebuscat
Desenvolupament de Funcions en el Sistema Informàtic
Funció select()● La funció select() ens permet comprovar l'estat
dels sockets abans de fer-hi les operacions
● La estructura timeval ens determinarà el temps que esperarà select() per si hi ha dades en un canal
#include <sys/time.h>#include <sys/types.h>#include <unistd.h>int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
struct timeval { int tv_sec; // segundos int tv_usec; // microsegundos};
Desenvolupament de Funcions en el Sistema Informàtic
Conjunts de descriptors● Els conjunts de descriptors són de tipus fd_set
i permet treballar-hi a través d'una sèrie de macros:
– FD_ZERO(fd_set *set): Esborra el conjunt
– FD_SET(int fd, fd_set *set): Afegeix fd al conjunt
– FD_CLR(int fd, fd_set *set): Elimina fd del conjunt
– bool FD_ISSET(int fd, set *set): Diu si fd està en el conjunt o no
● Quan acaba select() en els conjunts només hi ha els descriptors que tenen activitat
Desenvolupament de Funcions en el Sistema Informàtic
Funcionament de select()● Abans de cridar select() omplim els fd_set amb
els descriptors de socket que volem comprovar si tenen activitat
– Cada un en el fd_set corresponent: lectura/recepció, escriptura/enviament
Desenvolupament de Funcions en el Sistema Informàtic
Funcionament de select()● Executem select() especificant-hi els
paràmetres següents:– en el primer paràmetre una unitat més gran
que el descriptor màxim (79 en l'exemple)
– Els tres fd_set són per:● Activitat de lectura (estem rebent dades)● Activitat d'escriptura (estan esperant que enviem
dades)● Excepcions
– El cinquè paràmetre és el temps que el select estarà comprovant si hi ha activitat en els descriptors
Desenvolupament de Funcions en el Sistema Informàtic
Funcionament de select()● Un cop s'ha acabat la execució de select() en
els fd_set només hi quedaran els descriptors que tinguin activitat
Desenvolupament de Funcions en el Sistema Informàtic
Funcionament de select()● El select() només ens informa de que hi ha
activitat però les dades continuen en el descriptor
● És responsabilitat del nostre programa fer el recv() o el send() corresponent en el socket per obtenir-ne les dades
● No cal oblidar que el conjunt haurà perdut els sockets sense activitat
– Si volem tornar a escoltar (típic dels bucles) haurem de tornar a refer el fd_set amb els sockets a comprovar
Desenvolupament de Funcions en el Sistema Informàtic
Exemple select() sense sockets#include <stdio.h>#include <sys/time.h>#include <sys/types.h>#include <unistd.h>#define STDIN 0
int main(void){ struct timeval tv; fd_set readfds; tv.tv_sec = 2; tv.tv_usec = 500000; FD_ZERO(&readfds); FD_SET(STDIN, &readfds);
select(STDIN+1, &readfds, NULL, NULL, &tv); if (FD_ISSET(STDIN, &readfds)) printf("A key was pressed!\n"); else printf("Timed out.\n"); return 0;}
Desenvolupament de Funcions en el Sistema Informàtic
Select() i els sockets● Al acabar hem de comprovar quins sockets
queden en els conjunts● Generalment en el conjunt de lectura tindrem
que distingir entre:– Socket de socket(): L'activitat de lectura en el
socket original indica que tenim algú que està intentant connectar. Haurem de fer accept() per establir la connexió
– Sockets obtinguts per l'accept(): Indica que ens estan enviant dades i per tant haurem de fer recv() per aquest socket
Desenvolupament de Funcions en el Sistema Informàtic
Esquema de select()for(;;){ tv = tv2; Lectura = copia_Lectura select(max+1, &Lectura, NULL, NULL, &tv); for(i=0; i<max; i++) { if (FD_ISSET(i, &Lectura)) { if (i==socket_original) { Nova_connexió = accept(...); FD_SET(Nova_connexió, copia_Lectura); } else { // Connexió antiga recv(i,...); } } }}
Notes finals
Desenvolupament de Funcions en el Sistema Informàtic
Coneixements avançats
● Encapsulament de dades
● Missatges broadcast i multicast
● Treball amb sockets raw
Desenvolupament de Funcions en el Sistema Informàtic
Referències
* Beej's guide to network programming ( http://beej.us/guide/bgnet/ )
* Programación de socket Linux – Sean Walton. Prentice-Hall