Las prácticas de sockets están organizadas en tres partes:
- Servidores secuenciales (servidor y cliente de
eco, manipulación de opciones de sockets, análisis con
tcpdump , servidor de ficheros).
- Servidores concurrentes (procesos, hilos).
- 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.
-
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).
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)
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.
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é?
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?
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é?
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).
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?
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.
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.
|