1. Programació de
sockets amb C++
Xavier Sala Pujolar
Desenvolupament de Funcions en el Sistema Informàtic
IES Cendrassos
2. 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
4. Adreces de xarxa
● En IP v4 cada un dels ordinadors s'identificarà
a partir d'una adreça única de 32 bits
192.168.0.10
● En IP v6 cada un dels ordinadors s'identificarà
a partir d'una adreça única de 128 bits
2001:0db8:85a3:08d3:1319:8a2e:0370:7334
● 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
Desenvolupament de Funcions en el Sistema Informàtic
5. Adreces de xarxa
● Cada parell IP:port – IP:port permeten establir
un enllaç de comunicacions
Desenvolupament de Funcions en el Sistema Informàtic
6. 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
7. 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
8. 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
9. 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
10. 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
11. 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
Desenvolupament de Funcions en el Sistema Informàtic
13. 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:
#include <winsock.h>
● O bé el més recomanat:
#include <winsock2.h>
● Algunes funcions noves no estan disponibles si
no s'inclou <ws2tcpip.h>
#include <winsock2.h>
#include <ws2tcpip.h>
Sempre s'ha de posar ws2tcpip.h després de winsock2.h
Desenvolupament de Funcions en el Sistema Informàtic
14. Programar sockets en Windows
● Abans de cridar qualsevol funció de sockets en
Windows s'ha d'iniciar la estructura WSA:
WSADATA wsaData;
if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0)
{
fprintf(stderr, "WSAStartup failed.n");
exit(1);
}
● Al acabar de treballar hem de netejar el WSA
WSACleanup();
● 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
Desenvolupament de Funcions en el Sistema Informàtic
15. Sockets en Windows
● En Windows no es fa sevir la funció close() per
tancar els sockets sinó closesocket()
int closesocket(int socket);
● 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:
#pragma comment(lib,"WS2_32.lib")
– 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
Desenvolupament de Funcions en el Sistema Informàtic
17. Esquema d'un client de flux
Desenvolupament de Funcions en el Sistema Informàtic
18. 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 TYPE
PF_INET6 TCP/IP v6 SOCK_STREAM Fiable, flux de dades
seqüencial (TCP)
PF_IPX IPX
SOCK_DGRAM No fiable, dades en
PF_APPLETALK APPLETALK paquets datagrames (UDP)
PF_LOCAL Canals amb noms SOCK_RDM Fiable, dades en paquets
locals
SOCK_RAW No fiable, dades en
PROTOCOL paquets de baix nivell
Número de 32 bits, normalment sempre
serà 0
Desenvolupament de Funcions en el Sistema Informàtic
19. 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
20. 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
21. Funció connect()
#include <sys/socket.h>
#include <resolv.h>
int connect(int socket, struct sockaddr *server, int mida);
● 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
Desenvolupament de Funcions en el Sistema Informàtic
22. 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
23. Adreces: struct sockaddr
struct sockaddr {
unsigned shord int sa_family;
unsigned char sa_data[14];
}
● La estructura és prou genèrica com per
permetre tenir qualsevol adreça. Per exemple
per IPv4 tenim:
struct sockaddr_in {
sa_family_t sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
● El sin_zero es fa servir per igualar la mida o
sigui que sockaddr i sockaddr_in són iguals!
Desenvolupament de Funcions en el Sistema Informàtic
24. Adreces IP
struct in_addr {
unsigned long s_addr;
}
● 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 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
25. 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
26. Treball amb adreces IP
● Es recomana fer servir inet_aton en comptes
de inet_addr
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
● El mateix codi d'abans es pot escriure:
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);
● inet_aton torna 0 quan falla
Desenvolupament de Funcions en el Sistema Informàtic
27. Funció inet_pton()
● Però a més hi ha una funció que ens permetrà
treballar amb IPv4 i IPv6 indistintament
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
● Per IPv4
struct sockaddr_in sa;
inet_pton(AF_INET, "192.168.0.1", &(sa.sin_addr));
● Per IP v6
struct sockaddr_in6 sa6;
inet_pton(AF_INET6, "2001:db8:63b3:1::3490",
&(sa6.sin6_addr));
Desenvolupament de Funcions en el Sistema Informàtic
28. Treball amb adreces IP
● Per fer la conversió al revés també tenim:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);
● O el més modern (permet v4 i v6 d'IP):
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst,
socklen_t size);
char ip4[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(sa.sin_addr), ip4, INET_ADDRSTRLEN);
printf("L'adreça és is: %sn", ip4);
Desenvolupament de Funcions en el Sistema Informàtic
29. Informació sobre connexions
● Amb qui ens hem connectat?
#include <sys/socket.h>
int getpeername(int soc, struct sockaddr *addr, int *addrlen);
● Qui sóc jo? Obtenir el meu nom de host
#include <unistd.h>
int gethostname(char *hostname, size_t size);
Desenvolupament de Funcions en el Sistema Informàtic
30. 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()
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
● La funció read() retorna el número de bytes
llegits
● Tot i això disposem d'una funció específica
anomenada recv()
Desenvolupament de Funcions en el Sistema Informàtic
31. La funció recv()
● Però també ho podem fer amb la funció
específica recv()
int recv(int sockfd, void *buf, int len, unsigned int flags);
● 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)
Desenvolupament de Funcions en el Sistema Informàtic
32. 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
33. 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
34. Enviar dades
● Per enviar dades també podem fer servir la
biblioteca estàndard d'E/S
#include <unistd.h>
ssize_t write(int fd, void *buf, size_t count);
● Però també podem fer servir la funció
específica:
int send(int s, const void *msg, int len, unsigned int flags);
● 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)
Desenvolupament de Funcions en el Sistema Informàtic
35. 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
36. 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
37. Convertir a FILE*
● Es poden transformar els descriptors de
dispositiu a FILE*
FILE* fp;
int sock = socket(PF_INET,SOCK_STREAM,0);
... Connect
if ((fp = fdopen(sock,”rw”))==NULL)
{
perror(“Conversió 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
Desenvolupament de Funcions en el Sistema Informàtic
38. 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
39. Funció close()
● Un cop s'ha acabat l'enviament de dades s'ha
de tallar la connexió
#include <unistd.h>
int close(int sockfd);
● En Windows la funció té un nom diferent:
int closesocket(int sockfd);
● Només pot fallar si no tenim un socket obert
EBADFD
● Però també es pot tallar la connexió d'una
forma més refinada:
Desenvolupament de Funcions en el Sistema Informàtic
40. Funció shutdown()
● També es pot tancar la connexió d'una forma
més refinada: només en un sentit!
#include <sys/socket.h>
int shutdown(int sockfd, int how);
● 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()
Desenvolupament de Funcions en el Sistema Informàtic
41. Client d'exemple
● Includes en Linux:
#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>
● 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
42. Client d'exemple 2
● Definim les dades bàsiques i creem el socket
#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);
}
● És important comprovar que el socket ha
funcionat correctament
Desenvolupament de Funcions en el Sistema Informàtic
43. Client d'exemple 3
● Connectem amb el servidor
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);
}
● Sempre comprovant els errors. Estem
treballant en xarxa...
● És important el 'cast' de sockaddr_in a
sockaddr
Desenvolupament de Funcions en el Sistema Informàtic
44. Client d'exemple 4
● Si tot ha anat bé ja podem començar a enviar i
rebre dades
if ((numbytes=recv(sock, buf, MIDADADES-1, 0)) == -1)
{
perror("recv");
exit(1);
}
buf[numbytes] = '0';
printf("Rebut: %sn",buf);
● É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
Desenvolupament de Funcions en el Sistema Informàtic
45. Client d'exemple 5
● Per acabar tanquem el socket:
close(sock);
}
● 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
$ echo “Hola” | nc -l -p 8800
Desenvolupament de Funcions en el Sistema Informàtic
47. 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
48. 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
50. 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
#include <sys/socket.h>
#include <resolv.h>
int bind(int sockfd, struct sockaddr *addr, int addrlen);
● No podem fer servir ports que ja estiguin en ús
● Només el root pot fer servir els ports més petits
que 1024
Desenvolupament de Funcions en el Sistema Informàtic
51. 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
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);
}
● bind() retorna -1 en cas d'error
Desenvolupament de Funcions en el Sistema Informàtic
52. 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
53. Funció listen()
● Si volem connectar amb un servidor que ja està
atenent algú no podrem
● Podem definir cues d'espera al nostre
programa
int listen(int sockfd, int backlog);
● 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
Desenvolupament de Funcions en el Sistema Informàtic
54. Funció accept()
● Fa que el programa s'esperi a rebre un
connect() d'un client remot
#include <sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);
● 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
Desenvolupament de Funcions en el Sistema Informàtic
55. Funció accept()
● accept() dóna -1 en cas d'error
sockaddr_in ell;
int mida;
...
if ((nou_sock = accept(sock,(struct sockaddr *)&ell,
&mida))<0)
{
perror(“accept”);
}
● Per cada accept tenim un socket nou i per tant
quan acabem de parlar amb el client l'hem de
tancar
close(nou_sock);
● Aquest socket és independent de l'original
Desenvolupament de Funcions en el Sistema Informàtic
56. Exemple de servidor
● No faig el control d'errors ni poso els includes
per simplificar
int 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
57. Exemple de servidor 2
● Ara el processat de missatges
while(true)
{
new_fd = accept(sockfd, (struct sockaddr *)&ell, &mida);
Enviats = send(new_fd,missatge,strlen(missatge),0);
close(new_fd);
}
close(sockfd);
● 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”
Desenvolupament de Funcions en el Sistema Informàtic
59. 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
60. 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
63. 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.
int recvfrom(int sockfd, void *buf, int len,
unsigned int flags, struct sockaddr *from,
int *fromlen);
● 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
Desenvolupament de Funcions en el Sistema Informàtic
64. Funció sendto()
● De la mateixa forma tenim una funció per
enviar datagrames a una adreça determinada
int sendto(int sockfd, const void *msg, int len,
unsigned int flags, const struct sockaddr *to,
int tolen);
● 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
Desenvolupament de Funcions en el Sistema Informàtic
66. Consultes al DNS
● Disposem d'una funció que ens permet fer
consultes als DNS
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
● Li passem el nom del host i ens emplenarà una
estructura amb els diferents valors consultats al
DNS
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
67. Consultes al DNS
● gethostbyname() no emplena errno o sigui que
per saber quin error ha donat hem de cridar
herror()
hostent he;
if ((he = gethostbyname(“www.google.com”)) == NULL)
{
herror("gethostbyname");
return 2;
}
printf("Nom principal: %sn", 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");
● Actualment és millor fer servir getaddrinfo()
Desenvolupament de Funcions en el Sistema Informàtic
68. struct addrinfo
● Hi ha una estructura per agrupar tant
l'assignació d'adreces com la resolució:
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;
};
● La idea és preparar dades per utilitzar-les
posteriorment i resoldre noms i serveis
● Ara és del primer que cridarem
Desenvolupament de Funcions en el Sistema Informàtic
69. Funció getaddrinfo()
● La estructura s'emplena amb getaddrinfo
#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);
● 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
Desenvolupament de Funcions en el Sistema Informàtic
70. Funció getaddrinfo()
● En el primers paràmetres podem posar tant
noms de host i serveis com el seu valor
numèric
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: %sn",
gai_strerror(status));
exit(1);
}
...
● Això evita moltes comprovacions
Desenvolupament de Funcions en el Sistema Informàtic
71. 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);
Desenvolupament de Funcions en el Sistema Informàtic
73. 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
74. Multitasca
● Evidentment no hi ha res que ens impedeixi fer
servir pthreads en comptes de fork()
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void*), void *arg);
● S'ha de recordar que s'ha de compilar amb la
llibreria
$ gcc -o executable -lpthread codi.cpp
● La idea és la mateixa però el fill executarà una
funció
Desenvolupament de Funcions en el Sistema Informàtic
75. Multitasca en Windows
● En Windows els processos no s'executen o
sigui que hem de fer servir threads:
while(1) {
new_sock = accept(sockfd, (struct sockaddr *)&ell, &mida);
DWORD nThreadID;
CreateThread(0, 0, client, (void*)new_socket, 0,
&nThreadID);
}
● I definim la funció de procés 'client'
DWORD WINAPI client(void* new_)
{
int nRetval = 0;
SOCKET sd = (SOCKET)new_;
...
closesocket(sd);
return nRetval;
}
Desenvolupament de Funcions en el Sistema Informàtic
77. 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
78. 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
79. Funció select()
● La funció select() ens permet comprovar l'estat
dels sockets abans de fer-hi les operacions
#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);
● La estructura timeval ens determinarà el temps
que esperarà select() per si hi ha dades en un
canal
struct timeval {
int tv_sec; // segundos
int tv_usec; // microsegundos
};
Desenvolupament de Funcions en el Sistema Informàtic
80. 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
81. 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
82. 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
83. 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
84. 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
85. 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
86. 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
87. 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,...);
}
}
}
}
Desenvolupament de Funcions en el Sistema Informàtic
89. Coneixements avançats
● Encapsulament de dades
● Missatges broadcast i multicast
● Treball amb sockets raw
Desenvolupament de Funcions en el Sistema Informàtic
90. Referències
* Beej's guide to network programming ( http://beej.us/guide/bgnet/ )
* Programación de socket Linux – Sean Walton. Prentice-Hall
Desenvolupament de Funcions en el Sistema Informàtic