Programació de sockets amb C++

90
Programació de sockets amb C++ Xavier Sala Pujolar Desenvolupament de Funcions en el Sistema Informàtic IES Cendrassos

description

Curs de programació de sockets amb C++ fet durant el crèdit de Desevolupament de Funcions en el Sistema Informàtic de ASI

Transcript of Programació de sockets amb C++

Page 1: Programació de sockets amb C++

Programació de sockets amb C++

Xavier Sala PujolarDesenvolupament de Funcions en el Sistema InformàticIES Cendrassos

Page 2: Programació de sockets amb C++

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

Page 3: Programació de sockets amb C++

Desenvolupament de Funcions en el Sistema Informàtic

TCP/IP

Page 4: Programació de sockets amb C++

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

Page 5: Programació de sockets amb C++

Desenvolupament de Funcions en el Sistema Informàtic

Adreces de xarxa● Cada parell IP:port – IP:port permeten establir

un enllaç de comunicacions

Page 6: Programació de sockets amb C++

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

Page 7: Programació de sockets amb C++

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.

Page 8: Programació de sockets amb C++

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...

Page 9: Programació de sockets amb C++

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

Page 10: Programació de sockets amb C++

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, ...

Page 11: Programació de sockets amb C++

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

Page 12: Programació de sockets amb C++

Programació de sockets en Windows

Page 13: Programació de sockets amb C++

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>

Page 14: Programació de sockets amb C++

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);}

Page 15: Programació de sockets amb C++

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

Page 16: Programació de sockets amb C++

Clients de flux

Page 17: Programació de sockets amb C++

Desenvolupament de Funcions en el Sistema Informàtic

Esquema d'un client de flux

Page 18: Programació de sockets amb C++

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

Page 19: Programació de sockets amb C++

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

Page 20: Programació de sockets amb C++

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

Page 21: Programació de sockets amb C++

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

Page 22: Programació de sockets amb C++

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

Page 23: Programació de sockets amb C++

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];}

Page 24: Programació de sockets amb C++

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

Page 25: Programació de sockets amb C++

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

Page 26: Programació de sockets amb C++

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

Page 27: Programació de sockets amb C++

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

Page 28: Programació de sockets amb C++

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

Page 29: Programació de sockets amb C++

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

Page 30: Programació de sockets amb C++

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

Page 31: Programació de sockets amb C++

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

Page 32: Programació de sockets amb C++

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)

Page 33: Programació de sockets amb C++

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

Page 34: Programació de sockets amb C++

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

Page 35: Programació de sockets amb C++

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

Page 36: Programació de sockets amb C++

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

Page 37: Programació de sockets amb C++

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*”);}

Page 38: Programació de sockets amb C++

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)

Page 39: Programació de sockets amb C++

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

Page 40: Programació de sockets amb C++

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

Page 41: Programació de sockets amb C++

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>

Page 42: Programació de sockets amb C++

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

Page 43: Programació de sockets amb C++

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); }

Page 44: Programació de sockets amb C++

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

Page 45: Programació de sockets amb C++

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

Page 46: Programació de sockets amb C++

Servidors de flux

Page 47: Programació de sockets amb C++

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

Page 48: Programació de sockets amb C++

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..

Page 49: Programació de sockets amb C++

Desenvolupament de Funcions en el Sistema Informàtic

Esquema d'un servidor de flux

Page 50: Programació de sockets amb C++

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

Page 51: Programació de sockets amb C++

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);}

Page 52: Programació de sockets amb C++

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);}

Page 53: Programació de sockets amb C++

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

Page 54: Programació de sockets amb C++

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

Page 55: Programació de sockets amb C++

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

Page 56: Programació de sockets amb C++

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

Page 57: Programació de sockets amb C++

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

Page 58: Programació de sockets amb C++

Client i servidor de Datagrames

Page 59: Programació de sockets amb C++

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ó

Page 60: Programació de sockets amb C++

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)

Page 61: Programació de sockets amb C++

Desenvolupament de Funcions en el Sistema Informàtic

Client de datagrames

Page 62: Programació de sockets amb C++

Desenvolupament de Funcions en el Sistema Informàtic

Servidor de datagrames

Page 63: Programació de sockets amb C++

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

Page 64: Programació de sockets amb C++

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

Page 65: Programació de sockets amb C++

Resolució de noms

Page 66: Programació de sockets amb C++

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]

Page 67: Programació de sockets amb C++

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");

Page 68: Programació de sockets amb C++

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; };

Page 69: Programació de sockets amb C++

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

Page 70: Programació de sockets amb C++

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);}...

Page 71: Programació de sockets amb C++

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

Page 72: Programació de sockets amb C++

Multitasca

Page 73: Programació de sockets amb C++

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); }

Page 74: Programació de sockets amb C++

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

Page 75: Programació de sockets amb C++

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;}

Page 76: Programació de sockets amb C++

Sockets sense bloqueig

Page 77: Programació de sockets amb C++

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?

Page 78: Programació de sockets amb C++

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

Page 79: Programació de sockets amb C++

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};

Page 80: Programació de sockets amb C++

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

Page 81: Programació de sockets amb C++

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

Page 82: Programació de sockets amb C++

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

Page 83: Programació de sockets amb C++

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

Page 84: Programació de sockets amb C++

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

Page 85: Programació de sockets amb C++

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;}

Page 86: Programació de sockets amb C++

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

Page 87: Programació de sockets amb C++

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,...); } } }}

Page 88: Programació de sockets amb C++

Notes finals

Page 89: Programació de sockets amb C++

Desenvolupament de Funcions en el Sistema Informàtic

Coneixements avançats

● Encapsulament de dades

● Missatges broadcast i multicast

● Treball amb sockets raw

Page 90: Programació de sockets amb C++

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