Introducció als ​Sockets  3 de Febrer de 2015 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Introducció als ​Sockets 
 
 
 
 
 
 
 
 ...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
1. Què és un ​socket​?
En l’àmbit informàtic, s’utilitza el terme ​sock...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
La ​Network Application Programming Interface anomenada BSD: ​Berkeley ...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
Sockets no orientats a la connexió (UDP)
S’utilitza aquest tipus de soc...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
 
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
Per omplir aquest dues estructures existeixen algunes facilitats, és a ...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
anirà acumulant i no les transmetrà a l’aplicació fins que aquesta no h...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
 
#include <sys/types.h> 
#include <sys/socket.h> 
 
int connect(int so...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
 
Ara bé, la ​Network API ​Berkley Sockets ens ofereix les seves funcio...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
Existeix una característica comuna per a totes les funcions que han de ...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
Paràmetres de la funció 
● sockfd​: descriptor de fitxer del ​socket​ c...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
8. Tancar els sockets (tancar les connexions)
Finalment, només ens qued...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
implementar una arquitectura client/servidor, en la qual el servidor of...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
Codi del client: 
 
 
 
Com veiem al codi del client, l’adreça IP del s...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
char** h_add_list; /* Array de les IPs de la màquina. L’últim element é...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
aquest motiu no és necessari fer la crida a ​htonl() en el moment en qu...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
Molt bé, ja ho tenim. Ara només ens queda veure com enviaríem i rebríem...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
A continuació, la funció que rep l’estructura de dades al servidor: 
 
...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
Codi del servidor 
 
 
 
 
 
Ferran Verdés Castelló   Pàgina 19 de 22 
 
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
Codi del client 
 
 
 
Ferran Verdés Castelló   Pàgina 20 de 22 
 
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
Com podem veure en el codi, s’utilitza l’estructura de dades que trobem...
 
Introducció als ​Sockets  3 de Febrer de 2015 
 
Llicència del document
 
Per a fer ús d’aquest document, heu de respect...
Próxima SlideShare
Cargando en…5
×

Introducció als Sockets[C] [Català]

117 visualizaciones

Publicado el

Es tracta d'uns apunts personals que tenen per objectiu explicar el concepte de socket i les seves possibles implementacions.

Publicado en: Software
0 comentarios
0 recomendaciones
Estadísticas
Notas
  • Sé el primero en comentar

  • Sé el primero en recomendar esto

Sin descargas
Visualizaciones
Visualizaciones totales
117
En SlideShare
0
De insertados
0
Número de insertados
3
Acciones
Compartido
0
Descargas
0
Comentarios
0
Recomendaciones
0
Insertados 0
No insertados

No hay notas en la diapositiva.

Introducció als Sockets[C] [Català]

  1. 1.   Introducció als ​Sockets  3 de Febrer de 2015                                  Introducció als ​Sockets                                              Ferran Verdés Castelló  ferranverdes@gmail.com  www.ferranverdes.cat  Primera versió    Ferran Verdés Castelló   Pàgina 1 de 22   
  2. 2.   Introducció als ​Sockets  3 de Febrer de 2015    1. Què és un ​socket​? En l’àmbit informàtic, s’utilitza el terme ​socket per fer referència a quatre conceptes diferents,                            i per tant, podem dir que té exactament quatre definicions. Anem a anomenar­les:    ● Socket (def. [1]): ranura de la placa base on es fixa i es connecta el                              microprocessador sense soldar, la qual cosa ens permet extreure’l posteriorment                    sense complicacions.    ● Socket (def. [2]): també s’anomena ​socket a aquella combinació composta per una                        adreça IP i un número de port, com per exemple, 210.34.72.19:80. Opcionalment                        també es pot indicar el protocol de transport.    ● Socket (def. [3])​: s’utilitza per designar un concepte abstracte pel qual dos                        programes, normalment situats en computadores diferents, poden intercanviar un flux                    de dades a través d’un canal de comunicació.      ● Sockets (def. [4])​: concretament, BSD: ​Berkeley Sockets​, que s’abrevia a ​Sockets​.                      És el nom d’una NAPI (​Network Application Programming Interface​), és a dir, el nom                            d’una interfície de treball que s’utilitza per relacionar la capa d’aplicació amb la capa                            dels protocols específics de xarxa. Normalment aquest tipus d’interfícies les                    proporciona el sistema operatiu. A continuació, altres NAPIs conegudes i una imatge                        representativa:  ○ TLI, XTI (de AT&T UNIX ​System​ V)  ○ Winsock (de ​Microsoft​)  ○ MacTCP (de ​Apple Computer​)      Ferran Verdés Castelló   Pàgina 2 de 22   
  3. 3.   Introducció als ​Sockets  3 de Febrer de 2015    La ​Network Application Programming Interface anomenada BSD: ​Berkeley Sockets s’ha                    convertit en un estàndard per a la utilització de ​sockets a la xarxa. Es tracta d’una NAPI                                  basada en llibreries (com per exemple la ​<sys/socket.h>​) que permet escriure programes en                          C per tal d’implementar les rutines d’intercomunicació entre processos. En realitat, està                        composa per un conjunt de funcions, tals com ​socket()​, ​bind()​, ​listen()​, ​connect()​, etc, on                            cadascuna realitza una tasca concreta per tal d’establir, mantenir o tancar el canal de                            comunicació.     D’alguna manera, podem considerar que el que ens proporciona aquesta ​Network API és la                            interacció amb uns fitxers, creats d’una forma especial, que representen els extrems de la                            comunicació i els quals podem llegir o escriure amb les clàssiques funcions ​write() i ​read()​,                              tot i que també tenim al nostre abast funcions especialment dedicades per a realitzar aquesta                              tasca, com són les funcions ​send() i ​recv()​. Seguidament, trobem una imatge que intenta                            representar gràficament el que s’ha descrit:        A partir d’ara, quan parlem de ​socket​, en singular, ens referirem a aquest fitxer especial que                                ens proporciona la ​Network API anomenada BSD: ​Berkeley Sockets​, i en canvi, quan parlem                            de ​sockets​, en plural, ens referirem al concepte abstracte pel qual dos programes poden                            intercanviar un flux d’informació.  2. Els tipus de ​sockets Existeixen dos tipus de sockets, aquells que són orientats a la connexió i aquells que no són                                  orientats a la connexió.  Sockets orientats a la connexió (TCP) Garantitzen que totes les dades arribaran d’un programa a l’altre correctament. S’utilitzen                        quan la informació a transmetre és important i no ens podem permetre perdre cap dada.     Es basen en la utilització del protocol TCP, i aquest fet, certifica que fins que els dos                                  programes no mantinguin una connexió correctament establerta, cap dels dos començarà a                        transmetre. De la mateixa manera, si un dels dos programes queda ocupat momentàniament                          i no és capaç d’atendre la comunicació, l’altre es quedarà bloquejat fins que l’altre extrem                              estigui preparat de nou.    Ferran Verdés Castelló   Pàgina 3 de 22   
  4. 4.   Introducció als ​Sockets  3 de Febrer de 2015    Sockets no orientats a la connexió (UDP) S’utilitza aquest tipus de sockets quan només es vol garantir que les dades a rebre siguin                                correctes, però sense la necessitat de garantir l’arribada de totes les dades.    En aquest cas, no és necessari que els programes es connectin. Qualsevol d’ells pot                            transmetre dades en qualsevol moment, independentment de si l’altre extrem està atenent les                          comunicacions o no. Per aquest tipus de sockets s’empra el protocol UDP.  3. Les funcions que proporciona la NAPI BSD: ​Berkeley Sockets Anem a veure, a continuació, quines són les funcions que ens ofereix aquesta ​Network​ API  per a la implementació de ​sockets​.  1. Creació del ​socket #include <sys/types.h>  #include <sys/socket.h>    int socket(int family, int type, int protocol);    Es tracta d’una crida al sistema que retorna un descriptor de fitxer, de la mateixa manera que                                  ho fa la funció ​open() al crear o obrir un arxiu. Aquest descriptor de fitxer serà utilitzat                                  posteriorment per associar­lo a una connexió de xarxa, és a dir, durant aquesta crida es                              reserven els recursos necessaris per implementar un extrem de la comunicació, però no es                            concreta cap aspecte referent a l’adreça IP i el port que s’hi associarà.    Paràmetres de la funció  ● family​: especifica la família del protocol. Es podrà establir com a ​AF_INET si es                            desitja realitzar una comunicació a través d’Internet o bé com a ​AF_UNIX si es desitja                              realitzar una comunicació interna del sistema. També tenim l’opció ​AF_INET6 ​per a                        realitzar comunicacions a través d’Internet sobre el protocol IPv6 o bé ​AF_IPX per                          utilitzar el protocol IPX.  ● type​: especifica el tipus de servei, és a dir, si volem orientar el ​socket a la connexió o                                    no. Les variables que solen aparèixer són ​SOCK_STREAM pels ​sockets orientats a la                          connexió i ​SOCK_DGRAM​ pels ​sockets​ no orientats a la connexió.  ● protocol​: especifica el protocol i normalment s’introdueix 0, que significa que es vol                          utilitzar el protocol per defecte segons la família escollida en el primer paràmetre.                          També es pot utilitzar ​IPPROTO_TCP​ o ​IPPROTO_UDP​.     Si s’ha produït algun problema durant l’execució de la funció aquesta retornarà ­1.  2. Associar una direcció IP i un port al ​socket #include <sys/types.h>  #include <sys/socket.h>    Ferran Verdés Castelló   Pàgina 4 de 22   
  5. 5.   Introducció als ​Sockets  3 de Febrer de 2015      int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);    Aquesta crida permet assignar una adreça IP i número de port al ​socket​. El número de port,                                  en realitat, no és res més que una zona de memòria específica on rebrem les connexions.                                S’ha de destacar que desprès de fer aquesta crida el sistema encara no atendrà les                              connexions, sinó que es necessari utilitzar altres funcions per realitzar aquest procediment,                        les quals veurem posteriorment. A tall de curiositat, podem dir que aquesta crida es coneix                              com a “ l’assignació d’un nom al ​socket ​”.     Igual que el cas anterior, retornarà ­1 si s’ha produït un error. Si tot ha funcionat correctament                                  retornarà 0.    Abans de seguir amb la descripció dels paràmetres de la funció cal comentar un detall:                              aquesta funció és l’encarregada de posar “un port a l’escolta” si el ​sockfd que rep com                                argument pertany a un ​socket UDP, és a dir, a un ​socket del tipus ​SOCK_DGRAM​. (obs:                                comanda “netstat ­punta” per veure el llistat de ports “actius” de la màquina).    Paràmetres de la funció  ● sockfd​: descriptor de fitxer del ​socket​ creat anteriorment per la funció ​socket()​.  ● addr​: Aquest paràmetre és enganyós, perquè normalment s’utilitza una estructura del                      tipus ​sockaddr_in enlloc d’una estructura ​sockaddr​. El que contindrà l’estructura                    sockaddr_in ​és l’adreça IP i el número de port a assignar:     #include <netinet/in.h>    struct sockaddr_in {  short sin_family; /* família de protocols. Idèntic a la funció Socket(); */  unsigned short sin_port; /* 16 bits per designar el número de port */  struct in_addr sin_addr;  /* 32 bits per l’adreça IP */  char sin_zero[8]; /* no emprat */  };    Com veiem, a la definició de l’estructura s’utilitza una altra estructura anomenada                        in_addr​, la qual representa el valor de la IP mitjançant el tipus ​unsigned long​:    #include <netinet/in.h>    struct in_addr {  unsigned long s_addr;   };      Ferran Verdés Castelló   Pàgina 5 de 22   
  6. 6.   Introducció als ​Sockets  3 de Febrer de 2015    Per omplir aquest dues estructures existeixen algunes facilitats, és a dir, podem deixar                          que el sistema operatiu ens col∙loqui l’adreça IP automàticament i que escolleixi un                          port lliure de forma aleatòria:    addr.sin_port = 0; /* Port aleatori */  sddr.sin_addr.s_addr = INADDR_ANY; /* Adreça autmàtica */    Compte! Cal destacar que el valors de ​sin_port i ​sin_addr s’han de trobar en ​network                              byte order​, és a dir, s’han de trobar en format ​big endian​: el format en que                                s’organitzen els ​bytes de les dades en les transmissions dels paquets. De la mateixa                            manera que en el cas anterior, existeixen facilitats per a dur a terme aquesta tasca, en                                concret, podem utilitzar les funcions de traducció ​htonl​, ​htons​, ​ntohl i ​ntohs​, les                          quals permeten realitzar les ordenacions pertinents dels ​bytes de les dades en funció                          de si es vol passar una dada del format de xarxa al format de host o l’inrevés. Anem a                                      veure’n els seus prototips:    #include <netinet/in.h>    /* Conversió del format de host al format de xarxa per enters curts (16 bits) */  uint16_t ​h​to​n​s​(uint16_t hostshort);      /* Conversió del format de xarxa al format de host per enters curts (16 bits) */  uint16_t ​n​to​h​s​(uint16_t netshort);    /* Conversió del format de host al format de xarxa per enters llargs (32 bits) */  uint32_t ​h​to​n​l​(uint32_t hostlong);    /* Conversió del format de xarxa al format de host per enters llargs (32 bits) */  uint32_t ​n​to​h​l​(uint32_t netlong);    Semànticament, cada lletra de la funció pren el significat següent:  h​  : ​host byte order  n​  : ​network byte order  s​  : ​short​ (16 ​bits​)  l​   : ​long​ (32 ​bits​)  ● addrlen​: mida en octets de ​addr​, és a dir, ​sizeof(struct sockaddr_in) si passem per                            paràmetre una estructura del tipus ​struct sockaddr_in​.   3. Atendre una connexió (posar la connexió a la cua) int listen(int sockfd, int backlog);    Amb aquesta crida s’avisa al sistema operatiu que comenci a atendre les connexions. El que                              farà serà crear una cua que anirà omplint amb les connexions que van arribant al ​socket​, les                                    Ferran Verdés Castelló   Pàgina 6 de 22   
  7. 7.   Introducció als ​Sockets  3 de Febrer de 2015    anirà acumulant i no les transmetrà a l’aplicació fins que aquesta no ho sol∙liciti mitjançant la                                crida ​accept()​, la qual veurem posteriorment. Com podem pensar, si arriben connexions de                          client més ràpid que les que pot atendre l’aplicació, el sistema les emmagatzemarà a la cua i                                  les anirà transmeten progressivament. Per últim, cal comentar que aquesta crida només                        s’aplica als ​sockets orientats a connexió, és a dir, aquells que són del tipus ​SOCK_STREAM​.                              A diferència dels ​sockets ​UDP, aquesta funció és l’encarregada de posar “un port a l’escolta”                              pels ​sockets​ TCP.    Com és d’esperar, retornarà ­1 si s’ha produït algun error.     Paràmetres de la funció  ● sockfd​: descriptor de fitxer del ​socket​ creat anteriorment per la funció ​socket()​.  ● backlog​: nombre màxim de connexions en cua. Indica la quantitat de connexions que  la cua pot contenir.   4. Acceptar una connexió (extreure una connexió de la cua i processar-la) #include <sys/types.h>  #include <sys/socket.h>    int accept(int sockfd, struct sockaddr* addr_client, int* addrlen);    Aquesta crida demana i accepta una connexió d’un client, és a dir, li indica al sistema                                operatiu que extregui la següent petició de connexió de la cua de connexions pendents. Si no                                hi ha clients es quedarà bloquejada fins que algun client es connecti. Igual que la funció                                anterior, aquesta només té sentit pel ​sockets​ orientats a la connexió.     El que fa internament, és crear un nou ​socket amb les mateixes propietats que les que conté                                  sockfd i en retorna el seu descriptor de fitxer, el qual s’utilitzarà per comunicar­se amb el                                client. Cal remarcar, que el ​socket ​sockfd no rep cap alteració per aquesta crida, simplement                              se’n extreuen les seves característiques.    Com de costum, si ha sorgit algun problema en l’execució es retornarà ­1.    Paràmetres de la funció  ● sockfd​: descriptor de fitxer del ​socket​ creat anteriorment per la funció ​socket()​.  ● addr_client​: estructura on s’emmagatzemarà l’adreça de l’altre extrem de la connexió                      (contindrà l’adreça IP i el port).  ● addrlen​: longitud en ​bytes de ​addr_client​, normalment s’utilitzarà ​sizeof(struct                  sockaddr_in)​ si el tipus de ​addr_client​ és ​sockaddr_in​.   5. Iniciar/sol·licitar una connexió Fins ara hem parlat de funcions que ofereixen establir una connexió. En aquest cas, aquesta                              funció realitza la tasca “adversa”:    Ferran Verdés Castelló   Pàgina 7 de 22   
  8. 8.   Introducció als ​Sockets  3 de Febrer de 2015      #include <sys/types.h>  #include <sys/socket.h>    int connect(int sockfd, const struct sockaddr* addr_server, socklen_t addrlen);    Aquesta crida sol∙licita una connexió al servidor i es quedarà bloquejada fins que aquest                            accepti la connexió o bé s’expiri un temps d’espera. Si no hi ha cap servidor que atengui,                                  s’obtindrà el valor de retorn ­1.    Com podem pensar, ​addr_server ha de contenir la direcció IP del servidor i el número de                                port al que es desitja connectar.    Paràmetres de la funció  ● sockfd​: descriptor de fitxer del ​socket​ creat anteriorment per la funció ​socket()​.  ● addr_server​: estructura on s’emmagatzema l’adreça de l’altre extrem de la connexió                      (conté l’adreça IP i el port).  ● addrlen​: longitud en ​bytes de ​addr_server​, normalment s’utilitzarà ​sizeof(struct                  sockaddr_in)​ si el tipus de ​addr_server​ és ​sockaddr_in​.     Aquesta funció és una mica especial, ja que és capaç de detectar si s’ha associat una                                direcció al ​socket (és a dir, si s’ha realitzar la crida ​bind()​) i en el cas que no sigui així, ho                                          realitzarà ella mateixa de forma transparent tot utilitzant els paràmetres per defecte. En altres                            paraules, si volem utilitzar la funció ​connect() podem estalviar­nos de fer la crida a la funció                                bind()​.  6. Llegir dades d’una connexió Abans hem dit que un ​Berkley socket és un fitxer creat de forma especial. Per tant, si és un                                      fitxer del qual en tenim el descriptor de fitxer, que ens impedeix utilitzar la crida al sistema                                  read() per a llegir­ne les dades? En efecte, aquest fet és possible. Podem utilitzar aquesta                              crida per llegir dades del ​socket​ a través del seu descriptor de fitxer:    #include <unistd.h>    ssize_t read(int fd, void* buffer, size_t count);    Retornarà el nombre de ​bytes​ llegits, 0 si s’ha arribat al final de fitxer (tancament de ​socket​) o  ­1 si s’ha produït algun error.     Paràmetres de la funció  ● fd​: descriptor de fitxer.  ● buffer​: ​buffer ​on s’emmagatzemaran les dades.  ● count​: número de ​bytes​ que es desitgen llegir.    Ferran Verdés Castelló   Pàgina 8 de 22   
  9. 9.   Introducció als ​Sockets  3 de Febrer de 2015      Ara bé, la ​Network API ​Berkley Sockets ens ofereix les seves funcions especialment                          dedicades per realitzar la lectura de les dades d’un ​socket​. La primera d’elles és:    #include <sys/types.h>  #include <sys/socket.h>    ssize_t recv(int sockfd, void* buffer, size_t len, int flags);    Aquesta funció recull les dades rebudes dels ​sockets de tipus ​SOCK_STREAM​, és a dir,                            orientats a la connexió. Similar a la clàssica funció ​read() també retorna el número d’octets                              llegits, 0 si s’ha realitzat el tancament del ​socket​ o ­1 si s’ha produït algun error.     Paràmetres de la funció  ● sockfd​: descriptor de fitxer del ​socket​ creat anteriorment per la funció ​accept()​.  ● buffer​: ​buffer ​on s’emmagatzemaran les dades.  ● len​: número de ​bytes​ que es desitgen llegir.  ● flags​: paràmetres especials, generalment 0, però poden ser altres com ​MSG_OOB​,                      MSG_PEEK​, ​MSG_TRUNC​, etc.    En segon lloc, tenim una funció que llegeix les dades pels ​sockets ​de tipus ​SOCK_DGRAM​,                              és a dir, no orientats a la connexió:    #include <sys/types.h>  #include <sys/socket.h>    ssize_t recvfrom(int sockfd, void* buffer, size_t len, int flags,   struct sockaddr* src_addr, socklen_t* addrlen);    Igual que en el cas anterior, també retorna el número d’octets llegits, 0 si s’ha realitzat el                                  tancament del ​socket​ o ­1 si s’ha produït algun error.     Paràmetres de la funció  ● sockfd​: descriptor de fitxer del ​socket​ creat anteriorment per la funció ​sock()​.  ● buffer​: ​buffer ​on s’emmagatzemaran les dades.  ● len​: número de ​bytes​ que es desitgen llegir.  ● flags​: paràmetres especials, generalment 0, però poden ser altres com ​MSG_OOB​,  MSG_PEEK​, ​MSG_TRUNC​, etc.  ● src_addr​: estructura on s’emmagatzemarà l’adreça de l’altre extrem de la connexió                      (contindrà l’adreça IP i el port).  ● addrlen​: longitud en ​bytes de ​src_addr​, normalment s’utilitzarà ​sizeof(struct                  sockaddr_in)​ si el tipus de ​src_addr​ és ​sockaddr_in​.       Ferran Verdés Castelló   Pàgina 9 de 22   
  10. 10.   Introducció als ​Sockets  3 de Febrer de 2015    Existeix una característica comuna per a totes les funcions que han de llegir dades d’un                              socket i és que tant el client com el servidor han de conèixer el format de les dades que                                      esperen rebre i com tractar­les, és a dir, han de saber quina estructura segueix la informació                                que es troba emmagatzemada dins el ​buffer per tal de no realitzar males interpretacions                            d’aquestes (p.e no confondre un valor ​int​ per una array de ​char​s de mida 4).    Una altra cosa molt important és que les funcions per llegir dades, per defecte, es queden                                esperant a rebre dades, és a dir, no els expira cap ​timeout i es queden bloquejades fins a                                    rebre alguna dada. Com solucionem aquest problema? Per una banda, podem configurar el                          socket amb un ​timeout​, i per l’altra, podem esperar que l’altre extrem tanqui el ​socket i                                d’aquesta manera les funcions ens retornaran 0.  7. Escriure dades en una connexió De la mateixa manera que en la lectura de les dades, podem utilitzar la crida al sistema                                  write() per escriure dades a través del descriptor del ​socket​, pel mateix motiu que s’ha                              comentat en el punt anterior:    #include <unistd.h>    ssize_t write(int fd, const void* buffer, size_t count);    En aquest cas, retorna el nombre de ​bytes​ escrits, 0 si no s’ha escrit res o ­1 si s’ha produït  algun error.     Paràmetres de la funció  ● fd​: descriptor de fitxer.  ● buffer​: ​buffer ​on s’emmagatzemen les dades a escriure.  ● count​: número de ​bytes​ que es desitgen escriure.    Algunes de les funcions que es troben a la ​Network API ​Berkley Sockets per tal de dur                                  aquesta tasca són les següents:    #include <sys/types.h>  #include <sys/socket.h>    ssize_t send(int sockfd, const void* buffer, size_t len, int flags);    En aquest cas, es tracta d’una funció per treballar amb els ​sockets de tipus ​SOCK_STREAM​.                              Si tot ha funcionat correctament, retornarà el nombre de ​bytes enviats, en canvi, si s’ha                              produït algun error retornarà ­1.           Ferran Verdés Castelló   Pàgina 10 de 22   
  11. 11.   Introducció als ​Sockets  3 de Febrer de 2015    Paràmetres de la funció  ● sockfd​: descriptor de fitxer del ​socket​ creat anteriorment per la funció ​socket()​.  ● buffer​: ​buffer ​on s’emmagatzemen les dades.  ● len​: número de ​bytes​ màxim que es desitgen escriure.  ● flags​: paràmetres especials, generalment 0, però poden ser altres com ​MSG_OOB​,                      MSG_CONFIRM​, ​MSG_DONTWAIT​, etc.    La funció especialitzada per treballar amb els ​sockets​ de tipus ​SOCK_DGRAM​ és la següent:    #include <sys/types.h>  #include <sys/socket.h>    ssize_t sendto(int sockfd, const void* buffer, size_t len, int flags,   const struct sockaddr* dest_addr, socklen_t addrlen);    De la mateixa manera que en el cas anterior, si tot ha funcionat correctament retornarà el                                nombre de ​bytes​ enviats, en canvi, si s’ha produït algun error retornarà ­1.     Paràmetres de la funció  ● sockfd​: descriptor de fitxer del ​socket​ creat anteriorment per la funció ​sock()​.  ● buffer​: ​buffer ​on s’emmagatzemen les dades.  ● len​: número de ​bytes​ màxim que es desitgen escriure.  ● flags​: paràmetres especials, generalment 0, però poden ser altres com ​MSG_OOB​,  MSG_PEEK​, ​MSG_TRUNC​, etc.  ● dest_addr​: estructura on s’emmagatzemarà l’adreça de l’altre extrem de la connexió                      (contindrà l’adreça IP i el port).  ● addrlen​: longitud en ​bytes de ​dest_addr​, normalment s’utilitzarà ​sizeof(struct                  sockaddr_in)​ si el tipus de ​dest_addr​ és ​sockaddr_in​.     A tall de curiositat, podem comentar que l’única diferència entre la funció ​write() i la funció                                send() és la presència dels ​flags​, i per tant, si utilitzem el valor 0 com argument per aquest                                    paràmetre podem dir que la funció ​write() és equivalent a la funció ​send()​. De la mateixa                                manera, la següent crida:    send(sockfd, buffer, len, flags);    és equivalent a aquesta:    sendto(sockfd, buffer, len, flags, NULL, 0);    Ferran Verdés Castelló   Pàgina 11 de 22   
  12. 12.   Introducció als ​Sockets  3 de Febrer de 2015    8. Tancar els sockets (tancar les connexions) Finalment, només ens quedar parlar de les funcions que realitzen el tancament del ​socket​.                            Tal i com ha succeït en els apartats de lectura i escriptura, podem utilitzar la crida al sistema                                    que ens serveix per tancar una descriptor de fitxer:    #include <unistd.h>    int close(int fd);    Aquesta funció, si no s’ha produït cap problema retornarà 0 i en cas contrari retornarà ­1.    Paràmetres de la funció  ● fd​: descriptor de fitxer (ja pot ser del ​socket​ creat anteriorment per la funció ​socket()​ ​o  bé per ​accept()​).     Cal comentar però, que també disposem d’una funció proporcionada per la ​Network API                          Berkley Sockets​:    #include <sys/socket.h>    int shutdown(int sockfd, int how);    Si tot ha funcionat correctament la funció retornarà 0, en canvi si s’ha produït algun error                                retornarà ­1.    Paràmetres de la funció  ● sockfd​: descriptor de fitxer del socket (ja pot haver estat creat per la funció ​socket() ​o                                bé per la funció ​accept()​).   ● how​: canvia la usabilitat del ​socket​. Si s’envia 0 el ​socket no podrà rebre més dades,                                si s’envia 1 no podrà enviar més dades i si s’envia 2 no podrà ni rebre ni enviar més                                      dades.    La diferència respecte la funció ​close() és que la funció ​shutdown() no tanca el descriptor de                                fitxer sinó que simplement l’inhabilita.   4. Tot plegat, ho passem a codi? Són moltes les funcions que s’han vist fins aquest punt del document i comprensiblement,                            resulta impossible mantenir un esquema mental de tot el funcionament. Per aquest motiu, a                            continuació trobarem petits programes exemplars que implementen les funcions que s’han                      descrit. Abans però, cal comentar que, tal i com podem haver intuït en la lectura dels punts                                  anteriors, algunes funcions tenen un objectiu “advers” a unes altres, és a dir, unes funcions                              esperen una connexió i altres la sol∙liciten. Aquest fet succeeix per la necessitat de poder                                Ferran Verdés Castelló   Pàgina 12 de 22   
  13. 13.   Introducció als ​Sockets  3 de Febrer de 2015    implementar una arquitectura client/servidor, en la qual el servidor ofereix algun servei que ha                            de cobrir les necessitats del client. Així doncs, per tal de fer una representació més exacta a                                  allò que trobem a la realitat, els següents programes estan pensats tenint en ment aquesta                              arquitectura, és a dir, mantenint l’esquema d’un client i un servidor.    En primer lloc, trobem un programa extremadament senzill. Per una banda tenim un servidor                            que espera acceptar una connexió (i això vol dir que parlem d’un ​socket TCP) i per l’altra, un                                    client que realitza aquesta connexió:    Codi del servidor:                Ferran Verdés Castelló   Pàgina 13 de 22   
  14. 14.   Introducció als ​Sockets  3 de Febrer de 2015    Codi del client:        Com veiem al codi del client, l’adreça IP del servidor s’ha introduït manualment a partir de la                                  seva representació en hexadecimal. I això no és una mica primitiu? Sí, i per aquest motiu, la                                  Network API ​Berkley Sockets també proporciona mitjans per facilitar aquesta tasca. Més                        concretament, proporciona una funció com aquesta:    #include <netdb.h>    struct hostent* gethostbyname(const char* name);    La qual retorna ​NULL si no s’ha trobat el host o bé si durant la seva execució s’ha produït                                      algun problema. En canvi, si el procediment ha funcionat retorna un punter a una estructura                              hostent​:     struct hostent {  char* h_name; /* Nom de la màquina */  char** h_alias; /* Array d’àlies de la màquina. L’últim element és NULL */  int  h_addrtype; /* Tipus d’adreça IP (p.e AF_INET o AF_INET6) */  int h_lenght; /* Longitud de l’adreça IP. Serà 4 per IPv4 */    Ferran Verdés Castelló   Pàgina 14 de 22   
  15. 15.   Introducció als ​Sockets  3 de Febrer de 2015    char** h_add_list; /* Array de les IPs de la màquina. L’últim element és NULL */  char* h_addr; /* És un àlies de h_addr_list[0], apunta a la primera IP */  }    Amb aquesta crida s’obté informació, tal com l’adreça IP, que té la màquina amb el nom que                                  s’ha especificat per paràmetre mitjançant ​const char* name​. Anem a veure com quedaria el                            codi anterior amb l’ús d’aquesta funció, tenint en compte que el servidor no rebrà cap                              modificació:    Codi del client:        Pel que fa al codi, podem veure com s’utilitza l’estructura de dades anomenada ​in_addr que                              hem comentat anteriorment. El seu ús en realitat podria abreviar­se, ja que l’únic que conté                              és la representació d’un ​unsigned ​long​, però per costum s’utilitza. Un altre aspecte rellevant                            del codi, és que els valors de l’estructura ​hostent ja es troben en el format de xarxa i per                                        Ferran Verdés Castelló   Pàgina 15 de 22   
  16. 16.   Introducció als ​Sockets  3 de Febrer de 2015    aquest motiu no és necessari fer la crida a ​htonl() en el moment en que assignem la IP del                                      servidor a l’estructura ​addr_server​.     Cal remarcar, que per a que aquest codi funcioni en el cas que tinguem el servidor en una                                    màquina local, el client ha d’haver afegit al fitxer “/etc/hosts” una entrada corresponent amb la                              traducció del nom del servidor a la seva corresponent adreça IP, ja que sinó el client llançarà                                  una pregunta DNS intentant resoldre el nom en qüestió i si el DNS no es troba correctament                                  configurat o bé és un DNS extern, i per tant, no en sap res del nostre servidor, li contestarà                                      “​No such name​” i aleshores el client no serà capaç d’omplir l’estructura ​hostent​.     Bé, a continuació anem a veure com el client i el servidor s’intercanvien alguna dada. En                                concret s’intercanviaran una estructura de dades com aquesta:    struct Data {  int id;  char name[16];  double avg;  }    Pot existir alguna mena de problema si volem enviar aquesta estructura? (La pregunta                          gairebé és una afirmació). Recordem que per estàndard hem d’enviar les nostres dades en                            format ​big­endian (encara que si nosaltres mateixos hem de ser els receptors podem fer­ho                            de la manera que vulguem), però no obstant, en aquest exemple seguirem el bon costum.                              Considerant que la màquina emissora treballa amb el format ​little­endian els atributs ​int i                            double són als que hem de capgirar l’ordre dels ​bytes, i ja sabem que per a l’atribut ​int tenim                                      una funció que ens realitzarà la conversió, ​htonl()​. Però, i per l’atribut ​double​? Anem a fer­ho                                nosaltres manualment:    double swapByteOrder(double src) {  int i;    double dst;  char* psrc = (char*) &src;  char* pdst = (char*) &dst;        for(i = 0; i < sizeof(double); i++) {  pdst[i] = psrc[(sizeof(double) ­ i) ­ 1];  }      return dst;  }      Ferran Verdés Castelló   Pàgina 16 de 22   
  17. 17.   Introducció als ​Sockets  3 de Febrer de 2015    Molt bé, ja ho tenim. Ara només ens queda veure com enviaríem i rebríem les dades i en quin                                      punt del codi, tant pel servidor com pel client, ho efectuaríem. Pel que fa al client, seria en                                    aquest punt:    /* 5. Iniciar/Sol∙licitar una connexió */  . . .     sendDataStruct(sockfd);    /* 8. Tancar el socket */  . . .     I pel que fa al servidor, molt semblant:    /* 4. Acceptar una connexió */  . . .    recvDataStruct(newSockfd);    /* 8. Tancar els sockets */  . . .    Tot seguit, anem a veure les dues funcions que utilitzarem per enviar i rebre les dades. En                                  primer lloc, la del client, que en aquest cas és l’encarregada d’enviar les dades:        Com podem veure, per tal de fer la comprovació del funcionament, s’han utilitzat les funcions                              read() i ​write() estàndard com les funcions ​recv() i ​send() que ens ofereix la ​Network API                                Berkley Sockets​ referents als ​sockets ​TCP.            Ferran Verdés Castelló   Pàgina 17 de 22   
  18. 18.   Introducció als ​Sockets  3 de Febrer de 2015    A continuació, la funció que rep l’estructura de dades al servidor:      5. Els ​sockets​, un món per descobrir Fins ara tot el que hem vist són exemples, i els exemples només són això, exemples. A                                  continuació, segueix una petita implementació en model client/servidor que ofereix una ​shell                        de comandes del servidor al client, és a dir, el client es connectarà al servidor i obtindrà un                                    consola d’aquest de la mateixa manera que si utilitzés ​telnet o ​ssh​, però tot implementat                              manualment a partir de ​sockets​ i de forma simplificada.                       ( espai en blanc intencionat )        Ferran Verdés Castelló   Pàgina 18 de 22   
  19. 19.   Introducció als ​Sockets  3 de Febrer de 2015    Codi del servidor            Ferran Verdés Castelló   Pàgina 19 de 22   
  20. 20.   Introducció als ​Sockets  3 de Febrer de 2015    Codi del client        Ferran Verdés Castelló   Pàgina 20 de 22   
  21. 21.   Introducció als ​Sockets  3 de Febrer de 2015    Com podem veure en el codi, s’utilitza l’estructura de dades que trobem a continuació, la qual                                ens permet determinar el temps de ​timeout​ que volem assignar al ​socket​:    #include <sys/time.h>    struct timeval {  long tv_sec; /* Segons */  long tv_usec; /* Microsegons */  }    Aquesta estructura es passada per paràmetre a la següent funció:    #include <sys/types.h>  #include <sys/socket.h>    int setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t optlen);    Aquesta funció és capaç de manipular la configuració establerta per un ​socket​. El que s’està                              fent en el codi del client és configurar el ​socket per a que quan s’estigui efectuant una lectura                                    de les dades que li arriben que aquestes ho facin obligatòriament en menys d’un segon de                                temps, ja que sinó les funcions de lectura expiraran el ​timeout i retornaran ­1. Anem a veure                                  amb més detall els paràmetres de la funció:  ● sockfd​: descriptor de fitxer del socket (ja pot haver estat creat per la funció ​socket() ​o                                bé per la funció ​accept()​).   ● level​: indica fins a quin nivell afectarà la configuració, normalment per a tot el ​socket                              mitjançant ​SOL_SOCKET​, però per exemple, es pot indicar que una opció només ha                          de ser interpretada pel protocol TCP mitjançant ​IPPROTO_TCP​.  ● optname​: nom de l’opció del ​socket​ que volem modificar.   ● optval​: punter al ​buffer​ que conté el nou valor de l’opció a modificar.  ● optlen​: mida del ​buffer​ en ​bytes​.     Aquesta funció retorna 0 si tot ha funcionat correctament o ­1 si s’ha produït algun error.               Ferran Verdés Castelló   Pàgina 21 de 22   
  22. 22.   Introducció als ​Sockets  3 de Febrer de 2015    Llicència del document   Per a fer ús d’aquest document, heu de respectar la següent llicència:      Llicència de Creative Commons Reconeixement­NoComercial­CompartirIgual 4.0 Internacional              Ferran Verdés Castelló   Pàgina 22 de 22   

×