Seguimos subiendo la pila. La capa de Internet (04-03) movía paquetes entre máquinas; la capa de transporte conecta procesos entre sí: el navegador de Marta con el servidor web de la intranet, el cliente de correo de Jon con el servidor SMTP, cada uno identificado por un puerto. Los dos inquilinos de esta capa — TCP y UDP — ya los diseccionaste en 02-04 (handshake, números de secuencia, ventana, datagramas), así que aquí no repetiremos su mecánica interna. Lo que aporta esta lección es la capa vista desde el teclado: cómo un programa usa TCP a través de sockets (con pseudocódigo línea a línea), cómo se leen los estados de conexión con ss y netstat, qué significa operativamente cada estado, y qué pasa cuando dos procesos se pelean por el mismo puerto — el famoso "address already in use" que un día parará la intranet de Meridiano. Es importante porque, para un desarrollador o un administrador, la capa de transporte no es teoría: es la interfaz con la que su código toca la red todos los días.
Contenido
- Qué vive en la capa de transporte
- TCP y UDP: dos servicios, un repaso en una tabla
- Sockets: la capa vista por el programador
- Un servidor TCP mínimo, línea a línea
- Un cliente TCP mínimo, línea a línea
- Estados de conexión en la práctica:
ssynetstat - Puertos en uso y conflictos: "address already in use"
- TCP vs UDP como decisión de arquitectura
Qué vive en la capa de transporte
La capa de transporte de TCP/IP corresponde a la capa 4 de OSI (03-05) y tiene dos habitantes principales:
- TCP (Transmission Control Protocol): servicio orientado a conexión y fiable — establece una sesión con el three-way handshake, numera los bytes, confirma con ACKs, retransmite lo perdido y regula el ritmo con la ventana (todo ello visto en 02-04).
- UDP (User Datagram Protocol): servicio sin conexión y sin garantías — envía datagramas sueltos y se desentiende. Rápido, ligero, y honesto sobre lo que no promete.
Ambos comparten la aportación clave de la capa: el multiplexado por puertos. IP entrega el paquete a la máquina 192.168.10.10; el número de puerto de destino decide a qué proceso de esa máquina va el contenido. La pareja completa que identifica una conversación es el socket pair: (IP origen, puerto origen, IP destino, puerto destino) — y en TCP, además, el protocolo. Por eso el servidor de la intranet puede atender a Marta y a Ana a la vez en el mismo puerto 443: las dos conversaciones difieren en la IP y el puerto de origen.
CAPA DE TRANSPORTE en 192.168.10.10
+---------------------------------------------------+
paquete | puerto 443 -> proceso nginx (intranet HTTPS) |
IP --> | puerto 22 -> proceso sshd (administración) |
| puerto 3306 -> proceso mysqld (base de datos) |
+---------------------------------------------------+
una máquina, una IP, muchos procesos: los puertos
son el "número de piso" dentro del edificioTCP y UDP: dos servicios, un repaso en una tabla
Sin repetir la mecánica de 02-04, fijemos el contraste como contrato de servicio que la capa ofrece a las aplicaciones:
| Propiedad | TCP | UDP |
|---|---|---|
| Conexión previa | Sí (handshake de 3 pasos) | No: se envía y ya |
| Fiabilidad | Garantizada (ACK + retransmisión) | Ninguna: lo perdido, perdido está |
| Orden de llegada | Garantizado (números de secuencia) | No garantizado |
| Unidad que ve la app | Flujo de bytes continuo | Datagramas con fronteras |
| Control de flujo/congestión | Sí (ventana) | No |
| Coste de arranque | 1 RTT extra por el handshake | Cero |
| Cabecera | 20+ bytes | 8 bytes |
| Segmento/PDU | Segmento TCP | Datagrama UDP |
Un matiz que en 02-04 quedó implícito y que ahora, con la mirada de programador, importa mucho: TCP entrega un flujo, no mensajes. Si el cliente hace dos send() de 100 bytes, el servidor puede recibirlos en un solo recv() de 200, o en tres trozos de 80+80+40. Las fronteras las pone la aplicación (por ejemplo, HTTP con su Content-Length). En UDP, en cambio, cada datagrama llega entero o no llega: un send = un recv.
Sockets: la capa vista por el programador
Un socket es el objeto que el sistema operativo ofrece a los programas para usar la capa de transporte. Recuerda de 04-01 que la pila TCP/IP vive dentro del kernel: tu programa no construye segmentos ni calcula ACKs; pide al SO "dame un socket TCP", escribe bytes en él, y el kernel hace todo lo demás (segmentar, numerar, retransmitir, y pasarlo a la capa de Internet).
La API de sockets (nacida en el Unix BSD de los años 80 y calcada desde entonces en C, Python, Java, Go, JavaScript...) gira en torno a un puñado de operaciones:
| Operación | Quién la usa | Qué hace |
|---|---|---|
socket() |
ambos | Crear el socket (elegir TCP o UDP) |
bind() |
servidor | Reservar una IP local y un puerto |
listen() |
servidor | Declararse dispuesto a aceptar conexiones |
accept() |
servidor | Esperar y aceptar una conexión entrante |
connect() |
cliente | Iniciar el handshake hacia IP:puerto remotos |
send() / recv() |
ambos | Escribir/leer bytes del flujo |
close() |
ambos | Terminar la conexión (dispara el cierre TCP) |
La asimetría es la del mundo real: el servidor espera en un puerto conocido; el cliente llama. Veámoslo en pseudocódigo.
Un servidor TCP mínimo, línea a línea
Así arranca, en esencia, el servidor de la intranet de Meridiano (lo real es nginx + una API, pero el esqueleto de sockets es este):
1 s = socket(TCP) # pide al kernel un socket TCP
2 bind(s, 192.168.10.10, 8080) # reserva la IP local y el puerto 8080
3 listen(s) # "estoy abierto al público"
4 bucle infinito:
5 c = accept(s) # BLOQUEA hasta que llegue una conexión
6 peticion = recv(c) # lee bytes del flujo (p.ej. una petición HTTP)
7 send(c, respuesta) # escribe la respuesta en el flujo
8 close(c) # cierra ESTA conversación (no el socket de escucha)Línea a línea, con lo que ocurre por debajo:
- Línea 1 —
socket(TCP): el kernel crea la estructura interna y devuelve un identificador (en Unix, un descriptor de fichero: para el programa, un socket se lee y escribe casi como un fichero). - Línea 2 —
bind(...): reserva el puerto 8080 en la IP192.168.10.10. Es el único punto donde puede saltar el error "address already in use" (lo veremos en el apartado 7). Muchos servidores hacen bind a0.0.0.0("todas mis IPs") en lugar de a una concreta. - Línea 3 —
listen(s): a partir de aquí el kernel completa los handshakes él solo. Cuando el SYN de Marta llegue (02-04), el kernel responde SYN-ACK y encola la conexión terminada, sin que el programa haga nada. El socket pasa al estadoLISTEN. - Línea 5 —
accept(s): saca de esa cola una conexión ya establecida y devuelve un socket nuevo (c) exclusivo para esa conversación. Detalle crucial: el socket de escuchassigue vivo y aceptando; por eso el servidor atiende a muchos clientes con un solo puerto. Si no hay conexiones pendientes,acceptbloquea (el proceso duerme hasta que llegue alguien). - Líneas 6-7 —
recv/send: leer y escribir en el flujo. Recuerda: flujo de bytes, no mensajes;recvpuede devolver "media petición" y la aplicación debe saber recomponerla. - Línea 8 —
close(c): inicia el cierre ordenado de TCP (intercambio de FIN/ACK, visto conceptualmente en 02-04). Este cierre es el origen del estadoTIME_WAITque enseguida verás enss.
Los servidores reales añaden concurrencia (un hilo o tarea por conexión aceptada) y bucles de lectura, pero el esqueleto — socket → bind → listen → accept → recv/send → close — es idéntico en cualquier lenguaje.
Un cliente TCP mínimo, línea a línea
Y así se ve el lado de Marta cuando su navegador (o un script suyo) pide algo a la intranet:
1 c = socket(TCP) # socket TCP, como en el servidor
2 connect(c, 192.168.10.10, 8080) # dispara el three-way handshake
3 send(c, "GET /api/proyectos ...") # escribe la petición en el flujo
4 respuesta = recv(c) # lee la respuesta
5 close(c) # cierre ordenado- Línea 1 — igual que en el servidor: el socket es la misma pieza en ambos lados.
- Línea 2 —
connect(...): aquí ocurre el handshake completo de 02-04 (SYN → SYN-ACK → ACK). Cuandoconnectretorna sin error, la conexión estáESTABLISHEDen ambos extremos. Fíjate en lo que no hizo el cliente: ningúnbind. El kernel le asigna solo un puerto efímero de origen (típicamente entre 32768 y 60999 en Linux) — por eso en las capturas los clientes siempre hablan "desde" puertos altos y raros. - Líneas 3-5 — simétricas al servidor: escribir, leer, cerrar.
Un fallo en connect es diagnóstico puro, y ya lo conoces de 03-05: rechazo inmediato (RST) = la máquina está viva pero nadie escucha en ese puerto; timeout = la máquina o la red no responden. La capa de transporte te habla; hay que saber escucharla.
Estados de conexión en la práctica: ss y netstat
Toda conexión TCP vive en una máquina de estados (el handshake y el cierre son transiciones dentro de ella). No hace falta memorizarla entera: en el día a día, tres estados explican el 95 % de lo que verás. La herramienta moderna en Linux es ss (sustituta de netstat, que sigue existiendo y es lo que hay en Windows).
En el servidor de la intranet, un martes cualquiera:
$ ss -tn
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 192.168.10.10:443 192.168.10.21:52814
ESTAB 0 0 192.168.10.10:443 192.168.10.35:49102
TIME-WAIT 0 0 192.168.10.10:443 192.168.20.7:51230
$ ss -tln
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 511 0.0.0.0:443 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*Desglose de las opciones: -t = solo TCP, -n = números sin resolver (no traduce 443 a "https" ni IPs a nombres — más rápido y sin ambigüedad), -l = solo sockets en escucha. En Windows, netstat -ano da la vista equivalente (la -o añade el PID del proceso).
Los tres estados clave, leídos operativamente:
| Estado | Qué significa | Lectura operativa |
|---|---|---|
LISTEN |
Socket de servidor esperando conexiones (tras listen()) |
"El servicio está arrancado y aceptando". Si esperabas un LISTEN en el 443 y no está, el problema es el proceso, no la red |
ESTABLISHED |
Handshake completado; flujo abierto en ambos sentidos | Una conversación viva. La primera línea de arriba es Marta (.10.21) navegando por la intranet ahora mismo |
TIME_WAIT |
La conexión ya se cerró; el extremo que cerró primero retiene el socket pair unos 60 s | Normal y sano: evita que segmentos rezagados de la conexión vieja se cuelen en una nueva con el mismo socket pair. Ver muchos TIME_WAIT en un servidor con tráfico es rutina, no fuga |
La tercera línea del ejemplo cuenta una historia completa: Jon (192.168.20.7, Bilbao) consultó la intranet a través de la VPN, terminó, y su conexión está en TIME_WAIT esperando su minuto de cuarentena antes de desaparecer.
Otros estados que verás de pasada: SYN-SENT (cliente esperando el SYN-ACK; si se acumulan, el destino no responde), CLOSE-WAIT (el otro lado cerró y tu aplicación aún no ha llamado a close() — si se acumulan durante horas, sospecha de un bug en la aplicación: sockets sin cerrar).
Puertos en uso y conflictos: "address already in use"
El incidente clásico en Meridiano: Ana despliega una versión nueva de la API de la intranet y el arranque muere con:
Traducción exacta: el bind() de la línea 2 del servidor pidió un puerto que otro socket ya tiene reservado. Las dos causas habituales, de más a menos frecuente:
-
El proceso viejo sigue vivo. El despliegue no mató la versión anterior, que conserva su
LISTENen el puerto. Diagnóstico en una línea:$ ss -tlnp | grep :8080 LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:(("python3",pid=1147,fd=3))La opción
-p(requiere root) delata al ocupante: procesopython3, PID 1147. Solución: parar ese proceso ordenadamente (el gestor de servicios —systemctl restart— existe precisamente para no llegar aquí). En Windows:netstat -ano | findstr :8080y buscar el PID en el Administrador de tareas. -
Conexiones residuales del proceso anterior (
TIME_WAITcon ese puerto como origen local en ciertos escenarios de reinicio rápido). Por eso los servidores bien escritos activan la opción de socketSO_REUSEADDRantes delbind: le dice al kernel "permíteme reutilizar el puerto aunque queden restos enTIME_WAIT". Es la primera línea que un desarrollador añade a un servidor tras sufrir esto una vez.
Regla operativa: "address already in use" nunca es un problema de red — es un problema local de la máquina, entre procesos. La red ni se ha enterado.
Y su primo hermano en el cliente: si un cliente abre y cierra miles de conexiones por segundo hacia el mismo destino, puede llegar a agotar los puertos efímeros (todos en TIME_WAIT). Es raro en una pyme como Meridiano, pero explica por qué las aplicaciones intensivas reutilizan conexiones (connection pooling, HTTP keep-alive) en lugar de abrir una por petición.
TCP vs UDP como decisión de arquitectura
Elegir transporte no es una preferencia: es una decisión de diseño que se toma respondiendo a dos preguntas — ¿puedo permitirme perder datos? y ¿puedo permitirme esperar? Repasemos los servicios reales de Meridiano:
| Servicio de Meridiano | Transporte | Por qué |
|---|---|---|
Intranet HTTPS (GET /api/proyectos) |
TCP (443) | Una respuesta JSON con un byte perdido es basura: fiabilidad obligatoria |
Ficheros compartidos (SMB, servidor .10.10) |
TCP (445) | Un documento corrupto es inaceptable; además son transferencias largas: la ventana de TCP las regula |
| Correo (SMTP/IMAP) | TCP (587/993) | Perder un correo a medias no es una opción |
| SSH al router y al servidor | TCP (22) | Cada tecla debe llegar, y en orden |
| DNS (consultas de resolución) | UDP (53) | Pregunta y respuesta caben en un datagrama; si se pierde, el cliente repregunta a los ~2 s. El handshake de TCP costaría más que la consulta entera (TCP queda de reserva para respuestas grandes) |
| DHCP (el DORA de 02-05) | UDP (67/68) | El cliente aún no tiene IP: imposible mantener una conexión; son 4 datagramas de difusión |
| Videollamadas Valencia–Bilbao | UDP (RTP) | Un fotograma que llega tarde ya no sirve: mejor perderlo que parar el vídeo esperando una retransmisión. La app corrige a su manera (ajusta calidad) |
| Monitorización del router (SNMP) | UDP (161) | Sondas pequeñas y frecuentes; una perdida se repite en el siguiente ciclo |
El patrón que emerge es fiable como una ley: datos que deben llegar íntegros → TCP; tiempo real o intercambios minúsculos donde reintentarlo sale más barato que garantizarlo → UDP. Y una tercera vía moderna que conviene conocer de nombre: QUIC, el transporte de HTTP/3, que corre sobre UDP pero reimplementa fiabilidad, orden y cifrado en la propia aplicación — la prueba de que cuando UDP "no da garantías" en realidad está dando libertad para construir las tuyas.
Errores Comunes y Consejos
- Creer que TCP entrega "mensajes". Entrega un flujo de bytes: dos
send()pueden llegar como un solorecv()o como varios trozos. Las fronteras de mensaje las define el protocolo de aplicación. Este malentendido es la causa número uno de bugs de red en código de principiantes. - Asustarse por los
TIME_WAIT. Son el funcionamiento correcto del cierre TCP, no conexiones colgadas ni fugas. Preocúpate en cambio porCLOSE-WAITacumulados: esos sí indican que tu aplicación no cierra sockets. - Buscar en la red un "address already in use". Es siempre un conflicto local entre procesos por un puerto.
ss -tlnp(onetstat -ano) identifica al ocupante en segundos. - Confundir el socket de escucha con las conexiones. El
LISTENdel puerto 443 es uno; cada cliente aceptado tiene su propio socketESTABLISHED. Cerrar una conexión no afecta alLISTEN, y matar elLISTENno corta las conexiones ya establecidas. - Elegir UDP "porque es más rápido" sin plan B. UDP no es TCP-sin-lastre: es un contrato distinto. Si eliges UDP, la fiabilidad que necesites la programas tú (o asumes las pérdidas, como hace el vídeo).
- Consejo: memoriza el trío
ss -tln(¿qué escucha?),ss -tn(¿qué conversa?),ss -tlnp(¿quién es el proceso?). Con esas tres órdenes se resuelve la mayoría de dudas "¿está levantado el servicio?" antes de tocar nada más.
Ejercicios
-
Ana ejecuta en el servidor de la intranet
ss -tny ve la líneaSYN-SENT 0 1 192.168.10.10:41230 192.168.20.7:443repetida durante 30 segundos, hasta que desaparece con un error. (a) ¿Quién está actuando como cliente y quién como servidor en este intento? (b) ¿Qué significa que el estado se quede clavado enSYN-SENT? (c) Da dos hipótesis compatibles, usando lo aprendido en 03-05. -
En el pseudocódigo del servidor del apartado 4, explica qué ocurriría exactamente (y qué vería Marta desde su navegador) si: (a) se elimina la línea 3 (
listen); (b) el programa se queda procesando una petición lenta en la línea 6 y mientras tanto llegan tres clientes nuevos; (c) se elimina la línea 8 (close) y el servidor atiende cientos de peticiones. -
Meridiano va a montar dos servicios nuevos: (a) un sistema de copia de seguridad nocturna que sube los ficheros del servidor de Valencia a un almacenamiento en Bilbao a través de la VPN; (b) un panel en recepción que muestra la temperatura de la sala de servidores, actualizada cada 2 segundos desde un sensor. Elige transporte para cada uno y justifícalo con las dos preguntas del apartado 8.
Soluciones
-
(a) El servidor de la intranet actúa aquí como cliente: el puerto de origen es efímero (41230) y el destino es el 443 del PC de Jon o de algún equipo de Bilbao (
192.168.20.7) — probablemente un chequeo o integración. Los roles cliente/servidor son por conexión, no por máquina. (b)SYN-SENTclavado significa que se envió el SYN y nunca llegó el SYN-ACK: el handshake no arranca; el kernel reintenta el SYN varias veces y acaba rindiéndose (ese es el error a los ~30 s). (c) Hipótesis compatibles con un timeout (03-05): la máquina.20.7está apagada o inalcanzable (¿VPN caída? — comprobable conping), o un cortafuegos descarta silenciosamente los SYN hacia ese puerto. Si el puerto simplemente estuviera cerrado con la máquina viva, habría llegado un RST y el error sería inmediato ("connection refused"), no un timeout. -
(a) Sin
listen, el socket nunca entra en estadoLISTENy el kernel responde a los SYN entrantes con RST: Marta vería un "conexión rechazada" inmediato. (Según el lenguaje, además, elacceptde la línea 5 fallaría al ejecutarse.) (b) Nada grave a corto plazo: el kernel completa los handshakes por su cuenta y encola las tres conexiones ya establecidas (dentro del límite del backlog); los tres clientes ven su conexión abierta pero sin respuesta hasta que el servidor termine y vuelva aaccept. Perciben lentitud, no rechazo — así se ve un servidor monohilo saturado. Si la cola se desborda, los siguientes SYN sí se descartan. (c) Cada conexión atendida queda abierta para siempre: losESTABLISHED(y en el cliente, esperas colgadas) se acumulan, y con ellos los descriptores de fichero del proceso, hasta agotar el límite del SO y hacer queacceptempiece a fallar. Es la fuga de sockets clásica: se diagnostica viendo cientos de conexiones viejas enss -tnasociadas al proceso. -
(a) Copia de seguridad: TCP. ¿Puedo perder datos? No — un backup corrupto es peor que no tener backup. ¿Puedo esperar? Sí — es nocturno, la latencia da igual; y la ventana de TCP además aprovechará bien el túnel VPN regulando el ritmo. (b) Panel de temperatura: UDP es razonable. ¿Puedo perder datos? Sí — si se pierde la lectura de las 10:00:02, la de las 10:00:04 la sustituye; el dato viejo caduca solo. ¿Puedo esperar? No tiene sentido retransmitir una lectura obsoleta. Un datagrama pequeño cada 2 s sin handshake es el ajuste natural. (TCP tampoco sería incorrecto aquí — con tan poco tráfico apenas se notaría — pero UDP expresa mejor el diseño: última lectura gana.)
Conclusión
La capa de transporte es donde la red deja de ser fontanería y se convierte en interfaz de programación: TCP y UDP son dos contratos de servicio — flujo fiable frente a datagramas sin promesas — y el socket es la puerta por la que cualquier programa los usa, con el ritual socket → bind → listen → accept en el servidor y socket → connect en el cliente, mientras el kernel hace el trabajo sucio de 02-04 por debajo. Ya sabes leer el estado de esa maquinaria (ss -tln para lo que escucha, ss -tn para lo que conversa), interpretar operativamente LISTEN, ESTABLISHED y TIME_WAIT, desarmar un "address already in use" en dos órdenes, y elegir transporte como lo que es: una decisión de arquitectura que se responde con dos preguntas. Nos queda un último peldaño en la pila: la capa de aplicación, donde viven los protocolos que Marta y Jon usan sin saberlo — y donde veremos, paso a paso, qué ocurre desde que el código de la intranet pide GET /api/proyectos hasta que esos bytes entran en el socket que acabas de aprender a abrir.
Curso de Redes
Módulo 1: Introducción a las Redes
Módulo 2: Protocolos de Comunicación
- Introducción a los Protocolos de Comunicación
- Protocolos de Enlace de Datos
- Protocolos de Red
- Protocolos de Transporte
- Protocolos de Aplicación
Módulo 3: El Modelo OSI
- Introducción al Modelo OSI
- Capa Física
- Capa de Enlace de Datos
- Capa de Red
- Capa de Transporte
- Capa de Sesión
- Capa de Presentación
- Capa de Aplicación
Módulo 4: El Modelo TCP/IP
- Introducción al Modelo TCP/IP
- Capa de Acceso a la Red
- Capa de Internet
- Capa de Transporte
- Capa de Aplicación
- Comparativa entre OSI y TCP/IP
