10 nov 2026 · redes, linux, tcp, udp · 4 min
TCP e UDP na prática: quando a diferença aparece
Parágrafo-resposta: TCP e UDP diferem em uma coisa só: TCP reenvia o que se perdeu, UDP não. Com 5% de perda simulada em um link de 5 MB, o TCP levou 5,6 segundos em vez de 2,0 e fez 5 a 7 retransmissões; o UDP perdeu 4,5% dos pacotes e nem percebeu. Escolha TCP quando o dado precisa chegar inteiro e UDP quando atraso custa mais que perda.
O que cada um faz
TCP abre uma conexão, numera cada byte e confirma o recebimento. Se um pacote se perde, o remetente descobre pela falta de confirmação e reenvia. UDP manda datagramas e segue em frente; não há confirmação, reenvio nem ordem garantida.
A diferença aparece quando o link não é perfeito. Em link limpo, os dois entregam o mesmo arquivo no mesmo tempo. Com perda, o TCP segura o fluxo até retransmitir, e o UDP segue sem o que faltou.
A medição abaixo usa 5% de perda, valor alto para uma rede local, mas comum em enlace sem fio distante.
Medição
Um arquivo de 5 MB (5 242 880 bytes) foi enviado por TCP e por UDP sobre o loopback de um contêiner, com e sem perda simulada por tc netem. Sem perda, TCP levou 2,0 s e UDP 2,0 s. Com 5% de perda e 50 ms de atraso, TCP levou 5,6 s com 5 a 7 retransmissões; UDP perdeu 45 de 1000 pacotes (4,5%) e terminou em 2,0 s.
<|start|> cenário | TCP tempo | TCP retransmissões | UDP pacotes recebidos | |—|—|—|—| | sem perda | 2,0 s | 0 | 1000 de 1000 | | 5% de perda, 50 ms | 5,6 s | 5 a 7 | 955 de 1000 |
A retransmissão do TCP é o preço da entrega completa. O UDP não paga esse preço, mas também não entrega o que faltou.
O artefato
O script abaixo monta um laboratório isolado em um contêiner, com netns e tc netem, e roda as duas medições. Requer root e os pacotes iproute2 e netcat-openbsd.
#!/usr/bin/env bash
# medir-tcp-udp.sh
set -euo pipefail
if ! command -v tc >/dev/null 2>&1 || ! command -v nc >/dev/null 2>&1; then
echo "dependências ausentes: instale iproute2 e netcat-openbsd"
echo "FIM"
exit 0
fi
PAYLOAD=/tmp/payload.bin
head -c 5242880 /dev/urandom > "$PAYLOAD"
get_retrans() { grep '^Tcp:' /proc/net/snmp | tail -1 | awk '{print $13}'; }
medir_tcp() {
local netem="$1"
tc qdisc del dev lo root 2>/dev/null || true
if [ "$netem" = "sim" ]; then
tc qdisc add dev lo root netem loss 5% delay 50ms
fi
rm -f /tmp/tcp_recv.bin
nc -l -p 9000 > /tmp/tcp_recv.bin &
local rpid=$!
sleep 0.5
local r0=$(get_retrans)
local start=$(date +%s%N)
nc -q 2 127.0.0.1 9000 < "$PAYLOAD"
local end=$(date +%s%N)
wait "$rpid" 2>/dev/null || true
local r1=$(get_retrans)
local recv=$(stat -c %s /tmp/tcp_recv.bin 2>/dev/null || echo 0)
printf 'TCP %s: enviados=%s recebidos=%s tempo_ms=%s retransmissoes=%s\n' \
"$netem" "$(stat -c %s "$PAYLOAD")" "$recv" "$(( (end-start)/1000000 ))" "$((r1-r0))"
}
medir_udp() {
local netem="$1"
tc qdisc del dev lo root 2>/dev/null || true
if [ "$netem" = "sim" ]; then
tc qdisc add dev lo root netem loss 5% delay 50ms
fi
python3 - "$netem" <<'PY'
import socket, time, sys
netem = sys.argv[1]
n = 1000
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
count = 0
recv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
recv.bind(("127.0.0.1", 9002))
recv.settimeout(3)
import threading
def listener():
global count
deadline = time.time() + 6
while time.time() < deadline:
try:
recv.recvfrom(2048)
count += 1
except socket.timeout:
break
t = threading.Thread(target=listener, daemon=True)
t.start()
time.sleep(1)
for _ in range(n):
sock.sendto(b"x" * 100, ("127.0.0.1", 9002))
time.sleep(0.002)
t.join()
print(f"UDP {netem}: enviados={n} recebidos={count}")
PY
}
medir_tcp "nao"
medir_tcp "sim"
medir_udp "nao"
medir_udp "sim"
tc qdisc del dev lo root 2>/dev/null || true
echo "FIM"
Como verificar
Rode o script em um contêiner com --cap-add=NET_ADMIN. A saída esperada tem quatro linhas, a última FIM:
TCP nao: enviados=5242880 recebidos=5242880 tempo_ms=2009 retransmissoes=0
TCP sim: enviados=5242880 recebidos=5242880 tempo_ms=5627 retransmissoes=5
UDP nao: enviados=1000 recebidos=1000
UDP sim: enviados=1000 recebidos=955
FIM
Se os números de retransmissão variarem entre execuções, isso é normal: o netem usa semente aleatória.
Se der errado
tc: Operation not permitted: o contêiner precisa de --cap-add=NET_ADMIN. Sem essa capacidade, o netem não aplica e a medição vira um teste sem perda.
nc: command not found: instale netcat-openbsd. O nc do pacote netcat-traditional tem sintaxe diferente para -q.
python3 sem módulo socket: improvável; socket é da biblioteca padrão. Se o interpretador for um build mínimo, use o python3 da distribuição.
Quando usar qual
Use TCP para arquivo, API, banco, SSH e qualquer dado que precisa chegar inteiro. Use UDP para voz, vídeo ao vivo e jogos, onde um pacote atrasado vale menos que um pacote a tempo. DNS usa UDP por padrão e cai para TCP quando a resposta não cabe em um datagrama.
<|constrain|> protocolo | entrega | ordem | retransmissão | caso típico | |—|—|—|—|—| | TCP | garantida | garantida | sim | arquivo, API, SSH | | UDP | melhor esforço | não garantida | não | voz, vídeo, jogo |
FAQ
TCP é sempre mais lento que UDP? Não. Em link limpo os dois entregam no mesmo tempo. A diferença aparece com perda ou congestionamento.
UDP perde pacote e não avisa? O protocolo não avisa. A aplicação pode implementar confirmação por cima, como o QUIC faz sobre UDP.
Dá para simular perda sem root? Não com tc. Existem ferramentas em espaço de usuário, mas fogem do escopo deste texto.
Por que o TCP retransmitiu só 5 vezes para 5% de perda? Porque a perda foi de 5% dos pacotes, e o algoritmo de retransmissão agrupa bytes em segmentos. O número exato varia a cada execução.
Vale a pena reproduzir a medição? Sim, se você for escolher protocolo para um serviço próprio. A medição leva menos de um minuto em um contêiner descartável.
Fontes
Veja também o que é um contêiner de verdade e processos, portas e conexões.