Está en la página 1de 14

UNIVERSIDAD DE LAS AMERICAS

A P L I C AC I O N E S
D I S T R I BU I DA S
SOCKETS
UNIVERSIDAD DE LAS AMERICAS ING. HUGO FERNANDO CHIMBO ACOSTA

TABLA DE CONTENIDO
El modelo cliente servidor ______________________________________________ 3
sockets ______________________________________________________________ 3
Tipos de sockets y de dominios de dirección ___________________________________ 3
Código de ejemplo ________________________________________________________ 4
Código del servidor_____________________________________________________________ 4
Código del cliente ______________________________________________________________ 7
Mejoras al código del servidor ____________________________________________________ 9
ANEXOS___________________________________________________________ 10
Códigos de ejemplo ______________________________________________________ 10
Código de Servidor ____________________________________________________________ 10
Código de cliente _____________________________________________________________ 11
Código Servidor Mejorado ______________________________________________________ 12

2
UNIVERSIDAD DE LAS AMERICAS ING. HUGO FERNANDO CHIMBO ACOSTA

SISTEMAS DISTRIBUIDOS
SOCKETS

E L M O D E L O C L I E N T E S E RV I D O R

La mayoría de comunicaciones entre procesos utiliza el modelo cliente servidor. Estos


términos hacen referencia a los procesos que deben comunicarse entre sí. Uno de los dos
procesos, el cliente, se conecta al otro proceso, el servidor, típicamente para realizar una petición
de información. Una buena analogía es una persona que hace una llamada a otra.

Note que el cliente necesita saber de la existencia y la dirección del servidor, pero el servidor
no necesita saber la dirección (ni la existencia) del cliente antes de que la conexión sea establecida.
Note también que una vez que una conexión es establecida, ambos lados pueden enviar y recibir
información.

SOCKETS

Las llamadas al sistema para establecer una conexión son un poco diferentes entre el cliente y
el servidor, pero ambos involucran la construcción básica de un socket. Un socket es el extremo
de un canal de comunicación entre procesos. Cada uno de estos dos procesos establecen su
propio socket.

Los pasos involucrados en el establecimiento de un socket en el lado del cliente son:

1. Crear el socket con la función socket()

2. Conectar al socket con la dirección del servidor usando la función connect()

3. Enviar y recibir datos. Existen varias maneras de hacer esto, pero el más simple es
utilizar las funciones read() y write()

Los pasos involucrados en el establecimiento de un socket en el lado del servidor son:

1. Crear un socket con la función socket()

2. Enlazar el socket a una dirección utilizando la función bind().

3. Escuchar por conexiones con la función listen()

4. Aceptar una conexión con la función accept(). Esta llamada típicamente bloquea el
proceso hasta que un cliente se conecte con el servidor

5. Enviar y recibir datos.

TIPOS DE SOCKETS Y DE DOMINIOS DE DIRECCIÓN

Cuando se crea un socket, el programa tiene que especificar el dominio de direcciones y el


tipo de socket. Dos procesos pueden comunicarse con cada otro solo si sus sockets son del
mismo tipo y están en el mismo dominio. Existen dos dominios de dirección ampliamente
utilizados, el dominio UNIX, en el cual los dos procesos comparten un sistema de archivos

3
UNIVERSIDAD DE LAS AMERICAS ING. HUGO FERNANDO CHIMBO ACOSTA

común, y el dominio Internet, en el que los dos procesos corren en dos hosts dentro de la
comunidad de Internet. Cada uno de estos dominios tiene su propio formato de direcciones.

La dirección de un socket en el dominio Unix es una cadena de caracteres que es


básicamente una entrada en el sistema de archivos.

La dirección de un socket en el dominio Internet consiste en la dirección de Internet de la


máquina host (cada computador en Internet tiene una dirección de 32 bits única, frecuentemente
llamada dirección IP). Además, cada socket necesita un número de puerto en eso host. Los
números de puerto son enteros sin signo de 16 bits. Los números más bajos estánr eservados en
UNIX para servicios estándar. Por ejemplo, el puerto para el servidor FTP es 21. Es importante
que los servicios estándares estén en el mismo puerto en todas las computadoras de tal manera
que los clientes conozcan bien sus direcciones. Sin embargo, los puertos superiores al 2000 están
generalmente disponibles.

Esisten dos tipos de sockets ampliamente utilizados, stream y datagram. Los sockets stream
tratan la comunicación como un flujo continuo de caracteres, mientras que los datagram tienen
que leer mensajes enteros cada vez. Cada uno utiliza su propio protocolo de comunicaciones. Los
sockets stream utilizan TCP que es un protocolo confiable, orientado a flujos, en tanto que los
datagram utilizan UDP, que es un protocolo orientado a mensajes no confiable.

CÓDIGO DE EJEMPLO

El código de un cliente y un servidor se muestran en el Anexo 1. Estos procesos se


comunicarán utilizando sockets stream en el dominio Internet. El código se describe
detalladamente a continuación.

CÓDIGO DEL SERVIDOR

El servidor utiliza varios constructores, por lo que se describirá línea por línea.

#include <stdio.h>

Esta cabecera contiene declaraciones utilizadas en la mayoría de entradas y salidas y es


típicamente incluido en todos los programas de C

#include <sys/types.h>

Esta cabecera contiene declaraciones de varios tipos de datos utilizadas en las funciones.
Estos tipos de datos on utilizados en las dos cabeceras siguientes

#include <sys/socket.h>

La cabecera socket.h incluye varias definiciones de estructuras necesarias para sockets.

#include <netinet/in.h>

La cabecera netinet/in.h contiene constantes y estructuras necesarias para el dominio de


direcciones de Internet

void error(char *msg)


{
perror(msg);
exit(1);

4
UNIVERSIDAD DE LAS AMERICAS ING. HUGO FERNANDO CHIMBO ACOSTA

}
Esta función es llamada cuando una función falla. Desplega un mensaje de error en stderr y
sale del programa

int main(int argc, char *argv[])


{
int sockfd, newsockfd, portno, clilen, n;

sockfd y newsokfd son descriptores de archivos, es decir; elementos dentro de la tabla de


descritores de archivos. Estas dos variables almacenan los valores retornados por las funciones
socket y connect

portno almacena el número de puerto en el que el servidor aceptará conexiones

clilen almacena el tamaño de las direcciones del cliente. Es necesario para la función accept

n es el valor de retorno de las funciones read y write; es decir, contiene el número de


caracteres leídos o escritos

char buffer[256];

El servidor lee caracteres desde el socket en este buffer

struct sockaddr_in serv_addr, cli_addr;

Una estructura sockaddr_in contiene una dirección Internet. Esta estructura es definida en
<netinet/in.h>. Su definición es:

struct sockaddr_in {
short sin_family; /* must be AF_INET */
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8]; /* Not used, must be zero */
};

Una estructura in_addr, definida en el mismo archivo de cabecera, contiene solo un campo
entero sin signo llamado s_addr. La variable serv_addr contendrá la dirección del cliente que se
conecta al servidor

if (argc < 2) {
fprintf(stderr,"ERROR, no se ha ingresado el puerto\n");
exit(1);
}

El usuario necesita pasar en el número de puerto en el que el servidor aceptará conexiones


como un argumento. Este código muestra un mensaje de error si el usuario no lo hace.

sockfd = socket(AF_INET, SOCK_STREAM, 0);


if (sockfd < 0)
error("ERROR abriendo el socket");

La función socket crea un nuevo socket. Toma tres argumentos. El primero es el dominio de
dirección del socket. Los valores pueden ser las constantes AF_UNIX o AF_INET para los
dominios UNIX e Internet respectivamente. El segundo parámetro es el tipo del socket. Los
valores pueden ser SOCK_STREAM y SOCK_DATAGRAM, para los tipos stream y datagram,
respectivamente. El tercer parámetro es el protocolo. Si el argumento es cero, el sistema

5
UNIVERSIDAD DE LAS AMERICAS ING. HUGO FERNANDO CHIMBO ACOSTA

operativo determinará el protocolo más apropiado. Seleccionará TCP para sockets stream y UDP
para sockets datagram.

La función socket retorna una entrada dentro de la tabla del conjunto de descriptores. Este
valor es utilizado para todas las referencias subsecuentes para este socket. Si la función socket
falla retorna -1. En este caso el programa desplegará un mensaje de error y terminará. Sin
embargo esta función no falla comúnmente.

bzero((char *) &serv_addr, sizeof(serv_addr));

la función bzero() establece todos los valores de un buffer como cero. Toma dos
argumentos, el puntero al buffer y el tamaño del buffer.

portno = atoi(argv[1]);

El número de puerto en el que el servidor escuchara conexiones es transformado a entero

serv_addr.sin_family = AF_INET;

La variable serv_addr es una estructura de tipo sockadder_in. Esta estructura tiene cuatro
campos. El primero es sin_family que contiene el código correspondiente a la familia de
dirección

serv_addr.sin_port = htons(portno);

El segundo campo de serv_addr es sin_port, que contiene el número de puerto. Sin embargo,
en lugar de simplemente copiar el número de puerto a este campo, es necesario convertirlo a
network byte order utilizando la función htos() que convierte un número de puerto en un host
byte order a un número de puerto en network byte order

serv_addr.sin_addr.s_addr = INADDR_ANY;

El tercer campo de sockaddr_in es una structura de tipo in_addr que contiene solo un campo
llamado s_addr. Este campo contiene la dirección IP del host. Para el código del servidor, debe
ser siempre la dirección IP de la máquina en la que el servidor esté corriendo, y existe la
constante simbólica INADDR_ANY que obtiene esta dirección.

if (bind(sockfd, (struct sockaddr *) &serv_addr,


sizeof(serv_addr)) < 0)
error("ERROR en bind");

La función bind enlaza un socket a una dirección, en este caso la dirección del host actual y el
puerto en el que el servidor atenderá. Toma tres argumentos, el descriptor del socket, la dirección
a la que enlazará y el tamaño de la dirección a la que se enlazará. El segundo argumento es un
puntero a una estructura de tipo sockaddr, pero que es pasado en una estructura sockaddr_in, y
por eso debe realizarse un cast para corregir el tipo.

Esta llamada puede fallar por varias razones, la más obvia es que el puerto este ya en uso en
esta máquina.

listen(sockfd,5);

6
UNIVERSIDAD DE LAS AMERICAS ING. HUGO FERNANDO CHIMBO ACOSTA

La función listen permite al proceso escuchar en el socket por conexiones. El primer


argumento es el descriptor del socket y el segundo es el tamaño de la cola; es decir, el número de
conexiones que pueden esperar mientras el proceso está atendiendo una conexión particular. Este
debería ser establecido en 5, el máximo tamaño permitido por la mayoría de sistemas. Si el primer
argumento es un socket valido, esta función no fallará y por eso no se chequea por errores.

clilen = sizeof(cli_addr);
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0)
error("ERROR en accept");

La función accept causa que el proceso se bloquee hasta que un cliente se conecte al servidor.
Es decir, se despierta cuando una conexión desde un cliente ha sido exitosamente establecida.
Retorna un nuevo descriptor de archivo, y todas las comunicaciones en esta conexión deben ser
llevadas a cabo utilizando el nuevo descriptor. El segundo argumento es un puntero a la dirección
del cliente del otro extremo de la conexión, y el tercer argumento es el tamaño de esta estructura.

bzero(buffer,256);
n = read(newsockfd,buffer,255);
if (n < 0) error("ERROR leyendo del socket");
printf("El mensaje es: %s\n",buffer);

Note que solo se llegará a este punto después de que un cliente se ha conectado
satisfactoriamente a nuestro servidor. Este código inicializa el buffer utilizando la función
bzero(), y entonces lee del socket. Debemos notar que la llamada a la función read utiliza el
descriptor de archivo retornado por socket(). Debemos tener en cuenta también que read()
bloqueará hasta que exista algo que leer en el socket, es decir, después de que el cliente haya
ejecutado un write(). Se leerá ya sea el número total de caracteres en el socket hasta 255 o menos
y retornará el número de caracteres leídos.

n = write(newsockfd,"Recibí su mensaje",18);
if (n < 0) error("ERROR escribiendo en el socket");

Una vez que una conexión ha sido establecida, los dos extremos pueden leer y escribir en la
conexión. Naturalmente, cada cosa escrita por el cliente debe ser leída por el servidor y viceversa.
Este código simplemente escribe un mensaje al cliente. El último argumento de write es el
tamaño del mensaje

return 0;
}

Esto termina la función main y entonces el programa.

CÓDIGO DEL CLIENTE

Analizaremos el programa cliente línea por línea.

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

7
UNIVERSIDAD DE LAS AMERICAS ING. HUGO FERNANDO CHIMBO ACOSTA

Los archivos de cabecera son los mismos que los declarados en el servidor salvo el archivo
netdb.h que define la estructura hostent.

void error(char *msg)


{
perror(msg);
exit(0);
}

int main(int argc, char *argv[])


{
int sockfd, portno, n;
struct sockaddr_in serv_addr;
struct hostent *server;

La función error() es idéntica a la del servidor, al igual que las variables sockfd, portno y n. La
variable serv_addr contendrá la dirección del servidor al que nos deseamos conectar. Es del tipo
struct sockaddr_in

La variable server es un puntero a una estructura de tipo hostent define una computadora en
Internet; su definición es la siguiente:

struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses from name server */
#define h_addr h_addr_list[0] /* address, for backward compatiblity */
};

El siguiente código es igual al del servidor

char buffer[256];
if (argc < 3) {
fprintf(stderr,"uso %s host puerto\n", argv[0]);
exit(0);
}
portno = atoi(argv[2]);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR abriendo el socket");

server = gethostbyname(argv[1]);
if (server == NULL) {
fprintf(stderr,"ERROR, no existe ese host\n");
exit(0);
}

argv[1] contiene el nombre de un host en internet. La función

struct hostent *gethostbyname(char *name)

Toma ese nombre como un argumento y retorna un puntero a hostent conteniendo


información sobre el host. El campo *h_addr contiene la dirección IP. Si esta structura es NULL,
el sistema no puede localizar un host con ese nombre.

8
UNIVERSIDAD DE LAS AMERICAS ING. HUGO FERNANDO CHIMBO ACOSTA

Antes, esta función trabajaba buscando un archivo de sistema llamado /etc/hosts pero con el
crecimiento del Internet, se torno imposible para los administradores mantener actualizado este
archivo. Entonces, el mecanismo mediante el cual esta función trabaja es complejo,
frecuentemente involucra consultar grandes bases de datos.

bzero((char *) &serv_addr, sizeof(serv_addr));


serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr,
(char *)&serv_addr.sin_addr.s_addr,
server->h_length);
serv_addr.sin_port = htons(portno);

Este código establece los campos de serv_addr. Mucho de esto es lo mismo que en el
servidor. Sin embargo, dado que el campo server->h_addr es una cadena de caracteres,
utilizaremos la función bcopy() para copiarlo en el campo correspondiente.

if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0)
error("ERROR connecting");

La función connect es llamada desde el cliente para establecer una conexión al servidor.
Toma tres argumentos, el descriptor del socket, la dirección del host en al que se quiere conectar
(incluyendo el número de puerto) y el tamaño de esta dirección. Esta función retorna 0 si se
completa exitosamente o -1 si falla.

Note que el cliente necesita saber el número de puerto del servidor, pero no necesita saber
cuál es su propio puerto. Este es típicamente asignado por el sistema cuando se llama a connect.

printf("Ingrese el mensaje: ");


bzero(buffer,256);
fgets(buffer,255,stdin);
n = write(sockfd,buffer,strlen(buffer));
if (n < 0)
error("ERROR escribiendo en el socket");
bzero(buffer,256);
n = read(sockfd,buffer,255);
if (n < 0)
error("ERROR leyendo del socket");
printf("%s\n",buffer);
return 0;
}

El código restante es claro. Pregunta al usuario por un mensaje, lo lee desde el teclado, lo
escribe en el socket, lee la respuesta del socket y despliega la respuesta en pantalla

MEJORAS AL CÓDIGO DEL SERVIDOR

El ejemplo del servidor solo atiende a una conexión y muere. Un servidor real debe correr
indefinidamente y debe tener la capacidad de manejar varias conexiones simultáneas, cada una en
su propio proceso

El siguiente código tiene una función llamada dostuff (int sockfd). Esta función maneja la
conexión luego de que fue establecida y provee cualquier servicio que el cliente requiera. Como
podemos ver, una vez que una conexión es establecida, los dos extremos pueden utilizar read y
write para enviar información al otro extremo y los detalles de la información enviada y recibida

9
UNIVERSIDAD DE LAS AMERICAS ING. HUGO FERNANDO CHIMBO ACOSTA

no nos interesa. Para escribir un servidor de real, no debe realizar cambios en la función main() y
todo el código que provee el servicio debería estar en dostuff()

Para permitir al servidor manejar varias conexiones simultáneas, tenemos que realizar los
siguientes cambios al código:

1. Poner la sentencia accept dentro de un lazo infinito

2. Después de que una conexión es establecida, llamar a fork() para crear un nuevo proceso

3. El proceso hijo cerrará sockfd y llamará a dostuff, pasándolo el nuevo descriptor de


socket como argumento. Cuando los dos procesos hayan completado su conversación el
proceso simplemente termina

4. El proceso padre cerrará newsockfd. Debido a que todo este código está en un lazo
infinito, retornará a la sentencia accept para esperar a la siguiente conexión

El código es el siguiente:

while (1) {
newsockfd = accept(sockfd,
(struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0)
error("ERROR en accept");
pid = fork();
if (pid < 0)
error("ERROR en fork");
if (pid == 0) {
close(sockfd);
dostuff(newsockfd);
exit(0);
}
else close(newsockfd);
}

A N E XO S

CÓDIGOS DE EJEMPLO

CÓDIGO DE SERVIDOR

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

void error(char *msg)


{
perror(msg);
exit(1);
}

int main(int argc, char *argv[])


{

10
UNIVERSIDAD DE LAS AMERICAS ING. HUGO FERNANDO CHIMBO ACOSTA

int sockfd, newsockfd, portno, clilen;


char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n;

if (argc < 2) {
fprintf(stderr,"ERROR, no se ha ingresado el puerto\n");
exit(1);
}

sockfd = socket(AF_INET, SOCK_STREAM, 0);


if (sockfd < 0)
error("ERROR opening socket");

bzero((char *) &serv_addr, sizeof(serv_addr));

portno = atoi(argv[1]);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);

if (bind(sockfd, (struct sockaddr *) &serv_addr,


sizeof(serv_addr)) < 0)
error("ERROR en binding");

listen(sockfd,5);

clilen = sizeof(cli_addr);

newsockfd = accept(sockfd,
(struct sockaddr *) &cli_addr,
&clilen);

if (newsockfd < 0)
error("ERROR en accept");

bzero(buffer,256);
n = read(newsockfd,buffer,255);
if (n < 0) error("ERROR leyendo del socket");
printf("Here is the message: %s\n",buffer);
n = write(newsockfd,"Recibi tu mensaje",18);
if (n < 0) error("ERROR escribiendo en el socket");
return 0;
}

CÓDIGO DE CLIENTE

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

void error(char *msg)


{
perror(msg);
exit(0);
}

11
UNIVERSIDAD DE LAS AMERICAS ING. HUGO FERNANDO CHIMBO ACOSTA

int main(int argc, char *argv[])


{
int sockfd, portno, n;
struct sockaddr_in serv_addr;
struct hostent *server;
char buffer[256];

if (argc < 3) {
fprintf(stderr,"uso %s host puerto\n", argv[0]);
exit(0);
}

portno = atoi(argv[2]);
sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0)
error("ERROR opening socket");

server = gethostbyname(argv[1]);

if (server == NULL) {
fprintf(stderr,"ERROR, no existe el host\n");
exit(0);
}
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr,
(char *)&serv_addr.sin_addr.s_addr,
server->h_length);
serv_addr.sin_port = htons(portno);

if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0)
error("ERROR conectando");

printf("Ingrese el mensaje: ");


bzero(buffer,256);
fgets(buffer,255,stdin);
n = write(sockfd,buffer,strlen(buffer));
if (n < 0)
error("ERROR escribiendo en el socket");
bzero(buffer,256);
n = read(sockfd,buffer,255);
if (n < 0)
error("ERROR leyendo del socket");
printf("%s\n",buffer);
return 0;
}

CÓDIGO SERVIDOR MEJORADO

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

void dostuff(int);

12
UNIVERSIDAD DE LAS AMERICAS ING. HUGO FERNANDO CHIMBO ACOSTA

void error(char *msg)


{
perror(msg);
exit(1);
}

int main(int argc, char *argv[])


{
int sockfd, newsockfd, portno, clilen, pid;
struct sockaddr_in serv_addr, cli_addr;

if (argc < 2) {
fprintf(stderr,"ERROR, no se ha ingresado el puerto\n");
exit(1);
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0)
error("ERROR opening socket");

bzero((char *) &serv_addr, sizeof(serv_addr));


portno = atoi(argv[1]);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);

if (bind(sockfd, (struct sockaddr *) &serv_addr,


sizeof(serv_addr)) < 0)
error("ERROR en binding");

listen(sockfd,5);

clilen = sizeof(cli_addr);

while (1) {
newsockfd = accept(sockfd,
(struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0)
error("ERROR en accept");
pid = fork();
if (pid < 0)
error("ERROR en fork");
if (pid == 0) {
close(sockfd);
dostuff(newsockfd);
exit(0);
}
else close(newsockfd);
}
return 0;
}

void dostuff (int sock)


{
int n;
char buffer[256];

bzero(buffer,256);

13
UNIVERSIDAD DE LAS AMERICAS ING. HUGO FERNANDO CHIMBO ACOSTA

n = read(sock,buffer,255);
if (n < 0) error("ERROR leyendo del socket");
printf("Here is the message: %s\n",buffer);
n = write(sock,"recibi tu mensaje",18);
if (n < 0) error("ERROR escribiendo en el socket");
}

14

También podría gustarte