Acceso Remoto Usando el TCP

Vicente González Ruiz

December 14, 2014

Contents

1 El emisor
2 El receptor

El TCP proporciona, junto al direccionamiento de procesos, control de flujo y control de congestin. El direccionamiento de procesos es distinto al que se utiliza en UDP. Cuando un segmento (nombre que recibe un paquete de datos en el contexto del TCP) llega hasta un host, el SO utiliza:

(dir IP origen, puerto origen, dir IP destino, puerto destino)

para encontrar el proceso al que va dirigido dicho segmento.

El control del flujo permite que el receptor controle el volumen de datos que le llega en cada momento. Esto se consigue simplemente indicando al emisor qu cantidad de datos desea recibir en cada segmento.

El control de la congestin evita que la red se congestione por un exceso de trfico. Cuando el emisor detecta la congestin, l reduce la tasa de transferencia de datos con la esperanza de que los routers puedan sacar adelante el trabajo atrasado (transmitir todos los datagramas IP que tienen almacenados en sus colas).

1 El emisor

En esta seccin se muestra un ejemplo sencillo con un emisor de datos que emplea el TCP. Los datos son ledos de la entrada estndar, enviados a la red, y escritos sobre la salida estndar (para continuar el pipeline). El emisor acepta los siguientes parmetros:

  1. El tamao del payload del segmento. El IP limita el tamao de un datagrama IP a 64 Kbytes. Por tanto, el TCP tiene tambin este lmite. Tamaos tpicos son 512 o 1024 bytes porque son suficientemente grandes como para mantener pequeo el overhead producido por las cabeceras y suficientemente pequeos como para evitar la fragmentacin producida por el IPv4 cuando los datagramas atravienan redes con MTUs (Maximun Transfer Unit) ms pequeos. Ntese adems que el proceso del control del flujo puede hacer que el tamao de los segmentos sea diferente. Por tanto, este parmetro no define necesariamente el tamao del payload de los segmentos.
  2. El host destino de los segmentos. Se puede usar el DNS.
  3. El puerto (proceso) destino de los segmentos. Tiene que ser un valor comprendido entre 1 y 65535, aunque tpicamente ser superior al 1024 (los primeros 1024 puertos se dedican a los servicios (procesos) “conocidos”).
  4. El Time-To-Live (TTL). Indica el nmero mximo de saltos (pasos por router) que realizar el segmento. Este valor se utiliza para evitar que los segmentos lleguen demasiado lejos, y en general, para evitar segmentos eternos que estando mal enrrutados podran consumir ancho de banda en Internet de forma innecesaria.

El programa send.c puede descargarse de http://www.ace.ual.es/\~vruiz/redes_industriales/tcp_send.c y su cdigo fuente es:

 
1/****************************************************************************** 
2 * tcp_send.c -- Emisor de datos que usa el TCP. 
3 * 
4 * Enva una copia de la entrada estndar hacia una direccin IP 
5 * usando el protocolo TCP. La entrada es tamben copiada a la salida 
6 * estndar. 
7 * 
8 * gse. 2010. 
9 *****************************************************************************/ 
10 
11/****************************************************************************** 
12 * 
13 * Ficheros cabecera. 
14 * 
15 *****************************************************************************/ 
16 
17/* Entrada y salida de streams buffereados. */ 
18#include <stdio.h> 
19 
20/* Biblioteca de funciones estndar (exit(), EXIT_SUCCESS, 
21   EXIT_FAILURE, ...) */ 
22#include <stdlib.h> 
23 
24/* Manipulacin de cadenas y movimiento de memoria (memset(), ...). */ 
25#include <string.h> 
26 
27/* Biblioteca estndar de funciones relacionadas con el sistema 
28   operativo Unix (read(), write(), getopt(), ...). */ 
29#include <unistd.h> 
30 
31/* Llamadas al sistema para manejo de descriptores de ficheros, 
32   sockets, etc. */ 
33#include <fcntl.h> 
34 
35/* Sockets. */ 
36/* Ms info en: 
37 * 
38 * http://www.csce.uark.edu/~aapon/courses/os/examples/concurrentserver.c 
39 * http://www.fortunecity.com/skyscraper/arpanet/6/cc.htm 
40 * http://beej.us/guide/bgnet/ 
41 */ 
42 
43/* Tipos de datos primitivos del sistema. */ 
44#include <sys/types.h> 
45 
46/* Sockets (socket(), listen(), ...). */ 
47#include <sys/socket.h> 
48 
49/* Direcciones IP, opciones y definiciones. */ 
50#include <netinet/in.h> 
51 
52/* Servicio de resolucin de nombres. */ 
53#include <netdb.h> 
54 
55/* Signals (interrupciones) (signal(), SIGINT). */ 
56#include <signal.h> 
57 
58/****************************************************************************** 
59 * 
60 * Definiciones. 
61 * 
62 *****************************************************************************/ 
63 
64/* Tamao (esperado) del payload de los segmentos. */ 
65#define PAYLOAD_SIZE 512 
66 
67/* Host destino de los datagramas. */ 
68#define RECEIVER_HOST "localhost" 
69 
70/* Puerto destino de los datagramas. */ 
71#define RECEIVER_PORT 6666 
72 
73/* TTL por defecto. 1 -> que el datagrama no saldr de la red local. */ 
74#define TTL 1 
75 
76/****************************************************************************** 
77 * 
78 * Variable globales. 
79 * 
80 *****************************************************************************/ 
81 
82/****************************************************************************** 
83 * 
84 * Funciones. 
85 * 
86 *****************************************************************************/ 
87 
88/* Cuerpo principal del programa. */ 
89int main(int argc, char *argv[]) { 
90  work(argc, argv); 
91  return EXIT_SUCCESS; 
92} 
93 
94work(int argc, char *argv[]) { 
95 
96  int payload_size = PAYLOAD_SIZE; 
97 
98  /* Direccin destino por defecto de los datagramas. */ 
99  char *receiver_host = "localhost"; 
100 
101  /* Puerto destino por defecto de los datagramas. */ 
102  int receiver_port = RECEIVER_PORT; 
103 
104  /* TTL del los segmentos. */ 
105  unsigned char ttl = TTL; 
106 
107  /* Manipulamos los parmetros de entrada. */ 
108  int c; 
109  while ((c = getopt (argc, argv, "s:h:p:t:H")) != -1) { 
110    switch (c) { 
111    case s: 
112      payload_size = atoi(optarg); 
113      break; 
114    case h: 
115      receiver_host = optarg; 
116      break; 
117    case p: 
118      receiver_port = atoi(optarg); 
119      break; 
120    case t: 
121      ttl = atoi(optarg); 
122      break; 
123    case H: 
124      fprintf(stderr, "Usage:send[OPTION...]\n"); 
125      fprintf(stderr, "\n"); 
126      fprintf(stderr, "-spayload_size\n"); 
127      fprintf(stderr, "-hreceiver_host\n"); 
128      fprintf(stderr, "-preceiver_port\n"); 
129      fprintf(stderr, "-tTTL\n"); 
130      fprintf(stderr, "\n"); 
131      fprintf(stderr, "CopiesthestdintothestdoutandaTCPsocket\n"); 
132      return; 
133    case ?: 
134      if (isprint (optopt)) 
135        fprintf (stderr, "Unknownoption‘-%c’.\n", optopt); 
136      else 
137        fprintf (stderr, 
138                 "Unknownoptioncharacter‘\\x%x’.\n", 
139                 optopt); 
140      return 1; 
141    default: 
142      abort (); 
143    } 
144  } 
145 
146  fprintf(stderr, "%s:payloadsize=\"%d\"\n", argv[0], payload_size); 
147  fprintf(stderr, "%s:receiverhost=\"%s\"\n", 
148          argv[0], receiver_host); 
149  fprintf(stderr, "%s:destinationport=\"%d\"\n", 
150          argv[0], receiver_port); 
151  fprintf(stderr,"%s:TTL=\"%d\"\n", argv[0], ttl); 
152 
153  /* Preguntamos al DNS por la dir IP destino. */ 
154  struct hostent *receiver_IP; 
155  if ((receiver_IP = gethostbyname(receiver_host)) == NULL) { 
156    perror("gethostbyname"); 
157    exit(EXIT_FAILURE); 
158  } 
159 
160  int sd; /* Socket descriptor */ { 
161 
162    /* Obtenemos el nmero del protocolo. */ 
163    struct protoent *ptrp; /* Pointer to a protocol table entry. */ 
164    ptrp = getprotobyname("tcp"); 
165    if(ptrp == NULL) { 
166      perror("getprotobyname"); 
167      exit(EXIT_FAILURE); 
168    } 
169 
170    /* Creamos el socket descriptor. */ 
171    sd = socket(AF_INET, SOCK_STREAM, ptrp->p_proto); 
172    if(sd < 0) { 
173      perror("socket"); 
174      exit(EXIT_FAILURE); 
175    } 
176  } 
177 
178  /* Si pulsamos CRTL+C, el programa acaba ejecutando la funcin 
179     end(). */ 
180  void end() { 
181    fprintf(stderr,"%s:CTRL+Cdetected.Exiting...", argv[0]); 
182    close(sd); 
183    fprintf(stderr,"done\n"); 
184    exit(EXIT_SUCCESS); 
185  } 
186  /* Activamos la senal SIGINT. */ 
187  signal(SIGINT, end); 
188 
189  /* Especificamos el host y puerto destino de los segmentos. */ 
190  struct sockaddr_in socket_addr; { 
191    /* Borramos la estructura. */ 
192    memset((char  *)&socket_addr,0,sizeof(socket_addr)); 
193    /* Usaremos Internet. */ 
194    socket_addr.sin_family = AF_INET; 
195    /* IP destino de los segmentos */ 
196    socket_addr.sin_addr = *((struct in_addr *)receiver_IP->h_addr); 
197    /* Puerto destino de los segmentos */ 
198    socket_addr.sin_port = htons(receiver_port); 
199  } 
200 
201  /* Actualizamos el TTL del datagrama. */ 
202  setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); 
203 
204  /* Nos conectamos al receptor. */ 
205  connect(sd, (const struct sockaddr *)&socket_addr, sizeof(socket_addr)); 
206 
207  /* Reservamos memoria para el payload de los segmentos. */ 
208  char *payload = (char *)malloc(payload_size*sizeof(char)); 
209 
210  for(;;)  /* Enviamos, enviamos y enviamos. */ { 
211 
212 
213    /* Lemos la stdin. */ 
214    int bytes_read = read(0, (void *)payload, payload_size); 
215    fprintf(stderr,"r"); fflush(stderr); 
216    if(bytes_read < 1) break; 
217 
218    /* Escribimos la stdout, para poder continuar el pipe. */ 
219    write(1, (void *)payload, bytes_read); 
220    fprintf(stderr,"w"); fflush(stderr); 
221 
222    /* Enviamos el segmento. */ 
223    send(sd, (void *)payload, bytes_read, 0); 
224    fprintf(stderr,"s"); fflush(stderr); 
225 
226  } 
227  close(sd); 
228 
229}

2 El receptor

El correspondiente receptor escucha en un puerto, a la espera de que lleguen los datos. Cuando esto ocurre, los escribe sobre la salida estndar. El nico parmetro del recpetor es:

  1. El puerto de escucha. Es el puerto usado por el receptor para establecer las conexiones y recibir los datos.

En http://www.ace.ual.es/\~vruiz/redes_industriales/tcp_receive.c est el cdigo del receptor:

 
1/****************************************************************************** 
2 * tcp_receive.c -- Receptor de datos que utiliza el TCP. 
3 * 
4 * Recibe un flujo de datos a travs de un puerto y lo copia a la 
5 * salida estandard. 
6 * 
7 * gse. 2010. 
8 *****************************************************************************/ 
9 
10/****************************************************************************** 
11 * 
12 * Ficheros cabecera. 
13 * 
14 *****************************************************************************/ 
15 
16/* Entrada y salida de streams buffereados. */ 
17#include <stdio.h> 
18 
19/* Biblioteca de funciones estndar (exit(), EXIT_SUCCESS, 
20   EXIT_FAILURE, ...) */ 
21#include <stdlib.h> 
22 
23/* Manipulacin de cadenas y movimiento de memoria (memset(), ...). */ 
24#include <string.h> 
25 
26/* Biblioteca estndar de funciones relacionadas con el sistema 
27   operativo Unix (read(), write(), getopt(), ...). */ 
28#include <unistd.h> 
29 
30#include <fcntl.h> 
31 
32/* Sockets. */ 
33/* Ms info en: 
34 * 
35 * http://www.csce.uark.edu/~aapon/courses/os/examples/concurrentserver.c 
36 * http://www.fortunecity.com/skyscraper/arpanet/6/cc.htm 
37 * http://beej.us/guide/bgnet/ 
38 */ 
39 
40/* Tipos de datos primitivos del sistema. */ 
41#include <sys/types.h> 
42 
43/* Sockets (socket(), listen(), ...). */ 
44#include <sys/socket.h> 
45 
46/* Direcciones IP, opciones y definiciones. */ 
47#include <netinet/in.h> 
48 
49/* Servicio de resolucin de nombres. */ 
50#include <netdb.h> 
51 
52/* Signals (interrupciones) (signal(), SIGINT). */ 
53#include <signal.h>  /* signal() */ 
54 
55/****************************************************************************** 
56 * 
57 * Definiciones. 
58 * 
59 *****************************************************************************/ 
60 
61/* Tamao del buffer de datos usado para comunicarnos con el socket. */ 
62#define PAYLOAD_SIZE 512 
63 
64/* Puerto destino de los segmentos. */ 
65#define LISTENING_PORT 6666 
66 
67/* Nmero mximo de clientes que esperan a que la conexin sea 
68   establecida. */ 
69#define QLEN 1 
70 
71/****************************************************************************** 
72 * 
73 * Variable globales. 
74 * 
75 *****************************************************************************/ 
76 
77/****************************************************************************** 
78 * 
79 * Funciones. 
80 * 
81 *****************************************************************************/ 
82 
83/* Cuerpo principal del programa. */ 
84int main(int argc, char *argv[]) { 
85  work(argc, argv); 
86  return EXIT_SUCCESS; 
87} 
88 
89work(int argc, char *argv[]) { 
90 
91  /* Tamao de buffer de memoria que vamos a usar para extraer datos 
92     del socket y escribirlos sobre la salida estndar. */ 
93  int payload_size = PAYLOAD_SIZE; 
94 
95  int listening_port = LISTENING_PORT; 
96 
97  /* Manipulamos los parmetros de entrada. */ 
98  int c; 
99  while ((c = getopt (argc, argv, "p:H")) != -1) { 
100    switch (c) { 
101    case p: 
102      listening_port = atoi(optarg); 
103      break; 
104    case H: 
105      fprintf(stderr, "Usage:tcp_receive[OPTION...]\n"); 
106      fprintf(stderr, "\n"); 
107      fprintf(stderr, "-plistening_port\n"); 
108      fprintf(stderr, "\n"); 
109      fprintf(stderr, "Receivesdatafromthelistening_portandcopiesthe"); 
110      fprintf(stderr, "datatothestdout\n"); 
111      return; 
112    case ?: 
113      if (isprint (optopt)) 
114        fprintf (stderr, "Unknownoption‘-%c’.\n", optopt); 
115      else 
116        fprintf (stderr, 
117                 "Unknownoptioncharacter‘\\x%x’.\n", 
118                 optopt); 
119      return 1; 
120    default: 
121      abort (); 
122    } 
123  } 
124 
125  fprintf(stderr, "%s:listeningport=\"%d\"\n", argv[0], listening_port); 
126 
127  int listen_sd; /* Socket descriptor */ { 
128 
129    /* Obtenemos el nmero del protocolo. */ 
130    struct protoent *ptrp; /* Pointer to a protocol table entry. */ 
131    ptrp = getprotobyname("tcp"); 
132    if(ptrp == NULL) { 
133      perror("getprotobyname"); 
134      exit(EXIT_FAILURE); 
135    } 
136 
137    /* Creamos el socket descriptor. */ 
138    listen_sd = socket (PF_INET, SOCK_STREAM, ptrp->p_proto); 
139    if(listen_sd < 0) { 
140      perror("socket"); 
141      exit(EXIT_FAILURE); 
142    } 
143  } 
144 
145  /* Si pulsamos CRTL+C, el programa acaba ejecutando la funcin 
146     end(). */ 
147  void end() { 
148    fprintf(stderr,"%s:CTRL+Cdetected!Exiting...", argv[0]); 
149    close(listen_sd); 
150    fprintf(stderr,"done.\n"); 
151    exit(EXIT_SUCCESS); 
152  } 
153  /* Activamos la senal SIGINT. */ 
154  signal(SIGINT, end); 
155 
156  /* Cuando hagamos el bind(), usaremos el puerto de servicio sin 
157     esperar a que ste est a la espera de un time-out de 
158     liberacin. */ 
159  int yes = 1; 
160  if(setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) { 
161    perror("setsockopt"); 
162    exit(EXIT_FAILURE); 
163  } 
164 
165  /* A la llamada bind() hay que pasarle tres parmetros importantes: 
166     (1) el tipo de socket que vamos a utilizar, (2) la direccin IP 
167     del adaptador de red por el que vamos a recibir los datos y (3) 
168     el puerto de escucha. */ { 
169 
170    /* Estructura de datos que necesita bind(). */ 
171    struct sockaddr_in socket_addr; 
172    memset((char  *)&socket_addr,0,sizeof(socket_addr)); 
173 
174    /* (1) Usaremos Internet. */ 
175    socket_addr.sin_family = AF_INET; 
176 
177    /* (2) Cualquiera de los adaptadores de red (debidamente 
178       configurados con el IP) pueden recibir los datos. */ 
179    socket_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
180 
181     /* (3) Definimos el puerto de escucha. */ 
182    socket_addr.sin_port = htons((u_short)listening_port); 
183 
184    /* Y finalmente, el bind(). */ 
185    if (bind(listen_sd, 
186             (struct sockaddr *)&socket_addr, sizeof(socket_addr)) < 0) { 
187      perror("bind"); 
188      exit(EXIT_FAILURE); 
189    } 
190  } 
191 
192  /* Definimos el nmero mximo de peticiones de establecimiento de 
193     conexin. El receptor todava no se bloquea! */ 
194  if (listen(listen_sd, QLEN) < 0) { 
195    perror("listen"); 
196    exit(EXIT_FAILURE); 
197  } 
198 
199  /* El socket que se utiliza para establecer las conexiones 
200     ("listen_sd" en este cdigo) no puede ser el mismo que el socket 
201     que se utiliza para enviar y recibir los datos. En este punto 
202     creamos un nuevo socket cuando aceptamos la conexin (mientras 
203     estamos bloqueados) y cerrammos "listen_sd". */ 
204  fprintf(stderr,"%s:waitingforincomingdata...\n", argv[0]); 
205  int service_sd = accept(listen_sd, 0, 0); 
206  if(service_sd < 0) { 
207    perror("accept"); 
208    exit (EXIT_FAILURE); 
209  } 
210  fprintf(stderr,"%s:acceptedconextion!\n", argv[0]); 
211  close(listen_sd); 
212 
213  /* Asignamos memoria para almacenar los datos entrantes. */ 
214  char *buffer = (char *)malloc(payload_size*sizeof(char)); 
215 
216  for(;;) { 
217 
218    /* Recibimos un segmento. */ 
219    int bytes_read = recv(service_sd, (void *)buffer, payload_size, 0); 
220    if(bytes_read < 1) break; 
221    fprintf(stderr,"r"); fflush(stderr); 
222 
223    /* Escribimos el segmento a stdout. */ 
224    write(1, (void *)buffer, bytes_read); 
225    fprintf(stderr,"w"); fflush(stderr); 
226 
227  } 
228}

Taller 1:

usuario@receiver:~$ ls tcp_receive.c  
tcp_receive.c  
usuario@receiver:~$ make tcp_receive  
cc     tcp_receive.c   -o tcp_receive  
usuario@receiver:~$ ./tcp_receive -H  
Usage: tcp_receive [OPTION...]  
 
  -p listening_port  
 
Receives data from the listening_port and copies the data to the stdout  
usuario@receiver:~$ ./tcp_receive > file  
./receive: listening port = "6666"  
 
# Ahora el receptor est esperando a que le lleguen datos ...  
 
usuario@sender:~$ ls tcp_send.c  
tcp_send.c  
usuario@sender:~$ make tcp_send  
cc     tcp_send.c   -o tcp_send  
usuario@sender:~$ ./tcp_send -H  
Usage: tcp_send [OPTION...]  
 
  -s payload_size  
  -h receiver_host  
  -p receiver_port  
  -t TTL  
 
Copies the stdin to the stdout and a TCP socket  
usuario@sender:~$ ./tcp_send < tcp_send.c > /dev/null

Ejercicio 1: Verifique si se pueden solicitar transmisiones de payloads cuya longitud sea superior a 64K bytes. ¿Es esto posible? ¿Qu tamaos tienen finalmente los segmentos que atraviesan la red?