IMPORTANT: Per accedir als fitxer de subversion: http://acacha.org/svn (sense password). Poc a poc s'aniran migrant els enllaços. Encara però funciona el subversion de la farga però no se sap fins quan... (usuari: prova i la paraula de pas 123456)

http://acacha.org/svn/LinuxProgramacio/moodle/sessio5/transparencies/ProgramacioXarxes.pdf
Alert.png Aquesta wiki forma part dels materials d'un curs
Curs: ProgramacioLinux
Fitxers: ProgramacioXarxes.pdf (ProgramacioXarxes.odp)
Repositori SVN: https://svn.projectes.lafarga.cat/svn/iceupc/LinuxProgramacio/moodle/sessio5/transparencies
Usuari: anonymous
Paraula de pas: sense paraula de pas
Autors: Sergi Tur Badenas
Recomanació/Actualització: tenir en compte eines més actuals com Node.js

Conceptes previs de xarxes

Nivell de Transport

Consulteu l'article Nivell_de_transport_TCP/IP.

Adreces IP i ports

Consulteu les següents seccions d'articles per a més informació:

Respecte als ports només dir que són números enters que van des de l'1 cap endavant. Normalment els primers ports (fins a 1024) estan reservats per a l'administrador del sistema. Podeu saber el límit màxim executant:

$ cat /usr/include/netinet/in.h  | grep IPPORT_RESERVED
  IPPORT_RESERVED = 1024,

Per tant, si voleu crear un servei que utilitzi aquest port heu de ser superusuari quan utilitzeu la crida de sistema bind.

Comandes Unix

Consulteu la secció Comandes de xarxa de l'article Xarxes Linux.

Comanda netstat

Amb la comanda netstat, entre d'altres coses, podem veure les connexions de xarxa establertes. És una comanda que pot ser molt útil per depurar aplicacions de xarxa. Veiem alguns exemples.

A l'executar netstat sense cap altre paràmetre addicional obtenim quelcom similar a:

$ netstat 
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 casa-linux.local:34388  nf-in-f99.google.co:www ESTABLISHED
tcp        0      0 casa-linux.local:42822  nf-in-f104.google.c:www ESTABLISHED
tcp        0      0 casa-linux.local:53474  nobel.upc.es:imaps      ESTABLISHED
tcp        0      0 casa-linux.local:44479  ik-in-f83.google.co:www ESTABLISHED
...............
Active UNIX domain sockets (w/o servers)
Proto RefCnt Flags       Type       State         I-Node Path
unix  2      [ ]         DGRAM                    8255     @/com/ubuntu/upstart
unix  2      [ ]         DGRAM                    8430     @/org/kernel/udev/udevd
unix  2      [ ]         DGRAM                    15960    @/org/freedesktop/hal/udev_event
unix  16     [ ]         DGRAM                    15849    /dev/log
.....................

Netstat ens mostra les connexions actives (sockets) tant per sockets d'Internet com sockets Unix. Si volem veure només les connexions d'un protocol especific hem d'utilitzar:

$ netstat --inet 
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 casa-linux.local:55542  ik-in-f17.google.co:www ESTABLISHED
tcp        0      0 casa-linux.local:53474  nobel.upc.es:imaps      ESTABLISHED
tcp        0      0 casa-linux.local:32910  mg-in-f18.google.co:www ESTABLISHED
tcp        0      0 casa-linux.local:32914  mg-in-f18.google.co:www ESTABLISHED
tcp        0      0 casa-linux.local:58467  ug-in-f91.google.co:www ESTABLISHED
tcp        0      0 casa-linux.local:37513  nobel.upc.es:imaps      ESTABLISHED
udp        0      0 localhost:32769         localhost:32769         ESTABLISHED  

Només ens mostra les connexions d'Internet.

$ nestat --unix
Active UNIX domain sockets (w/o servers)
Proto RefCnt Flags       Type       State         I-Node Path
unix  2      [ ]         DGRAM                    8255     @/com/ubuntu/upstart
unix  2      [ ]         DGRAM                    15960    @/org/freedesktop/hal/udev_event
unix  3      [ ]         STREAM     CONNECTED     33881    /tmp/.ICE-unix/6534
unix  3      [ ]         STREAM     CONNECTED     33880      

Hi ha altres protocols, però en la majoria de sistemes moderns no els utilitzem. Per exemple IPX:

$ netstat -a --ipx | more
netstat: no support for `AF IPX' on this system.

Netstat sense més paràmetres només ens mostra les connexions actives. Una connexió és activa si:

  • El socket d'internet està en estat ESTABLISHED
  • El socket unix està en estat CONNECTED

Si volem que ens mostrin totes les connexions amb independència del seu estat utilitzarem el paràmetre -a:

$ sudo netstat -a 
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp6       0      0 *:ssh                   *:*                     LISTEN     
.........
udp        0      0 localhost:32769         localhost:32769         ESTABLISHED
.............
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node Path
unix  2      [ ACC ]     STREAM     LISTENING     29408    /tmp/orbit-sergi/linc-1aca-0-413751a4e26db
........
unix  3      [ ]         STREAM     CONNECTED     33881    /tmp/.ICE-unix/6534
........  

Ara anem a veure amb més detall les dades que ens mostra netstat per a les connexions d'internet:

$ netstat --inet -a | more
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 *:nfs                   *:*                     LISTEN     
tcp        0      0 *:9571                  *:*                     LISTEN     
tcp        0      0 localhost:postgresql    *:*                     LISTEN     
tcp        0      0 casa-linux.local:39168  ik-in-f83.google.co:www ESTABLISHED
tcp        0      0 casa-linux.local:53474  nobel.upc.es:imaps      ESTABLISHED
udp        0      0 localhost:32769         localhost:32769         ESTABLISHED
udp        0      0 casa-linux.l:netbios-ns *:*                                
udp        0      0 casa-linux.l:netbios-ns *:*                                  

La primera columna ens mostra el protocol que normalment és tcp, udp o raw.

Si observem la primera línia:

tcp        0      0 *:nfs                   *:*                     LISTEN     

Veiem que és un socket tcp obert a la nostra màquina i en estat LISTEN. Això normalment és un servidor (en aquest cas el servidor NFS) que està a l'espera de rebrer peticions de connexió NFS. L'asterisc de Local Address significa que s'ha utilitzat ANY_ADDR al fer el bind del socket. Si us fixeu a la línia:

tcp        0      0 localhost:postgresql    *:*                     LISTEN

El bind només s'ha fet a localhost i per tant només s'accepten connexions locals al servidor postgresql.

Els dos asteriscs de la Foreign Adress indiquen que no s'ha establert cap connexió ja que el socket està en estat LISTEN.

Tant Local Address com Foreign Address ens indiquen l'adreça IP o nom de màquina i el servei o port al que ens connectem. Tenim diferents opcions:

  • *:*: S'utilitza amb l'estat LISTEN.
  • localhost:ssh: Si el servei es troba indicat al fitxer /etc/services netstat ens mostra el nom del servei en comptes del número de port.
  • localhost:22: Si el servei no està indicat al fitxer /etc/services o utilitzem el paràmetre -n es mostra el número de port.
  • 127.0.0.1:ssh: Si no hi ha resolució de DNS per a la màquina de la connexió o utilitzant el paràmetre -n aleshores es mostren les adreces IP de les connexions.

Com podeu veure a la línia:

tcp        0      0 casa-linux.local:39168  ik-in-f83.google.co:www ESTABLISHED

Si la connexió està activa (ESTABLISHED) podem veure tant el Local Address com Foreign Address.

Anem ara a veure amb més detall els sockets unix (--unix):

$ netstat --unix -an | more
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node Path
unix  2      [ ACC ]     STREAM     LISTENING     17187    /var/run/avahi-daemon/socket
unix  2      [ ]         DGRAM                    8255     @/com/ubuntu/upstart
unix  2      [ ]         DGRAM                    8430     @/org/kernel/udev/udevd
unix  2      [ ACC ]     STREAM     LISTENING     29263    /tmp/orbit-sergi/linc-1ab9-0-17bdb1062ef9d
unix  2      [ ACC ]     STREAM     LISTENING     29343    /tmp/orbit-sergi/linc-1abd-0-17bdb106b3882
unix  3      [ ]         STREAM     CONNECTED     33878      

Com podem veure hi ha dos tipus de sockets (Orientats a connexió: STREAM i no orientats a connexió DGRAM). En el cas del sockets unix l'identificador d'un socket és el seu Camí (Path) absolut, a diferència dels sockets d'Internet que es diferencien per l'adreça IP i número de port. Els sockets Unix són un tipus de fitxer especial com podem veure si executem:

$ ls -la /var/run/avahi-daemon/socket
srwxrwxrwx' 1 avahi avahi 0 2007-05-09 07:43 /var/run/avahi-daemon/socket

Recursos:

Sockets

Java

Unix Domain sockets

Domini unix. PF_UNIX, PF_LOCAL

Un socket Unix està determinat pel seu camí dins del sistema de fitxers. Els sockets a Unix són un tipus de fitxer tal i com podreu apreciar si executeu:

$ ls -la /var/run
total 92
srw-rw-rw-  1 root        root           0 2007-05-08 07:51 acpid.socket
........
srw-rw-rw-  1 root        root           0 2007-05-08 07:51 gdm_socket
srw-rw-rw-  1 root        root           0 2007-05-08 07:52 sdp
 .......
srwxr-xr-x  1 root        root           0 2007-05-08 07:51 vmnat.5762
.........

El caràcter s indica que el fitxer és un socket.

L'estructura de l'adreça és:

#define UNIX_PATH_MAX    108
 
struct sockaddr_un {
    sa_family_t    sun_family;               /* AF_UNIX */
    char           sun_path[UNIX_PATH_MAX];  /* pathname */
};


Domini IP. PF_INET

L'interfície de programació és compatible amb els sockets BSD. Un socket ip es crea mitjançant la crida de sistema socket. Els sockets vàlids amb IP són:

  • SOCK_STREAM: Per un socket TCP.
  • SOCK_DGRAM: Per un socket UDP
  • SOCK_RAW: Per uns socket RAW (permet accedir al protocol IP directament...)

L'únic valor del paràmetre protocol (el tercer de la crida de sistema #socket) vàlid per UDP i TCP és 0:

tcp_socket = socket(PF_INET, SOCK_STREAM, 0); 
udp_socket = socket(PF_INET, SOCK_DGRAM, 0); 

Pel protocol RAW cal indicar un protocol vàlid:

raw_socket = socket(PF_INET, SOCK_RAW, protocol); 

Quan un socket vol rebre noves connexions o paquets entrants ha de lligar (bind) el socket a l'adreça local d'una interfície de xarxa amb la crida de sistema bind. Només un socket pot ser assignat per cada parella adreça IP - port.

Es pot utilitzar la constant INADDR_ANY per fer un bind. Amb aquesta IP especial, el bind no es vincula a cap port concret de les interfícies de xarxa locals. Quan les crides al sistema listen o connect són utilitzades amb un socket d'aquest tipus s'utilitza un port aleatori d'entre els ports lliures de l'adreça local per tal d'establir la comunicació.

Estructura de l'adreça:

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    u_int16_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
    u_int32_t      s_addr;     /* address in network byte order */
};

Recursos:

Crides de sistema

Socket

int socket(int domain, int type, int protocol);

Els includes necessaris són:

#include <sys/types.h>
#include <sys/socket.h>

Aquesta crida al sistema crea un socket. El valor de retorn és l'identificador del socket (file descriptor).

Domain:

Hi ha diferents dominis possibles definits al fitxer sys/socket.h (realment està al fitxer bits/socket.h):

PF_UNIX, PF_LOCAL Local communication. Veieu man unix(7) PF_INET IPv4 Internet protocols. Veieu man ip(7)

Altres:

  • PF_INET6: IPv6 Internet protocols
  • PF_IPX: IPX - Novell protocols
  • PF_NETLINK: Kernel user interface device netlink(7)
  • PF_X25: ITU-T X.25 / ISO-8208 protocol x25(7)
  • PF_AX25': Amateur radio AX.25 protocol
  • PF_ATMPVC: Access to raw ATM PVCs
  • PF_APPLETALK: Appletalk ddp(7)
  • PF_PACKET: Low level packet interface packet(7)

Els dominis que més s'utilitzen són el PF_UNIX, PF_LOCAL per comunicació entre processos Linux i PF_INET per comunicacions TCP/IP.

NOTA: Les constats AF_* (Adress Families) són equivalents a les constants PF_* (Protocol Families), tal i com es pot observar al fitxer bits/socket.h.

type:

  • SOCK_STREAM: Mode orientat a connexió, seqüencial, segur i bidireccional.
  • SOCK_DGRAM: Mode no orientat a connexió. DATAGRAMES/Paquets.

Altres:

  • SOCK_SEQPACKET
  • SOCK_RAW
  • SOCK_RDM
  • SOCK_PACKET

Protocol:

Indica el número del protocol. Els números de protocols es poden consultar al fitxer /etc/protocols:

$ cat /etc/protocols
ip      0       IP              # internet protocol, pseudo protocol number
#hopopt 0       HOPOPT          # IPv6 Hop-by-Hop Option [RFC1883]
icmp    1       ICMP            # internet control message protocol
...............
tcp     6       TCP             # transmission control protocol
...............
udp     17      UDP             # user datagram protocol  

Per als tipus SOCK_STREAM i SOCK_DGRAM s'utilitza sempre el protocol 0. Si utilitzem SOCK_RAW s'ha d'escollir el número de protocol amb el qual treballem.

Snippets

TCP Socket:

int socket_fd;
if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
   perror("Error obrint el socket\n");
   exit(1);
}

UDP Socket:

int socket_fd;
if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
   perror("Error obrint el socket\n");
   exit(1);
} 

RAW_SOCKET:

int socket_fd;
if ((socket_fd = socket(PF_INET, SOCK_RAW, 0)) == -1) {
   perror("Error obrint el socket\n");
   exit(1);
} 

Recursos:

Bind

int bind (int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);

Els includes necessaris són:

#include <sys/types.h>
#include <sys/socket.h>

La funció bind associa un procés a un socket. La crida al sistema socket només inicialitza el socket. Amb bind li assignem una adreça (una IP o un camí a un fitxer segons els tipus de socket). Tradicionalment a aquesta acció també se l'anomena "asignar un nom al socket".

El primer paràmetre és l'identificador de fitxer del socket al qual volem associar el procés. Aquest identificador l'obtenim com a valor de retorn de la crida al sistema socket.

L'adreça depèn del domini del socket. Podeu consultar les seccions PF_UNIX, PF_LOCAL i PF_INET per comunicació entre processos Linux i per comunicacions TCP/IP respectivament.

Recursos:


Snippets

struct sockaddr_in sin;
int sock_descriptor;
int port = 8000;  

sock_descriptor = socket(AF_INET, SOCK_STREAM, 0); 

//Inicialització de l'estructura sockaddr_in
memset(&sin, 0 , sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(port); 

if (bind (sock_descriptor, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
  perror("Error al cridar bind...");
  exit(1);
}

listen

int listen (int sockfd, int backlog); 

Els includes necessaris són:

#include <sys/socket.h>

La crida al sistema listen només s'aplica en els sockets orientats a connexió (tipus SOCK_STREAM or SOCK_SEQPACKET).

El primer argument és descriptor de fitxer d'un socket (valor obtingut prèviament amb la crida al sistema socket). El segon paràmetre (backlog) determina la mida màxima de la cua de peticions en espera.

Recursos:


Snippet

//Creació d'una cua d'espera de 20 connexions
if (listen(sock_descriptor, 20) == -1) {
  perror("Error al cridar listen...");
  exit(1);
}

Accept

int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen);

Els includes necessaris són:

#include <sys/types.h>
#include <sys/socket.h>

La crida de sistema accept() només s'utilitza amb els sockets orientats a connexió (SOCK_STREAM, SOCK_SEQPACKET). Accept extreu la primera petició de connexió de la cua de peticions pendents i crea un nou socket per establir la comunicació entre client i servidor. El socket nou passa a l'estat d'escolta (listening). El socket original no es veu afectat per accept.

Per tant, l'ordre de crides al sistema en un servidor amb un socket orientat a connexió és:

  • socket()
  • bind()
  • listen()
  • accept()

Recursos:


Snippet

El més normal en un servidor és posar la crida al sistema accept en un bucle infinit de tal manera que puguem processar infinites peticions de clients:

struct sockaddr_in pin;
int sock_descriptor;
int temp_sock_descriptor;
int address_size;
while(1) {
   address_size=sizeof(pin)
   temp_sock_descriptor = accept(sock_descriptor, (struct sockaddr *)&pin, &address_size);
   if (temp_sock_descriptor == -1) {
     perror("Error al cridar accept...");
     exit(1);
   }
   
   // Codi amb la comunicacions entre client i servidor.... 
   // Veieu les crides de sistema recv i send

  close(temp_sock_descriptor);

}

Connect

int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
#include <sys/types.h>
#include <sys/socket.h> 

La crida de sistema connect s'utilitza per connectar un socket local a un servei remot.


Snippet

int socket_fd;
struct sockaddr_in adreca;
char * host_name = "127.0.0.1"; // 
int port = 8000;

if ((server_host_name = gethostbyname(host_name)) == 0) {
   perror("Error resolent l'adreça del servidor\n");
   exit(1);
}   

memset(&adreca, 0, sizeof(adreca));
adreca.sin_family = AF_INET;
adreca.sin_addr.s_addr = htonl(INADDR_ANY);
adreca.sin_addr.s_addr = ((struct in_addr *)(server_host_name->h_addr))->s_addr;
adreca.sin_port = htons(port);  

if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
  perror("Error creant el socket\n\n");
  exit(1);
} 

if (connect(socket_fd, (void *)&adreca, sizeof(adreca)) == -1) {
  perror("Error conectant-se al socket\n");
  exit(1);
}

Recursos:

Recv

ssize_t recv(int s, void *buf, size_t len, int flags);
ssize_t recvfrom(int s, void *buf, size_t len, int flags,
                     struct sockaddr *from, socklen_t *fromlen);
ssize_t recvmsg(int s, struct msghdr *msg, int flags);
#include <sys/types.h>
#include <sys/socket.h>

La crida de sistema recv() s'utilitza només en sockets connectats (veieu connect) i és idèntica a la crida de sistema recvfrom() amb el paràmetre from establert a NULL.

Les crides recvfrom() i recvmsg() són utilitzades per rebre missatges des d'un socket i poden ser utilitzades tant per a comunicacions orientades a connexió com no.

Les tres rutines tenen com a valor de retorn la longitud del missatge en cas d'èxit. Si el missatges és massa llarg per al buffer proveït els bits en excés poden ser descartats o no depenen del tipus de connexió.

Si no hi ha missatges al socket les crides recv es queden en espera del següent missatge a no ser que el socket estigui en mode de no bloqueig.

Les crides a sistema select(2) o poll poden ser utilitzades per veure si han arribat més dades.

El paràmetre flags es forma per operacions OR amb els següents valors:

  • MSG_DONTWAIT
  • MSG_ERRQUEUE
  • MSG_OOB
  • MSG_PEEK
  • MSG_TRUNC
  • MSG_WAITALL

Recursos:

Send

ssize_t send(int s, const void *buf, size_t len, int flags);
ssize_t sendto(int s, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
ssize_t sendmsg(int s, const struct msghdr *msg, int flags);
#include <sys/types.h>
#include <sys/socket.h>

Les crides de sistema send(), sendto() i sendmsg() s'utilitzen per a transmetre missatges cap a un socket.

La crida de sistema send() només es pot utilitzar amb sockets connectats (veieu connect). L'única diferència entre send() i write() és la presència de FALGS. Amb el paràmetre FLAGS a 0, send() és equivalent a write().

El paràmetre s' és el descriptor de fitxer del socket que envia el missatge.

El paràmetre flags és la combinació OR dels següents valors:

  • MSG_CONFIRM
  • MSG_DONTROUTE
  • MSG_DONTWAIT
  • MSG_EOR
  • MSG_MORE
  • MSG_NOSIGNAL
  • MSG_OOB

Però normalment s'estableix a 0.

Send() retorna la quantitat de dades enviades, que pot ser menor a la quantitat de dades del missatge. És responsabilitat del programador comparar les dades realment enviades amb les que es volien enviar i enviar les dades que no s'han enviat en la propera crida a send().

Recursos:

NOTA: A l'enviar missatges utilitzant un buffer de caràcters és millor que utilitzeu valors múltiples de 2 per evitar trobar-vos en valors brossa en cas d'enviar missatges curts. Alguns valors 2 a la n són: 2,4,8,16,32,64,128,256,512,1024,2048 etc....


Snippet

if (send(socket_fd, str, strlen(str), 0) == -1) {
   perror("Error enviant el missatge...\n");
   exit(1);
}

Estructura sockaddr

Aquesta estructura la tenim definida al fitxer socket.h de la següent forma:

struct sockaddr {

           sa_family_t sa_family;
           char        sa_data[14];
       }

S'utilitza a la crida al sistema bind.

Estats dels sockets

800px-TCP state diagram.png

SocketsDiagrama.png

TimeOutsTCPConnections.jpg

  • ESTABLISHED: Connexió establerta.
  • SYN_SENT: El socket està intentant establir de forma activa una connexió.
  • SYN_RECV: S'ha rebut una petició de connexió des de la xarxa
  • FIN_WAIT1: El socket està tancat i la connexió s'està apagant.
  • FIN_WAIT2: El socket està tancat i la connexió està esperant l'apagament de la connexió remota.
  • TIME_WAIT: El socket està esperant la recepció de paquets de la xarxa tot i haver-se apagat la connexió
  • CLOSE: El socket està apagat.
  • CLOSE_WAIT: La connexió remota s'ha apagat i s'està esperant que el socket es tanqui.
  • LAST_ACK: La connexió remota s'ha apagat i el socket està apagat però en espera d'un acknowledgement.
  • LISTEN: El socket està a l'espera de peticions de connexió.
  • CLOSING: Els dos sockets estan tancats però encara no s'han rebut totes les dades.

Altres funcions

gethostbyname()

#include <netdb.h>
extern int h_errno;

struct hostent *gethostbyname(const char *name);

#include <sys/socket.h>       /* for AF_INET */
struct hostent * 

gethostbyaddr(const void *addr, int len, int type); 

La funció gethostbyname() retorna una estructura de tipus hostent amb la informació de la màquina identificada pel nom de màquina passat com a paràmetre. El nom pot ser tant un nom de màquina com una adreça IPv4 amb la notació estàndard (P.ex. 127.0.0.1) o una adreça IPv6.

Fitxers:

  • /etc/hosts
  • /etc/host.conf
  • /etc/resolv.conf

L'estructura hostent està definida al fitxer netdb.h de la següent forma:

struct hostent {
       char    *h_name;        /* official name of host */
       char    **h_aliases;    /* alias list */
       int     h_addrtype;     /* host address type */
       int     h_length;       /* length of address */
       char    **h_addr_list;  /* list of addresses */
}
#define h_addr  h_addr_list[0]  /* for backward compatibility */
 
  • h_name: El nom oficial de la màquina.
  • h_aliases: Un array amb els noms alternatius.
  • h_addrtype: El tipus d'adreça (AF_INET o AF_INET6).
  • h_length: La longitud de l'¡adreça en bytes
  • h_addr_list: Un array amb punters a les adreces de xarxa del host.
  • h_addr: La primera adreça de la llista anterior (h_addr_list)

Recursos:

Htons(), htonl(), ntohl(), ntohs()0

  • htons(): Host to network small.
  • htonl(): Host to network small.
  • ntohl(): Network to host long.
  • ntohs(): Network to host small.

Host fa referència a la forma d'escriure adreces (tant adreces IPs com números de port) més humana. Exemples:

  • IP: 127.0.0.1, 214.215.63.35 ,etc.
  • Port: 80, 8080, 25, etc.

Els tipus de dades small s'utilitzen normalment pels ports i els large per a les adreces IP.

Network fa referència a les adreces en format de quatre parells hexàdecimals (network byte order).

Exemples:

  • INADDR_ANY: 0x00000000 --> 0.0.0.0
  • INADDR_BROADCAST: 0xffffffff --> 255.255.255.255
  • INADDR_LOOPBACK: 0x7f000001--> 127.0.0.1.

Snippet:

char * host_name = "127.0.0.1"; // 
int port = 8000; 
struct sockaddr_in adreca;
struct hostent *server_host_name;
  
if ((server_host_name = gethostbyname(host_name)) == 0) {
  perror("Error resolent l'adreça del servidor\n");
  exit(1);
}

memset(&adreca, 0, sizeof(adreca));
adreca.sin_family = AF_INET;
adreca.sin_addr.s_addr = htonl(INADDR_ANY);
adreca.sin_addr.s_addr = ((struct in_addr *)(server_host_name->h_addr))->s_addr;
adreca.sin_port = htons(port);

Recursos:

Getprotoent(), getprotobyname() i getprotobynumber()

#include <netdb.h>
struct protoent *getprotoent(void);
struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
void setprotoent(int stayopen);
void endprotoent(void);

Recursos:

Getservent(), getservbyname(), getservbyport(), setservent(), endservent()

#include <netdb.h>
struct servent *getservent(void); 
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
void setservent(int stayopen);
void endservent(void);
  • Getservent(): function reads the next line from the file /etc/services and returns a structure servent containing the broken out fields from the line. The /etc/services file is opened if necessary
  • Getservbyname(): function returns a servent structure for the line from /etc/services that matches the service name using protocol proto. If proto is NULL, any protocol will be matched.
  • Getservbyport(): Retorna una estructura servent amb la informació del servei function returns a servent structure for the line that matches the port port given in network byte order using protocol proto. If proto is NULL, any protocol will be matched
  • setservent(): function opens and rewinds the /etc/services file. If stayopen is true (1), then the file will not be closed between calls to getservbyname() and getservbyport().
  • Endservent(): Tanca el fitxer /etc/services.

L'estructura servent és: structure is defined in <netdb.h> as follows:

struct servent {
        char    *s_name;        /* official service name */
        char    **s_aliases;    /* alias list */
        int     s_port;         /* port number */
        char    *s_proto;       /* protocol to use */
}
  • s_name: Nom oficial del servei
  • s_aliases: Noms alternatius del servei
  • s_port: El número de port del servei.
  • s_proto: El nom del protocol que utilitza aquest servei (tcp/udp)

Recursos:


CONSTANTS

Adreces IP:

  • INADDR_LOOPBACK: 127.0.0.1 (0x7f000001) localhost a través del dispositiu de loopback.
  • INADDR_ANY: 0.0.0.0 (0x00000000) significa qualsevol adreça per a fer el binding.
  • INADDR_BROADCAST: 255.255.255.255 (0xffffffff) significa qualsevol host. Té el mateix efecte que INADDR_ANY al fer un bind per raons històriques.

Fitxers

/etc/services

Veieu /etc/services.

/etc/protocols

Veieu el fitxer /etc/protocols

Exemple Client-Servidor. Echo TCP

Podeu trobar l'exemple a:

http://xarxantoni.net/~sergi.tur/docs/cursplinux/sessio5/ClientServerEcho/

O descarregar-lo del servidor SVN:

svn checkout https://svn.lafarga.cpl.upc.edu/dades/svn/plinux/sessio5/ClientServerEcho
  • Usuari:anonymous
  • Contrasenya:anonymous


Servidor

El servidor d'aquest exemple és un servidor d'echo. El servidor enviarà al client exactament el mateix missatge que ha enviat el client.

Després de les declaracions de variables, el primer que fa el servidor és declarar el socket:

sock_descriptor = socket(AF_INET, SOCK_STREAM, 0);

Com podeu veure s'utilitza la crida de sistema socket per tal de crear un socket d'Internet (PF_INET/AF_INET) orientat a connexió (SOCK_STREAM). TCP/IP.

Durant tot el codi del servidor el control d'errors és molt similar tal i com podem veure a les següents línies:

if (sock_descriptor == -1) {
   perror("Error al cridar socket...");
   exit(1);
 }

Bàsicament s'envia un missatge d'error al canal estàndard d'error amb la funció perror i es finalitza el programa si qualsevol de les crides de sistema retorna el valor -1.

El següent és lligar aquest socket a un parell adreça IP -port. Per això hem d'utilitzar la crida de sistema bind prèvia configuració de l'estructura de dades sockaddr_in:

memset(&sin,0 , sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(port);
if (bind(sock_descriptor, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
  perror("Error al cridar bind...");
  exit(1);
}

La funció memset reserva memòria per a l'estructura.

Al tractar-se d'un socket AF_INET l'estructura de dades és la següent:

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    u_int16_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
    u_int32_t      s_addr;     /* address in network byte order */
};

Tant les adreces IP com els ports estan en format network byte order i per aquesta raó cal utilitzar la funció de conversió htons() pel port. La constant INADDR_ANY ja està en aquest format i no cal fer cap conversió.

La constant INADDR_ANY ens permet escoltar peticions des de qualsevol de les adreces IP que tingui el servidor.

El següent és la crida de sistema listen:

if (listen(sock_descriptor, 20) == -1) {
   perror("Error al cridar listen...");
   exit(1);
 }

Listen s'utilitza en comunicacions orientades a connexió com la del nostre exemple (TCP). En aquest cas hem establert una cua de 20.

Ara ja tenim un socket a l'espera de rebre peticions. Un cop rebrem una petició, el servidor ha d'establir una nova connexió a través d'un nou socket per a cada petició que ens arribi. Per aquesta raó s'utilitza un bucle infinit i dins del bucle la crida de sistema accept:

while(1) {
   temp_sock_descriptor = accept(sock_descriptor, (struct sockaddr *)&pin,&address_size);
   if (temp_sock_descriptor == -1) {
     perror("Error al cridar accept...");
     exit(1);
   } 
   ....................
 }
}

NOTA: Tingueu en compte que aquest exemple processa les peticions d'una en una. Sovint s'utilitza la crida de sistema fork per tal de processar múltiples peticions al mateix temps.

La crida de sistema accept extreu una nova petició de la cua i crea un nou socket per establir la comunicació client-servidor.

NOTA: Noteu que a partir d'aquest punt ja no toquem el socket que escolta noves peticions (sin) i passem a treballar amb el socket pin.

Ara només queda rebre el missatge del client amb la crida de sistema recv, tornar l'echo amb send i tancar el nou socket amb close:

..................
if (recv(temp_sock_descriptor, buf, 16384, 0) == -1) {
   perror("Error al cridar recv...");
   exit(1);
}

printf("S'ha rebut del client el següent String:%s\n", buf);
 
if (send(temp_sock_descriptor, buf, strlen(buf), 0) == -1) {
   perror("Error al cridar send");
   exit(1);
}
close(temp_sock_descriptor);
.................... 


Compilant, executant i provant el servidor

Per compilar podeu executar:

$ gcc -o server server.c

O utilitzar el [*Codi font: Makefile de l'exemple:

$ make

Per executar el servidor només cal fer:

./server

I per provar que funciona podem utilitzar telnet:

$ telnet localhost 8000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
hola
hola
Connection closed by foreign host. 

Client

El primer que fem és la creació del socket:

if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
   perror("Error creant el socket\n");
   exit(1);
 }  

Com podeu veure s'utilitza la crida de sistema socket per tal de crear un socket d'Internet (PF_INET/AF_INET) orientat a connexió (SOCK_STREAM). El control d'error és molt similar al cas del servidor.

El següent és la crida de sistema connect i tota la preparació prèvia a la crida.

if ((server_host_name = gethostbyname(host_name)) == 0) {
  perror("Error resolent l'adreça del servidor\n");
  exit(1);
}
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_addr.s_addr = ((struct in_addr *)(server_host_name->h_addr))->s_addr;
sin.sin_port = htons(port);
if (connect(socket_fd, (void *)&sin, sizeof(sin)) == -1) {
  perror("Error conectant-se al socket\n");
  exit(1);
}

I un cop hem fet la connexió només cal enviar el missatge amb la crida de sistema send, rebre la resposta amb recv i tancar el socket amb close:

printf("Enviant missatge %s al servidor...\n", str);
if (send(socket_fd, str, 8192, 0) == -1) {
  perror("Error enviant el missatge...\n");
  exit(1);
}
printf("..Misatge enviat .. esperant la resposta...\n");
if (recv(socket_fd, buf, 8192, 0) == -1) {
  perror("Error reben la resposta del servidor...\n");
  exit(1);
}
printf("\nResposta del servidor:\n\n%s\n", buf);
close(socket_fd);

Exemple UDP. Enviament de missatges UDP

Podeu trobar descarregar l'exemple aquí.

O descarregar-lo del servidor SVN:

svn checkout https://svn.lafarga.cpl.upc.edu/dades/svn/plinux/sessio5/udp


Receptor

El codi font de l'exemple amb UDP no és gaire diferent de l'exemple amb TCP. Anem a comentar les diferències. La primera és la creació del socket:

if ((socket_descriptor = socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
  perror("Error obrint el socket\n");
  exit(1);
}

Utilitzem el tipus SOCK_DGRAM en comptes de SOCK_STREAM.

L'estructura del socket continua sent del tipus sockaddr_in i s'inicialitza l'estructura i s'executa bind de forma anàloga a l'exemple TCP:

bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_port = htons(port);
if (bind(socket_descriptor, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
   perror("Error cridant a bind");
}

Una diferència important és que aquest cop no estem utilitzant una arquitectura client-servidor, sinó que simplement enviem un conjunt de dades d'un emissor a un receptor. Per aquesta raó el bucle no és infinit, sinó que finalitza quan arriba un sentinella informant de l'acabament de la transmissió (missatge "stop").

while (1) {
   sin_len = sizeof(sin);
   if (recvfrom(socket_descriptor, message, 256, 0,  

(struct sockaddr *)&sin, &sin_len) == -1) { 

     perror("Error rebent resposta del servidor\n");
   }

   printf("Resposta del servidor:\n%s\n", message);
   if (strncmp(message, "stop", 4) == 0) break;
 }
 close(socket_descriptor);
}  

A diferència de l'exemple TCP no necessitem les crides de sistema listen() i accept() per establir la comunicació. Ara bé, al rebre el missatge amb la crida de sistema recvfrom, hem d'indicar quin socket volem utilitzar:

if (recvfrom(socket_descriptor, message, 256, 0, 

(struct sockaddr *)&sin, &sin_len) == -1) { 

   perror("Error rebent resposta del servidor\n");
}


Compilant, executant i provant el receptor

Per compilar podeu executar:

$  gcc -o recieve recieve.c

O executar make:

$ make

Ara executeu el receptor amb:

./recieve

Podeu executar:

$ netstat -a -u --inet -c| grep 6789

Per veure de forma continua la recepció de dades pel port 6789.

Emissor

Un altre cop els canvis respecte a l'exemple TCP es centren en la creació del socket (utilitzem SOCK_DGRAM ):

 socket_descriptor = socket(AF_INET, SOCK_DGRAM, 0);

I en que no utilitzem la crida de sistema accept() sinó que enviem el missatge directament amb la crida sendto():

do {
   sprintf(buf,"paquet de dades amb l'identificador: %d\n", iter);
   if (iter >20) {
     sprintf(buf, "stop\n");
     process = 0;
   }
   if (sendto(socket_descriptor,

buf, sizeof(buf), 0, (struct sockaddr *)&address, sizeof(address)) < 0) {  

      perror("Error utilitzant la crida de sistema sendto()");
     exit(1);
   }
   iter++;
 } while (process);
}  

En aquest cas el que es fa és enviar 20 missatges al receptor. A la crida sendto hem d'indicar el socket al qual li volem enviar les dades:

sendto(socket_descriptor, buf, sizeof(buf), 0, (struct sockaddr *)&address, sizeof(address))

Compilant, executant i provant el receptor

Per compilar podeu executar:

$  gcc -o send send.c

O executar make:

$ make

Ara executeu el receptor amb:

$ ./send

Podeu executar:

$ netstat -a -u --inet -c

Per saber quin port us assignat el sistema localment.


Exemple UNIX. Enviament de missatges

Podeu trobar descarregar l'exemple aquí.

O descarregar-lo del servidor SVN:

svn checkout https://svn.lafarga.cpl.upc.edu/dades/svn/plinux/sessio5/unix

Receptor

El codi font de l'exemple UNIX és idèntic a l'exemple amb UDP. Anem a comentar les diferències. La primera és la creació del socket:

if ((socket_descriptor = socket(AF_LOCAL, SOCK_DGRAM, 0)) == -1) {
  perror("Error obrint el socket\n");
  exit(1);
}

Utilitzem el domini AF_LOCAL (o AF_UNIX o PF_UNIX o PX_LOCAL) en comptes de AF_INET. En aquest cas també utilitzarem una comunicació no orientada a connexió (SOCK_DGRAM).

L'estructura del socket ha canviat éssent del tipus sockaddr_un i s'inicialitza l'estructura i s'executa bind de forma anàloga a l'exemple UDP:

bzero(&sun, sizeof(sun));
sun.sun_family = AF_LOCAL;
strcpy(sun.sun_path,"/tmp/mysocket");

if (bind(socket_descriptor, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
  perror("Error cridant a bind");
}

NOTA: Per poder utilitzar la nova estructura cal afegir la capçalera:

#include <sys/un.h>

Cal destacar que ara l'identificador del socket és un fitxer del sistema:

$ ls -la /tmp/mysocket 
srwxr-xr-x 1 sergi sergi 0 2007-05-10 17:09 /tmp/mysocket 

La resta del codi és tot igual que l'exemple UDP.


Compilant, executant i provant el receptor

Per compilar podeu executar:

$  gcc -o recieve recieve.c

O executar make:

$ make

Ara executeu el receptor amb:

./recieve

Podeu executar:

$ netstat -a -u --unix -c| grep /tmp/mysocket

Emissor

Un altre cop els canvis respecte a l'exemple UDP es centren en la creació del socket (utilitzem AF_UNIX ):

socket_descriptor = socket(AF_UNIX, SOCK_DGRAM, 0);

També canvia la definició de l'estructura sockaddr_un:

memset(&address, 0, sizeof(address));
address.sun_family = AF_UNIX;
strcpy(address.sun_path,"/tmp/mysocket");

La resta del codi és idèntic.

Compilant, executant i provant el receptor

Per compilar podeu executar:

$  gcc -o send send.c

O executar make:

$ make

Ara executeu el receptor amb:

$ ./send

Podeu executar:

$ netstat -a --unix | grep /tmp/mysocket


TroubleShooting. Solució de problemes

Error amb accept

Tal i com podem llegir si consultem el man d'accept:

$ man 2 accept
..................
The addrlen argument is a value-result argument: it should initially contain the size of the structure pointed to by addr; on return it will contain  
the actual length (in  bytes) of the address returned. When addr is NULL nothing is filled in.

Cal iniciar la variable address_size a la mida del l'estructura sockaddr de la següent forma:

address_size=sizeof(pin);
temp_sock_descriptor =accept(sock_descriptor, (struct sockaddr *)&pin,&address_size);

Si no inicialitzem el valor, depenent del que hi hagi en memòria en aquell moment, accept pot funcionar o donar un error.

: Bad address al utilitzar la crida de sistema send()

Si a l'utilitzar la crida de sistema send() obtenim el següent missatge:

Error enviant el missatge...
: Bad address

En general indica un problema amb el missatge/buffer que s'envia, per exemple que s'ha indicat de forma incorrecta la mida del buffer.

Vegeu també