🤝
TCP de verdade: handshake, congestion control, retransmissão
⏱ 17 min de leitura·+85 XP
TCP entrega dados de forma confiável sobre IP — que não garante entrega, ordem ou integridade. Entender como TCP faz isso (handshake, números de sequência, janela deslizante, controle de congestionamento) explica por que a web funciona e onde ela é lenta.
Three-way handshake e estados de conexão
# TCP Three-Way Handshake:
# Cliente Servidor
# |----SYN seq=X----------->| SYN_SENT → SYN_RECEIVED
# |<---SYN-ACK seq=Y,ack=X+1|
# |----ACK ack=Y+1---------->| ESTABLISHED (ambos)
# SYN = Synchronize: "vou começar com seq número X"
# ACK = Acknowledge: "recebi até seq número X, aguardando X+1"
# ISN (Initial Sequence Number): aleatório para evitar session hijacking
import socket
import time
# Medir latência do handshake:
def tcp_handshake_time(host: str, port: int) -> float:
inicio = time.perf_counter()
with socket.create_connection((host, port), timeout=5):
handshake_ms = (time.perf_counter() - inicio) * 1000
return handshake_ms
# ESTADOS TCP (máquina de estados):
# LISTEN → servidor aguardando conexão
# SYN_SENT → cliente enviou SYN
# SYN_RCVD → servidor recebeu SYN, enviou SYN-ACK
# ESTABLISHED → conexão ativa, dados podem fluir
# FIN_WAIT_1 → iniciou encerramento
# TIME_WAIT → aguarda 2×MSL (Maximum Segment Lifetime ≈ 2min) para
# garantir que ACK final chegou
# Ver estados das conexões:
# ss -tn ← sockets TCP, sem resolver nomes
# ss -tn state ESTABLISHED
# ss -tn state TIME_WAIT | wc -l ← muitas TIME_WAIT = muitas conexões curtas
# Four-way termination:
# Ativo Passivo
# |----FIN---------------->| FIN_WAIT_1
# |<---ACK-----------------| CLOSE_WAIT
# |<---FIN-----------------| LAST_ACK
# |----ACK---------------->| TIME_WAIT → CLOSED
# (servidor pode ter dados para enviar antes do FIN — por isso 4 mensagens)
# Otimização: TCP Fast Open (TFO)
# Envia dados junto com o SYN, elimina 1 RTT na primeira conexão
# Habilitado com: sysctl net.ipv4.tcp_fastopen=3Números de sequência, janela e entrega confiável
# Janela deslizante (sliding window): permite enviar múltiplos segmentos # sem esperar ACK de cada um — mantém o pipe cheio # send window = min(rwnd, cwnd) # rwnd = receiver window (capacidade do receptor) # cwnd = congestion window (estimativa da rede) # Receive window (rwnd): campo no header TCP (16 bits, máx 65535 bytes) # Com window scaling (RFC 7323): até 1GB (shift de até 14 bits) # Exemplo: RTT = 50ms, banda = 100Mbps # Bandwidth-Delay Product (BDP) = RTT × banda = 0.05s × 100Mbps = 5Mbit = 625KB # Para utilizar 100% da banda, a janela deve ser ≥ 625KB # Janela padrão de 65KB → utilização máxima: 65KB/625KB ≈ 10% # → window scaling NECESSÁRIO para links de alta velocidade # Verificar configurações de janela no Linux: # sysctl net.ipv4.tcp_rmem → [min, default, max] receive buffer # sysctl net.ipv4.tcp_wmem → [min, default, max] send buffer # sysctl net.ipv4.tcp_window_scaling → deve ser 1 (habilitado) # SACK (Selective ACK): informa exatamente quais segmentos chegaram # Sem SACK: perda de 1 segmento → reenvia tudo depois dele # Com SACK: reenvia só o perdido # sysctl net.ipv4.tcp_sack → deve ser 1 # Números de sequência — como detectar reordenação e duplicatas: # seq=1000, len=1000 → cobre bytes 1000-1999 # seq=2000, len=500 → cobre bytes 2000-2499 # Se chegar seq=2000 antes de seq=1000: receptor bufferiza, ACK=1000 (não avança) # Quando seq=1000 chega: receptor entrega ambos, ACK=2500
Controle de congestionamento
| Algoritmo | Crescimento | Reação à perda | Uso |
|---|---|---|---|
| Tahoe | Slow start + AIMD | cwnd=1, slow start | Legado |
| Reno | Slow start + AIMD | Fast recovery (halve) | Legado |
| CUBIC | Função cúbica do tempo | Halve cwnd | Default Linux |
| BBR v1 | Modelo de banda+RTT | Não reage a perda direta | YouTube/Google |
| BBR v2 | Modelo + perda | Balanceado | Padrão crescente |
| QUIC CC | Pluggable (CUBIC/BBR) | Por stream | HTTP/3 |
# Slow start e Congestion Avoidance — simulação conceitual:
def simulate_tcp_cubic(rtts: int = 20) -> list:
"""Simula crescimento simplificado do cwnd TCP Cubic."""
cwnd = 1 # começa em 1 MSS (Maximum Segment Size ≈ 1460 bytes)
ssthresh = 32 # slow start threshold (initial)
history = []
for rtt in range(rtts):
history.append(cwnd)
if cwnd < ssthresh:
cwnd *= 2 # slow start: dobra por RTT (exponencial)
else:
cwnd += 1 # congestion avoidance: +1 por RTT (linear)
# Simulando perda no RTT 12 (3 ACKs duplicados):
if rtt == 12:
ssthresh = max(cwnd // 2, 2)
cwnd = ssthresh # fast recovery: halve
print(f"RTT {rtt}: Perda! ssthresh={ssthresh}, cwnd={cwnd}")
return history
history = simulate_tcp_cubic()
for i, w in enumerate(history):
bar = "█" * min(w, 64)
print(f"RTT {i:2d}: cwnd={w:3d} {bar}")
# Verificar variante em uso:
# sysctl net.ipv4.tcp_congestion_control → cubic (padrão)
# ss -tin dst 8.8.8.8 | grep cubic ← mostra por conexão
# sysctl net.ipv4.tcp_available_congestion_control
# BBR (Bottleneck Bandwidth and RTT):
# Mede: max(bandwidth_samples) e min(RTT_samples)
# Modelo: a rede pode suportar max_bw na latência min_RTT
# Não reage a perda (considera ruído, não congestionamento)
# Usado por: YouTube, Google Search, Cloudflare em alguns casosRetransmissão: timeout vs fast retransmit
# RTO (Retransmission Timeout): calculado dinamicamente
# RTO = SRTT + 4 × RTTVAR
# SRTT = Smoothed RTT (média exponencialmente ponderada)
# RTTVAR = variância do RTT
# Backoff exponencial: RTO dobra a cada retransmissão falha
# Fast Retransmit (RFC 5681):
# 3 ACKs duplicados → reenvia imediatamente sem esperar RTO
# Indica segmento perdido isolado (os posteriores chegaram)
# Timeline do fast retransmit:
# T=0ms: envia seg 1,2,3,4,5
# T=50ms: seg 1 chega → ACK 2
# T=50ms: seg 2 PERDIDO
# T=55ms: seg 3 chega → ACK 2 (dup #1)
# T=60ms: seg 4 chega → ACK 2 (dup #2)
# T=65ms: seg 5 chega → ACK 2 (dup #3) ← fast retransmit! reenvia seg 2
# T=115ms: seg 2 entregue → ACK 6 (avança para o final)
# Total: ~115ms vs ~1000ms de timeout!
import socket
import time
import struct
def get_tcp_info(sock: socket.socket) -> dict:
"""Lê métricas TCP do kernel via TCP_INFO (Linux)."""
# TCP_INFO: struct tcp_info do kernel
TCP_INFO = 11
fmt = "BBBBBBBBIIIIIIIIIIIII" # simplificado
data = sock.getsockopt(socket.IPPROTO_TCP, TCP_INFO, 200)
fields = struct.unpack(fmt, data[:struct.calcsize(fmt)])
return {
"state": fields[0],
"retrans": fields[4], # retransmissões
"rtt_us": fields[16], # RTT em microssegundos
"rttvar_us": fields[17], # variância do RTT
"snd_cwnd": fields[14], # janela de congestionamento
}
# Uso:
# sock = socket.create_connection(("example.com", 80))
# info = get_tcp_info(sock)
# print(f"RTT: {info['rtt_us']/1000:.2f}ms, cwnd: {info['snd_cwnd']} MSS")
# Nagle Algorithm: agrega pequenos writes em um segmento
# Ativo por padrão → aumenta latência para protocolos interativos
# Desativar para aplicações de baixa latência (jogos, telnet, SSH):
# sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)✅
Modelo mental: TCP garante entrega confiável com três mecanismos — números de sequência (detecta perda e reordenação), ACKs cumulativos + retransmissão (reentrega perdidos), e janela deslizante (mantém o pipe cheio). Controle de congestionamento (CUBIC/BBR) sonda a capacidade da rede gradualmente. Fast retransmit detecta perdas isoladas em ~1 RTT em vez de esperar ~1s de timeout. TCP_NODELAY desativa Nagle para latência mínima em protocolos interativos.
💡
Próximo: UDP, QUIC e HTTP/3 — por que Google construiu um protocolo de transporte do zero em cima de UDP.
🧩
Quiz rápido
3 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito
Continue lendo