Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Historia
Revision Version 1.0.0 August, 1995 Revised by: beej
Initial version.
Revision Version 1.5.5 January 13, 1999 Revised by: beej
Latest HTML version.
Revision Version 2.0.0 March 6, 2001 Revised by: beej
Converted to DocBook XML, corrections, additions.
Revision Version 2.0.3 April 2, 2001 Revised by: beej
inet_aton() return values corrected, selectserver changed, typos fixed, added
recvtimeout().
Traduccin al espaol Domingo 22 de Abril de 2001 por: K@osk
ndice
1. INTRODUCCIN
1.1. AUDIENCIA
1.2. PLATAFORMA Y COMPILADOR
1.3. PAGINA OFICIAL
1.4. NOTA PARA SOLARIS/SUNOS
1.5 NOTA PARA WINDOWS
1.6. MIRRORS
1.7. NOTA PARA LOS TRADUCTORES
1.8. COPYRIGHT Y DISTRIBUCIN
4
4
4
4
5
5
5
6
2. QU ES UN SOCKET?
7
8
11
12
13
4. LLAMADAS AL SISTEMA.
15
15
15
17
18
19
20
21
22
23
23
24
5. SISTEMA CLIENTE-SERVIDOR
26
26
28
30
34
6.1. BLOQUEANDO
6.2. SELECT() MULTIPLEXAMIENTO SINCRONIZADO DE E/S
6.3. MANEJANDO ENVOS (SEND()S ) PARCIALES
6.4. HIJO DE LA ENCAPSULACIN
34
34
40
41
7. MS REFERENCIAS
44
44
44
45
8. PREGUNTAS COMUNES
46
49
1. Introduccin
Hey! La programacin en Socket te tiene bajoneao? Esto es muy difcil para aprenderlo de
las pginas man? Quieres hacer un programa genial para Internet, pero no tienes tiempo
para gastar tratando de descifrar si tienes que llamar bind() antes de connect(), etc, etc.
Bueno, adivina! Yo ya he hecho este asqueroso trabajo, y me muero por compartir la
informacin con todos! Has llegado al lugar indicado. Este documento debera darle al
programador(a) C el apoyo que necesita para dedicarse a la programacin en red.
1.1. Audiencia
Este documento ha sido escrito como un tutorial, no como una referencia. Ser de mejor
ayuda a quienes estn empezando en la programacin con Sockets y estn buscando un
puntapi. Realmente no es la gua definitiva para programacin, pero todo vale.
Espero, fuertemente, que sea suficiente para que esas pginas man empiecen a tener
sentido... .
1.2. Plataforma y Compilador
Los ejemplos que contiene este documentos fueron compilados en un PC Linux usando el
compilador GNU gcc. Deberan, de todas maneras, poder ser construidos en cualquier
sistema que use gcc. Naturalmente, esto no se aplica si ests programando en Windows
(ve la seccin de Windows, abajo).
1.3. Pagina Oficial
La direccin oficial de este documento es:
http://www.ecst.csuchico.edu/~beej/guide/net/
La direccin de la traduccin es espaol es:
http://masev.iespana.es/masev/guias/npg.htm
1.6. Mirrors
Eres bienvenido si deseas hacer un mirror de este sitio. Ya sea pblico o privado. Si es
pblico y deseas que yo lo linkee desde la pgina oficial, trame un mail a
beej@piratehaven.org.
2. Qu es un Socket?
T escuchas sobre Sockets todo el tiempo, y quizs te est preguntado que son
exactamente. Bueno, son esto: una manera de habla a otro programa usando
descriptores de archivos Unix estndar.
Qu!?
Ok, tu debes haber escuchado que todo en Unix es un archivo. Lo que esto quiere decir
es que cuando un programa Unix realiza cualquier tipo de E/S, lo hacen leyendo o
escribiendo a un descriptor de archivo. Un descriptor de archivo es simplemente un
numero entero asociado a un archivo abierto. Pero (y aqu est la conexin), ese archivo
puede ser una conexin de red, un FIFO, un terminal, un archivo real en el disco, o
cualquier otra cosa. Todo en Unix es un archivo. As que cuando deseas comunicarte con
otro programa por Internet, vas a tener que hacerlo a travs de un descriptor de archivo,
mejor que lo creas.
Donde consigo este descriptor de archivo para comunicacin de Red, Mr. Capo? es
probablemente la ltima pregunta en tu cerebro, pero la voy a responder igual: Haces una
llamada a la rutina de sistema socket(). sta devuelve el descriptor del socket, y te
comunicas a travs de el usando las funciones especializadas send() y recv().
Pero, oye!,debes estar gritando ahora, Si es un descriptor, porque en el nombre de
Linus no puedo usar las llamadas read() y write() para comunicarme a travs del socket?
La respuesta simple es: S puedes! La respuesta real sera: Puedes, pero send() y
recv() ofrecen un mejor control sobre tu transmisin de datos.
Qu sigue? Que tal esto: hay todo tipo de sockets. Estn las direcciones de Internet
DARPA (sockets de Internet), nombres de ruta en un nodo local (sockets de Unix),
direcciones CCITT X.25 (sockets X.25 que se pueden ignorar), y probablemente muchos
otros dependiendo en que sabor de Unix ests. Este documento trabaja con el primero:
Sockets de Internet.
Porqu usar stream sockets? Bueno, debes haber escuchado de las aplicaciones telnet,
si? ... Ocupa stream sockets. Todos los caracteres que tipeas necesitan llegar en el
mismo orden en que los mandaste, correcto? Adems, los navegadores web usan el
protocolo HTTP el que usa stream sockets para obtener las pginas. De hecho, si tu
haces telnet a un sitio en el puerto 80, y tipeas GET /, te arrojar el cdigo html.
Cmo los stream sockets mantienen la alta calidad de esta transmisin de datos? Ellos
usan un protocolo llamado Protocolo de Control de Transmisin, conocido como TCP
(ve RFC-793 para detalles). TCP se asegura de que tus datos lleguen secuencialmente y
libre de errores. Debes haber escuchado acerca de TCP antes en TCP/IP donde IP
es Protocolo de Internet (RFC-791). IP trata primeramente el rutamiento en Internet y no
se responsabiliza por la integridad de los datos.
Cool. Que pasa con los Datagram sockets? Por qu son llamados sockets sin conexin?
Qu onda? Por qu no son confiables? Bueno, he aqu algunos hechos: si envas un
datagram, puede que llegue. Puede llegar desordenado. Si llega, los datos del paquete
estarn libres de errores.
Datagram sockets tambin usan IP para el rutamiento, pero ellos no usan TCP; ellos usan
Protocolo de Datagramas de Usuario o UDP (RFC-768).
Por qu sin conexin? Bueno, bsicamente, es porque no tienes que mantener una
conexin abierta como con stream sockets. Tu slo construyes un paquete, le pegas un
encabezado IP con la informacin del destino, y lo mandas. Son generalmente usados
para transferencia de informacin paquete por paquete. Algunas aplicaciones son: tftp,
bootp, etc.
Suficiente! puedes gritar. Como van a funcionar estos programas si los datagramas se
pierden? Bueno, amigo mortal, cada uno tiene su propio protocolo en la cima del UDP.
Por ejemplo, el protocolo tftp dice que por cada paquete enviado, el receptor debe enviar
un paquete de vuelta que diga, Lo tengo (un paquete ACK). Si el que enva el paquete
original no tiene respuesta en, digamos, 5 segundos, el retransmitir el paquete hasta que
finalmente le llegue un ACK. Este conocido procedimiento es muy importante cuando
implementamos aplicaciones SOCK_DGRAM.
luego todo (el encabezado TFTP incluido) es encapsulado de nuevo por el siguiente
protocolo (UDP, digamos), luego nuevamente por el siguiente (IP), luego de nuevo por el
protocolo final el capa (fsica) de hardware (sea Ethernet).
Cuando la otra computadora recibe el paquete, el hardware extrae el encabezado
Ethernet, el kernel hace lo mismo con los encabezados UDP e IP, el programa TFTP
extrae el encabezado TFTP, y al final slo queda el dato.
Ahora por fin puedo hablar de la infame Capa de modelo de Red. Esta describe un
sistema de red que tiene muchas ventajas sobre otros modelos. Por ejemplo, tu puedes
escribir programas de sockets que son exactamente iguales sin preocuparte de cmo se
transfieren los datos fsicamente (serial, thin Ethernet, AUI, como sea), ya que programas
en niveles inferiores hacen el trabajo por ti. El hardware y la topologa de red actuales son
transparentes para el programador de sockets.
Sin entrar en ms, te presento las capas del hablado modelo. Recuerda esto para tu
examen de Redes:
Capa Fsica
Capa de enlace
Capa de Red
Capa de Transporte
Capa de Sesin
Capa de Presentacin
Capa de Aplicacin
La Capa Fsica es el Hardware (serial, Ethernet, etc.). La Capa de Aplicacin esta los ms
lejos de la Fsica, y como te puedes imaginar es el lugar donde los usuarios interactan
con la Red.
Ahora, este modelo es tan general que lo podras emplear para repara autos si quisieras...
. Un modelo de capas ms consistente con Unix sera ms o menos:
En este punto, probablemente puedas ver como estas capas actan en la encapsulacin
del dato original.
Ves cuanto trabajo requiere construir un simple paquete? Y tienes que tipearlos tu mismo
usando cat!... . No. Todo lo que tienes que hacer para los STREAM SOCKETS es
send() (enviar) los datos. El kernel construye la Capa de Transporte y de Internet por ti y
el hardware se encarga de la Capa de Acceso de Red. OH!! Tecnologa Moderna.
As termina nuestra corta expedicin dentro de la Teora de Red. Oh!, s, se me olvido
decirles todo lo que quera acerca del enrutamiento: nada! As es, no voy a hablar para
nada de ello. El router (ruteador) extrae el paquete del encabezado IP, consulta su tabla
de ruteo, blah blah blah. Mira el IP RFC si realmente te interesa. Si nunca aprendes sobre
eso, bueno, sobrevivirs.
10
sa_family puede ser una variedad de cosas, pero ser AF_INET para todo lo que
hagamos en este documento. sa_data contiene una direccin de destino y un nmero de
puerto para el socket. Esto mas bien es poco manejable, ya que no querrs empaquetar
la direccin en sa_data a mano.
Para trabajar con STRUCT SOCKADDR, los programadores crearon una estructura paralela:
STRUCT SOCKADDR_IN (IN por Internet).
struct sockaddr_in {
short int
unsigned short int
struct in_addr
unsigned char
};
sin_family;
sin_port;
sin_addr;
sin_zero[8];
// Familia de direccin
// Nmero de Puerto
// Direccin de internet
// Mismo tamao que struct sockaddr
Esta estructura facilita la referencia a elemento de direccin de socket. Nota que sin_zero
(el cual es incluido para acomodar la estructura al largo de STRUCT SOCKADDR) debera ser
establecido a todos los ceros (zeros) con la funcin memset(). Adems, y este es un dato
importante, un apuntador a STRUCT SOCKADDR_IN puede lanzar un puntero a STRUCT
SOCKADDR y vise-versa. As que incluso si socket() necesita un STRUCT SOCKADDR*,
puedes an usar STRUCT SOCKADDR_ IN y ponerlo en el ltimo minuto! Adems, nota que
11
Pero, dices. Como toda la estructura, STRUCC IN_ADRR SIN_ADDR, puede estar en NBT
(Network Byte Order)? Esta pregunta requiere un profundo anlisis de la estructura
STRUCC IN_ADRR, una de las peores uniones vivas:
// Internet address (una estructura de razonos histricas)
struct in_addr {
unsigned long s_addr;
};
Bueno, solan estar unidos, pero en estos das parece que ha desaparecido. Una buena
prdida. As que si has declara ina del tipo STRUCT SOCKADDR_ IN, luego
ina.sin_addr.s_addr hace referencia a la direccin IP de 4 byte (en NBT). Nota que si tu
sistema aun usara STRUCC IN_ADRR, igual puede indicar la direccin IP de 4 byte
exactamente de la misma forma que lo hicimos arriba (gracias a #defne).
Ahora debes creer que te ests poniendo al tanto. Puedes pensar, Qu hago si tengo
que cambiar el orden de los bytes en un char? Luego dices, Uh, no importa. Tambin
puedes creer que como tu mquina 68000 ya usa NBT, no tienes que llamar a htonl() en
tu direccin IP. Deberas estar en lo correcto, pero si tratas de portar un programa a una
mquina que tiene un NBT inverso, tu programa fallar. Se portable! Este es un mundo
Unix! (Por ms que Bill Gates quiera pensar de otra manera). Recuerda: Pone tus bytes
en NBT antes de ponerlos en la Red.
Un punto final: por qu SIN_ADDR y SIN_PORT necesitan estar en NBT en STRUCT
SOCKADDR_IN, pero SIN_FAMILY no? La respuesta es: SIN_ADDR y SIN_PORT son
12
inet_aton(), en contraste con las otras funciones de sockets, devuelve un valor no cero
cuando es exitosa, y cero cuando falla. (Si alguien sabe porque, por favor dgame). Y la
direccin es devuelta en inp.
Desafortunadamente, no todas las plataformas implementan inet_aton(), aunque es
preferible usarlo. Pero en esta gua ocuparemos inet_addr().
13
// this is 192.168.4.14
// this is 10.12.110.57
Esto imprimir:
address 1: 10.12.110.57
address 2: 10.12.110.57
14
4. Llamadas al sistema.
Esta es la seccin donde nos meteremos en las llamadas de sistema que permiten
acceder a la funcionalidad de la Red en una mquina Unix. Cuando llamas a una de estas
funciones, el kernel toma el control y hace todo el trabajo por ti automagicamente.
El lugar donde la mayora de la gente queda atorada es en saber en que orden hacer las
llamadas. En esto, las pginas man no son tiles, como debes haber descubierto. Bueno,
en orden de ayudarlos, en las siguientes secciones las llamadas han sido hechas en
exactamente (aproximadamente) el mismo orden que t necesitars llamarlas en tus
programas.
Eso, acompaado con unos pocos cdigos de ejemplo por aqu y all, algo de leche y
galletas (lo cual confo que puedes obtener por ti solo(a)), y un poco de agallas y coraje, y
estars irradiando mensajes por Internet como el hijo de Juan Cartero!
Pero, que son esos argumentos? Primero, domain debera ser "AF_INET", justo como en
STRUCT SOCKADDR_in (arriba). El siguiente, el argumento type, le dice al kernel que tipo de
socket es: SOCK_STREAM o SOCK_DGRAM.
Finalmente, slo dele el valor 0 a protocol para que socket() escoja el protocolo correcto
basado type. (Observacin: hay muchos ms domains y tambin hay mas types de los
que he nombrado. Mira la pgina man socket(). Adems hay una mejor manera de dar
valor a protocol. Ve la pgina man getprotobyname() ).
socket() simplemente te devuelve un descriptor de socket que puedes usar en llamadas a
sistemas posteriores, o -1 en error. La variable global errno toma el valor del error (ve la
pgina man de perror() ).
Bien, pero, en qu ayuda esto? La respuesta es que por si solo no ayuda en nada, y
necesitas leer ms y hacer ms llamadas a sistema para que tome sentido.
15
socket. Si slo te vas a conectar (connect() ), esto puede se innecesario. Lelo de todas
maneras.
Esta es un avance de la llamada al sistema bind():
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
De esto hay que decir unas pocas cosas: my_addr.SIN_PORT est en NBO. Tambin
my_addr.sin_addr.s_addr. Otra cosa que debemos chequear es que los encabezados del
programa pueden variar de sistema a sistema. Para estar seguro, deberas mirar tus
pginas man.
Por ltimo, sobre bind(), debera mencionar que algunos procesos para identificar tu
propia direccin IP y / o puerto, pueden ser automatizados:
my_addr.sin_port = 0;
my_addr.sin_addr.s_addr = INADDR_ANY;
Mira, al setear SETTING MY_ADDR.SIN_PORT a cero, le estas diciendo a bind() que escoja el
puerto por ti. De la misma manera, al setear my_addr.sin_addr.s_addr a INADDR_ANY, le
ests diciendo que automticamente llene la direccin IP con la que tiene tu PC.
Si eres minucioso, puede que te hallas dado cuenta que un puse INADDR_ANY en NBO!
Que pillo soy. Como sea, tengo que INADDR_ANY es realmente cero! Cero tendr cero bits
aun cuando reordenes los bytes. Aunque, los cuticos dirn que podra haber una
16
Ahora somos tan portables que probablemente no lo creas. Slo quera sealar eso, ya
que en la mayora del cdigo que encontrars no importa correr INADDR_ANY a travs de
htonl().
Adems bind() devuelve -1 en error y setea errno con el valor de error.
Otra cosa que resaltar cuando se llama a bind(): no ocupes nmeros de puerto muy
pequeos. Todos los puertos hasta 1024 estn reservados (a menos que seas el sperusuario)! Puedes ocupar cualquier puerto sobre se, hasta 65535 (si no estn siendo
ocupados por otro programa).
Algunas veces, algunas, quieras reiniciar un programa servidor (server) y bind() falla,
reclamando la direccin ya se esta usando. Qu significa eso? Bueno, un bit de un
socket que estaba conectado anda dando vueltas por el kernel, y est ocupando el puerto.
Puedes hacer dos cosas: esperar a que se desocupe (un minuto ms o menos), o aadir
un cdigo a tu programa para que se pueda reutilizar el puerto, as:
int yes=1;
// lose the pesky "Address already in use" error message
if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
perror("setsockopt");
exit(1);
}
Una pequea nota final sobre bind(): habrn veces en que no lo llamars para nada. Si te
esta conectando (connect() ) a una mquina remota y no te importa cual es tu puerto local
(este es el caso con telnet cuando slo importa el puerto remoto), tu simplemente puedes
llamar a connect(), ste chequear si el socket est disponible, y har bind() a un puerto
libre si es necesario.
17
18
Dejar hasta aqu el cdigo de ejemplo, ya que se explica casi solo. (El cdigo de la
seccin accept() que sigue, es ms completo). La parte realmente importante aqu es la
llamada a accept().
19
my_addr.sin_family = AF_INET;
// host byte order
my_addr.sin_port = htons(MYPORT);
// short, network byte order
my_addr.sin_addr.s_addr = INADDR_ANY; // se llena con mi IP
memset(&(my_addr.sin_zero), \0, 8);
// zero the rest of the struct
// dont forget your error checking for these calls:
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
listen(sockfd, BACKLOG);
sin_size = sizeof(struct sockaddr_in);
new_fd = accept(sockfd, &their_addr, &sin_size);
.
.
.
Nuevamente, fjate que usaremos el descriptor de socket new_fd para todas las llamadas
send() y recv(). Si casi siempre solamente tienes una conexin, puedes close() (cerrar) el
sockfd que est escuchando en orden de prevenir ms llamadas al mismo puerto, si lo
deseas.
sockfd es el descriptor de socket donde deseas enviar data (ya sea el que es devuelto por
socket() o el que obtienes con accept() ). msg es un puntero al dato que deseas enviar, y
len sera el largo del dato en bytes. Slo setea flags a 0. (Ve la pgina man de send() para
ms informacin concerniente a flags).
Este puede ser un cdigo de ejemplo:
char *msg = "Beej was here!";
int len, bytes_sent;
.
.
len = strlen(msg);
20
send() devuelve el nmero de bytes que en realidad salieron puede ser menos que el
nmero que le indicaste que enviara! Mira, algunas veces le ordenas que envi un monton
de datos que simplemente no puede controlar. El lanzar la mayor cantidad de data que
pueda, y confiar en que t enviars el resto despus. Recuerda, que si el valor devuelto
por send() no concuerda con el valor de len, depende de ti enviar el resto de la cadena. La
buena noticia es esta: si el paquete es pequeo (menos de 1K) probablemente ser
enviado en un solo paso. Nuevamente, devolver un 1 en error y fijar la variable errno
con el nmero de error.
La llamada recv() es similar en muchos aspectos:
int recv(int sockfd, void *buf, int len, unsigned int flags);
sockfd es el descriptor de socket del cual leeremos, buf es el buffer (espacio de memoria
para almacenamiento temporal de datos) para leer la informacin, len es el largo mximo
del buffer, y flags nuevamente puede tomar el valor 0.
recv() devuelve el nmero de bytes que realmente se leen en el buffer, o -1 en error (con
errno seteado, como corresponde).
Espera! recv() puede devolver 0. Esto puede significar slo una cosa: el lado remoto ha
cerrado la conexin contigo! Recibir un valor 0 es como dejarte saber que esto ha
ocurrido.
Eso fue fcil, o no? Ahora puedes pasar data de ida y vuelta en stream sockets! Bien!
Eres un programador de Redes Unix!
Como puedes ver, esta llamada es bsicamente igual a send() con la adicin de dos
piezas de informacin. to es un puntero a STRUCT SOCKADDR (el cual probablemente
tendrs como STRUCT SOCKADDR_in y llamars en el ltimo minuto) que contiene la
direccin IP y el puerto de destino. tolen puede ser simplemente seteado a sizeof (STRUCT
SOCKADDR).
21
Igual que con send(), sendto() devuelve el nmero de bytes que se envan en realidad (el
cual, nuevamente, puede ser menor que el nmero de bytes que habas indicado!), o -1
en error.
Igualmente similares son recv() y recvfrom(). La forma de recvform() es:
int recvfrom(int sockfd, void *buf, int len, unsigned int flags,
struct sockaddr *from, int *fromlen);
Nuevamente, esto es como recv() con la adicin de una pareja de campos. from es
puntero a un STRUCT SOCKADDR local que ser llenado con la direccin IP y el puerto de la
mquina de origen. fromlen es un puntero a un int (variable entera) local que debera ser
iniciada en sizeof(STRUCT SOCKADDR). Cuando la funcin vuelva, fromlen contendr el
largo de la direccin actualmente guardad en from.
recvfrom() devuelve el nmero de bytes recibido, o -1 en error (con errno evaluado
acordemente).
Recuerda, si tu connect() un datagram socket, puedes simplemente usar send() y recv()
para todas tus transacciones. El socket en si es an un datagram socket y los paquetes
an usarn UDP, pero la interfaz de socket aadir automticamente el destino y fuente
de informacin, por ti.
Esto no permitir que se escriba o lea en el socket. Cualquiera que intente leer o escribir
remotamente en el socket recibir un error.
Slo en el caso de que desees un poco ms de control sobre como cerrar el socket,
puedes usar la funcin shutdown(). sta te permite cortar la comunicacin en cierta
direccin, o en ambas (igual que close() ). Y es:
int shutdown(int sockfd, int how);
sockfd es el descriptor de archivo de socket que deseas apagar (shutdown), y how puede
tomar los siguientes valores:
22
23
Como ves, devuelve un puntero a STRUCT HOSTENT. A continuacin se muestra como es:
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]
24
/*
** getip.c - a hostname lookup demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
struct hostent *h;
if (argc != 2) { // error check the command line
fprintf(stderr,"usage: getip address\n");
exit(1);
}
if ((h=gethostbyname(argv[1])) == NULL) { // get the host info
herror("gethostbyname");
exit(1);
}
printf("Host name : %s\n", h->h_name);
printf("IP Address : %s\n", inet_ntoa(*((struct in_addr *)h->h_addr)));
return 0;
}
Con gethostbyname(), no puedes usar perror() para imprimir un mensaje de error (ya que
errno no se usa). En vez, llama a herror().
Es bastante directo. Simplemente le pasas la cadena que contiene el nombre del host
(whitehouse.gov) a gethostbyname(), y luego pesca la informacin que es devuelta por
struct hostent.
Lo nico que podra resultar extrao es la escritura de la direccin IP. h->h_addr es un
char*, pero inet_ntoa() requiere STRUCT IN_ADDR PASSED. As que yo pongo h->h_addr en
STRUCT IN_ADDR*, luego le hago deferencia para conseguir los datos.
25
5. Sistema Cliente-Servidor
Este es un mundo Cliente-Servidor, baby. Casi todo lo que envuelve a la Red tiene que
con procesos de cliente hablando con procesos de servidor, y vise-versa. Toma a telnet,
por ejemplo. Cuando te conectas (t cliente) con un host remoto al puerto 23,
26
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#define MYPORT 3490 // the port users will be connecting to
#define BACKLOG 10 // how many pending connections queue will hold
void sigchld_handler(int s)
{
wait(NULL);
}
int main(void)
{
int sockfd, new_fd; // listen on sock_fd, new connection on new_fd
STRUCT SOCKADDR_in my_addr; // my address information
STRUCT SOCKADDR_in their_addr; // connectors address information
int sin_size;
struct sigaction sa;
int yes=1;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
if (setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
perror("setsockopt");
exit(1);
}
my_addr.SIN_FAMILY = AF_INET; // host byte order
my_addr.SIN_PORT = htons(MYPORT); // short, network byte order
my_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
bzero(&(my_addr.sin_zero), 8); // zero the rest of the struct
if (bind(sockfd, (STRUCT SOCKADDR *)&my_addr, sizeof(STRUCT SOCKADDR))
== -1) {
perror("bind");
exit(1);
}
if (listen(sockfd, BACKLOG) == -1) {
perror("listen");
exit(1);
}
sa.sa_handler = sigchld_handler; // reap all dead processes
sigemptyset(&sa.sa_mask);
27
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
while(1) { // main accept() loop
sin_size = sizeof(STRUCT SOCKADDR_in);
if ((new_fd = accept(sockfd, (STRUCT SOCKADDR *)&their_addr,
&sin_size)) == -1) {
perror("accept");
continue;
}
printf("server: got connection from %s\n",
inet_ntoa(their_addr.sin_addr));
if (!fork()) { // this is the child process
close(sockfd); // child doesnt need the listener
if (send(new_fd, "Hello, world!\n", 14, 0) == -1)
perror("send");
close(new_fd);
exit(0);
}
close(new_fd); // parent doesnt need this
}
return 0;
}
En caso de que sean curiosos, yo tengo este cdigo en una funcin main() bien grande
para (por lo que a mi respecta) mantener la claridad de la sintaxis. Considrate libre de
dividirla en funciones ms pequeas si te hace sentir mejor.
(Adems, todo esto de sigaction() puede ser nuevo para ti - lo cual est bien. El cdigo
presentado es responsable de recoger todos los procesos zombis que aparecen cuando
el proceso hijo de fork() existe. Si haces un montn de procesos zombis y no los recojes,
tu administrador de sistema se estremecer.)
Puedes obtener datos de este servidor usando el cliente mostrado en la prxima seccin.
28
29
30
int sockfd;
STRUCT SOCKADDR_in
STRUCT SOCKADDR_in
Hay que destacar que en nuestra llamada a socket() estamos finalmente usando
SOCK_DGRAM. Adems, que no hay necesidad de escuchar (listen() ) o aceptar (accept() ).
Este es uno de los beneficio de usar datagram socket desconectados!
Ahora es el turno de talker.c14:
/*
** talker.c - a datagram "client" demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
31
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define MYPORT 4950 // the port users will be connecting to
int main(int argc, char *argv[])
{
int sockfd;
STRUCT SOCKADDR_in
32
Y eso es todo! Corre listener en alguna mquina, luego corre talker en otra. Obsrvalos
comunicarse! Entretencin para toda la familia (R-G)!
Excepto por un pequeo detalle que he mencionado muchas veces antes: datagram
sockets conectados. Tengo que hablar de esto aqu, ya que estamos en la seccin de
datagramas. Digamos que talker llama a connect() y especifica la direccin de listener.
Desde ese punto, talker slo enva y recibe de la direccin ordenada en connect(). Por
esta razn, no tienes que usar sendto() y recvfrom(); simplemente usa send() y recv().
33
6.1. Bloqueando
Bloqueando. Has escuchado sobre esto ahora, qu diablos es? De manera fcil,
bloquear es una jerga tcnica para dormir. Probablemente notaste, que cuando corres
listener, arriba, solamente espera hasta que el paquete llega. Lo que sucedi es lo que
llamamos recvfrom(), no hay datos, entonces recvfrom() es bloqueado (dormido) hasta
que algn dato llegue.
Muchas funciones bloqueadas. accept() bloqueado. Todas la funciones recv() bloqueadas.
La razn por la que pueden hacer esto es porque se les permite. Cuando creaste por
primera vez el descriptor de socket con socket(), el kernel lo sete en bloqueado. Si no
deseas que un socket se bloquee, debes que hacer una llamada a fcntl():
#include <unistd.h>
#include <fcntl.h>
.
.
sockfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sockfd, F_SETFL, O_NONBLOCK);
.
.
34
Finalmente, qu es struct timeval? Bueno, algunas veces no deseas esperar por siempre
que alguien te envi algn dato. A lo mejor cada 96 segundos desees imprimir Todava
espero... en el terminal an si nada pasa. Esta estructura de tiempo te permite especificar
un tiempo de espera. Si el tiempo es sobrepasado y select() an no ha encontrado algn
descriptor de archivo listo, ser devuelto para que puedas continuar procesando.
La estructura de tiempo, es como sigue:
struct timeval {
int tv_sec; // seconds
int tv_usec; // microseconds
};
35
puede ser renovado para mostrar el tiempo que falta. Esto depende de que sabor de Unix
ests corriendo.
Yay! Tenemos un cronometro de microsegundos! Bueno, no cuentes con eso. El tiempo
estndar que ocupa Unix es de 100 milisegundos, as que tendrs que esperar todo ese
tiempo, sin importar como hayas seteado struct timeval.
Otra cosa de inters: si llenas los campos de struct timeval con 0, select() terminar
enseguida. Si das seteas el parmetro timeout a NULL, nunca terminar, y esperar hasta
que el primer descriptor de archivo este listo. Finalmente, si no te interesa esperar por
cierto valor, simplemente puedes registrarlo como NULL en la llamada a select().
El siguiente cdigo espera 2.5 segundos por que aparezca un entrada estndar:
/*
** select.c - a select() demo
*/
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define STDIN 0 // file descriptor for standard input
int main(void)
{
struct timeval tv;
fd_set readfds;
tv.tv_sec = 2;
tv.tv_usec = 500000;
FD_ZERO(&readfds);
FD_SET(STDIN, &readfds);
// dont care about writefds and exceptfds:
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;
}
Si estas en un terminal en lnea, la tecla que presiones debera ser devuelta o se
terminar de todas formas.
Ahora, alguno de Uds. puede pensar que esta es una buena manera de esperar por datos
en un datagram socket y est en lo correcto: puede ser. Algunas mquinas Unix pueden
36
usar select() de esta manera, y otros no. Deberas que dicen tus pginas man, para ver
cual usar.
Algunos equipos Unix renuevan el tiempo en tu struct timeval para reflejar la cantidad de
tiempo que queda antes de acabar. Pero otros no. No confes en eso si quieres ser
portable. (Usa gettimeofday() si necesitas seguir el tiempo transcurrido. No es muy bueno,
lo s, pero es la forma de hacerlo).
Qu sucede si cuando estamos leyendo un socket cierra la conexin? Bueno, en este
caso, select() regresa con ese socket y lo setea a listo para leer. Cuando en realidad
haces recv() de l, recv() devolver 0. As es como sabes que el cliente ha cerrado la
conexin.
Otra observacin interesante sobre select(): si tienes un socket que est escuchando,
puedes revisar si hay una conexin nueva al poner el descriptor de ese socket en readfds.
Y eso, mis amigos, es una mirada rpida a la toda poderosa funcin select().
Pero, por demanda popular, he aqu un ejemplo completsimo. Desafortunadamente, la
diferencia entre el insignificante ejemplo de arriba y ste es importante. Pero lee la
descripcin que lo acompaa.
Este programa16 acta como un simple servidor multiusuario de chat. Empieza corrindolo
en una ventana, luego hazle un telnet (telnet hostname 9034) desde varias ventanas.
Cuando escribas algo en una sesin de telnet, debera aparecer en las otras.
/*
** selectserver.c - a cheezy multiperson chat server
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 9034 // port were listening on
int main(void)
{
fd_set master; // master file descriptor list
fd_set read_fds; // temp file descriptor list for select()
STRUCT SOCKADDR_in myaddr; // server address
STRUCT SOCKADDR_in remoteaddr; // client address
int fdmax; // maximum file descriptor number
int listener; // listening socket descriptor
int newfd; // newly accept()ed socket descriptor
char buf[256]; // buffer for client data
int nbytes;
37
38
39
40
}
*len = total; // return number actually sent here
return n==-1?-1:0; // return -1 on failure, 0 on success
}
en este ejemplo, s es el socket al que deseas enviar los datos, buf es el buffer que
contiene los datos, y len es un puntero a un int que contiene el nmero de bytes que hay
en el buffer.
La funcin devuelve -1 en error (y errno todava es registrado desde la llamada a send() ).
Adems, el nmero de bytes realmente enviados es devuelto en len. Este ser el mismo
nmero de bytes que pediste se enviaran, a menos que ocurra un error. sendall() har su
mejor esfuerzo para enviar los datos, pero si ocurre algn error, regresa a ti de inmediato.
Para completitud, he aqu una llamada de ejemplo a la funcin:
char buf[10] = "Beej!";
int len;
len = strlen(buf);
if (sendall(s, buf, &len) == -1) {
perror("sendall");
printf("We only sent %d bytes because of the error!\n", len);
}
Qu pasa en lado que recibe cuando llega un paquete? Si los paquetes son de largo
variable, cmo sabe cuando un paquete termina y otro empieza? S, escenarios reales
son un verdadero dolor en el cu...! Probablemente tendrs que encapsular (recuerdas la
seccin de encapsulamiento de datos?). Lela para detalles!
41
El problema es que los mensajes pueden ser de largo variable. Una persona llamada
tom puede decir, Hola, y otra persona llamada Benjamn puede decir, Hey, que
onda?
Entonces envas (send()s ) todo esto a los clientes como llega. Lo que envas luce as:
tomHiBenjaminHeyguyswhatisup?
Y ms. Cmo sabe un cliente cuando un mensaje termina y otro empieza? Podras, se
deseas, hacer todos los mensajes del mismo largo y llamar a sendall(), como hicimos
anteriormente. Pero gasta ancho de banda! No queremos enviar 1024 bytes para que
tom pueda decir Hola.
Por lo tanto encapsulamos la data en un pequeo encabezado y una estructura de
paquete. Ambos, cliente y servidor, saben como empaquetar y desempaquetar esta data.
No mires ahora, pero hemos empezado a definir un protocolo que describe como se
comunican cliente y servidor!
En este caso, asumamos que el nombre de usuario es igual o menor a 8 caracteres,
finalizado con \0. Y luego asumimos que la data es de largo variable, con un mximo de
128 caracteres. Echemos un vistazo a una estructura de paquete de ejemplo que
podramos usar en esta situacin:
1. len (1 byte, sin firman) El largo total del paquete, contando los 8 bytes de nombre
de usuario y los datos de chat.
2. name (8 bytes) El nombre de usuario, con final NUL si es necesario.
3. chatdata (n-bytes) El dato en s, no ms de 128 bytes. El largo del paquete
debera ser calculado como el largo de este dato mas 8 (nombre de usuario).
Por qu eleg 8 y 128 como limite para los campos? Los saqu del aire, asumiendo que
seran suficiente. A lo mejor, 8 byte es muy poco para ti, puedes tener un nombre de 30
bytes, o lo que sea. Es tu eleccin.
Usando la definicin de paquete entregada, el primer paquete debera tener la siguiente
informacin (en hex y ASCII):
0A
(largo)
74 6F 6D 00 00 00 00 00
T o m
(padding)
48 69
H i
Y el segundo es similar
14
(largo)
42 65 6E 6A 61 6D 69 6E
B e n j a m i n
48 65 79 20 67 75 79 73 20 77 ...
H e y
g u y s
w ...
(El largo es guardado en NBO, por supuesto. En este caso, es slo un byte, as que no
importa, pero hablando de forma general desears todos tus enteros binarios en NBO en
tus paquetes).
42
Cuando ests enviando estos datos, deberas ser precavido y usar un comando similar a
sendall(), arriba, as sabras que todo fue enviado, an si tomar muchas llamadas a
send() .
De la misma forma, cuando recibes estos datos, necesitas hacer un poco de trabajo extra.
Para estar seguro, deberas asumir que podras recibir un paquete parcial. Necesitamos
llamar a recv() una y otra vez hasta que se haya recibido el paquete completo.
Pero cmo? Bueno, nosotros sabemos el nmero total de bytes que tenemos que recibir
para que el paquete este completo, ya que el nmero es obtenido del frente del paquete.
Tambin sabemos que el largo mximo del paquete es 1 + 8 +128, o 137 bytes (porque
as dijimos que sera).
Lo que puedes hacer es declarar un arreglo lo suficientemente grande para dos paquetes.
Este es tu arreglo de trabajo donde reconstruirs paquetes como vayan llegando.
Cada vez que recibas datos, los llevars al buffer de trabajo y revisars si el paquete est
completo. Eso es, el nmero de bytes en el buffer es mayor o igual al largo especificado
en el encabezado (+1, porque el largo en el encabezado no incluye el byte para el largo
del mismo). Si el nmero de bytes en el buffer es menor que 1, el paquete no esta
completo, obviamente. Tienes que hacer un caso especial para esto, ya que el primer byte
es basura y no puedes basarte en l para saber el correcto largo del paquete.
Una vez, que el paquete est completo, puede hacer lo que necesitabas. salo, y qutalo
de tu buffer de trabajo.
Uf! Ya ests haciendo malabarismo con tus ideas? Bueno, aqu esta el segundo del par:
puede que hayas ledo un paquete y el principio de otro en una simple llamada recv().
Esto sucede donde tienes un buffer de trabajo con un paquete completo, y una parte del
siguiente! Demonios. (Pero por esto hicimos el buffer de trabajo lo suficientemente grande
para manejar dos paquetes en caso de que esto suceda!).
Ya que sabes el largo del primer paquete por el encabezado, y has llevado un registro del
nmero de bytes en el buffer de trabajo, puedes restar y calcular cuantos bytes en el
buffer pertenecen al segundo (incompleto) paquete. Cuando has trabajado el primero,
puedes quitarlo del buffer y mover el segundo paquete (parcial) al frente para quedar
listos para el prximo recv().
(Alguno de ustedes notar que mover el segundo paqueta al principio del buffer toma
tiempo, y el programa puede ser codificado para no requerir esto usando un buffer
circular. Desafortunadamente para el resto de ustedes, una discusin sobre buffer
circulares escapa a este documento. Si todava sientes curiosidad, agarra un libro de
estructura de datos y sigue de ah).
Nunca dije que fuera fcil. De acuerdo, si lo dije. Y lo es; slo necesitas prctica y muy
luego te saldr naturalmente. Lo juro por Excalibur!
43
7. Ms Referencias
Has andado un largo camino, y ahora ests pidiendo ms! Dnde puedes ir para seguir
aprendiendo?
htonl ()
htons()
ntohl()
ntohs()
inet_aton()
inet_addr()
inet_ntoa()
socket()
socket options
bind()
connect()
listen()
accept()
send()
recv()
sendto()
recvfrom()
close()
shutdown()
getpeername()
getsockname()
gethostbyname()
gethostbyaddr()
getprotobyname()
fcntl()
select()
perror()
gettimeofday()
7.2. Libros
Tambin, revisa estos libros.
Internetworkin with TCP/IP, volumes I-III de Douglas E. Comer y David L. Stevens.
Publicado por Prentice Hall. Segunda edicin ISBNs: 0-13-468505-9, 0-13-472242-6, 013-474222-2. Hay una tercera edicin que cubre IPv6 e IP sobre ATM.
44
Using C on the Unix System de David A. Curry. Publicado por OReilly & Associates, Inc.
ISBN 0-937175-23-4.
TCP/IP Network Administration de Craig Hunt. Publicado por OReilly & Associates, Inc.
ISBN 0-937175-82-X.
TCP/IP Illustrated, volumes 1-3 de W. Richard Stevens y Gary R. Wright. Publicado por
Addison Wesley. ISBNs: 0-201-63346-9, 0-201-63354-X, 0-201-63495-3.
Unix Network Programming de W. Richard Stevens. Publicado por Prentice Hall. ISBN 013-949876-1.
7.4. RFCs
45
8. Preguntas Comunes
P: Dnde puedo conseguir los archivos de encabezados?
R: Si no los tienes ya en tu sistema, probablemente no los necesites. Revisa el manual de
tu plataforma. Si ests programando para Windows, slo necesitas #include <winsock.h>.
P: Qu debo hacer cuando bind() dice La direccin ya se est usando?
R: Debes usar setsockpt() con la opcin so_reuseaddr en el socket que est escuchando.
Revisa la seccin bind() y la seccin select().
P: Cmo obtengo una lista de sockets abiertos en el sistema?
R: Usa netstat. Mira la pgina man de netstat para ms informacin.
P: Cmo puedo saber si el lado remoto ha cerrado la conexin?
R: Lo sabes porque recv() devuelve 0.
P: Cmo programo para Windows?
R: Primero, borra Windows e instala Linux o BSD . No, en realidad, slo mira la seccin
de Windows en la introduccin.
P: Cmo programo para Solaris/SunOS? Sigo teniendo errores de linkeo cuando compilo!
R: Los errores de linkeo ocurren porque las cajas Sun no compilan automticamente las
libreras de sockets. Mira la seccin de Solaris al principio de este documento.
P: Qu hace que select() falle en una seal?
R: Las seales tienden a bloquear llamadas de sistema cuando regresan -1 con errno
seteado a EINTR. Cuando instalas un manejador de seales con sigaction(), puedes
setear la celda sa_restart, la cual debera reiniciar la llamada de sistema despus de que
sufriera una interrupcin.
Naturalmente, esto no siempre funciona.
Mi solucin favorita involucra al estamento goto. Ya sabes que esto molesta a tus
profesores, as que hagmoslo.
select_restart:
if ((err = select(fdmax+1, &readfds, NULL, NULL, NULL)) == -1) {
if (errno == EINTR) {
// some signal just interrupted us, so restart
goto select_restart;
}
// handle the real error here:
perror("select");
}
Seguro, no necesitas usar goto en este caso; puedes usar otras estructuras para
controlarlo. Pero pienso que el estamento goto es en realidad ms transparente.
46
47
48
49
NOTAS
1. http://www.ecst.csuchico.edu/~beej/guide/net/
2. http://www.cyberport.com/~tangent/programming/winsock/
3. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/send.2.inc
4. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/recv.2.inc
5. http://www.rfc-editor.org/rfc/rfc793.txt
6. http://www.rfc-editor.org/rfc/rfc791.txt
7. http://www.rfc-editor.org/rfc/rfc768.txt
8. http://www.rfc-editor.org/rfc/rfc791.txt
9. http://www.rfc-editor.org/rfc/rfc1413.txt
10. http://www.ecst.csuchico.edu/~beej/guide/net/examples/getip.c
11. http://www.ecst.csuchico.edu/~beej/guide/net/examples/server.c
12. http://www.ecst.csuchico.edu/~beej/guide/net/examples/client.c
13. http://www.ecst.csuchico.edu/~beej/guide/net/examples/listener.c
14. http://www.ecst.csuchico.edu/~beej/guide/net/examples/talker.c
15. http://www.ecst.csuchico.edu/~beej/guide/net/examples/select.c
16. http://www.ecst.csuchico.edu/~beej/guide/net/examples/selectserver.c
17. http://linux.com.hk/man/showman.cgi?manpath=/man/man3/htonl.3.inc
18. http://linux.com.hk/man/showman.cgi?manpath=/man/man3/htons.3.inc
19. http://linux.com.hk/man/showman.cgi?manpath=/man/man3/ntohl.3.inc
20. http://linux.com.hk/man/showman.cgi?manpath=/man/man3/ntohs.3.inc
21. http://linux.com.hk/man/showman.cgi?manpath=/man/man3/inet_aton.3.inc
22. http://linux.com.hk/man/showman.cgi?manpath=/man/man3/inet_addr.3.inc
23. http://linux.com.hk/man/showman.cgi?manpath=/man/man3/inet_ntoa.3.inc
24. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/socket.2.inc
25. http://linux.com.hk/man/showman.cgi?manpath=/man/man7/socket.7.inc
26. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/bind.2.inc
27. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/connect.2.inc
28. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/listen.2.inc
29. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/accept.2.inc
30. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/send.2.inc
31. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/recv.2.inc
32. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/sendto.2.inc
33. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/recvfrom.2.inc
34. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/close.2.inc
35. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/shutdown.2.inc
36. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/getpeername.2.inc
37. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/getsockname.2.inc
38. http://linux.com.hk/man/showman.cgi?manpath=/man/man3/gethostbyname.3.inc
39. http://linux.com.hk/man/showman.cgi?manpath=/man/man3/gethostbyaddr.3.inc
40. http://linux.com.hk/man/showman.cgi?manpath=/man/man3/getprotobyname.3.inc
41. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/fcntl.2.inc
42. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/select.2.inc
43. http://linux.com.hk/man/showman.cgi?manpath=/man/man3/perror.3.inc
44. http://linux.com.hk/man/showman.cgi?manpath=/man/man2/gettimeofday.2.inc
45. http://www.amazon.com/
50
51