Vous êtes sur la page 1sur 26

Sockets

Una de las mayores contribuciones de las distribuciones de


Berkeley al sistema UNIX es incontestablemente el interfaz de
comunicacin que ha sido desarrollado. Ofrece un mecanismo de
comunicacin general entre dos procesos cualquiera que pertenezcan
a un mismo sistema o a dos sistemas diferentes.

DEFINICIN DE SOCKET
Un socket es un punto de comunicacin por el cual un proceso puede emitir o
recibir informacin. En el interior de un proceso, se identificar por un descriptor de la
misma naturaleza que los que identifican los archivos.

Caractersticas de un socket
A cada una de ellas le corresponde una constante simblica predefinida en el
archivo <sys/socket.h>.

Dominio de un socket:
Conjunto de sockets con los cuales se podr establecer una comunicacin por medio
de l. Asimismo, especifica el formato de las direcciones que se podrn dar al socket y los
diferentes protocolos soportados por las comunicaciones va los sockets de este conjunto.
La estructura genrica para el formato de una direccin de socket es:
struct sockaddr{
u_short
char
};

sa_family;
/* familia de direccin */
sa_data[14]; /* 14 octetos de direccin (mximo) */

que ser reemplazada por la estructura correspondiente del dominio de comunicacin


utilizado, para una aplicacin particular.
Dominio UNIX (AF_UNIX) Los sockets son locales al sistema donde han sido
definidos. Permiten la comunicacin interna de procesos. La estructura de una direccin en
este dominio est predefinida en <sys/un.h> :
struct sockaddr_un{
short sun_family;
char sun_data[108];
};

/* dominio UNIX: AF_UNIX */


/* referencia de direccin */

Dominio Internet (AF_INET) Las direcciones de los sockets tienen la


estructura sockaddr_in. Por mediacin de sta, ser posible designar un servicio sobre una
mquina particular.
struct in_addr{
u_long s_addr;
};

struct sockaddr_in{
short
sin_family;
u_short
sin_port;
struct in_addr sin_addr;
char
sin_zero[8];
};

/* familia de direccin: AF_INET */


/* el nmero de puerto */
/* la direccin Internet */
/* un campo de 8 ceros */

Para comunicar utilizando los protocolos Internet, el primer campo tendr como
valor AF_INET. El segundo campo ser un nmero de puerto; en el caso de servicios
estndares, se podr utilizar un nombre simblico para designar el puerto
(IPPORT_TCP o IPPORT_TELNET) y en el caso contrario, un nmero de puerto
nulo o superior a IPPORT_RESERVED. El tercer campo es una direccin Internet (en
representacin estndar) o el valor INADDR_ANY (en el caso de que el sistema local
sea una pasarela y, por tanto, disponga de varias direcciones diferentes). El ltimo campo
sirve para hacer coincidir el tamao de esta estructura con la de la estructura de direccin
genrica sockaddr (14 octetos de direccin mximo-).

Tipo de un socket:
Define las propiedades de las comunicaciones en las cuales est implicado.
Propiedades de comunicacin
1. Fiabilidad de la transmisin. Ningn dato transmitido se pierde.
2. Conservacin del orden de los datos. Los datos llegan en el orden en el
que han sido emitidos.
3. No duplicacin de datos. Slo llega al destino un ejemplar de cada dato
emitido.
4. Comunicacin en modo conectado. Se establece una conexin entre dos
puntos antes del principio de la comunicacin. A partir de entonces, una
emisin desde un extremo est implcitamente destinada al otro extremo
conectado.
5. Conservacin de los lmites de los mensajes. Los lmites de los mensajes
emitidos se pueden encontrar en el destino.
6. Envo de mensajes <<urgentes>>. Corresponde a la posibilidad de enviar
datos fuera del flujo normal y por consecuencia accesibles
inmediatamente (datos fuera de flujo u out of band).
Tipos disponibles
SOCK_DGRAM Sockets destinados a la comunicacin en modo
no conectado para el envo de datagramas de tamao limitado. Las
comunicaciones correspondientes tienen la propiedad 5. En el dominio
Internet, el protocolo subyacente es el protocolo UDP.

SOCK_STREAM. Los sockets de este tipo permiten


comunicaciones fiables en modo conectado (propiedades 1-4) y
eventualmente autorizan (segn el protocolo aplicado) los mensajes fuera
de banda (propiedad 6). El protocolo subyacente en el dominio Internet es
TCP.

SOCK_RAW. Permite el acceso a los protocolos de ms bajo nivel


(por ejemplo, el protocolo IP en el dominio Internet). Su uso est
reservado al superusuario. Permite implantar nuevos protocolos.

Creacin y Supresin de un Socket


Creacin:
int socket (dominio, tipo, protocolo)
int dominio; /* AF_UNIX, AF_INET, ...*/
int tipo;
/* SOCK_DGRAM, SOCK_STREAM, ...*/
int protocolo; /* 0: protocolo por defecto */
En caso de fallo, se devuelve el valor 1; si no, un descriptor para su uso. El tercer
parmetro es generalmente 0: el sistema elige entonces el protocolo por defecto asociado
al tipo y al dominio del socket creado.

Supresin:
Un socket es suprimido cuando ya no hay ningn proceso que posea un descriptor
para accederlo. Esta supresin es el resultado de, al menos, una llamada a la primitiva
close.

Enlazamiento socket-direccin
Primitiva bind:
Despus de su creacin, un socket no es accesible ms que por los procesos que
conocen su descriptor. La primitiva bind permite con una sola operacin dar un nombre
a un socket:
int bind (sock, p_direccion, lg)
int
sock;
struct sockaddr *p_direccion;
int
lg;

/* descriptor de socket */
/* puntero de memoria a la direccin */
/* longitud de la direccin */

Para una utilizacin en un dominio particular, el puntero p_direccion apunta a una

zona cuya estructura es la de una direccin en ese dominio (sockaddr_un para


AF_UNIX y sockaddr_in para AF_INET).
El socket nombrado despus de realizarse con xito la primitiva (valor de retorno 0),
puede ser identificado por cualquier proceso por medio de la direccin que se le ha dado sin
necesidad de poseer un descriptor (lo que es a priori imposible si el proceso no pertenece al
sistema del socket).

El dominio Unix:
Los sockets de este dominio no se destinan ms que a una comunicacin local. Por
tanto, les corresponden direcciones locales que son referencias UNIX idnticas a las de los
archivos.
Un socket de este dominio aparecer despus del nombrado en los resultados
producidos por la orden ls con el tipo s. La supresin de una referencia de este tipo es por
medio de la orden rm o de la primitiva unlink.
La orden siguiente ilustra la creacin y conexin de un socket del dominio UNIX:
$ sockunix /tmp/a &
[1] 17271
enlazamiento conseguido
$ cat sockunix.c
/* Orden de creacin y nombrado de un socket del dominio UNIX.
El nombre del socket se da como parmetro */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
void main (int n, char *v[])
{
int sock; /* descriptor del socket */
static struct sockaddr_un adr; /* direccin del socket */
if (n != 2) {
fprintf(stderr, error de parmetro\n);
exit(2);
}
sock = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sock == -1) {
perror(socket);
exit(2);
}
adr.sun_family = AF_UNIX;
bcopy(v[1], adr.sun_data, strlen(v[1]));
if (bind(sock, &adr, sizeof(adr)) == -1) {

perror(bind);
exit(2);
}
printf(enlazamiento conseguido\n);
while (1);
}

El dominio Internet:
La conexin a una direccin Internet de un socket de este dominio necesita la
preparacin de un objeto que tenga la estructura sockaddr_in. Esto supone:
- el conocimiento de la direccin de la mquina local (primitiva gethostname y
funcin gethostbyname) o la eleccin del valor INADDR_ANY;
- la eleccin de un nmero de puerto. Puerto de un servicio existente (funcin
getservbyname), cualquier nmero de puerto (>=IPPORT_RESERVED) elegido por
el usuario o eleccin del sistema (valor 0 en el campo sin_port).
En ciertas circunstancias, un proceso puede disponer de un descriptor de un socket
conectado o enlazado a una direccin, pero sin saber cul es (si la conexin ha sido
realizada por otro proceso y el proceso ha heredado el descriptor o si la conexin ha sido
realizada sin especificar el nmero de puerto).
La primitiva getsockname permite recuperar la direccin relacionada con el socket
de descriptor sock.
int getsockname (sock, p_adr, p_lg)
int
sock; /* descriptor del socket */
struct sockaddr *p_adr; /* puntero a la zona de direccin */
int
*p_lg; /* puntero a la longitud de la direccin */
Cuando se llama a esta primitiva, el tercer parmetro se utiliza como dato y como
resultado:
- en la llamada, *p_lg tiene como valor el tamao de la zona reservada a la
direccin p_addr para recuperar la direccin del socket;
- en el retorno de la llamada, *p_lg tiene como valor el tamao efectivo de la
direccin.
El valor de retorno de la primitiva es 0 -1, segn si la llamada ha tenido xito o
no.
A continuacin, se muestra el cdigo de una funcin crearsock, que permite la
creacin y la conexin de un socket del dominio Internet:

$ cat crearsock.c
/* Funcin de creacin de un socket.
Nmero de puerto dado como parmetro (modificado si 0).
Se devuelve como resultado el descriptor del socket. */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
int crearsock (int *port, int type)
{
int desc; /* descriptor del socket */
struct sockaddr_in nom; /* direccin del socket */
int longitud; /* longitud de la direccin */
/* creacin del socket */
if ( (desc = socket(AF_INET, type, 0) == -1) {
perror(creacin imposible del socket);
exit(2);
}
/* preparacin de la direccin */
bzero((char *)&nom, sizeof(nom));
nom.sin_port = *port;
nom.sin_addr.s_addr = INADDR_ANY;
nom.sin_family = AF_INET;
if ( bind(desc, &nom, sizeof(nom)) == -1 ) {
perror(nombrado del socket imposible);
exit(3);
}
longitud = sizeof(nom);
if ( getsockname(desc, &nom, &longitud) == -1 ) {
perror(obtencin del nombre del socket);
exit(4);
}
*port = ntohs(nom.sin_port);
return (desc);
}

LA COMUNICACIN POR DATAGRAMAS


Este apartado est dedicado al estudio de los mecanismos relativos a la
comunicacin de procesos por medio de sockets del tipo SOCK_DGRAM (cualquiera que
sea su dominio de definicin).
Un proceso que desee emitir un mensaje con destino a otro debe, por una parte,
disponer de un punto de comunicacin local (descriptor del socket sobre el sistema local) y
por otra parte, conocer una direccin del sistema al cual pertenezca su interlocutor
(esperando que este interlocutor disponga de un socket conectado o enlazado a esta
direccin...este tipo de comunicacin no permite estar seguro de ello).
Los sockets de este tipo se utilizan en modo no conectado: en principio toda
peticin de envo de un mensaje debe incluir la direccin del socket de destino.
Sin embargo, veremos que es posible establecer una <<pseudoconexin>> entre dos
sockets del tipo SOCK_DGRAM haciendo implcita la direccin del socket para una
emisin.

Operaciones de envo y de recepcin


Primitiva sendto:
int sendto (sock, msg, lg, opcion, p_dest, lgdest)
int
sock;
char
*msg;
int
lg;
int
opcion;
struct sockaddr *p_dest;
int

lgdest;

/* descriptor del socket de emisin */


/* direccin del mensaje a enviar */
/* longitud del mensaje */
/* = 0 para el tipo SOCK_DGRAM */
/* puntero a la direccin del socket */
/* destino */
/* longitud de la direccin del socket */
/* destino */

El valor de retorno de la primitiva es, en caso de xito, el nmero de caracteres


enviados y 1 en caso de fallo. Insistamos en el hecho de que los nicos errores detectados
son los locales. As, no hay ninguna deteccin de que un socket est enlazado a la direccin
dada. Esto significa en particular que si un mensaje se emite con destino a una mquina
existente sobre un puerto no asociado a un socket, el mensaje se perder y el emisor no ser
avisado.
Por el contrario, la validez del descriptor sock o de la direccin del mensaje se
comprueban. Incluso, el sistema indica un error si el tamao del mensaje a emitir, debido al
protocolo subyacente, es incompatible con la atomicidad de la emisin (de este modo, para
el protocolo UDP utilizado para este tipo de envo en el dominio Internet, un mensaje debe
ser en general de longitud < 2 K).

Aadir que no es necesario que un socket est nombrado para que pueda ser
utilizado para enviar un mensaje con la primitiva sendto. El sistema realiza
automticamente su conexin durante el primer envo.
Es prctico utilizar la primitiva sendmsg en el caso que un proceso deba realizar
una sucesin de envos de mensajes. El inters de utilizar esta primitiva es que se realiza el
envo de varios mensajes con una sola llamada al sistema, lo que mejora el rendimiento. La
forma de esta primitiva es:
int sendmsg (sock, msg, opcion)
int
sock;
struct msghdr msg[];
int
opcion;

/* descriptor del socket de emisin */


/* tabla de los envos a efectuar */

La estructura msghdr est predefinida en <sys/socket.h>:


struct msghdr {
caddr_t
msg_name;
int
msg_namelen;
struct iovec *msg_iov;
int
msg_iovlen;
caddr_t
msg_accrights;
int
msg_accrightslen;
};

/* direccin opcional */
/* tamao de la direccin */
/* tabla de mensajes */
/* n de elementos en msg_iov */
/* inutilizada para los sockets */
/* inutilizada para los sockets */

y la estructura iovec en <sys/uio.h>:


struct iovec {
caddr_t iov_base;
int
iov_len;
};

/* direccin del mensaje */


/* longitud del mensaje */

Primitiva recvfrom:
int recvfrom (sock, msg, lg, opcion, p_exp, p_lgexp)
int
char

sock;
*msg;

/* descriptor del socket de recepcin */


/* direccin de recuperacin del */
/* mensaje recibido */
int
lg;
/* tamao del espacio reservado a la */
/* direccin msg */
int
opcion;
/* 0 MSG_PEEK*/
struct sockaddr *p_exp;
/* para recuperar la direccin de */
/* expedicin */
int
*p_lgexp; /* tamao del espacio reservado a */
/* p_exp y longitud del resultado */

Esta primitiva permite a un proceso extraer un mensaje de un socket del cual posea
un descriptor. Para que haya un mensaje, naturalmente es preciso que este socket haya sido
enlazado o conectado a una direccin (o pseudoconectado en su creacin con la primitiva
socketpair) y que un proceso haya enviado un mensaje despus de que haya tenido lugar
este enlace.
El papel del parmetro lg es el de dar el tamao del espacio reservado para
memorizar el mensaje que ser extrado. Es preciso no perder de vista que se extraer del
socket un mensaje completo, que corresponde exactamente a la informacin enviada en una
sola operacin sendto. La informacin recibida provendr de un solo mensaje y los
caracteres del mensaje se perdern si el parmetro lg es menor que la longitud del mensaje
recibido. La primitiva devuelve el nmero de caracteres recibidos (-1 en caso de error).
Adems, la llamada es bloqueante si no hay ningn mensaje para extraer del socket salvo
peticin contraria formulada previamente con una llamada a la primitiva ioctl de la forma:
#include <sys/ioctl.h>
.
.
int sock;
int on = 1;
.
.
ioctl(sock, FIONBIO, &on);
.
.
En el caso en el que el puntero p_exp es distinto de NULL, a la vuelta, la zona de
direccin p_exp contiene la direccin del socket emisor. Es esencial en el caso en que la
llamada *p_lgexp contiene el tamao de la zona reservada a la direccin p_exp; a la vuelta,
*p_lgexp tiene el valor de la longitud efectiva de la direccin. El proceso receptor tiene
entonces la posibilidad de responder, si hay necesidad de ello, al mensaje recibido por
medio de la primitiva sendto (recordemos que los sockets permiten la comunicacin en los
dos sentidos).
Esto queda esquemticamente en el dominio Internet:
.
.
struct sockaddr_in adr;
int sock, n, lg, lgrep;
char msg[1024], rep[1024];
.
.
lg = sizeof(struct sockaddr_in);
n = recvfrom(sock, msg, 1024, 0, &adr, &lg);
.
.
n = sendto(sock, rep, lgrep, 0, &adr, lg);

.
.
La opcin MSG_PEEK permite consultar el socket: se lee un mensaje, pero no se
extrae como es el caso sin esta opcin.
La primitiva recvmsg permite realizar una serie de recepciones de mensajes:
int recvmsg (sock, msg, opcion)
int
sock;
struct msghdr msg[];
int
opcion;

/* descriptor del socket de recepcin */


/* tabla de mensajes recibidos */

Ejemplo completo
El siguiente ejemplo completo pone en relacin un proceso cliente con un proceso
servidor (un proceso <<demonio>>). Los dos procesos intercambian datagramas en el
dominio. El proceso servidor est en espera de recepcin de mensajes sobre un socket
enlazado a un puerto UDP conocido por los clientes que quieren dirigirse a l (aqu,
PORT = 2222). Una peticin formulada por un cliente consiste simplemente en una cadena
de caracteres que el cliente quiere saber si corresponde a la identificacin de un usuario
sobre el sistema al que pertenece el servidor, y si es ste el caso, obtener las informaciones
(contenidas en el archivo /etc/passwd) relativas a este usuario.

El servidor:
El cdigo del servidor comporta las siguientes etapas:
1. Creacin y enlace al puerto de servicio de un socket (se utiliza la funcin
crearsock descrita anteriormente).
2. Desconexin del servidor de su terminal de lanzamiento o ejecucin.
3. Bucle infinito en el cual el servidor:
Espera una peticin.
La trata (llamada a la funcin estndar getpwnam que
permite consultar el archivo /etc/passwd).
Pone la forma de respuesta (el campo type permite distinguir
el caso que el usuario es desconocido (1) y el que no lo es (2).
Enva la respuesta.
El servidor se ejecuta en background.
$ cat quienesd.c
/* Servidor Internet en el puerto UDP nmero 2222 que devuelve las */
/* informaciones relativas a un usuario cuyo nombre se le ha dado */
#include <stdio.h>
#include <sys/types.h>

#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <pwd.h>
#define LGUSER 20
#define LGREP 256
#define PORT 2222
void main (int n, char *v[])
{
int lg, sock, port, n;
int d;
char user[LGUSER]; /* el mensaje recibido */
struct sockaddr_in adr; /* direccin del socket remoto */
struct passwd *getpwnam(), *p;
/* la respuesta enviada */
struct respuesta {
char type; /* 1: error; 2: OK */
char info[LGREP]; /* las informaciones pedidas si type = 2 */
} rep;
port = PORT;
if ( (sock = crearsock(&port, SOCK_DGRAM)) == -1 ) {
fprintf(stderr, Fallo en la creacin/conexin del socket\n);
exit(2);
}
/* desconexin del servidor del terminal */
close(0); close(1); close(2);
if ( (d = open(/dev/tty)) > -1 ) {
ioctl(d, TIOCNOTTY, 0);
close(d);
}
setpgrp();
while (1) { /* espera de pregunta sobre el socket */
lg = sizeof(adr);
bzero(user, LGUSER);
bzero((char *)&rep, sizeof(rep));
n = recvfrom(sock, user, LGUSER, 0, &adr, &lg);
if ( (p = getpwnam(user)) == NULL)
rep.type = 1;
else {
rep.type = 2;
sprintf(rep.info, %d %d %s %s %s\n, p->pw_uid,
p->pw_gid, p->pw_gecos, p->pw_dir, p->pw_shell);
}
/* envo de la respuesta */

n = sendto(sock, (char *)&rep, sizeof(struct respuesta), 0, &adr, lg);


}
}
$ quienesd &
[1] 23708

El Cliente:
Su cdigo comporta las siguientes etapas:
1. Creacin del socket local (su enlace o conexin no es necesario, ya que
el cliente comenzar emitiendo y el enlace se realizar
automticamente).
2. Preparacin de la direccin del servidor.
3. Envo del mensaje.
4. Espera del resultado.
5. Explotacin del resultado.
$ cat user.c
/* Programa que permite obtener las informaciones contenidas en el
archivo /etc/passwd de una mquina de nombre dado como primer
parmetro, concernientes a un usuario que se da como segundo
parmetro.
El servicio correspondiente en la mquina remota est asociado al
puerto 2222 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#define PORT 2222
#define LGREP 256
void main (int n, char *v[])
{
int lg, sock, uid, gid, n;
char p[LGREP];
struct sockaddr_in adr;
struct hostent *hp;
struct {
char type;
char info[LGREP];
} rep;

if (n < 3) {
fprintf(stderr, nmero de parmetros incorrecto\n); exit(2); }
/* preparacin de la direccin del socket destino */
if ( (hp = gethostbyname(v[1])) == NULL ) {
perror(nombre de la mquina);
exit(2);
}
bzero((char *)&adr, sizeof(adr));
sock = socket(AF_INET, SOCK_DGRAM, 0);
adr.sin_family = AF_INET;
adr.sin_port = htons(PORT);
bcopy(hp->h_addr, &adr.sin_addr, hp->h_length);
/* envo del mensaje constituido por el nombre del usuario */
if ( sendto(sock, v[2], strlen(v[2]), 0, &adr, sizeof(adr)) == -1 ) {
perror(sendto);
exit(2);
}
/* espera de la respuesta por parte del servidor */
lg = sizeof(adr);
n = recvfrom(sock, (char *)&rep, sizeof(rep), 0, &adr, &lg);
/* explotacin de la respuesta */
if ( rep.type == 1 )
fprintf(stderr, %s: usuario desconocido en %s\n, v[2], v[1]);
else {
printf(usuario %s en %s\n\n, v[2], v[1]);
sscanf(rep.info, %d %d % [^\n], &uid, &gid, p);
printf( uid = %d gid = %d \n, uid, gid);
printf( %s \n, p);
}
}
$ user germinal dupond
dupond: usuario desconocido en germinal
$ user germinal jmr
usuario jmr en germinal
uid = 102 gid = 20
Rifflet Jean-Marie /ens/jmr /bin/csh
La estructura hostent se encuentra predefinida en netdb.h y corresponde a una
entrada en el archivo /etc/hosts:
struct hostent{
char
char
int
int

*h_name;
**h_aliases;
h_addrtype;
h_length;

/* nombre oficial de la mquina */


/* lista de alias */
/* tipo de direccin: AF_INET */
/* longitud de la direccin */

char
**h_addr_list; /* lista de direcciones */
#define h_addr h_addr_list[0]
/* la primera direccin de la lista */
};

Las <<pseudoconexiones>>
Este mecanismo permite simplemente alegar la escritura de aplicaciones en el caso
de que un socket del tipo SOCK_DGRAM no se utilice ms que para comunicar slo con
otro. Sin embargo, es necesario tener en cuenta que una pseudocomunicacin de este tipo
no modifica en nada las caractersticas generales del modo de comunicacin (en particular
en lo que concierne a su fiabilidad).

Primitiva connect:
Utilizada con sockets del tipo SOCK_DGRAM, permite asociar un socket de
direccin dada a un socket local (dado por su descriptor). Se ver que tiene un papel
diferente con los sockets SOCK_STREAM.
int connect (sock, p_adr, lgadr)
int
sock;
struct sockaddr *p_adr;
int
lgadr;

/* descriptor del socket local */


/* puntero a la direccin del socket asociado */
/* longitud de la direccin */

Despus de la <<pseudoconexin>> de un socket a otro, todas las emisiones de


mensajes, va el primero, tendrn como destino el segundo, que no podr recibir ms que
los mensajes que provengan del otro. Es importante notar que esta pseudoconexin es local:
no tiene ninguna incidencia sobre el socket del cual se ha dado la direccin (la direccin del
socket remoto est memorizada en la estructura asociada al socket local). Un segundo
parmetro igual a NULL en una llamada a la primitiva connect suprime una asociacin
anterior.

Primitiva socketpair:
Permite crear dos sockets y asociarlos.
int socketpair (dominio, tipo, protocolo, p_sock)
int dominio; /* AF_UNIX */
int tipo;
/* SOCK_DGRAM, SOCK_STREAM */
int protocolo; /* 0 */
int *p_sock; /* tabla de dos enteros */
Se recupera en la direccin *p_sock el valor de los descriptores de los dos sockets
creados. Adems, con esta primitiva no se puede trabajar ms que localmente (es decir, en
el dominio UNIX).

Primitiva send:
La direccin de destino es implcita.
int send (sock, msg, lg, opcion)
int
char
int
int

sock;
*msg;
lg;
opcion;

/* descriptor del socket local */


/* direccin de memoria del mensaje */
/* longitud del mensaje */
/* = 0 */

Primitiva recv:
La direccin de origen es implcita.
int recv (sock, msg, lg, opcion)
int
char

sock;
*msg;

int

lg;

int

opcion;

/* descriptor del socket local */


/* direccin de memoria para guardar el */
/* mensaje */
/* longitud de la zona reservada a la */
/* direccin msg */
/* 0 MSG_PEEK*/

LA COMUNICACIN EN MODO CONECTADO


Es el modo utilizado por las aplicaciones estndar de la familia Internet, tales como
telnet, ftp o aplicaciones UNIX como rlogin. Aporta la fiabilidad de los intercambios de
informacin con el precio de un incremento de su volumen.
Como indica el nombre de este modo de comunicacin, es necesario para utilizarlo
realizar una conexin (establecer un <<circuito virtual>>) entre dos puntos.
Las caractersticas importantes de la comunicacin en este modo son:

la fiabilidad;
el aspecto continuo de la informacin (<<flujo>>).

Punto de vista del servidor


Su papel es pasivo en el establecimiento de la comunicacin: despus de haber
avisado al sistema al que pertenece de que est preparado para responder a las peticiones de
servicio, el servidor se pone a la espera de peticiones de conexin que provengan de
clientes. Para esto dispone de un socket de escucha, enlazado al puerto TCP
correspondiente al servicio, sobre el que espera las peticiones de conexin. Cuando llega al
sistema una peticin de este tipo, se despierta al proceso servidor y se crea un nuevo socket:
es este ltimo, que llamaremos socket de servicio, el que se conecta al del cliente.
Entonces el servidor podr, por una parte, delegar el trabajo necesario para la realizacin
del servicio a un nuevo proceso (creado por fork) que utilizar entonces efectivamente la
conexin y, por otra parte, retomar su <<vigilia>> sobre el socket de escucha.
El esquema general de funcionamiento de un servicio se presenta a continuacin:
Creacin y enlace del socket de escucha
socket/bind

Apertura del servicio


listen

Espera de demanda de conexin


accept

Creacin de un subproceso
fork
PADRE

HIJO
Tratamiento de la demanda

No se volver sobre la primera etapa cuyo objetivo es simplemente dotar al servidor


de un punto de comunicacin direccionable desde el exterior.

Primitiva listen:
Esta primitiva permite a un servidor sealar a un sistema que acepta las peticiones
de conexin.
Para llamar con xito a la primitiva listen (por tanto, con valor de retorno 0), el
proceso debe disponer de un descriptor de socket del tipo SOCK_STREAM. Si el socket no
est previamente enlazado a un puerto, el sistema procede a este enlace. En este caso, ser
necesario hacer una llamada a la primitiva getsockname para conocer el nmero del puerto
atribuido.
int listen (sock, nb)
int sock;
int nb;

/* descriptor del socket de escucha */


/* nmero mximo de peticiones de conexin pendientes */

El segundo parmetro define el tamao de un archivo en el cual se memorizan las


peticiones de conexin formuladas, preparadas (desde el punto de vista de los clientes estn
establecidas), pero todava no notificadas al servidor. El tamao del archivo est limitado a
un cierto valor por el sistema (en general cinco).

Primitiva accept:
Esta primitiva permite extraer una conexin pendiente en el archivo asociado a un
socket para la cual se ha realizado una llamada a listen. De esta manera, el sistema toma
conocimiento de un enlace realizado. Como se ha dicho, el enlace con el socket del cliente
se realiza con un nuevo socket cuyo descriptor se enva como resultado de la funcin:
int accept (sock, p_adr, p_lgadr)
int
sock;
/* descriptor del socket */
struct sockaddr *p_adr;
/* direccin del socket conectado */
int
*p_lgadr; /* puntero al tamao de la zona reservada a */
/* p_adr */
El socket de servicio creado se enlaza a un nuevo puerto (tomado del conjunto de
puertos no reservados).
A la vuelta, tambin se recupera en memoria, en la zona apuntada por p_adr, la
direccin del socket del cliente con el cual se ha establecido la conexin. El valor de

*p_lgadr se modifica: si en la llamada era el tamao de la zona reservada para guardar la


direccin, en el retorno da el tamao efectivo de la direccin.
En el caso en el que no existe ninguna conexin pendiente en el archivo, el proceso
se bloquea hasta que exista una (o hasta que llegue una seal!) a menos que el socket est
en modo no bloqueante. En este ltimo caso, la primitiva devuelve el valor 1 y la variable
errno tiene como valor EWOULDBLOCK.
$ cat servidor.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <netdb.h>
#define PORT 1234
void service (int sock)
/* Proceso de servicio: redireccin de entrada/salida en el socket
de servicio cuyo descriptor se da como parmetro y ejecucin de
un shell interactivo. */
{
int i;
for (i=0;i<3;i++) {
close(i);
dup(sock);
}
execl("/bin/bsh", "bsh", "-i", 0);
}
int crearsock (int *port, int type)
/* Funcin de creacin de un socket.
Nmero de puerto dado como parmetro (modificado si 0).
Se devuelve como resultado el descriptor del socket. */
{
int desc; /* descriptor del socket */
struct sockaddr_in nom; /* direccin del socket */
int longitud; /* longitud de la direccin */
/* creacin del socket */
if ( (desc = socket(AF_INET, type, 0)) == -1) {
perror("creacin imposible del socket");
exit(2);
}

/* preparacin de la direccin */
bzero((char *)&nom, sizeof(nom));
nom.sin_port = *port;
nom.sin_addr.s_addr = INADDR_ANY;
nom.sin_family = AF_INET;
if ( bind(desc, &nom, sizeof(nom)) == -1 ) {
perror("nombrado del socket imposible");
exit(3);
}
longitud = sizeof(nom);
if ( getsockname(desc, &nom, &longitud) == -1 ) {
perror("obtencin del nombre del socket");
exit(4);
}
*port = ntohs(nom.sin_port);
return (desc);
}
void main (int n, char *v[])
/* Servidor Internet en el puerto TCP nmero 1234 */
{
int sock_escucha, sock_service;
struct sockaddr_in adr;
int lgadr = sizeof(adr);
int d;
int port = PORT;
/* creacin del socket de escucha */
if ( (sock_escucha = crearsock(&port, SOCK_STREAM)) == -1 ) {
fprintf(stderr, "Fallo en la creacin/conexin del socket\n");
exit(2);
}
/* desconexin del servidor del terminal */
close(0); close(1); close(2);
if ( (d = open("/dev/tty", O_RDWR)) > 0 ) {
ioctl(d, TIOCNOTTY, 0);
close(d);
}
/* eliminacin de procesos de servicio que terminan; */
/* el proceso servidor ignora la seal SIGCLD */
signal(SIGCLD, SIG_IGN);
/* creacin de la cola de conexiones pendientes */
listen(sock_escucha, 5);
/* bucle de aceptacin de la conexin */
while (1) {
lgadr = sizeof(adr);
sock_service = accept(sock_escucha, &adr, &lgadr);

if (fork() == 0) {
/* el proceso de servicio no utiliza el socket de escucha */
close(sock_escucha);
/* llamada a la funcin de servicio */
service(sock_service);
exit(0);
}
/* el proceso padre no utiliza el socket de servicio */
close(sock_service);
}
}

Punto de vista del cliente


Un cliente es la entidad activa en el establecimiento de una conexin: es el que toma
la iniciativa de la demanda de conexin a un servidor.
Esta demanda se realiza por medio de la primitiva connect de la que ya se ha
hablado para el establecimiento de pseudoconexiones de sockets del tipo SOCK_DGRAM.
Sin embargo, aqu la semntica de la primitiva es completamente diferente: solicita el
establecimiento de una conexin que ser conocida por los dos extremos. Adems, el
cliente est informado del xito o del fracaso del establecimiento de la conexin.
La organizacin general de un cliente se presenta a continuacin:
Creacin (y eventualmente enlace de un socket)
socket/bind

Construccin de la direccin del


servidor

Fallo

Demanda de conexin
connect
xito
Dilogo con el servidor

Primitiva connect:
Toda conexin entre dos sockets del tipo SOCK_STREAM, en un dominio distinto

del UNIX, es el resultado de una llamada con xito a esta primitiva (en el dominio UNIX,
una llamada a la primitiva socketpair crea un par de sockets conectados). As, se crea un
circuito virtual entre los dos procesos cuyos extremos son los sockets. Este circuito permite
intercambios bidireccionales.
La forma de la primitiva es idntica a la que hemos dado para la pseudoconexin de
sockets dedicados al intercambio de datagramas:
int connect (sock, p_adr, lgadr)
int
sock;
struct sockaddr *p_adr;
int
lgadr;

/* descriptor del socket local */


/* direccin del socket remoto */
/* longitud de la direccin */

El socket correspondiente al descriptor sock ser conectado o enlazado a una


direccin local, en el caso de que no lo estuviera ya previamente.
La conexin puede establecerse (y la primitiva connect devuelve el valor 0) si se
cumplen las siguientes condiciones:
a) los parmetros son <<localmente>> correctos;
b) la direccin *p_adr se asocia a un socket del tipo SOCK_STREAM en
el mismo dominio que el socket local de descriptor sock y un proceso
(servidor) tiene solicitado escuchar sobre este socket (por una llamada a
listen);
c) la direccin *p_adr no est utilizada por otra conexin;
d) el archivo de conexiones pendientes del socket distante o remoto no est
lleno.
En caso de xito, el socket local sock est conectado con un nuevo socket y la
conexin est pendiente hasta que el servidor tenga conocimiento de ella a travs de la
primitiva accept. Sin embargo, el cliente puede comenzar a escribir o a leer del socket.
En el caso de que no se cumpla alguna de las condiciones a), b) o c), el valor
devuelto por la primitiva es 1 y la variable errno permite conocer la razn del fallo.
El comportamiento de la primitiva es particular si no se cumple la condicin d):

Si el socket es de modo bloqueante, el proceso se bloquea. La peticin


de conexin se repite durante un cierto tiempo; si al cabo de este lapso
de tiempo la conexin no se ha podido establecer, el proceso es
despertado (valor devuelto 1 y errno = ETIMEDOUT).
Si el socket es de modo bloqueante, la vuelta es inmediata (errno =
EINPROGRESS). Sin embargo, la peticin de conexin no se abandona
en seguida (se repite durante el mismo lapso de tiempo).

El dilogo Servidor / Cliente


Una vez establecida la conexin entre un servidor y un cliente a travs de dos
sockets, los dos procesos pueden intercambiar flujos de informacin. A diferencia de lo que

pasa en la comunicacin por datagramas, el corte en diferentes mensajes no est preservado


en el socket destino. Esto significa que el resultado de una operacin de lectura puede
provenir de la informacin resultado de varias operaciones de escritura. Adems, en el caso
de los sockets del dominio Internet, una peticin de escritura de una cadena de caracteres
larga, puede provocar el partido de esta cadena, siendo accesibles los diferentes fragmentos
por el socket destino. En este caso, la nica garanta que proporciona el protocolo TCP es
que los fragmentos son accesibles en el orden correcto. Esto implica que la sincronizacin
de una recepcin y una emisin en una conexin de un mismo nmero de elementos no est
asegurada por este mecanismo.

La emisin:
La escritura sobre un socket conectado es directamente realizable por medio de la
primitiva de escritura estndar write:
int write (sock, msg, lg)
int sock;
char *msg;
int lg;

/* descriptor del socket local */


/* direccin de memoria del mensaje a enviar */
/* longitud del mensaje */

Sin embargo, una operacin de este tipo no permite utilizar plenamente los
mecanismos ofertados por los protocolos particulares (por ejemplo, para el protocolo TCP,
la posibilidad de enviar informaciones urgentes). En el caso en que se desea explotar estas
posibilidades, es necesario utilizar la primitiva especfica send:
int send (sock, msg, lg, opcion)
int sock;
char *msg;
int lg;
int opcion;

/* descriptor del socket local */


/* direccin de memoria del mensaje a enviar */
/* longitud del mensaje */
/* 0 MSG_OOB*/

El uso de esta primitiva con el valor 0 para el parmetro opcion es equivalente al de


la primitiva write.
La escritura en un socket bloquea el proceso que la realiza en el caso de que se d
alguna de las dos condiciones siguientes:
- el tampn de recepcin del socket destino est lleno;
- el tampn de emisin del socket local est lleno.
Sin embargo, un socket puede convertirse en no bloqueante mediante una llamada a
la primitiva ioctl. El valor devuelto es el nmero de caracteres emitidos (-1 en caso de
error).

La recepcin:
Igual que para la emisin sobre un socket, es posible utilizar la primitiva estndar de
lectura:
int read (sock, msg, lg)
int sock;
char *msg;
int lg;

/* descriptor del socket local */


/* direccin de memoria para grabar el mensaje */
/* longitud de la zona reservada a la direccin msg */

y la primitiva recv que permite, por una parte, la extraccin de informacin urgente
(MSG_OOB) y, por otra parte, la consulta sin extraccin (MSG_PEEK).
int recv (sock, msg, lg, opcion)
int sock;
char *msg;
int lg;
int opcion;

/* descriptor del socket local */


/* direccin de memoria para grabar el mensaje */
/* longitud de la zona reservada a la direccin msg */
/* 0 MSG_OOB MSG_PEEK */

Estas diferentes primitivas son bloqueantes, salvo solicitud contraria: si no llega


ningn carcter al socket (y la conexin todava est establecida), el proceso lector est
bloqueado.

El corte de la conexin:
La primitiva:
int shutdown (desc, sens)
int desc;
int sens;

/* descriptor de socket */
/* 0, 1 2 */

permite a un proceso especificar que ya no desea recibir (sens = 0), emitir (sens = 1) o ni
recibir ni emitir (sens = 2) sobre un socket conectado de descriptor desc.
$ cliente germinal 1234
$ hostname
germinal
$ ^D
$

/* en la mquina remota */
/* volvemos a la mquina local */

$ cat cliente.c
/* Programa que recibe como parmetros el nombre de la mquina remota y el puerto de
escucha al que solicita una conexin. A continuacin, lee una orden del teclado, la transmite
y lee la respuesta en el socket; finalmente visualiza el resultado en la pantalla. */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#define TAMANO 256
void main (int n, char *v[])
{
int desc; /* descriptor del socket creado */
struct sockaddr_in nom; /* direccin del socket destino */
struct hostent *hp, *gethostbyname();
char com[TAMANO]; /* para las comunicaciones */
if (n != 3) {
fprintf(stderr, "nmero de parmetros incorrecto\n"); exit(1); }
/* creacin del socket */
if ( (desc = socket(AF_INET, SOCK_STREAM, 0)) == -1 ) {
perror("creacin del socket imposible"); exit(2); }
/* bsqueda de la direccin internet del servidor */
if ( (hp = gethostbyname(v[1])) == NULL ) {
fprintf(stderr, "%s: lugar desconocido\n", v[1]);
exit(3);
}
/* preparacin de la direccin del socket destino */
bcopy(hp->h_addr, &nom.sin_addr, hp->h_length);
nom.sin_family = AF_INET;
nom.sin_port = htons(atoi(v[2]));
/* demanda de conexin */
if ( connect(desc, &nom, sizeof(nom)) == -1 ) {
perror("connect"); exit(4); }
bzero(com, TAMANO);
while ( scanf("%s", com) != EOF ) {
com[strlen(com)] = '\n'; /* Indispensable al shell!!! */
send(desc, com, strlen(com), 0);
bzero(com, TAMANO);
recv(desc, com, TAMANO, 0);

printf("%s", com);
bzero(com, TAMANO);
}
}