🧠FFVAcademy
🤝

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=3

Nú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

AlgoritmoCrescimentoReação à perdaUso
TahoeSlow start + AIMDcwnd=1, slow startLegado
RenoSlow start + AIMDFast recovery (halve)Legado
CUBICFunção cúbica do tempoHalve cwndDefault Linux
BBR v1Modelo de banda+RTTNão reage a perda diretaYouTube/Google
BBR v2Modelo + perdaBalanceadoPadrão crescente
QUIC CCPluggable (CUBIC/BBR)Por streamHTTP/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 casos

Retransmissã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