Home UC3M
Home IT
Home / Docencia / Ing. de Telecomunicación / Redes de Ordenadores / Prácticas de sockets I  
anterior


MATERIAL DE APOYO

 

Páginas del manual on-line: socket(2), send(2), recv(2), read(2), write(2), setsockopt(2), fcntl(2), select(2), tcp(7), ip(7).

Manual de sockets

Guía Beej de Programación en Redes (manual on-line)

Manual de tcpdump

Capítulos 6, 7 y 8 de "Linux Socket Programming" de Sean Walton, Sams Publishing Co. 2001

 

 PRÁCTICAS DE SOCKETS

 

Las prácticas de sockets están organizadas en tres partes:
  1. Servidores secuenciales (servidor y cliente de eco, manipulación de opciones de sockets, análisis con tcpdump, servidor de ficheros).
  2. Servidores concurrentes (procesos, hilos).
  3. Entrada/salida (manejadores de señales, poll, select)

Servidores Secuenciales

Los servidores secuenciales son los servidores más sencillos, generalmente los encontramos en servicios que se prestan por UDP (con sockets de tipo SOCK_DGRAM), pero también los podemos encontrar en servicios TCP (con sockets de tipo SOCK_STREAM).

El ciclo de vida de las aplicaciones que utilizan sockets pasivos es el siguiente: creación del socket (open), asociarlo con un puerto (bind), conversión del socket en un socket pasivo para prepararlo para recibir conexiones entrantes (listen), poner el socket en estado de espera de conexiones entrantes (accept), lectura/escritura (read/write) o recibir/enviar (recv/send), cierre del socket y liberación de la conexión (close), y volver al estado de espera de conexiones.

En el archivo psockets1.tgz se encuentra el código de un servidor de eco secuencial, y de un cliente de eco. Aquí adjuntamos el código del servidor, TCPechod_seq.c:
/* TCPechod_seq.c - main, TCPechod_seq */
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <sys/errno.h>
#include <netinet/in.h>

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define QLEN 5 /* maximum connection queue length */
#define BUFSIZE 4096

extern int errno;

int TCPechod_seq(int fd);
int errexit(const char *format, ...);
int passiveTCP(const char *service, int qlen);

/*---------------------------------------------------------------------------
* main - Concurrent TCP server for ECHO service
*--------------------------------------------------------------------------*/

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

    char *service = "echo"; /* service name or port number */
    struct sockaddr_in fsin; /* the address of a client */
    int alen; /* length of client's address */
    int msock; /* master server socket */
    int ssock; /* slave server socket */

    switch (argc) {
        case 1:
            break;
        case 2:
            service = argv[1];
            break;
        default:
            errexit("usage: TCPechod [port]\n");
    }

    msock = passiveTCP(service, QLEN); /* encapsulates calls to socket API */

    while (1) {
        alen = sizeof(fsin);
        ssock = accept(msock, (struct sockaddr *)&fsin, &alen);
        if (ssock < 0) {
            if (errno == EINTR)
                continue;
            errexit("accept: %s\n", strerror(errno));
        }
        TCPechod(ssock);
    }

}

/*---------------------------------------------------------------------------
* TCPechod - echo data until end of file
*--------------------------------------------------------------------------*/

int TCPechod(int fd)
{
    char buf[BUFSIZ];
    int cc;

    while (cc = read(fd, buf, sizeof(buf))) {
        if (cc < 0)
            errexit("echo read: %s\n", strerror(errno));
        write(0,buf,cc);
        if (write(fd, buf, cc) < 0)
            errexit("echo write: %s\n", strerror(errno));
    }
    return 0;
}

Y el cliente TCPecho.c:
/* TCPecho.c - main, TCPecho */

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <netinet/tcp.h>

extern int errno;

int TCPecho(const char *host, const char *service);
int errexit(const char *format, ...);
int connectTCP(const char *host, const char *service);

#define LINELEN 1500

/*---------------------------------------------------------------------------
* main - TCP client for ECHO service
*--------------------------------------------------------------------------*/

int main(int argc, char *argv[])
{
    char *host = "localhost"; /* host to use if none supplied */
    char *service = "echo"; /* default service name */

    switch (argc) {
        case 1:
            host = "localhost";
            break;
        case 3:
            service = argv[2];
        /* FALL THROUGH */
        case 2:
            host = argv[1];
            break;
        default:
            fprintf(stderr, "usage: TCPecho [host [port]]\n");
            exit(1);
    }

    TCPecho(host, service);
    exit(0);
}

/*---------------------------------------------------------------------------
* TCPecho - send input to ECHO service on specified host and print reply
*--------------------------------------------------------------------------*/

int TCPecho(const char *host, const char *service)
{
    char buf[LINELEN+1]; /* buffer for one line of text */
    int s, n; /* socket descriptor, read count*/
    int outchars, inchars; /* characters sent and received */

    s = connectTCP(host, service); /* encapsulates calls to socket API */

    while (fgets(buf, sizeof(buf), stdin)) {
        buf[LINELEN] = '\0'; /* ensure line null-terminated */
        outchars = strlen(buf);
        (void) write(s, buf, outchars);

        /* read it back */
        for (inchars = 0; inchars < outchars; inchars+=n ) {
            n = read(s, &buf[inchars], outchars - inchars);
            if (n < 0)
                errexit("socket read failed: %s\n", strerror(errno));
        }
        fputs(buf, stdout);
    }

}

En la explicación de la práctica se habla del puerto 8xxx, esto indica que debeis utilizar como puerto el resultado de sumarle a 8000 y los tres últimos números de la dirección IP de la máquina en la que ejecuta el servidor. De esta forma, evitamos interferencias entre las prácticas realizadas por los diferentes grupos.

  1. Compile:
    ~>make clean; make
    Ejecute el servidor en el puerto 8xxx:
    ./TCPechod_seq 8xxx
    y en otra ventana, ejecute el cliente:
    ./TCPecho <server host> 8xxx
    y observe su comportamiento.

Vamos a observar las conexiones TCP con la herramienta tcpdump (ejecutable se encuentra en /usr/dist/sbin/tcpdump). Vamos a decirle que nos reporte todo el tráfico con origen o destino el puerto 8xxx y que se escuche en el interfaz del bucle local (loopback).

  1. Ejecute el siguiente comando en otra ventana:

    tcpdump -i lo port 8xxx
    Vuelva a lanzar el cliente y observe el intercambio de tramas con tcpdump (establecimiento de conexión, envío de datos en ambos sentidos, asentimientos y fin de conexión)

  2. Lance el cliente un par de veces y finalicelo con CTRL-D o CTRL-C. Ahora mate el servidor con CTRL-C, ¿qué observa con el tcpdump?, ¿por qué?. Puede ser útil observar el estado de las conexiones de red en el cliente, utilizando para ello el comando:

    netstat -tn
    Solucione el problema.

A partir de ahora vamos a lanzar los clientes en una máquina distinta. Para ello en una ventana nueva ejecute el comando slogin o ssh (para ejecutar comandos en otra máquina). Necesitaremos parar y rearrancar el tcpdump, esta vez sin especificar la interfaz, de tal forma que se monitoriza el tráfico por todas las interfaces de red salvo por el bucle local.

  1. Vuelva a arrancar el servidor (ya modificado), arranque un cliente y mate el servidor sin matar al cliente. ¿Qué ocurre al arrancar el servidor de nuevo? ¿Por qué?

  2. Mate el cliente, espere unos pocos segundos y lance de nuevo el servidor. Lance ahora dos clientes de eco contra el mismo servidor (desde dos máquinas distintas). ¿Qué ocurre si después de lanzar el primer cliente y antes de pedir eco, lanzamos el segundo?

  3. Lance otros dos clientes de nuevo y repita la operación, pero esta vez mate al segundo cliente y después al primero. Repita la operación, esta vez dejando pasar un par de minutos entre la matanza del segundo proceso y el primero. Debe de observar una trama de RESET, ¿por qué?

  4. Cambie el tamaño de la cola de conexiones (backlog del socket) del servidor, definido por la constante QLEN en el fichero TCPechod_seq, a 1. Ahora lance cuatro clientes de eco desde otra máquina. ¿Qué aprecia desde tcpdump? Mate el último cliente que arrancó, ¿cuál es el efecto? ¿Qué explicación se le ocurre?

Vamos a manipular las opciones del socket que utiliza el cliente utilizando setsockopt. Una de las opciones que se puede modificar es la de utilizar o no el algoritmo de Nagle. En el código proporcionado, el algoritmo de Nagle puede deshabilitarse descomentando las siguentes líneas de código en el fichero connectsock.c justo antes de realizar la llamada a socket:
int no_nagle=1;

if (setsockopt(s,SOL_TCP,TCP_NODELAY,&no_nagle, sizeof(no_nagle)) != 0) errexit("setsockopt: no puedo deshabilitar Nagle");
Sin embargo, con el kernel de Linux que está instalado en los laboratorios, este efecto no es visible. Por esta razón no experimentaremos con este caso, pero veremos los efectos que tiene el establecimiento de otras posibles opciones. Para más información sobre las opciones que pueden cambiarse, se sugiere consultar las páginas del manual ip(7), tcp(7) y setsockopt(2).
  1. Modificar el tamaño de los segmentos enviados por el cliente - activando la opción TCP_MAXSEG - recompila y comprueba este efecto en un cliente escribiendo textos más largos que la longitud especificada. ¿Es posible especificar MSS inferiores a 36?. Si no se puede, ¿cuál es el motivo?

  2. Ahora vamos a permitir enlazar un socket a un puerto que ya está en uso - activando la opción SO_REUSEADDR - (mientras no exista un socket pasivo en estado activo ya enlazado a él). Observe el resultado de modificar esta opción revisando la pregunta 4.

  3. A partir del servicio de eco proporcionado en la práctica (con su servidor y su cliente), cree un servicio de ficheros muy simplificado, de forma que el cliente envía el camino a un fichero en el servidor y el servidor le devuelve el contenido correspondiente a ese fichero.


Localización | Personal | Docencia | Investigación | Novedades | Intranet
inicio | mapa del web | contacta