Sei sulla pagina 1di 51

Usando Internet Sockets

Brian "Beej" Hall


beej@piratehaven.org
Copyright 1995-2001 by Brian "Beej" Hall

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?

2.1. DOS TIPOS DE SOCKETS DE INTERNET


2.2. COSAS DE BAJO NIVEL Y TEORA DE RED

7
8

3. STRUCTS Y MANEJO DE DATOS

11

3.1. CONVIRTIENDO A LOS NATIVOS!


3.2. DIRECCIONES IP Y COMO LIDIAR CON ELLAS

12
13

4. LLAMADAS AL SISTEMA.

15

4.1. SOCKET() CONSIGUE EL DESCRIPTOR DE ARCHIVO!


4.2. BIND() - EN QUE PUERTO ESTOY?
4.3. CONNECT() HEY, T!
4.4. LISTEN() - ALGUIEN ME LLAMARA POR FAVOR?
4.5. ACCEPT() - GRACIAS POR LLAMAR AL PUERTO 3490.
4.6. SEND() Y RECV() - HBLAME, BABY!
4.7. SENDTO() Y RECVFROM() HBLAME, AL ESTILO DGRAM!
4.8. CLOSE() Y SHUTDOWN() SALE DE MI VISTA!
4.9. GETPEERNAME() QUIN ERES?
4.10. GETHOSTNAME() QUIN DIABLOS SOY?
4.11. DNS T DICES WHITEHOUSE.GOV, YO DIGO 192.137.240.92

15
15
17
18
19
20
21
22
23
23
24

5. SISTEMA CLIENTE-SERVIDOR

26

5.1. UN SIMPLE SERVIDOR DE FLUJO (STREAM)


5.2. UN SIMPLE CLIENTE DE FLUJO
5.3. DATAGRAM SOCKETS

26
28
30

6. TCNICAS LIGERAMENTE AVANZADAS

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

7.1. PGINAS MAN


7.2. LIBROS
7.4. RFCS

44
44
45

8. PREGUNTAS COMUNES

46

9. DESLIGAMIENTO DE RESPONSABILIDAD Y PETICIN DE AYUDA

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.4. Nota para Solaris/SunOs


Cuando se compila para Solaris o SunOS, necesitas especificar algunas lneas extras de
comandos para linkear las libreras apropiadas. Para hacer esto, simplemente aade: lnsl -lsocket -lresolv al final del comando para compilar, ms o menos as:
$ cc o server server.c -lnsl -lsocket -lresolv
Si sigues teniendo errores, podras tratar agregando -lxnet al final de la lnea. Yo no s
que hace eso, exactamente, pero algunas personas parecen necesitarlo.

1.5 Nota para Windows


Tengo un particular disgusto por Windows, y te recomiendo que pruebes Linux, BSD o
Unix . Que quede dicho, t aun puedes usar esto bajo Windows.
Primero, ignora casi todos los encabezados que menciono en este documento. Todo lo
que necesitas incluir es:
#include <winsock.h>
Espera! Adems tienes que hacer una llamada a WSAStartup() antes de hacer algo con la
librera de Sockets. El cdigo para hacer eso, es mas o menos:
#include <winsock.h>
{
WSAData wsaData;
If (WSAStartup{MAKEWORD(1, 1), &wsaData} != 0) {
Fprintf(stderr, WSAStartup failed.\n);
Exit(1);
}
Una vez hecho esto, el resto de los ejemplos en este tutorial deberan funcionar, con unas
pocas excepciones. Por un lado, no puedes usar close() para cerrar un socket, necesitas
usar closesocket(), en vez. Adems, select() slo trabaja con descriptores de socket, no
con descriptores de archivo (como 0 para stdin).
Para ms informacin sobre Winsock, lee el FAQ de Winsock.

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.

1.7. Nota para los Traductores


Si deseas traducir esta gua a otro lenguaje, escrbeme y yo pondr la direccin en mi
pgina.
Sintete libre de agregar tu nombre e e-mail a la traduccin.

1.8. Copyright y Distribucin


Beejs Guide to Network Programming es Copyright 1995-2001 Brian Beej Hall.
Esta gua puede ser reimpresa libremente, siempre que no sea alterada, y se mantengan
los derechos de autor intactos.
Se recomienda a los educadores que provean esta gua a sus estudiantes.
Esta gua puede ser traducida libremente en cualquier lenguaje, y debe ser enteramente
traducida. La traduccin puede incluir el nombre y la manera de contactar al traductor.
El cdigo C presentado en este documento es dado al dominio pblico.
Contactar a beej@piratehaven.org para ms informacin.
Contactar a kaosk@hotpop.com para ms informacin (traductor).

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.

2.1. Dos tipos de Sockets de Internet


Qu es esto? Hay dos tipos de socket en Internet? Si. Bueno... no. Estoy mintiendo. Hay
ms, pero no quiero asustarte. Slo voy a hablar de dos tipos aqu. Pero debo decirte que
los Raw Sockects son muy poderosos y deberas echarle una ojeada.
Todo bien hasta ahora. Pero cuales son los dos tipos? Uno es Stream Sockets (Sockets
de Flujo) y el otro Datagram Sockets, a los que nos referiremos como SOCK_STREAM Y
SOCK_DGRAM, respectivamente. Los SOCK_DGRAM son a veces llamados sockets sin
conenxion.
Los stream sockets son flujos confiables de comunicacin de dos-vas. Si mandas dos
tem por el socket en el orden 1, 2, ellos llegarn en el orden 1, 2 al otro lado. Tambin
estarn libres de error. Cualquier error que encuentres son producidos por tu propia
mano, y no sern discutidos aqu.

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.

2.2. Cosas de Bajo Nivel y Teora de Red


Ya que hemos nombrados los protocolos, es hora de hablar de cmo funciona realmente
un red, y mostrar algunos ejemplos de cmo los paquetes SOCK_DGRAM son construidos.
Seguramente te podras saltar esta seccin, pero nunca est de ms aprender.
Hey, nios, es hora de aprender sobre Encapsulacin de datos! Esto es muy importante.
Es tan importante que podras encontrar una carrera entera sobre esto aqu en el Estado
de Chico. Bsicamente dice esto: un paquete nace, el paquete es envuelto
(encapsulado) en un encabezado (y raramente al final) por el primer protocolo (digamos,
TFTP),

Figura 1. Encapsulacin de Datos

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:

Capa de Aplicacin (telnet, ftp, etc.)


Capa de Transporte Host-to-Host (TCP, UDP)
Capa de Internet (IP y rutamiento)
Capa de Acceso a la Red (Ethernet, ATM, etc).

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

3. STRUCTS y Manejo de Datos


Por fin estamos aqu. Es tiempo de hablar sobre programacin. En esta seccin, cubrir
varios tipos de datos ocupados por la interfaz de sockets, ya que algunos son muy
difciles de deducir.
Primero el fcil: un descriptor de socket. Un descriptor de socket es el siguiente:
int
Un regular int.
Las cosas se pondrn raras ahora, as que lee todo y aguanta conmigo. Aprende esto:
hay dos tipos de orden de byte: byte ms importante primero (llamado a veces un
octeto), o el menos importante primero. Esta forma es llamada Red de Orden de Byte
(Network Byte Order). Algunas mquinas guardan sus nmeros internamente en este tipo
de Red, otras no. Cuando digo que algo tiene que estar en Network Byte Order, tienes
que llamar a una funcin (como htons() ) para cambiar de Host Byte Order. Si no digo
nada, entonces debes dejar en Host Byte Order.
(Para los curiosos Network Byte Order tambin se conoce como "Big-Endian Byte Order).
Mi primera estructura (Struct) STRUCT SOCKADDR. Esta estructura mantiene informacin
de las direcciones de sockets para varios tipos de estos:
struct sockaddr {
unsigned short sa_family; // direccin de familia, AF_xxx
char sa_data[14]; // 14 bytes de protocolo de direccin
};

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

SIN_FAMILY corresponde a sa_family en STRUCT SOCKADDR_IN y debera tomar el valor de


AF_INET. Finalmente, SIN_PORT y SIN_ADDR deben ser Network Byte Order!

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).

3.1. Convirtiendo a los Nativos!


Hemos sido conducidos justo a la prxima seccin. Se ha hablado mucho acerca de la
conversin de NBO a HBO (Host Byte Order), ahora es tiempo de actuar.
Bien. Hay dos tipos que puedes convertir: short (2 bytes) y long (4 bytes). Estas funciones
tambin funcionan para la variante unsigned. Digamos que quieres convertir un short de
HBO a NBO. Comienza con h por host, sigue con to, luego n de network, y s de
short: h-to-n-s, o htons() (se lee: Host to Network Short, Short de Host a Red).
Es casi demasiado fcil.
Puedes hacer cualquier combinacin que quieras usando: n, h, s y l, exceptuando
las estpidas. Por ejemplo, no existe la funcin stolh() (Short to Long Host), hasta ahora.
Pero estn:

htons() Host a Red Short.


htonl() Host a Red Long.
ntohs() Red a Hosto Short.
ntohl() Red a Host Long.

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

encapsulados en el paquete en las capas IP y UDP, respectivamente. Por eso, ellos


necesitan estar en NBT. Por otro lado, el campo SIN_FAMILY es slo usado por el kernel
para determinar que tipo de direccin contiene la estructura, as que debe estar en HBO.
Adems, ya que SIN_FAMILY nos es enviada a la red, puede estar en HBO.
3.2. Direcciones IP y como lidiar con ellas
Afortunadamente para ti, hay un montn de funciones que permiten manipular direcciones
IP. No hay que deducirlas a mano y embutirlas en un long con el operador .
Primero, digamos que tienes una estructura STRUCT SOCKADDR_IN INA, y la direccin IP
10.12.110.57 de deseas guardar en ella. La funcin que deseas usar, inet_adrr(),
convierta la direccin una direccin IP en la forma nmeros-y-puntos a la forma de un
entero largo (long) sin firmar. La transformacin se puede hacer como sigue:
ina.sin_addr.s_addr = inet_addr("10.12.110.57");
Fjate que inet_addr() devuelve la direccin en NBT lista, no tienes que llamar a htonl().
Genial!
Ahora, el cdigo de arriba no es muy robusto porque no hay comprobacin de errores.
Mira, inet_addr() devuelve 1 en error. Te acuerda de los binarios? (unsigned) -1 sucede
que corresponde a la direccin IP 255.255.255.255! Esa es la direccin de transmisin!
Mal, mal. Recuerda hacer tu comprobacin de errores como corresponde.
En realidad, hay una manera ms fcil en vez de usar inet_addr():, se llama inet_aton()
(aton significa ASCII a Red):
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);

He aqu un ejemplo de su uso al empaquetar un STRUCT SOCKADDR_IN (este ejemplo


tendr mucho ms sentido cuando llegues a la seccin de bind() y connect()).
STRUCT SOCKADDR_in my_addr;
my_addr.SIN_FAMILY = AF_INET;
// host byte order
// short, network byte order
my_addr.SIN_PORT = htons(MYPORT);
inet_aton("10.12.110.57", &(my_addr.sin_addr));
memset(&(my_addr.sin_zero), \0, 8); // zero the rest of the struct

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

Ok, ahora puedes convertir cadenas de direcciones IP en su representacin binaria. Pero


como lo hacemos al revs? Qu tal si tenemos un struct IN_ADDR y lo queremos escribir
en notacin de nmeros-y-puntos? En este caso, tu querrs usar la funcin inet_ntoa()
(ntoa significa red a ascii), como sigue:
printf("%s", inet_ntoa(ina.sin_addr));
Eso escribir la direccin IP. Nota que inet_ntoa() toma STRUCT IN_ADDR como un
argumento, no como long. Adems hay que destacar que devuelve un puntero a un char.
Este apunta a un arreglo char que contiene inet_ntoa(), as que cada vez que llames a
inet_ntoa(), este borrar la ultima direccin IP por la que preguntaste. Por ejemplo:
char *a1, *a2;
.
.
a1 = inet_ntoa(ina1.sin_addr);
a2 = inet_ntoa(ina2.sin_addr);
printf("address 1: %s\n",a1);
printf("address 2: %s\n",a2);

// 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

Si necesitas guardar las direcciones, haz tu propio arreglo.


Eso es todo por ahora. Ms tarde, aprenders a convertir una cadena del tipo
whitehouse.gov a su direccin IP correspondiente. (busca sobre DNS, ms abajo).

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!

4.1. socket() Consigue el Descriptor de Archivo!


Supongo que no puedo atrasarlo ms, tengo que hablar sobre la llamada a sistema
socket(). He aqu:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

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.

4.2. bind() - En que puerto estoy?


Una vez que tienes un socket, deberas tener que asociarlo a algn puerto en tu mquina
local. (Esto se hace comnmente si vas a escuchar (listen() ) por conexiones entrantes en
un puerto especfico, cuando te hagan telnet a x.y.z puerto 6969). El nmero del puerto
es usado por el kernel para asociar un paquete entrante con algn proceso descriptor de

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);

sockfd es descriptor de archivo de socket devuelto por socket(). my_addr es un puntero a


STRUCT SOCKADDR que contiene informacin sobre tu direccin, es decir, puerto y
direccin IP. addrlen puede ser sizeof (STRUCT SOCKADDR).
Uf! Es mucho que tragar de una mascada. Veamos un ejemplo:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MYPORT 3490
main()
{
int sockfd;
struct sockaddr_in my_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
// haz un poco de correcin de error!
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 = inet_addr("10.12.110.57");
memset(&(my_addr.sin_zero), \0, 8);
// zero the rest of the struct
// dont forget your error checking for bind():
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
.
.
.

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;

// escoge un puerto libre al azar


// usa mi direccin IP

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

dimensin paralela donde INADDR_ANY es, digamos, 12 y mi cdigo no funcionara. Esta


bien para mi:
my_addr.sin_port = htons(0);
// escoge un puerto libre al azar
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // usa mi direccin IP

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.

4.3. connect() Hey, t!


Pretendamos por algunos minutos que eres una aplicacin del telnet. Tu usuario te
ordena (como en la pelcula Tron) que adquieras un descriptor de archivo de socket. T
cumples y llamas a socket(). Luego, el usuario te dice que te conectes a 10.12.110.57 en
el puerto 23 (el estndar para telnet). Yow! Qu haces ahora?
Por suerte para ti, programa, tu estas ahora en la seccin connect() como conectar con
un host remoto. As que lee furiosamente. No hay tiempo que perder!
La llamada a connect() es como sigue:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

17

sockfd es nuestro amistoso vecino descriptor de archivo de socket, como devuelve la


llamada socket(), serv_addr es un STRUCT SOCKADDR que contiene la direccin IP y puerto
destino, y addrlen puede setearse a sizeof (STRUCT SOCKADDR).
No empieza a tener ms sentido? Veamos un ejemplo:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define DEST_IP "10.12.110.57"
#define DEST_PORT 23
main()
{
int sockfd;
struct sockaddr_in dest_addr;

// contendr la direccin de destino

sockfd = socket(AF_INET, SOCK_STREAM, 0);

// do some error checking!

dest_addr.sin_family = AF_INET; // host byte order


dest_addr.sin_port = htons(DEST_PORT); // short, network byte order
dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
memset(&(dest_addr.sin_zero), \0, 8);
// zero the rest of the struct
// dont forget to error check the connect()!
connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
.
.
.

Nuevamente, asegrate de chequear el valor devuelto por connect() este devolver un


-1 en error y fijar la variable errno.
Adems, hay que decir que no llamamos a bind(). Bsicamente, no nos preocupamos del
nmero del puerto local; slo nos interesa hacia donde vamos (el puerto remoto). El
kernel escoger el puerto local por nosotros, y el lugar donde nos conectamos recibir
automticamente esta informacin de nosotros. No hay que preocuparse.

4.4. listen() - Alguien me llamara por favor?


Ok, tiempo para un cambio de lugar. Qu tal si no deseas conectarte a un host remoto.
Digamos, slo por decir, que deseas esperar una conexin entrante y manejarla de
alguna manera. El consta de dos pasos: primero tu escuchas (listen() ), luego tu aceptas
(accept() ).
La llamada para escuchar es bastante simple, pero requiere una pequea explicacin:
int listen(int sockfd, int backlog);

sockfd es el descriptor de archivo de socket usual de la llamada al sistema socket().


backlog es el nmero de conexiones permitidas en la fila (como fila para el pan) entrante.
Qu significa esto? Bueno, la conexiones entrantes van a esperar en esta fila hasta que
las aceptes (accept() - mira abajo), y este es el lmite de cuantas pueden estar en la fila.
La mayora de los sistemas limitan este nmero a 20; puedes salir del paso con 5 o 10.

18

Nuevamente, como es usual, listen() devolver un -1 en error y fijar la variable errno.


Bueno, como seguramente imaginas, necesitamos llamar a bind() antes de listen() o el
kernel nos tendr escuchando en in puerto al azar. Entonces, si vas ha estar escuchando
por llamadas entrantes, la secuencia de llamadas a sistema sera:
socket();
bind();
listen();
/* accept() goes here */

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().

4.5. accept() - Gracias por llamar al puerto 3490.


Preprate la llamada accept() es un tanto rara! Lo que va a pasar es esto: alguien de
bien, bien lejos tratar de connect() (conectarse) a t mquina con un puerto que este
escuchando (listen() ). La conexin del otro quedar en la fila esperando a ser aceptada
(accept() ). Tu llamas a accept() y le dices que se comunique con la conexin pendiente.
ste te devolver un descriptor de archivo de socket nuevecito para ocupar solamente en
esta conexin! As es, de pronto tienes dos descriptores de archivo de socket por el precio
de uno! l original todava est escuchando en tu puerto y el nuevo est finalmente listo
para send() (enviar) y recv() (recibir). Estamos listos!
La llamada es como sigue:
#include <sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);

sockfd es el descriptor de socket que escucha (listen() ). Suficientemente fcil. addr


usualmente ser un puntero a STRUCT SOCKADDR_IN. Aqu es donde la informacin de la
conexin entrante ir (y con ella tu puedes determinar que host esta llamndote y de que
puerto). addrl es una variable local entera que deber tomar el valor de sizeof (STRUCT
SOCKADDR_in) antes su direccin es pasada a accept(). accept no pondr ms bytes de lo
que tiene en addr. Si pone menos, cambiar el valor de addrlen para reflejar el cambio.
Adivina qu? accept() devolver un 1 en error y fijar la variable errno. Apuesto a que no
lo habas pensado.
Como antes, esto es mucho que tragar de una sola mascada, as que aqu hay un cdigo
de ejemplo para tu estudio:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MYPORT 3490 // the port users will be connecting to

19

#define BACKLOG 10 // how many pending connections queue will hold


main()
{
int sockfd, new_fd;
// escu7cha en sock_fd, nueva conexin en on new_fd
struct sockaddr_in my_addr;
// informacin de mi direccin
struct sockaddr_in their_addr;
// informacin de la direccin del otro
int sin_size;
sockfd = socket(AF_INET, SOCK_STREAM, 0);

// do some error checking!

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.

4.6. send() y recv() - Hblame, baby!


Estas dos funciones son para comunicarse sobre stream socket o datagram sockets
conectados. Si desean usar datagran sockets desconectados, necesitars ver la seccin
siguiente de sendto() y recvfrom().
La llamada send():
int send(int sockfd, const void *msg, int len, int flags);

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

bytes_sent = send(sockfd, msg, len, 0);


.
.
.

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!

4.7. sendto() y recvfrom() Hblame, al estilo DGRAM!


Esto est bacn. Estas diciendo. Pero donde me deja con los datagram sockets
desconectados?. No hay problema, amigo. Tenemos la manera.
Ya que los datagram sockets no estn conectados a un host remoto, adivina que pieza de
informacin debemos dar antes de enviar el paquete? Eso es correcto! La direccin de
destino! He aqu la cuchara:
int sendto(int sockfd, const void *msg, int len, unsigned int flags,
const struct sockaddr *to, int tolen);

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.

4.8. close() y shutdown() Sale de mi vista!


Bien! Has estado send()ing (enviando) y recv()ing (recibiendo) datos durante todo el da, y
te has cansado de ello. Entonces ests listo para cerrar la conexin de tu descriptor de
socket. Esto es fcil. Puedes usar el descriptor Unix: la funcin close().
close(sockfd);

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:

0 Recibir est deshabilitado.


1 Enviar est deshabilitado.
2 Enviar y Recibir estn deshabilitados (como close() )

shutdown() devolver 0 cuando sea exitoso, y -1 en error (con errno ajustado


acordemente).

22

Si tratas de usar shutdown() en datagram sockets desconectados, simplemente har que


el socket est indispuesto para las llamadas send() (enviar) y recv() (recibir) (recuerda que
puedes usar esto si connect() tus datagram socket).
Es importante notar que shutdown() en realidad no cierra el descriptor de archivo
solamente cambia su disponibilidad. Para liberar un descriptor de socket, necesitas usar
close().
Nada ms.
4.9. getpeername() Quin eres?
Esta funcin es muy fcil.
Estn fcil que casi no le doy una seccin. Pero aqu est de todas maneras.
Las funcin getpeername() te dir quien esta conectado al final del stream socket. Es as:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

sockfd es el descriptor del stream socket conectado, addr es el puntero a STRUCT


SOCKADDR (o STRUCT SOCKADDR_in) que contendr la informacin acerca del otro lado de
la conexin, y addrlen es un puntero a un int, que debera ser inicializado a sizeof(STRUCT
SOCKADDR).
La funcin devuelve -1en error y registra errno adecuadamente.
Una vez que tienes su direccin, puedes usar inet_ntoa() o gethostbyaddr() para escribir u
obtener ms informacin. No, no puedes obtener su nombre de usuario. (Ok, ok. Si la otra
computadora est corriendo un demonio ident, es posible. Esto, de todas maneras, no es
parte de este documento. Chequea el RFC-14139 para ms informacin).

4.10. gethostname() Quin diablos soy?


Incluso ms fcil que getpeername() es la funcin gethostname(). sta devuelve el
nombre de la computadora donde est corriendo tu programa. El nombre puede ser usado
por gethostbyname(), abajo, para determinar la IP de tu mquina local.
Qu puede ser ms entretenido? Bueno, hay algunas cosas, pero no tienen que ver con la
programacin de sockets. Ok, as se hace:
#include <unistd.h>
int gethostname(char *hostname, size_t size);

Los argumentos son simples: hostname es un puntero a un arreglo que contendr el


nombre del host que devolvi la funcin, y size es el largo en bytes del arreglo hostname.

23

La funcin devuelve 0 cuando se completa bien, y -1 en error, seteando errno como es


usual.

4.11. DNS T dices whitehouse.gov, Yo digo 192.137.240.92


En caso que no sepas que es DNS, quiere decir Servicio de Nombre de Dominio
(Domain Name Service, por sus siglas en ingls). De modo fcil, tu le das la direccin
humanamente legible de un sitio, y l (DNS) te da la direccin IP (para que las uses con
bind(), connect(), sendto(), o para lo que sea que la necesites). De esta manera, cuando
alguien escriba:
$ telnet whitehouse.gov
telnet podr encontrar la manera para conectarse a 198.137.240.92.
Pero, cmo funciona? Estars usando la funcin gethostbyname():
#include <netdb.h>
struct hostent *gethostbyname(const char *name);

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]

Siguiendo se muestra la descripcin de los campos en struct hostent:

h_name Nombre oficial de Host.


h_aliases Un arreglo NULL de nombres alternativos para el host.
h_addrtype El tipo de direccin que se devuelve; usualmente AF_INET.
h_length El largo de la direccin en bytes.
h_addr_list Un arreglo terminado en cero de las direcciones para el host. Las
direcciones del host estn en NBO.
h_addr La primera direccin en h_addr_list.

gethostbyname() devuelve un puntero al ocupado struct hostent, o NULL en error (Pero


errno no toma valor, h_errno lo hace en su lugar. Ve herror(), ms abajo).
Pero cmo se usa esto? Algunas veces (como encontramos cuando leemos manual de
computador), lanzar informacin al lector no es suficiente. Esta funcin es mucho ms
fcil de lo que parece.
He aqu un programa de ejemplo10:

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.

5.2. Un simple Cliente de flujo


Esto es ms fcil que el servidor. Todo lo que estos clientes hacen es conectarse con el
host que especificas en la lnea de comando, puerto 3490. Y recibe la cadena que enva
el servidor.

28

La fuente del cliente12:


/*
** client.c - a stream socket client demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define PORT 3490 // the port client will be connecting to
#define MAXDATASIZE 100 // max number of bytes we can get at once
int main(int argc, char *argv[])
{
int sockfd, numbytes;
char buf[MAXDATASIZE];
struct hostent *he;
STRUCT SOCKADDR_in their_addr; // connectors address information
if (argc != 2) {
fprintf(stderr,"usage: client hostname\n");
exit(1);
}
if ((he=gethostbyname(argv[1])) == NULL) { // get the host info
perror("gethostbyname");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
their_addr.SIN_FAMILY = AF_INET; // host byte order
their_addr.SIN_PORT = htons(PORT); // short, network byte order
their_addr.SIN_ADDR= *((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero), 8); // zero the rest of the struct
if (connect(sockfd, (STRUCT SOCKADDR *)&their_addr,
sizeof(STRUCT SOCKADDR)) == -1) {
perror("connect");
exit(1);
}

29

if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {


perror("recv");
exit(1);
}
buf[numbytes] = \0;
printf("Received: %s",buf);
close(sockfd);
return 0;
}
Observa que si no corres en servidor antes que el cliente, connect() devolver
Connection refused. Muy til.

5.3. Datagram Sockets


Realmente no tengo mucho de que hablar aqu, por ende slo presentar algunos
programas de ejemplo: talker.c y lintener.c.
listener se estanca en una mquina esperando por un paquete entrante en el puerto
4950. talker enva un paquete a ese puerto, en la mquina especificada, que contiene lo
que sea que el usuario tipee en la lnea de comando.
Este es el cdigo de listener.c13:
/*
** listener.c - a datagram sockets "server" demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MYPORT 4950 // the port users will be connecting to
#define MAXBUFLEN 100
int main(void)
{

30

int sockfd;
STRUCT SOCKADDR_in
STRUCT SOCKADDR_in

my_addr; // my address information


their_addr; // connectors address information
int addr_len, numbytes;
char buf[MAXBUFLEN];
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket");
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);
}
addr_len = sizeof(STRUCT SOCKADDR);
if ((numbytes=recvfrom(sockfd,buf,MAXBUFLEN,0,
(STRUCT SOCKADDR *)&their_addr, &addr_len)) == -1) {
perror("recvfrom");
exit(1);
}
printf("got packet from %s\n",inet_ntoa(their_addr.sin_addr));
printf("packet is %d bytes long\n",numbytes);
buf[numbytes] = \0;
printf("packet contains \"%s\"\n",buf);
close(sockfd);
return 0;
}

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

their_addr; // connectors address information

struct hostent *he;


int numbytes;
if (argc != 3) {
fprintf(stderr,"usage: talker hostname message\n");
exit(1);
}
if ((he=gethostbyname(argv[1])) == NULL) { // get the host info
perror("gethostbyname");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket");
exit(1);
}
their_addr.SIN_FAMILY = AF_INET; // host byte order
their_addr.SIN_PORT = htons(MYPORT); // short, network byte order
their_addr.SIN_ADDR= *((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero), 8); // zero the rest of the struct
if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0,
(STRUCT SOCKADDR *)&their_addr, sizeof(STRUCT SOCKADDR))) == -1) {
perror("recvfrom");
exit(1);
}
printf("sent %d bytes to %s\n", numbytes,
inet_ntoa(their_addr.sin_addr));
close(sockfd);
return 0;
}

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. Tcnicas ligeramente avanzadas


Estas no son realmente avanzadas, pero empiezan a salirse de los niveles bsicos que
estbamos viendo. De hecho, si has llegado hasta ac, deberas considerarte un
entendido(a) en las bases de la programacin Unix de Red!
Felicitaciones!
Entonces aqu entramos en un nuevo mundo de las cosa ms esotricas que puedas
deseas aprender sobre sockets. Toma!

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);
.
.

Al setear un socket en no-bloquear, puedes efectivamente interrogar al socket por


informacin. Si tratas de leer de un socket no bloqueado y este no tiene data, no est
permitido bloquear devolver un -1 y errno ser seteado a ewouldblock.
Hablando de manera general, este tipo de interrogacin es mala idea. Si pones tu
programa a buscar data en el socket de manera forzada, chupars todo el tiempo de
CPU. Una forma ms elegante para ver si hay datos esperando ser ledos viene en la
siguiente seccin.

6.2. select() Multiplexamiento Sincronizado de E/S


Esta funcin es de alguna manera extraa, pero muy til. Toma la siguiente situacin: t
eres un servidor y deseas escuchar por conexiones entrantes mientras continuas leyendo
las conexiones que ya tienes.
No hay problema, dices, slo un accept() y un par de recv()s. No tan rpido, compadre!
Qu tal si estas bloqueando una llamada accept()? Cmo vas a recibir (recv() ) data al

34

mismo tiempo? Usa sockets no-bloqueados De ninguna manera! T no quieres criar un


CPU cerdo. Qu, entonces?
select() te da el poder de monitorear varios sockets al mismo tiempo. Te dir cuales estn
listos para lectura, cuales para escritura, y que sockets han levantado excepciones, si es
que realmente quieres saber eso.
Sin ir ms lejos, les muestro la notacin:
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int numfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);

La funcin monitorea los registros (sets) de los descriptores de archivo; en particular


readfds, writefds, y exceptfds. Si desea ver si puedes leer de una entrada estndar y
algn descriptor de archivo, sockfd, solo aade los descriptores de archivo 0 y sockfd al
registro de readfds. El parmetro numfds debera setearse al valor del ms alto descriptor
de archivo ms uno. En este ejemplo, debera tomar el valor de sockfd + 1, ya que es
(seguro) mayor que la entrada estndar (0).
Cuando select() regresa, readfds cambiar para mostrar cual de los descriptores de
archivos que seleccionaste esta listo para lectura. Puedes probarlos con la macro
FD_ISSET(), abajo.
Ante de ir ms lejos, me gustara hablar de cmo manipular estos registros. Cada registro
es del tipo fd_set. La siguientes macros operan en este tipo:

FD_ZERO(fd_set *set) borra el registro de un descriptor de archivo.


FD_SET(int fd, fd_set *set) agrega fd al registro.
FD_CLR(int fd, fd_set *set) quita fd del registro.
FD_ISSET(int fd, fd_set *set) comprueba si fd est en el registro.

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
};

Slo registra a tv_sec el nmero de segundos de espera, y a tv_usec el nmero de


microsegundos de espera. S, microsegundos, no milisegundos. Hay 1.000
microsegundos en un milisegundo, y 1.000 milisegundos en un segundo. Por ende, hay
1.000.000 de microsegundos en un segundo. Por qu es usec? La u se supone que es
la letra griega Mu, que se usa para micro. Adems, cuando la funcin retorna, timeout

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

int yes=1; // for setsockopt() SO_REUSEADDR, below


int addrlen;
int i, j;
FD_ZERO(&master); // clear the master and temp sets
FD_ZERO(&read_fds);
// get the listener
if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(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);
}
// bind
myaddr.SIN_FAMILY = AF_INET;
myaddr.sin_addr.s_addr = INADDR_ANY;
myaddr.SIN_PORT = htons(PORT);
memset(&(myaddr.sin_zero), \0, 8);
if (bind(listener, (STRUCT SOCKADDR *)&myaddr, sizeof(myaddr)) == -1) {
perror("bind");
exit(1);
}
// listen
if (listen(listener, 10) == -1) {
perror("listen");
exit(1);
}
// add the listener to the master set
FD_SET(listener, &master);
// keep track of the biggest file descriptor
fdmax = listener; // so far, its this one
// main loop
for(;;) {
read_fds = master; // copy it
if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
perror("select");
exit(1);
}
// run through the existing connections looking for data to read

38

for(i = 0; i <= fdmax; i++) {


if (FD_ISSET(i, &read_fds)) { // we got one!!
if (i == listener) {
// handle new connections
addrlen = sizeof(remoteaddr);
if ((newfd = accept(listener, &remoteaddr, &addrlen)) == -1) {
perror("accept");
} else {
FD_SET(newfd, &master); // add to master set
if (newfd > fdmax) { // keep track of the maximum
fdmax = newfd;
}
printf("selectserver: new connection from %s on "
"socket %d\n", inet_ntoa(remoteaddr.sin_addr), newfd);
}
} else {
// handle data from a client
if ((nbytes = recv(i, buf, sizeof(buf), 0)) <= 0) {
// got error or connection closed by client
if (nbytes == 0) {
// connection closed
printf("selectserver: socket %d hung up\n", i);
} else {
perror("recv");
}
close(i); // bye!
FD_CLR(i, &master); // remove from master set
} else {
// we got some data from a client
for(j = 0; j <= fdmax; j++) {
// send to everyone!
if (FD_ISSET(j, &master)) {
// except the listener and ourselves
if (j != listener && j != i) {
if (send(j, buf, nbytes, 0) == -1) {
perror("send");
}
}
}
}
}
} // its SO UGLY!
}
}
}
return 0;
}

39

Observa que tengo dos descriptores de archivo en el cdigo: master y read_fds. El


primero, master, sostiene todos los descriptores de archivo que se estn ejecutando,
incluido el descriptor de socket que est escuchando por conexiones nuevas.
La razn por la que tengo master que select() realmente cambia los registros que le
pasaste para reflejar que socket estn listos para leer. Ya que tengo que mantener un
seguimiento de las conexiones desde una llamada a select() a la otra, debo guardarlo en
algn lugar. En el ltimo minuto, copio master en read_fds, y llamo a select().
Pero esto no significa que cada vez que hay una conexin nueva, tengo que agregarla al
registro de master? Sp! Y cada vez que una se cierra, tengo que quitarla de master?
Tambin.
Nota que reviso cuando un socket que escucha esta listo para lectura. Cuando esto pasa,
significa que tengo una nueva conexin pendiente, la acepto y la agrego a master.
Igualmente, cuando una conexin cliente esta lista para lectura, y recv() devuelve 0, yo s
que el cliente ha cerrado la conexin, y debo quitarla de master.
Si el cliente recv() devuelve un no-cero, entonces, yo s que algn dato se ha recibido.
As que lo ubico, y luego voy a travs de la lista de master y envo ese dato a todos los
clientes restantes.
Y eso, mis amigos, es menos que una simple revisin del la poderosa funcin select().

6.3. Manejando envos (send()s ) Parciales


Te acuerdas de la seccin sobre send(), arriba, cuando dije que send() no podra no
enviar todos los datos que le ordenaste? Me refiero, que si quieres enviar 512 bytes, pero
enva 412. Qu pas con los 100 bytes restantes?
Bueno, todava estn en tu pequeo buffer esperando a ser enviados. Debido a
circunstancias fuera de tu control, el kernel decide no enviar todos los datos de un viaje, y
ahora, amigo, depende de ti que llegu a su destino.
Podras escribir una funcin como esta para hacerlo, tambin:
#include <sys/types.h>
#include <sys/socket.h>
int sendall(int s, char *buf, int *len)
{
int total = 0; // how many bytes weve sent
int bytesleft = *len; // how many we have left to send
int n;
while(total < *len) {
n = send(s, buf+total, bytesleft, 0);
if (n == -1) { break; }
total += n;
bytesleft -= n;

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!

6.4. Hijo de la Encapsulacin


Qu es en realidad encapsular datos? De manera sencilla, quiere decir que pegas un
encabezado con informacin de identidad, largo del paquete, o ambos.
Cmo debera lucir el encabezado? Bueno, es slo datos binarios que representa lo que
sea que sientas necesario para completar tu proyecto.
Wow. Eso es vago.
Ok. Por ejemplo, digamos que tiene un programa multiusuario de chat que usa
SOCK_STREAMS. Cuando un usuario tipea (dice) algo, dos piezas de informacin
necesitan ser enviadas al servidor: que se dijo y quien lo dijo.
Hasta ahora bien? Cul es el problema? preguntas.

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?

7.1. Pginas man


Prueba las siguientes pginas man, para principiantes:

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.3. Referencias Web


En el web:

BSD Sockets: A Quick And Dirty Primer http://www.cs.umn.edu/~bentlema/unix/


The Unix Socket FAQ http://www.ibrado.com/sock-faq/
Client-Server Computing http://pandonia.canberra.edu.au/ClientServer/
Intro to TCP/IP
gopher://gopherchem.ucdavis.edu/11/Index/Internet_aw/Intro_the_Internet/intro.to.i
p/
Internet Protocol Frequently Asked Questions http://www-iso88595.stack.net/pages/faqs/tcpip/tcpipfaq.html
The Winsock FAQ http://www.cyberport.com/~tangent/programming/winsock/

7.4. RFCs

RFCs the real dirt: http://www.rfc-editor.org/


RFC-768 The User Datagram Protocol (UDP) http://www.rfceditor.org/rfc/rfc768.txt
RFC-791 The Internet Protocol (IP) http://www.rfc-editor.org/rfc/rfc791.txt
RFC-793 The Transmission Control Protocol (TCP) http://www.rfceditor.org/rfc/rfc793.txt
RFC-854 The Telnet Protocol http://www.rfc-editor.org/rfc/rfc854.txt
RFC-951 The Bootstrap Protocol (BOOTP) http://www.rfc-editor.org/rfc/rfc951.txt
RFC-1350 The Trivial File Transfer Protocol (TFTP) http://www.rfceditor.org/rfc/rfc1350.txt

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

P: Cmo puedo implementar un tiempo de espera en una llama recv()?


R: Usa select()! l te permite especificar un tiempo de espera para un descriptor de
archivo del cual quieres leer. O, podras agarra toda la funcionalidad en una sola funcin,
como sigue:
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
int recvtimeout(int s, char *buf, int len, int timeout)
{
fd_set fds;
int n;
struct timeval tv;
// set up the file descriptor set
FD_ZERO(&fds);
FD_SET(s, &fds);
// set up the struct timeval for the timeout
tv.tv_sec = timeout;
tv.tv_usec = 0;
// wait until timeout or data received
n = select(s+1, &fds, NULL, NULL, &tv);
if (n == 0) return -2; // timeout!
if (n == -1) return -1; // error
// data must be here, so do a normal recv()
return recv(s, buf, len, 0);
}
// Sample call to recvtimeout():
.
.
n = recvtimeout(s, buf, sizeof(buf), 10); // 10 second timeout
if (n == -1) {
// error occurred
perror("recvtimeout");
}
else if (n == -2) {
// timeout occurred
} else {
// got some data in buf
}
.
.

47

Observa que recvtimeout() devuelve -2 en caso de finalizar el tiempo de espera. Por qu


no regresa 0? Bueno, si recuerdas, un valor 0 devuelto en una llamada a recv() significa
que el lado remoto a cerrado la conexin. Por ende el valor retornado habla por si solo, y
-1 significa error, entonces eleg 2 como mi indicador.

48

9. Desligamiento de Responsabilidad y Peticin de Ayuda


Bueno, eso es todo. Pongo esperanza en que algo de la informacin presentada sea
acertada y espero sinceramente que no haya muchos errores. Bueno, seguro, siempre
habrn.
Entonces, que sea una advertencia para ti! Lo siento si algn contenido errado te haya
causado algn problema, pero no puedes hacer responsable. Mira, yo no me
responsabiliz por ninguna palabra de este documento, legalmente hablando. Todo este
embrollo podra estar enteramente mal!
Pero probablemente no lo esta. Despus de todo, he gastado muchas, muchas horas
lidiando con estos problemas, e implementando varias utilidades de red TCP/IP en el
trabajo, he escrito varios motores para juegos de multiusuario, y dems. Pero no soy el
dios de los sockets; slo soy un tipo.
Por si acaso, si alguien tiene alguna critica constructiva (o destructiva) sobre este
documento, por favor enveme un mail a beej@piratehaven.org y har un esfuerzo por
mejorarlo.
En caso de que te este preguntando el porque de este documento, bueno, lo hice por el
dinero. Ha! No, realmente. Lo hice porque un montn de gente me haba estado
preguntando acerca de sockets y cuando les dije que iba a poner una pgina sobre
sockets, ellos dijeron, Cool! Adems, creo que todo este difcil y merecido conocimiento
se va a perder si no lo comparto. El web resulta ser un vehculo perfecto. Estimulo a otros
a proveer informacin siempre que sea posible.
Suficiente de esto de regreso al cdigo!

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

Potrebbero piacerti anche