SlideShare una empresa de Scribd logo
1 de 90
Descargar para leer sin conexión
Programació de
sockets amb C++


    Xavier Sala Pujolar
    Desenvolupament de Funcions en el Sistema Informàtic
    IES Cendrassos
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
                        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
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

                                 Desenvolupament de Funcions en el Sistema Informàtic
Programació de sockets en
       Windows
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
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
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
Clients de flux
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         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
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()
#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
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
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
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
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
#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
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
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
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
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
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
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
#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
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*
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
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ó
#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
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
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
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
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
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
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
Servidors de flux
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
#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
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
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
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
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
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
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
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
Client i servidor de
    Datagrames
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.
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
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
Resolució de noms
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
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
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
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
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
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
Multitasca
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()
#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
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
Sockets sense bloqueig
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
#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
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,...);
              }
        }
    }
}



                                     Desenvolupament de Funcions en el Sistema Informàtic
Notes finals
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




                                    Desenvolupament de Funcions en el Sistema Informàtic

Más contenido relacionado

Destacado (6)

Introducció a Docker
Introducció a DockerIntroducció a Docker
Introducció a Docker
 
Selenium web driver in Java
Selenium web driver in JavaSelenium web driver in Java
Selenium web driver in Java
 
Seo en Magento
Seo en MagentoSeo en Magento
Seo en Magento
 
Caso de estudio - Optimizacion de Google Adwords
Caso de estudio - Optimizacion de Google AdwordsCaso de estudio - Optimizacion de Google Adwords
Caso de estudio - Optimizacion de Google Adwords
 
Desarrollo de extensión en Magento
Desarrollo de extensión en MagentoDesarrollo de extensión en Magento
Desarrollo de extensión en Magento
 
B2B eCommerce, estado, desafíos y oportunidades
B2B eCommerce, estado, desafíos y oportunidadesB2B eCommerce, estado, desafíos y oportunidades
B2B eCommerce, estado, desafíos y oportunidades
 

Similar a Programació de sockets amb C++

Conf basica switch-p1
Conf basica switch-p1Conf basica switch-p1
Conf basica switch-p1
1 2d
 
Arduino seminario
Arduino seminarioArduino seminario
Arduino seminario
Rita Pucci
 
whatsoever, hardening linux webserver in 60 minutes
whatsoever, hardening linux webserver in 60 minuteswhatsoever, hardening linux webserver in 60 minutes
whatsoever, hardening linux webserver in 60 minutes
idsecconf
 
夜宴9期《CPU & Intel》
夜宴9期《CPU & Intel》夜宴9期《CPU & Intel》
夜宴9期《CPU & Intel》
Koubei Banquet
 
Lecture1
Lecture1Lecture1
Lecture1
orgil
 
CyberWar e CyberTerr
CyberWar e CyberTerrCyberWar e CyberTerr
CyberWar e CyberTerr
Matteo Flora
 

Similar a Programació de sockets amb C++ (20)

Conf basica switch-p1
Conf basica switch-p1Conf basica switch-p1
Conf basica switch-p1
 
Hardware & Software
Hardware & SoftwareHardware & Software
Hardware & Software
 
Hardware & Software
Hardware & SoftwareHardware & Software
Hardware & Software
 
El ordenador
El ordenadorEl ordenador
El ordenador
 
Arduino seminario
Arduino seminarioArduino seminario
Arduino seminario
 
Seguridad en Servicios Web
Seguridad en Servicios WebSeguridad en Servicios Web
Seguridad en Servicios Web
 
FISL11 2010 - Automação de Datacenters
FISL11 2010 - Automação de DatacentersFISL11 2010 - Automação de Datacenters
FISL11 2010 - Automação de Datacenters
 
Mikrotik%20 most%20wanted
Mikrotik%20 most%20wantedMikrotik%20 most%20wanted
Mikrotik%20 most%20wanted
 
whatsoever, hardening linux webserver in 60 minutes
whatsoever, hardening linux webserver in 60 minuteswhatsoever, hardening linux webserver in 60 minutes
whatsoever, hardening linux webserver in 60 minutes
 
夜宴9期《CPU & Intel》
夜宴9期《CPU & Intel》夜宴9期《CPU & Intel》
夜宴9期《CPU & Intel》
 
Mmix m5-uf2-nf1
Mmix m5-uf2-nf1Mmix m5-uf2-nf1
Mmix m5-uf2-nf1
 
Lecture1
Lecture1Lecture1
Lecture1
 
Xarxes i cablatge I comunicacio
Xarxes i cablatge I comunicacioXarxes i cablatge I comunicacio
Xarxes i cablatge I comunicacio
 
CyberWar e CyberTerr
CyberWar e CyberTerrCyberWar e CyberTerr
CyberWar e CyberTerr
 
张宴NGINX
张宴NGINX张宴NGINX
张宴NGINX
 
Vpn gw2gw
Vpn gw2gwVpn gw2gw
Vpn gw2gw
 
Ni multism 10
Ni multism 10Ni multism 10
Ni multism 10
 
Qt编程介绍
Qt编程介绍Qt编程介绍
Qt编程介绍
 
Mas alla de un gateway - Soluciones reales
Mas alla de un gateway - Soluciones realesMas alla de un gateway - Soluciones reales
Mas alla de un gateway - Soluciones reales
 
F5 Networks - Soluciones para Banca & Finanzas
F5 Networks - Soluciones para Banca & FinanzasF5 Networks - Soluciones para Banca & Finanzas
F5 Networks - Soluciones para Banca & Finanzas
 

Más de Xavier Sala Pujolar

Más de Xavier Sala Pujolar (13)

Fer App mòbils amb tecnologia web
Fer App mòbils amb tecnologia webFer App mòbils amb tecnologia web
Fer App mòbils amb tecnologia web
 
Git
GitGit
Git
 
Validació de Documents XML amb XSD
Validació de Documents XML amb XSDValidació de Documents XML amb XSD
Validació de Documents XML amb XSD
 
Criptografia
CriptografiaCriptografia
Criptografia
 
RIP
RIPRIP
RIP
 
OSPF
OSPFOSPF
OSPF
 
Openldap
OpenldapOpenldap
Openldap
 
Llei de Protecció de dades de caràcter personal (LOPD)
Llei de Protecció de dades de caràcter personal (LOPD)Llei de Protecció de dades de caràcter personal (LOPD)
Llei de Protecció de dades de caràcter personal (LOPD)
 
Virtualització (2 part)
Virtualització (2 part)Virtualització (2 part)
Virtualització (2 part)
 
Virtualització
VirtualitzacióVirtualització
Virtualització
 
Enginyeria social
Enginyeria socialEnginyeria social
Enginyeria social
 
Creació de documents xml
Creació de documents xmlCreació de documents xml
Creació de documents xml
 
Introducció a xml
Introducció a xmlIntroducció a xml
Introducció a xml
 

Programació de sockets amb C++

  • 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
  • 3. TCP/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
  • 49. Esquema d'un servidor de flux 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
  • 58. Client i servidor de Datagrames
  • 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
  • 61. Client de datagrames Desenvolupament de Funcions en el Sistema Informàtic
  • 62. Servidor de datagrames 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