9.10. TCP – un flujo fiable de bytes

TCP, el Transmission Control Protocol, es el otro servicio de la capa de transporte que se asienta sobre IP. Mientras que UDP se describe mejor como «envía un paquete y cruza los dedos», TCP es «abre una conexión entre dos extremos y trátala como una tubería bidireccional de bytes que el otro lado recibe con seguridad, en orden y exactamente una vez». La mayor parte del tráfico de internet lo usa, y la mayor parte de lo que la cámara hace en la red también.

9.10.1. Qué añade TCP a IP

TCP hace mucho más que UDP. Añade:

  • Una conexión. Antes de que circule cualquier dato, los dos extremos intercambian un breve apretón de manos para acordar que están comunicándose. La conexión tiene un estado – «abierta», «semicerrada», «cerrada» – que ambos lados siguen.

  • Entrega fiable. Cada byte que el emisor introduce es confirmado por el receptor. Todo lo que no se confirme dentro de un tiempo de espera se envía de nuevo. La aplicación nunca ve un byte perdido; ve un retraso mientras el protocolo reenvía.

  • Entrega ordenada. Los bytes llegan en el mismo orden en que se enviaron. Aunque los paquetes lleguen al receptor desordenados, TCP los reordena antes de que la aplicación los lea.

  • Control de flujo. Si el receptor es lento, se le indica al emisor que vaya más despacio; la conexión se adapta al ritmo del extremo más débil.

  • Control de congestión. Si la red intermedia empieza a descartar paquetes, el emisor se contiene hasta que las cosas se recuperan. Esto evita que una sola conexión colapse un enlace saturado.

Todo esto es automático. La API de Python que la aplicación utiliza es simplemente send(bytes) y recv(n); TCP se encarga de todo lo demás por debajo.

9.10.2. El apretón de manos

Una conexión TCP se abre con un intercambio en tres pasos antes de que se permita pasar cualquier dato:

Un diagrama con dos columnas etiquetadas "client" y "server". Una flecha del cliente al servidor etiquetada "SYN", luego una flecha del servidor al cliente etiquetada "SYN-ACK", luego una flecha del cliente al servidor etiquetada "ACK". Debajo de eso una flecha gruesa etiquetada "data flowing both ways".

El apretón de manos en tres pasos. Una vez que ambos lados se han confirmado, la conexión está abierta y los datos pueden fluir.

El cliente envía un paquete SYN (sincronizar) pidiendo abrir. El servidor responde con SYN-ACK (sincronizar + confirmar), aceptando. El cliente envía un ACK final para confirmar. Tras este viaje de ida y vuelta, ambos lados acuerdan que la conexión está abierta y han sincronizado sus contadores para seguir qué bytes se han enviado y recibido.

El apretón de manos cuesta un tiempo de ida y vuelta de latencia antes de que pase el primer byte útil. Para redes locales eso es un milisegundo; para conexiones transcontinentales son aproximadamente 100 ms. Este es el coste principal de TCP y la razón por la que los mensajes cortos y únicos a veces salen mejor parados usando UDP en su lugar.

9.10.2.1. El apretón de manos de cierre

Las conexiones TCP también se cierran con un intercambio (un FIN de cada lado). Cualquiera de los extremos puede cerrar su mitad de la conexión de forma independiente; un estado semicerrado, en el que un lado ha terminado de enviar pero el otro sigue hablando, es legal aunque poco habitual. La aplicación normalmente solo llama a close() y deja que el protocolo gestione la secuencia de cierre.

9.10.3. Qué no garantiza TCP

Algunas cosas que a veces se asume que TCP hace pero que no hace:

  • Límites de mensaje. La aplicación envía un flujo de bytes, no un flujo de mensajes. Dos llamadas send(b"hello") podrían llegar como un único recv() de b"hellohello", o como dos recv()s de tamaños variables. Si la aplicación quiere delimitar mensajes, tiene que añadir esa delimitación ella misma (un salto de línea, un prefijo de longitud, lo que sea). Enviar documentos JSON sobre TCP, por ejemplo, requiere separar cada documento con un salto de línea u otro marcador.

  • Cifrado. TCP transporta los bytes que la aplicación le dio, en claro, todo el camino. Si el contenido tiene que ser confidencial, la aplicación tiene que envolver la conexión en TLS (consulta Sockets cifrados y TLS).

  • Autenticación. TCP se asegura de que los bytes llegaron intactos. No dice nada sobre quién los envió. La autenticación también es un asunto de una capa superior.

  • Actividad en una conexión inactiva. Si ninguno de los lados envía datos durante mucho tiempo, la conexión técnicamente sigue abierta pero no puede detectar que el otro extremo se haya caído o desaparecido. Se pueden habilitar sondas keepalive en el socket para solucionar esto cuando importa.

9.10.4. Cuándo usar TCP

TCP es la respuesta correcta para casi cualquier conversación que encaje con la forma «el cliente abre una conexión a un servidor, intercambian algunos bytes y la conexión se cierra al terminar». Peticiones HTTP y HTTPS, sesiones SSH, consultas a bases de datos, transferencias de archivos, subidas de imágenes – todo sobre TCP.

Recurre a UDP solo cuando la conversación no encaje con esa forma: mensajes independientes y autocontenidos en los que la pérdida es aceptable, tráfico multicast, sincronización de tiempo, búsquedas de nombres, o casos extremadamente sensibles a la latencia en los que el coste del apretón de manos es prohibitivo.

Con los puertos, UDP y TCP ya sobre la mesa, la historia de la capa de transporte está completa. La API de Python que expone ambos está en la página siguiente: Objetos socket.