Transporte: TCP, UDP e QUIC
por Frank de Alcantara em 10/08/2026
O Artigo 1 fechou com uma afirmação que agora precisa ser cobrada: a física fixa o piso, e o que está acima do piso é decisão de protocolo. Este artigo cobra.
Índice da Série: Redes para Engenharia de Software
- 1. A Pilha TCP/IP e a Física da Rede
- 2. Transporte: TCP, UDP e QUIC (Você está aqui)
O IP não promete nada. Ele aceita um datagrama, tenta entregá-lo e não avisa se falhou. Sobre essa pobreza deliberada, que o argumento fim a fim justificou na Seção 1 do artigo anterior, o transporte pode oferecer contratos diferentes para a mesma pergunta: o que fazer quando um pacote se perde? O TCP oferece um fluxo confiável e ordenado, o UDP preserva datagramas sem acrescentar recuperação, e o QUIC combina confiabilidade, múltiplos fluxos e segurança sobre UDP. A escolha do contrato determina onde ficarão a ordem, a recuperação, o controle de congestionamento e, portanto, boa parte da arquitetura do sistema distribuído.
O TCP resolve um problema que a aplicação não deveria precisar resolver outra vez: entregar bytes em ordem, sem lacunas nem duplicatas, sobre uma rede que não promete nenhuma dessas propriedades. O preço é estado, e o estado aparece para a engenheira de software como conexões que demoram a abrir, fluxos que param atrás de um único byte perdido e transferências limitadas a uma fração do enlace contratado. Vamos deduzir esse preço, mecanismo por mecanismo, antes de mostrar quais parcelas o QUIC removeu e quais apenas deslocou para o espaço de usuário.
A régua continua a mesma. Usaremos $\text{RTT} = 76{,}65$ ms, o piso físico entre São Paulo e Ashburn calculado na Tabela 2 do artigo anterior, sempre que precisarmos de um número concreto para uma rota longa.
1. O que o transporte acrescenta ao IP
A camada de Internet entrega datagramas a uma máquina. Isso não basta, porque dezenas de processos podem compartilhar o mesmo endereço IP. O transporte acrescenta, no mínimo, a multiplexação: um número de porta de origem e outro de destino, com dezesseis bits cada, permitem ao sistema operacional decidir a qual soquete entregar os dados.
A tupla que identifica um fluxo tem, portanto, quatro elementos: endereço de origem, porta de origem, endereço de destino e porta de destino. Guardemos essa quádrupla, porque ela vai reaparecer com consequências em vários pontos da série. No Artigo 8 ela será a chave de agregação de fluxos; no Artigo 9 ela explicará o esgotamento de portas em um gateway de tradução de endereços; e na Seção 8 deste artigo ela explicará por que uma conexão TCP morre quando o telefone troca de rede e uma conexão QUIC não morre.
O segundo acréscimo é a detecção de corrupção, por meio de uma soma de verificação sobre o cabeçalho e os dados. Convém nomear o limite com precisão: essa soma de dezesseis bits não oferece integridade criptográfica nem garante que toda corrupção será percebida. Ela permite descartar cedo muitos datagramas alterados durante o transporte; a garantia de que o conteúdo recebido é o conteúdo pretendido continua pertencendo às pontas, conforme o argumento fim a fim.
A partir daí os dois protocolos clássicos divergem, e a divergência é total. O UDP acrescenta oito bytes de cabeçalho e para por aí. O TCP acrescenta vinte bytes, uma máquina de estados com onze estados, números de sequência, reconhecimentos, temporizadores, janelas e um algoritmo de controle de congestionamento que ocupa quatro RFCs. A Tabela 1 resume o que cada um promete.
| Propriedade | UDP | TCP | QUIC |
|---|---|---|---|
| entrega ordenada | não | sim, para toda a conexão | sim, por stream independente |
| retransmissão | não | sim | sim |
| controle de fluxo | não | sim | sim, por stream e por conexão |
| controle de congestionamento | não | sim | sim, no espaço de usuário |
| criptografia | não | opcional, em camada acima | obrigatória e integrada |
| estado no protocolo | nenhum | por conexão, em geral no núcleo | por conexão, em geral no processo |
| cabeçalho | 8 bytes | 20 bytes sem opções | 8 de UDP mais 13 típicos |
Tabela 1: contratos oferecidos pelos três transportes. O UDP preserva os limites dos datagramas e verifica seu comprimento e sua soma de verificação, mas não acrescenta ordem, recuperação nem controle de congestionamento.
2. UDP: a ausência de estado como recurso
Descrever o UDP pelo que ele não faz produz a impressão errada de um protocolo incompleto. Ele não é incompleto; ele é mínimo por projeto. O cabeçalho tem quatro campos de dezesseis bits: porta de origem, porta de destino, comprimento e soma de verificação. Oito bytes. Não há número de sequência, não há reconhecimento, não há temporizador, não há conexão.
A consequência mais importante não é o tamanho do cabeçalho: é a ausência de estado. Um servidor TCP com um milhão de conexões abertas mantém um milhão de blocos de controle no núcleo do sistema operacional, cada um com janelas, temporizadores e buffers. Um servidor UDP com um milhão de clientes ativos mantém, se quiser, zero.
Quatro famílias de aplicação escolhem UDP, e vale entender o motivo de cada uma porque os motivos são diferentes.
O DNS escolhe UDP porque a transação é uma pergunta e uma resposta, ambas cabendo em um datagrama. Abrir uma conexão TCP para trocar duzentos bytes custaria uma ida e volta inteira de handshake antes de qualquer dado, o que, pelos números do Artigo 1, dobraria o custo da resolução. O Artigo 3 vai quantificar isso.
Voz e vídeo em tempo real escolhem UDP porque, para eles, um pacote atrasado é pior que um pacote perdido. Se um quadro de áudio chega depois do instante em que deveria ter sido reproduzido, ele é lixo, e retransmiti-lo consome banda para entregar lixo. A retransmissão do TCP, que é uma virtude para um arquivo, é um defeito aqui.
Jogos e telemetria escolhem UDP porque o estado mais recente torna o anterior obsoleto. Se a posição do jogador no instante $t$ se perdeu e a posição no instante $t+1$ chegou, retransmitir a primeira é trabalho desperdiçado.
E o QUIC escolhe UDP por uma razão inteiramente distinta das três anteriores: porque precisava de um espaço em branco. Voltaremos a isso na Seção 8, e a razão é mais política que técnica.
Um aviso, porque ele custa caro a quem não o ouve. Escolher UDP não elimina os problemas que o TCP resolve; ele os transfere para a aplicação. Se a aplicação precisa de ordem, ela vai implementar números de sequência. Se precisa de confiabilidade, vai implementar reconhecimentos e temporizadores. Se precisa não afundar a rede, vai implementar controle de congestionamento, e essa última é a parte que quase todo mundo esquece. Uma aplicação sobre UDP que envia na taxa que quiser é, do ponto de vista da rede, um mau cidadão que se beneficia da educação alheia. O Artigo 15 mostrará protocolos que fazem isso direito.
3. O estabelecimento de conexão e o custo em idas e voltas
Aqui começa a matemática, e ela é aritmética de contagem: quantas idas e voltas acontecem antes do primeiro byte útil.
O TCP abre uma conexão com o aperto de mão de três vias. O cliente envia um segmento com a bandeira SYN e um número de sequência inicial; o servidor responde com SYN e ACK e o seu próprio número inicial; o cliente confirma esse número com um ACK. Quando o SYN e o SYN com ACK já percorreram o caminho, passou uma ida e volta. O ACK final pode carregar dados, embora o fluxo básico da RFC 9293 o mostre separado; por isso, uma conexão TCP comum custa uma ida e volta antes que o servidor possa receber dados da aplicação.
Sobre isso vem o TLS. Na versão 1.3, padronizada na RFC 8446, o aperto de mão criptográfico custa mais uma ida e volta. Somando:
\[T_{\text{TCP+TLS 1.3}} = 2 \times \text{RTT}.\]Com o RTT de $76{,}65$ ms entre São Paulo e Ashburn, são $153{,}30$ ms antes de o servidor ver a requisição. Com o TLS 1.2, que exigia duas idas e voltas, seriam três no total, ou $229{,}95$ ms, e é por isso que a migração para o 1.3 foi tratada como melhoria de desempenho e não apenas de segurança.
O QUIC funde os dois apertos de mão em um. Como o protocolo carrega o TLS 1.3 dentro de si, e não acima de si, a negociação de transporte e a de criptografia acontecem nos mesmos pacotes. O custo cai para uma ida e volta, ou $76{,}65$ ms.
E há o caso da retomada. Se o cliente já falou com aquele servidor antes e guardou um segredo de sessão, ele pode enviar dados de aplicação no primeiro pacote, antes de qualquer resposta. É o modo de zero ida e volta, e o custo de handshake é literalmente nulo.
A Figura 1 mostra as três linhas do tempo lado a lado, com o mesmo RTT.
Figura 1: TCP mais TLS 1.3 gasta 153,30 ms antes do primeiro byte útil; QUIC gasta 76,65 ms; QUIC com retomada gasta zero. A diferença é proporcional ao RTT, e portanto à distância.
A conta que interessa é a da página inteira. Uma página que abre seis conexões sequenciais ao mesmo servidor paga:
| Protocolo | Idas e voltas por conexão | Seis conexões sequenciais |
|---|---|---|
| TCP + TLS 1.3 | 2 | $919{,}80$ ms |
| QUIC, primeira visita | 1 | $459{,}90$ ms |
| QUIC, retomada | 0 | $0$ ms |
Tabela 2: custo de estabelecimento de seis conexões sequenciais, com RTT de 76,65 ms. A coluna da direita mede tempo em que nenhum dado de aplicação trafega.
Quase um segundo gasto em cerimônia. Note, contudo, que a conta supõe conexões sequenciais. Se as seis forem abertas em paralelo e o caminho não impuser outro gargalo, os períodos de estabelecimento se sobrepõem e o tempo de parede cai para o de uma conexão. O custo não desaparece, pois continuam existindo seis negociações, mas deixa de se somar no caminho crítico. É o mesmo princípio apresentado no laboratório do Artigo 1: podemos sobrepor latências independentes, mas não multiplicar a capacidade do enlace.
Um resultado que a esperta leitora deve guardar, porque ele governa quando vale a pena migrar de protocolo: a economia do QUIC é proporcional ao RTT. Em um enlace com $\text{RTT} = 5$ ms, dentro de uma região de nuvem, economizar uma ida e volta economiza $5$ ms, o que é irrelevante para qualquer requisição que faça trabalho. Em um enlace com $\text{RTT} = 200$ ms, economiza $200$ ms, o que é a diferença entre uma interface que responde e uma que trava. Migrar o tráfego entre serviços internos para QUIC é uma decisão que precisa de outro argumento; migrar o tráfego de usuários distantes é quase sempre defensável.
Fecho com a advertência que acompanha o modo de zero ida e volta, porque ela é uma armadilha de segurança e não de desempenho. O TLS e o QUIC exigem mecanismos contra repetição, mas reconhecem que essas defesas são imperfeitas: dados de aplicação enviados antes de uma resposta nova do servidor permanecem expostos a replay. Por isso, cada protocolo de aplicação precisa definir o que aceita em 0-RTT e como reduz o risco. Operações naturalmente idempotentes, ou protegidas por uma chave de idempotência verificada no servidor, são candidatas; uma transferência de valor sem essa proteção não é. A RFC 9001 atribui essa decisão ao protocolo de aplicação, e o HTTP/3 restringe o uso de dados antecipados.
3.1 Exercícios da Seção 3
1. Calcule o tempo até o primeiro byte com TCP e TLS 1.3
Com RTT de $76{,}65$ ms, quanto tempo se passa entre o primeiro pacote do cliente e o instante em que o servidor pode processar a requisição?
Solução: O aperto de mão de três vias do TCP consome uma ida e volta, porque o cliente só pode enviar dados no terceiro segmento. O aperto de mão do TLS 1.3 consome outra. Somando,
\[T = 2 \times 76{,}65 = 153{,}30\ \text{ms}.\]Nesse intervalo, nenhum byte de aplicação trafegou em nenhuma direção. O tempo é gasto integralmente em cerimônia, e o único recurso consumido é a distância. Compare com o piso da Tabela 2 do Artigo 1: o handshake sozinho custa duas vezes o piso físico da rota.
2. Repita para QUIC nas duas modalidades
Refaça o cálculo para QUIC em primeira visita e para QUIC com retomada de sessão.
Solução: O QUIC carrega o TLS 1.3 dentro do transporte, de modo que a negociação de chaves e a de parâmetros de conexão ocorrem nos mesmos pacotes. O custo é de uma ida e volta:
\[T_{\text{QUIC}} = 1 \times 76{,}65 = 76{,}65\ \text{ms}.\]Com retomada, o cliente possui um segredo derivado da sessão anterior e cifra dados de aplicação já no primeiro voo:
\[T_{\text{QUIC 0-RTT}} = 0\ \text{ms}.\]A economia sobre TCP com TLS 1.3 é de $76{,}65$ ms na primeira visita e de $153{,}30$ ms nas seguintes. Repare que os dois números são exatamente uma e duas vezes o RTT, o que torna a comparação transportável para qualquer rota sem refazer conta nenhuma.
3. Some o custo de seis conexões sequenciais
Uma página abre seis conexões sequenciais ao mesmo servidor. Calcule o tempo total gasto em estabelecimento nas três variantes.
Solução: Multiplicando o custo unitário por seis:
| Protocolo | Por conexão | Seis sequenciais | Seis em paralelo |
|---|---|---|---|
| TCP + TLS 1.3 | $153{,}30$ ms | $919{,}80$ ms | $153{,}30$ ms |
| QUIC, primeira visita | $76{,}65$ ms | $459{,}90$ ms | $76{,}65$ ms |
| QUIC, retomada | $0$ ms | $0$ ms | $0$ ms |
A coluna da direita revela que abrir em paralelo economiza mais do que trocar de protocolo: $919{,}80 - 153{,}30 = 766{,}50$ ms contra $919{,}80 - 459{,}90 = 459{,}90$ ms. As duas otimizações se compõem, e a ordem em que devem ser tentadas é a ordem do tamanho do ganho.
4. Compare o ganho do QUIC em duas rotas
Calcule a economia do QUIC sobre TCP com TLS 1.3 para RTT de $5$ ms e de $200$ ms, e explique por que o resultado muda a decisão.
Solução: A economia é de uma ida e volta na primeira visita, portanto:
| RTT | Economia por conexão | Economia em seis conexões sequenciais |
|---|---|---|
| $5$ ms | $5$ ms | $30$ ms |
| $200$ ms | $200$ ms | $1200$ ms |
A $5$ ms, a economia de uma página inteira é de trinta milissegundos, valor que desaparece dentro da variação natural de qualquer serviço, e que precisa ser confrontado com o custo de CPU do QUIC discutido na Seção 8. A $200$ ms, a economia é de $1{,}2$ segundo, o que é grande o bastante para ser percebido sem instrumentação. A regra de decisão que sai daí: a migração para QUIC se justifica pela distância dos usuários, não pela modernidade do protocolo.
5. Explique por que zero ida e volta exige idempotência
Mostre por que dados enviados em zero ida e volta não podem corresponder a uma operação não idempotente.
Solução: No modo de zero ida e volta, o cliente cifra os dados com uma chave derivada de um segredo da sessão anterior e os envia antes de qualquer resposta nova do servidor. Um adversário pode gravar esse primeiro voo e reapresentá-lo. As proteções contra repetição exigidas pelo TLS reduzem o risco, mas não garantem processamento único em todas as arquiteturas distribuídas; por isso, a aplicação não pode deduzir da validade criptográfica que recebeu uma operação inédita.
No pior caso, a reapresentação do primeiro voo pode levar o servidor a processar a mesma operação mais de uma vez. Para uma consulta de leitura sem efeitos colaterais, a consequência tende a ser desperdício de recursos. Para uma transferência de valor, uma única repetição já seria uma falha de correção.
A criptografia autentica a mensagem, mas não prova que esta é a primeira apresentação daquela mensagem. A defesa precisa incluir semântica de aplicação: ou repetir a operação preserva o mesmo efeito, ou a requisição carrega uma chave de idempotência que o servidor registra e usa para descartar repetições. Assim, o 0-RTT não exige apenas rapidez do transporte; exige que a ponta assuma explicitamente a garantia que o transporte não pode fornecer sozinho.
4. Confiabilidade: sequência, reconhecimento e retransmissão
Entregar bytes confiavelmente sobre um meio que perde exige três mecanismos: numerar, confirmar e reenviar o que não foi confirmado. O TCP faz os três, e cada um tem uma sutileza que custa caro a quem a ignora.
Numerar. O número de sequência do TCP conta bytes, não segmentos. Um campo de trinta e dois bits que rotula o primeiro byte de cada segmento. A escolha de contar bytes é o que permite ao TCP redividir dados livremente entre segmentos, e é a raiz de uma propriedade que a Seção 10 vai demonstrar em código: TCP é um fluxo de bytes, não uma fila de mensagens.
Confirmar. O reconhecimento é cumulativo: um ACK com valor $n$ informa que o receptor recebeu tudo até o byte $n-1$ e espera o byte $n$. Um único ACK confirma todos os segmentos anteriores, o que torna o mecanismo robusto à perda de reconhecimentos. A contrapartida é que o ACK cumulativo não consegue representar, sozinho, dois intervalos separados por uma lacuna. Para isso existe a extensão de reconhecimento seletivo da RFC 2018, e o exercício 5 quantifica o que ela poupa.
Reenviar. Aqui está a decisão difícil. Quanto tempo esperar antes de concluir que um segmento se perdeu? Esperar pouco produz retransmissões desnecessárias que agravam o congestionamento; esperar muito produz uma conexão que trava. E o valor certo depende do RTT, que varia.
A resposta do TCP é um estimador adaptativo, formalizado na RFC 6298, que mantém duas variáveis: a média suavizada do RTT, $\text{SRTT}$, e a variação suavizada, $\text{RTTVAR}$. Para a primeira medição $R$:
\[\text{SRTT} \leftarrow R, \qquad \text{RTTVAR} \leftarrow \frac{R}{2},\]e para cada medição subsequente $R’$, com $\alpha = 1/8$ e $\beta = 1/4$:
\[\text{RTTVAR} \leftarrow (1-\beta)\,\text{RTTVAR} + \beta\,\bigl|\text{SRTT} - R'\bigr|,\] \[\text{SRTT} \leftarrow (1-\alpha)\,\text{SRTT} + \alpha\,R'.\]Note a ordem: $\text{RTTVAR}$ é atualizada antes de $\text{SRTT}$, usando o valor antigo de $\text{SRTT}$. Inverter as duas linhas produz um estimador diferente e sistematicamente menor, e é um erro fácil de cometer e difícil de perceber.
O temporizador de retransmissão é então
\[\text{RTO} = \text{SRTT} + \max\bigl(G,\ 4\,\text{RTTVAR}\bigr),\]na qual $G$ é a granularidade do relógio. A variável $ ext{RTTVAR}$ é uma média móvel do desvio absoluto entre a amostra e o RTT suavizado, não um desvio padrão estatístico. O multiplicador quatro acrescenta uma margem proporcional à variabilidade observada; quando o caminho oscila mais, o temporizador cresce e evita declarar perda cedo demais.
Vale registrar uma discrepância entre o texto e a prática, porque ela explica um comportamento que confunde. A RFC 6298 determina que o $\text{RTO}$ calculado seja arredondado para cima até um segundo quando resultar menor. Com os números do exercício 1, o valor calculado é de $195$ ms e o valor prescrito pela RFC seria de $1000$ ms. Implementações reais usam pisos bem menores, tipicamente $200$ ms, porque o piso de um segundo torna a recuperação insuportavelmente lenta em enlaces rápidos. A leitora que instrumentar uma pilha e encontrar valores que não batem com a RFC não encontrou um defeito: encontrou uma decisão de engenharia documentada nos comentários do núcleo do sistema operacional.
Quando o temporizador expira, o TCP não apenas retransmite: ele dobra o $\text{RTO}$ a cada tentativa sucessiva. O recuo exponencial é a defesa contra o cenário em que a rede está congestionada e retransmitir agrava o problema. A tabela do exercício 3 mostra o efeito, e ele é brutal: cinco tentativas consecutivas consomem $6{,}2$ segundos.
Esperar o temporizador é caro, e o TCP tem um atalho. Se o receptor recebe um segmento fora de ordem, ele reenvia o último ACK cumulativo, produzindo um ACK duplicado. Três ACKs duplicados são interpretados como perda, e o remetente retransmite imediatamente, sem esperar o temporizador. É a retransmissão rápida, e ela transforma uma espera de centenas de milissegundos em uma espera de aproximadamente uma ida e volta. Por que três e não dois? Porque reordenação de pacotes é comum na Internet e produz um ou dois ACKs duplicados sem que haja perda alguma; três é o compromisso empírico entre reagir cedo e reagir por engano.
4.1 Exercícios da Seção 4
1. Calcule SRTT, RTTVAR e RTO ao longo de quatro medições
Uma conexão mede os RTTs $76$, $82$, $74$ e $120$ ms, nesta ordem. Calcule o estimador da RFC 6298 e o temporizador resultante, com $\alpha = 1/8$ e $\beta = 1/4$.
Solução: A primeira medição inicializa o estimador com $\text{SRTT} = R$ e $\text{RTTVAR} = R/2$. As demais aplicam as duas recorrências, sempre atualizando $\text{RTTVAR}$ antes de $\text{SRTT}$.
| $i$ | $R$ (ms) | $\text{RTTVAR}$ | $\text{SRTT}$ | $\text{RTO} = \text{SRTT} + 4\,\text{RTTVAR}$ |
|---|---|---|---|---|
| 1 | $76$ | $38{,}000$ | $76{,}000$ | $228{,}000$ |
| 2 | $82$ | $30{,}000$ | $76{,}750$ | $196{,}750$ |
| 3 | $74$ | $23{,}188$ | $76{,}406$ | $169{,}156$ |
| 4 | $120$ | $28{,}289$ | $81{,}855$ | $195{,}012$ |
Confira a linha 2 passo a passo: $\text{RTTVAR} = 0{,}75 \times 38 + 0{,}25 \times \vert 76 - 82\vert = 28{,}5 + 1{,}5 = 30{,}0$, e só então $\text{SRTT} = 0{,}875 \times 76 + 0{,}125 \times 82 = 66{,}5 + 10{,}25 = 76{,}75$.
O valor final de $195{,}012$ ms corresponde a $2{,}38$ vezes o $\text{SRTT}$. Repare no efeito da quarta medição: um único RTT de $120$ ms elevou $\text{RTTVAR}$ de $23{,}19$ para $28{,}29$ e o $\text{RTO}$ de $169$ para $195$ ms. O estimador reage à variação com mais força do que à média, que é exatamente o comportamento desejado, e é a mesma lição sobre cauda da Seção 6 do Artigo 1 aplicada a um temporizador.
2. Determine o efeito do piso da RFC sobre o resultado
A RFC 6298 exige que o $\text{RTO}$ seja arredondado para cima até um segundo. Qual seria o temporizador efetivo nas quatro medições do exercício anterior, e por que implementações reais desobedecem?
Solução: Todos os quatro valores calculados ficam abaixo de $1000$ ms, portanto o $\text{RTO}$ prescrito pela RFC seria $1000$ ms nas quatro linhas.
A consequência é que uma perda em uma conexão com RTT de $76$ ms causaria uma espera de um segundo, ou $13{,}0$ vezes o RTT, antes da primeira retransmissão. Em uma requisição cujo orçamento total é de duzentos milissegundos, isso não é uma degradação: é uma falha.
Implementações reais adotam pisos entre $200$ e $300$ ms, o que reduz a espera para cerca de $2{,}6$ vezes o RTT no mesmo exemplo. O piso não pode ser eliminado porque um $\text{RTO}$ menor que o RTT real produz retransmissões espúrias, que consomem banda e enganam o controle de congestionamento da Seção 6, fazendo a conexão reduzir a janela sem que tenha havido congestionamento algum.
3. Tabele o recuo exponencial
A partir de $\text{RTO} = 200$ ms, calcule os instantes das cinco primeiras retransmissões.
Solução: Cada expiração dobra o temporizador antes da tentativa seguinte:
| Tentativa | $\text{RTO}$ | Instante acumulado |
|---|---|---|
| 1 | $200$ ms | $0{,}200$ s |
| 2 | $400$ ms | $0{,}600$ s |
| 3 | $800$ ms | $1{,}400$ s |
| 4 | $1600$ ms | $3{,}000$ s |
| 5 | $3200$ ms | $6{,}200$ s |
O acumulado é $200(2^{k} - 1)$ milissegundos após $k$ tentativas. Depois de cinco tentativas, passaram-se $6{,}2$ segundos, e a aplicação, se tiver um tempo limite de cinco segundos, já terá desistido antes da quinta. Esse desencontro entre o temporizador do transporte e o tempo limite da aplicação é uma fonte comum de conexões que ficam penduradas: o TCP ainda está tentando, a aplicação já foi embora, e o soquete permanece consumindo recursos.
4. Determine o instante da retransmissão rápida
Um remetente envia os segmentos $1$ a $8$. O segmento $4$ se perde. Descreva os ACKs gerados e determine quando ocorre a retransmissão.
Solução: O receptor confirma cumulativamente. Os segmentos $1$, $2$ e $3$ produzem ACKs $2$, $3$ e $4$. Ao chegar o segmento $5$, fora de ordem, o receptor não pode confirmar além do $3$ e reenvia o ACK $4$, que é o primeiro duplicado. Os segmentos $6$, $7$ e $8$ produzem o segundo, o terceiro e o quarto duplicados.
| Segmento recebido | ACK enviado | Duplicados acumulados |
|---|---|---|
| 3 | 4 | 0 |
| 5 | 4 | 1 |
| 6 | 4 | 2 |
| 7 | 4 | 3 |
| 8 | 4 | 4 |
A retransmissão dispara ao chegar o terceiro duplicado, isto é, quando o segmento $7$ é recebido. O tempo decorrido desde o envio do segmento $4$ é de aproximadamente uma ida e volta, contra os $200$ ms ou mais que o temporizador exigiria. Se apenas dois segmentos estivessem em voo depois do perdido, o terceiro duplicado nunca chegaria e a conexão dependeria do temporizador, que é a razão de conexões com janelas pequenas se recuperarem tão mal de perdas.
5. Quantifique o que o reconhecimento seletivo poupa
Uma janela de $20$ segmentos sofre a perda dos segmentos $5$ e $12$. Quantos segmentos são retransmitidos com e sem reconhecimento seletivo?
Solução: Sem a extensão, o ACK cumulativo só consegue informar que se espera o segmento $5$. O remetente não tem como saber que os segmentos $6$ a $11$ e $13$ a $20$ chegaram, e o comportamento conservador é retransmitir do $5$ até o fim da janela:
\[20 - 5 + 1 = 16\ \text{segmentos}.\]Com reconhecimento seletivo, o receptor informa explicitamente os blocos contíguos recebidos, e o remetente retransmite apenas os segmentos $5$ e $12$. A economia é
\[\frac{16 - 2}{16} = 0{,}875 = 87{,}5\%.\]Catorze segmentos de retransmissão poupados por evento de perda. Em uma transferência com $1\%$ de perda e cem mil segmentos, isso corresponde a cerca de catorze mil segmentos a menos no fio. A extensão é negociada no primeiro segmento da conexão, junto com o escalonamento de janela que a próxima seção discute, e a leitora que encontrar uma conexão sem ela está diante de um sistema muito antigo ou de um intermediário que removeu a opção.
5. Controle de fluxo contra controle de congestionamento
Dois mecanismos do TCP limitam quanto o remetente pode ter em voo, e eles são confundidos com uma regularidade que produz diagnósticos errados. A distinção é simples e vale memorizar:
O controle de fluxo protege o receptor. O controle de congestionamento protege a rede.
O controle de fluxo é explícito e negociado. Cada segmento carrega um campo de dezesseis bits, a janela anunciada, que diz quantos bytes o receptor ainda consegue aceitar. Se a aplicação do receptor lê devagar, o buffer enche, a janela anunciada encolhe e o remetente desacelera. É um mecanismo de contrapressão, e ele funciona.
O controle de congestionamento é implícito e inferido. Nenhum roteador avisa que está congestionado; o remetente precisa descobrir sozinho, observando perdas e atrasos. Ele mantém uma variável interna, a janela de congestionamento, $\text{cwnd}$, que não aparece em nenhum campo de nenhum cabeçalho e que a Seção 6 vai construir.
A janela efetiva é o mínimo das duas:
\[W_{\text{eff}} = \min(\text{cwnd},\ \text{rwnd}).\]Diagnosticar uma vazão baixa começa por descobrir qual das duas está mandando, e as consequências são opostas. Se manda a janela do receptor, o problema está na aplicação de destino, que não lê rápido o bastante, ou nos buffers de soquete do sistema operacional. Se manda a janela de congestionamento, o problema está na rede, e mexer no receptor não vai adiantar nada.
Um exemplo com números. Com $\text{rwnd} = 64$ KiB, $\text{cwnd} = 200$ KiB e $\text{RTT} = 50$ ms, a janela efetiva é de $64$ KiB e a vazão é
\[\frac{65\,535 \times 8}{0{,}050} = 10{,}49\ \text{Mbit/s},\]enquanto a rede permitiria
\[\frac{204\,800 \times 8}{0{,}050} = 32{,}77\ \text{Mbit/s}.\]Três vezes mais. Aumentar a capacidade do enlace nesse cenário não muda absolutamente nada, e é o mesmo erro de modelo físico que a Seção 9 do Artigo 1 identificou como a alucinação característica daquele assunto.
Falta o teto do campo de dezesseis bits, que o Artigo 1 já anunciou. A janela anunciada não passa de $65\,535$ bytes, e a Seção 5 daquele artigo mostrou que isso limita uma conexão de $100$ Mbit/s com $100$ ms de ida e volta a $5{,}24$ Mbit/s. A correção é o escalonamento de janela da RFC 7323: um fator $s$, negociado apenas no primeiro segmento da conexão, que multiplica todo valor anunciado por $2^{s}$, com $0 \le s \le 14$. Com $s = 7$, a janela máxima passa a $65\,535 \times 128 = 8{,}39$ MB; com $s = 14$, chega a $1{,}07$ GB.
A restrição ao primeiro segmento merece ênfase. Se o SYN não trouxe a opção, ou se um intermediário a removeu no caminho, a conexão inteira viverá com o teto de $64$ KiB e não há como corrigir depois. Quando uma conexão permanece perto de $5{,}24$ Mbit/s em um enlace de gigabit com $100$ ms de RTT, sem perda visível, essa coincidência numérica justifica verificar primeiro o escalonamento de janela.
5.1 Exercícios da Seção 5
1. Determine a janela efetiva e o gargalo
Com $\text{rwnd} = 64$ KiB e $\text{cwnd} = 200$ KiB, qual a janela efetiva e quem está limitando?
Solução: A janela efetiva é $\min(204\,800,\ 65\,535) = 65\,535$ bytes. O limitante é o receptor, não a rede: o remetente está autorizado pelo seu próprio controle de congestionamento a manter $200$ KiB em voo e não pode, porque o outro lado não aceita.
A ação correta é aumentar o buffer de recepção do soquete no destino, ou corrigir a aplicação que não está drenando o buffer. Aumentar a capacidade do enlace, trocar o algoritmo de congestionamento ou reduzir a distância não produzem efeito nenhum, porque nenhum deles toca a variável que está mandando.
2. Calcule a vazão nas duas situações
Para $\text{RTT} = 50$ ms, calcule a vazão com a janela efetiva do exercício anterior e a que a rede permitiria.
Solução: A vazão de um protocolo de janela é $W/\text{RTT}$:
\[\frac{65\,535 \times 8}{0{,}050} = 10{,}49\ \text{Mbit/s}, \qquad \frac{204\,800 \times 8}{0{,}050} = 32{,}77\ \text{Mbit/s}.\]A razão é $3{,}12$. O sistema entrega menos de um terço do que poderia, e nenhuma métrica de rede acusa problema: não há perda, não há retransmissão, a utilização do enlace está baixa. O sintoma é uma transferência lenta sem nenhum sinal de erro, e o diagnóstico exige comparar a janela em uso com o produto banda-atraso, exatamente como a Seção 5 do Artigo 1 recomendou.
3. Dimensione o fator de escalonamento
Uma conexão precisa de janela de $2{,}5$ MB, o produto banda-atraso de um enlace de $1$ Gbit/s com $20$ ms de ida e volta. Qual o menor fator de escalonamento que serve?
Solução: O fator precisa satisfazer
\[65\,535 \times 2^{s} \ge 2\,500\,000 \quad\Longrightarrow\quad 2^{s} \ge 38{,}15.\]Como $2^{5} = 32 < 38{,}15$ e $2^{6} = 64 \ge 38{,}15$, o menor fator é $s = 6$, que permite anunciar até $65\,535 \times 64 = 4{,}19$ MB. Na prática o sistema operacional escolhe $s$ a partir do tamanho do buffer de recepção configurado, e não do BDP, que ele desconhece no instante do SYN. Isso significa que o tamanho do buffer decidido antes da conexão existir determina o teto da conexão inteira, o que transfere uma decisão de rede para um parâmetro de sistema operacional que quase ninguém revisa.
4. Explique a síndrome da janela boba
Um receptor com aplicação lenta anuncia janelas de poucos bytes conforme drena o buffer. Que patologia isso produz e como o TCP a evita?
Solução: Se o receptor anuncia uma janela de, digamos, dez bytes assim que libera dez bytes de buffer, o remetente enviará um segmento de dez bytes de dados dentro de um quadro de $68$ bytes no fio, contando os $58$ bytes de envelope calculados na Seção 2 do Artigo 1. A eficiência resultante é
\[\eta = \frac{10}{68} = 14{,}7\%.\]A conexão passa a gastar seis sétimos da capacidade com cabeçalhos, e o fenômeno se realimenta. A defesa tem dois lados. Do lado do receptor, a regra de Clark: não anunciar aumento de janela até que ele valha ao menos um MSS ou metade do buffer. Do lado do remetente, o algoritmo de Nagle: não enviar um segmento pequeno enquanto houver dados pequenos não confirmados em voo. A Seção 10 mostra o algoritmo de Nagle agindo, e mostra também o que se perde ao desligá-lo.
5. Diagnostique a limitação a partir de uma captura
Uma captura mostra $\text{rwnd}$ anunciada de $4$ MB, bytes em voo estáveis em $180$ KB, RTT de $30$ ms e nenhuma retransmissão. Quem limita?
Solução: A vazão observada é
\[\frac{180 \times 1024 \times 8}{0{,}030} = 49{,}15\ \text{Mbit/s}.\]A janela do receptor permite $4$ MB, muito acima dos $180$ KB em voo, portanto o receptor não é o gargalo. Como não há retransmissão, também não há perda recente que justifique uma janela de congestionamento reduzida por congestionamento. Restam duas hipóteses, e elas são distinguíveis: ou a aplicação remetente não está fornecendo dados rápido o bastante, caso em que o soquete de envio fica vazio e a limitação é da aplicação; ou o buffer de envio do sistema operacional está limitado a $180$ KB, caso em que o teto é de configuração.
O teste que separa as duas é direto: verificar a ocupação do buffer de envio. Se ele está cheio, o remetente quer enviar mais e não pode, e o limite é de configuração. Se está vazio, a aplicação é o gargalo e nenhum ajuste de rede vai ajudar. Registre o método, porque a Seção 7 do Artigo 8 vai transformá-lo em disciplina: cada hipótese precisa vir com o teste que a falsifica.
6. A máquina de congestionamento
Chegamos ao mecanismo que dá ao TCP sua assinatura mais reconhecível, e a mais mal compreendida.
O problema é o seguinte. Um remetente que comece a transmitir na taxa máxima do seu enlace vai, quase certamente, sobrecarregar algum ponto intermediário mais lento, encher a fila daquele ponto e provocar descarte. Pior: se todos os remetentes fizerem isso, a rede entra em colapso por congestionamento, um estado em que a maior parte da capacidade é gasta transmitindo retransmissões de pacotes que serão descartados. Isso aconteceu de verdade na Internet em outubro de 1986, quando a vazão entre o Laboratório Lawrence Berkeley e a Universidade da Califórnia em Berkeley, separados por quatrocentas jardas e dois saltos, caiu de $32$ kbit/s para $40$ bit/s. Um fator de $800$. A resposta de Van Jacobson a esse episódio é o algoritmo que ainda roda hoje.
A ideia central é que o remetente precisa descobrir a capacidade disponível, sem ninguém lhe dizer qual é, e precisa reagir quando ela mudar. A variável que carrega essa estimativa é a janela de congestionamento, $\text{cwnd}$, medida em MSS.
Partida lenta. O nome é irônico e enganoso: o crescimento é exponencial. A janela começa pequena, tipicamente $10$ MSS pela RFC 6928, e dobra a cada ida e volta, porque cada segmento confirmado autoriza o envio de dois. Depois de $k$ idas e voltas,
\[\text{cwnd}(k) = \text{cwnd}_0 \cdot 2^{k}.\]Começando em $10$ MSS, temos $20$, $40$, $80$, $160$ e $320$ MSS após cinco idas e voltas. É lento apenas em comparação com começar na taxa máxima, e é a fase em que a maioria absoluta das conexões da web vive e morre, porque uma requisição típica termina antes de a partida lenta acabar.
Quanto tempo leva para a partida lenta encher o cano? Para atingir o produto banda-atraso $\text{BDP}$, medido em MSS, são necessárias
\[k = \left\lceil \log_2 \frac{\text{BDP}}{\text{cwnd}_0} \right\rceil\]idas e voltas. Para o enlace de $1$ Gbit/s com $20$ ms do exercício 3 da seção anterior, $\text{BDP} = 2{,}5$ MB $= 1712$ MSS, e
\[k = \left\lceil \log_2 \frac{1712}{10} \right\rceil = \lceil 7{,}42 \rceil = 8,\]ou $160$ ms apenas para chegar à velocidade de cruzeiro. Uma transferência de $500$ KB termina antes disso, o que significa que ela nunca vê a capacidade do enlace. Esse é o argumento quantitativo para janelas iniciais maiores e para a reutilização de conexões.
Prevenção de congestionamento. Quando $\text{cwnd}$ atinge um limiar, $\text{ssthresh}$, o crescimento muda de exponencial para linear: aproximadamente um MSS por ida e volta. O TCP passa a sondar a capacidade com cautela, em vez de dobrar.
Reação à perda. Ao detectar perda por três ACKs duplicados, o algoritmo reduz a janela pela metade e continua em crescimento linear. É o AIMD, de Additive Increase, Multiplicative Decrease (aumento aditivo, redução multiplicativa). A escolha de somar na subida e dividir na descida não é estética: Chiu e Jain provaram em 1989 que, entre as combinações lineares possíveis, apenas o aumento aditivo com redução multiplicativa converge para uma repartição justa da capacidade entre fluxos concorrentes. Somar na descida não converge; multiplicar na subida diverge.
Se a perda for detectada pelo temporizador, e não por ACKs duplicados, a reação é muito mais severa: $\text{cwnd}$ volta ao valor inicial e a conexão reinicia a partida lenta. A diferença é intencional. Um temporizador expirado significa que nem sequer há ACKs chegando, o que sugere um problema grave; três ACKs duplicados significam que os dados continuam fluindo e apenas um segmento se perdeu.
O resultado gráfico é o dente de serra da Figura 2.
Figura 2: partida lenta exponencial até o limiar, crescimento linear depois, e queda a cada perda. A área sob a curva é o que foi transferido: com as mesmas três perdas, a janela média do CUBIC é 1,40 vez a do Reno. O dente de serra é o preço de descobrir a capacidade sem que ninguém a informe.
Quanto isso entrega. Mathis, Semke, Mahdavi e Ott derivaram em 1997 uma expressão para a vazão média de um fluxo em AIMD sujeito a uma taxa de perda $p$:
\[\text{BW} \le \frac{\text{MSS}}{\text{RTT}} \cdot \frac{C}{\sqrt{p}}, \qquad C = \sqrt{\tfrac{3}{2}} \approx 1{,}2247.\]A constante $C$ costuma ser omitida em citações informais, o que subestima o resultado em $18{,}4\%$. A Tabela 3 avalia a expressão para $\text{MSS} = 1460$ bytes e $\text{RTT} = 100$ ms.
| $p$ | Vazão prevista |
|---|---|
| $10^{-2}$ | $1{,}43$ Mbit/s |
| $10^{-3}$ | $4{,}52$ Mbit/s |
| $10^{-4}$ | $14{,}31$ Mbit/s |
Tabela 3: teto de Mathis para MSS de 1460 bytes e RTT de 100 ms. A vazão cai com a raiz da taxa de perda e com o inverso da ida e volta.
Duas leituras dessa fórmula mudam decisões de arquitetura, e as duas são desconfortáveis.
A primeira: a vazão é inversamente proporcional ao RTT. Dois fluxos com a mesma taxa de perda, um com $10$ ms e outro com $100$ ms de ida e volta, obtêm vazões que diferem por um fator de dez. Isso significa que, em um gargalo compartilhado, fluxos próximos roubam capacidade de fluxos distantes por construção do algoritmo, e não por má-fé de ninguém.
A segunda: para sustentar $1$ Gbit/s com $\text{RTT} = 100$ ms, a fórmula exige
\[p \le \left(\frac{\text{MSS} \cdot 8 \cdot C}{\text{RTT} \cdot 10^{9}}\right)^{2} = 2{,}05 \times 10^{-8},\]ou uma perda a cada $48{,}9$ milhões de pacotes, o equivalente a $71$ GB transferidos entre perdas consecutivas. Nenhuma rede real oferece isso. É por essa razão, e não por moda, que Reno foi substituído, e é o assunto da próxima seção.
6.1 Exercícios da Seção 6
1. Tabele a partida lenta
Com $\text{cwnd}_0 = 10$ MSS, calcule a janela após cada uma das seis primeiras idas e voltas e o total transferido no período.
Solução: A janela dobra a cada ida e volta:
| Ida e volta | $\text{cwnd}$ (MSS) |
|---|---|
| 0 | 10 |
| 1 | 20 |
| 2 | 40 |
| 3 | 80 |
| 4 | 160 |
| 5 | 320 |
O total enviado nas seis idas e voltas é a soma geométrica
\[\sum_{k=0}^{5} 10 \cdot 2^{k} = 10\,(2^{6} - 1) = 630\ \text{MSS} = 919{,}8\ \text{KB}.\]Menos de um megabyte em seis idas e voltas. Com o RTT de $76{,}65$ ms da nossa rota de referência, seriam $459{,}9$ ms para transferir novecentos e vinte kilobytes, o que corresponde a uma vazão média de $16{,}0$ Mbit/s em um enlace que pode ser de gigabit. A partida lenta domina o desempenho de qualquer transferência curta em rota longa.
2. Calcule o tempo para encher o cano
Quantas idas e voltas a partida lenta leva para atingir um BDP de $2{,}5$ MB, com $\text{MSS} = 1460$ bytes e $\text{cwnd}_0 = 10$ MSS? Quanto tempo isso representa com $\text{RTT} = 20$ ms?
Solução: O BDP em MSS é $2\,500\,000/1460 = 1712{,}3$. O número de duplicações necessárias é
\[k = \left\lceil \log_2 \frac{1712{,}3}{10} \right\rceil = \lceil 7{,}42 \rceil = 8,\]e o tempo é $8 \times 20 = 160$ ms.
Uma transferência de $1$ MB nesse enlace levaria, no regime permanente, $8$ ms. A partida lenta gasta vinte vezes isso apenas para chegar lá. A conclusão de engenharia é que manter conexões abertas vale mais do que otimizar o corpo da resposta, e é a justificativa quantitativa para conexões persistentes, para pools de conexão e para a multiplexação que o Artigo 14 vai discutir.
3. Trace a trajetória do AIMD
Uma conexão em prevenção de congestionamento com $\text{cwnd} = 100$ MSS sofre uma perda detectada por ACKs duplicados. Trace a janela pelas dez idas e voltas seguintes.
Solução: A perda por ACKs duplicados provoca redução multiplicativa por dois, seguida de aumento aditivo de um MSS por ida e volta:
\[100 \to 50 \to 51 \to 52 \to 53 \to 54 \to 55 \to 56 \to 57 \to 58 \to 59.\]Para retornar a $100$ MSS são necessárias $50$ idas e voltas, ou $3{,}83$ s com o RTT de $76{,}65$ ms. Durante essa recuperação, a janela média é de $75$ MSS, ou $75\%$ do valor anterior à perda. Uma perda a cada $50$ idas e voltas, portanto, custa aproximadamente um quarto da vazão em regime permanente. Compare com o caso da perda detectada por temporizador, em que a janela voltaria a $10$ MSS e a recuperação exigiria partida lenta até $50$ e depois $50$ idas e voltas lineares.
4. Aplique a equação de Mathis
Calcule a vazão prevista para $\text{MSS} = 1460$ bytes, $\text{RTT} = 100$ ms e taxas de perda de $10^{-2}$, $10^{-3}$ e $10^{-4}$, com e sem a constante $C$.
Solução: Aplicando $\text{BW} = (\text{MSS} \cdot 8/\text{RTT}) \cdot C/\sqrt{p}$ com $C = \sqrt{3/2} = 1{,}2247$, e notando que $\text{MSS} \cdot 8/\text{RTT} = 116\,800$ bit/s:
| $p$ | Com $C$ | Sem $C$ | Diferença |
|---|---|---|---|
| $10^{-2}$ | $1{,}43$ Mbit/s | $1{,}17$ Mbit/s | $+22{,}5\%$ |
| $10^{-3}$ | $4{,}52$ Mbit/s | $3{,}69$ Mbit/s | $+22{,}5\%$ |
| $10^{-4}$ | $14{,}31$ Mbit/s | $11{,}68$ Mbit/s | $+22{,}5\%$ |
A diferença é constante em $22{,}5\%$, porque $C$ é um fator multiplicativo. Omitir $C$ produz um resultado sistematicamente conservador, o que é aceitável para uma estimativa de ordem de grandeza e não é aceitável para comparar duas medições. A leitora que encontrar as duas formas na literatura agora sabe qual delas está lendo.
5. Determine a taxa de perda exigida para 1 Gbit/s
Que taxa de perda a equação de Mathis exige para sustentar $1$ Gbit/s com $\text{RTT} = 100$ ms? Comente a plausibilidade.
Solução: Isolando $p$ na equação,
\[p = \left(\frac{\text{MSS} \cdot 8 \cdot C}{\text{RTT} \cdot \text{BW}}\right)^{2} = \left(\frac{1460 \times 8 \times 1{,}2247}{0{,}1 \times 10^{9}}\right)^{2} = 2{,}05 \times 10^{-8}.\]Isso corresponde a uma perda a cada $1/p = 4{,}89 \times 10^{7}$ pacotes, ou $71$ GB transferidos entre duas perdas consecutivas. Enlaces ópticos de longa distância têm taxas de erro de bit da ordem de $10^{-12}$, o que sozinho já produziria perda de pacote na casa de $10^{-8}$, e isso antes de qualquer descarte por fila.
A conclusão é que o Reno, com esse comportamento, não consegue usar enlaces rápidos e longos, e a limitação é do algoritmo, não do meio. É o problema que CUBIC e BBR foram construídos para resolver, e que a próxima seção examina.
7. Reno, CUBIC e BBR
Três algoritmos, três respostas para a mesma pergunta: o que conta como sinal de congestionamento?
Reno responde: a perda. É a resposta original de Jacobson, e ela supõe que perda de pacote significa fila cheia, o que era verdade em 1988, quando buffers eram caros e enlaces sem fio não existiam na Internet. A suposição envelheceu mal por dois motivos. Primeiro, enlaces sem fio perdem pacotes por interferência, sem que haja congestionamento algum, e Reno reduz a janela pela metade em resposta a um problema que não existe. Segundo, o crescimento linear de um MSS por ida e volta é lento demais para as janelas exigidas por enlaces modernos, como o exercício 5 da seção anterior mostrou.
CUBIC responde: a perda, mas o crescimento é função do tempo, não das idas e voltas. É o algoritmo padrão do Linux desde 2006 e está padronizado na RFC 9438, que substituiu a RFC 8312 em 2023. A janela cresce segundo
\[W(t) = C\,(t - K)^{3} + W_{\max}, \qquad K = \sqrt[3]{\frac{W_{\max}\,(1 - \beta)}{C}},\]na qual $W_{\max}$ é a janela imediatamente anterior à última perda, $\beta = 0{,}7$ é o fator de redução multiplicativa, $C = 0{,}4$ é uma constante de agressividade e $t$ é o tempo decorrido desde a redução, em segundos.
Três propriedades saem dessa forma, e cada uma corrige um defeito do Reno.
A redução é para $0{,}7\,W_{\max}$ em vez de $0{,}5\,W_{\max}$, o que preserva mais capacidade a cada perda.
A recuperação é rápida perto da queda, porque a cúbica é íngreme longe de $K$, e depois desacelera ao aproximar-se de $W_{\max}$, sondando com cautela o valor que sabidamente causou perda. Ultrapassado $W_{\max}$, ela volta a acelerar para procurar a nova capacidade. O ponto de inflexão em $t = K$ é exatamente o valor anterior.
E, o mais importante, o crescimento não depende do RTT. Como $t$ é medido em segundos, dois fluxos com ida e volta muito diferentes crescem à mesma taxa em tempo absoluto, o que corrige a injustiça estrutural que a equação de Mathis expõe.
Os números tornam a diferença concreta. A Tabela 4 compara os dois algoritmos com $W_{\max} = 2000$ MSS e $\text{RTT} = 100$ ms.
| Algoritmo | Janela após a perda | Tempo para retomar $W_{\max}$ |
|---|---|---|
| Reno | $1000$ MSS | $100{,}0$ s |
| CUBIC | $1400$ MSS | $11{,}45$ s |
Tabela 4: recuperação após uma perda em janela grande. A vantagem do CUBIC cresce com $W{\max}$: é de 1,19 vez para 100 MSS, 8,74 vezes para 2000 MSS e 25,5 vezes para 10 000 MSS._
BBR responde: nenhum dos dois; o sinal é a banda e o atraso mínimo. Publicado pelo Google em 2016, o BBR abandona a perda como sinal e estima diretamente duas grandezas: a largura de banda do gargalo, $\text{BtlBw}$, medida como a taxa de entrega máxima observada, e o atraso de propagação de ida e volta, $\text{RTprop}$, medido como o RTT mínimo observado em uma janela de tempo. A janela alvo é
\[\text{BDP} = \text{BtlBw} \times \text{RTprop},\]e o algoritmo transmite a uma taxa que mantém aproximadamente esse valor em voo, sondando periodicamente com ganho de $1{,}25$ para descobrir se a capacidade aumentou.
A consequência mais visível é o comportamento diante de buffers grandes. CUBIC preenche o buffer até provocar perda, e portanto opera com a fila cheia, o que é exatamente o bufferbloat que a Seção 7 do Artigo 1 quantificou. BBR opera perto do joelho da curva, com fila próxima de zero, e obtém a mesma vazão com uma fração da latência.
Não há almoço grátis, e é preciso dizê-lo. Um fluxo BBR e um fluxo CUBIC no mesmo gargalo podem repartir a capacidade de modo desigual, e o resultado depende do tamanho do buffer, dos RTTs, da implementação e da carga concorrente. As revisões posteriores do BBR passaram a incorporar perdas e limites de voo ao modelo justamente porque estimar banda e atraso não elimina problemas de equidade. A leitora que trocar de algoritmo em produção precisa medir no próprio caminho, em vez de transportar para sua rede uma tabela obtida sob outro gargalo.
7.1 Exercícios da Seção 7
1. Compare a recuperação de Reno e CUBIC
Com $W_{\max} = 2000$ MSS e $\text{RTT} = 100$ ms, calcule o tempo que cada algoritmo leva para retomar a janela anterior após uma perda.
Solução: Reno reduz para $0{,}5 \times 2000 = 1000$ MSS e sobe um MSS por ida e volta. São $1000$ idas e voltas, ou
\[1000 \times 0{,}100 = 100{,}0\ \text{s}.\]CUBIC reduz para $0{,}7 \times 2000 = 1400$ MSS e retoma $W_{\max}$ em $t = K$:
\[K = \sqrt[3]{\frac{2000 \times 0{,}3}{0{,}4}} = \sqrt[3]{1500} = 11{,}45\ \text{s}.\]A razão é $100{,}0/11{,}45 = 8{,}74$. Cem segundos é tempo suficiente para a transferência inteira terminar e para o usuário desistir, o que torna o desempenho do Reno em enlaces rápidos e longos uma questão prática e não teórica.
2. Calcule a trajetória da função cúbica
Para $W_{\max} = 2000$ MSS, $C = 0{,}4$ e $\beta = 0{,}7$, calcule $W(t)$ em $t = 1$, $2$, $4$ e $8$ segundos.
Solução: Com $K = 11{,}447$ s e $W(t) = 0{,}4\,(t - K)^{3} + 2000$:
| $t$ (s) | $(t-K)^{3}$ | $W(t)$ (MSS) | Fração de $W_{\max}$ |
|---|---|---|---|
| $1$ | $-1140{,}2$ | $1543{,}91$ | $77{,}2\%$ |
| $2$ | $-843{,}2$ | $1662{,}74$ | $83{,}1\%$ |
| $4$ | $-413{,}0$ | $1834{,}79$ | $91{,}7\%$ |
| $8$ | $-40{,}9$ | $1983{,}62$ | $99{,}2\%$ |
Repare no formato: em um segundo o CUBIC já recuperou $77\%$ da janela, e gasta os sete segundos seguintes para percorrer os $22$ pontos percentuais restantes. É a assinatura da cúbica: agressiva onde a capacidade é sabidamente segura, cautelosa perto do valor que causou a perda. Reno, no mesmo primeiro segundo, teria subido de $1000$ para $1010$ MSS, ou $50{,}5\%$ de $W_{\max}$.
3. Calcule a janela alvo do BBR
Com $\text{BtlBw} = 100$ Mbit/s e $\text{RTprop} = 40$ ms, determine o produto banda-atraso e a janela que o BBR mantém em voo.
Solução: O BDP é
\[\text{BDP} = \frac{100 \times 10^{6} \times 0{,}040}{8} = 500\ \text{kB}.\]O BBR limita a janela a duas vezes o BDP, ou $1000$ kB, como salvaguarda contra estimativas ruins, mas opera com taxa de envio igual a $\text{BtlBw}$, de modo que a ocupação em regime permanece próxima de um BDP. A diferença em relação ao CUBIC é observável na latência e não na vazão: as duas entregam $100$ Mbit/s, e a fila do gargalo fica próxima de zero em um caso e cheia no outro.
4. Quantifique o custo da sondagem do BBR
O BBR eleva a taxa de envio por um fator de $1{,}25$ durante uma ida e volta a cada oito, para verificar se a banda aumentou. Qual o excesso médio de tráfego imposto?
Solução: Em oito idas e voltas, sete transmitem com ganho $1{,}0$ e uma com ganho $1{,}25$. Na implementação, o ciclo compensa a sondagem com uma ida e volta de ganho $0{,}75$, de modo que a média fica em $1{,}0$. Sem essa compensação, o excesso seria
\[\frac{1{,}25 - 1{,}0}{8} = 3{,}125\%.\]O número é pequeno e a compensação existe justamente porque, sem ela, o excesso permanente encheria a fila que o algoritmo tenta manter vazia. É um exemplo de decisão de projeto em que três por cento decidem entre o comportamento pretendido e o oposto dele.
5. Discuta a equidade entre BBR e CUBIC
Um fluxo BBR e um fluxo CUBIC compartilham um gargalo. Como a repartição depende do tamanho do buffer?
Solução: CUBIC aumenta a janela até provocar perda, e a perda só ocorre quando o buffer enche. BBR mantém em voo aproximadamente um BDP e não responde à perda como sinal primário.
Com buffer raso, da ordem de uma fração do BDP, o CUBIC provoca perda cedo e recua com frequência, enquanto o BBR mantém sua estimativa; o BBR tende a ficar com a maior parte da capacidade.
Com buffer profundo, de vários BDPs, o CUBIC cresce por muito tempo sem perda e ocupa uma parcela grande do buffer; o RTT observado pelo BBR sobe, sua estimativa de $\text{RTprop}$ pode ficar inflada e a repartição favorece o CUBIC.
A moral operacional é que não existe resposta independente do gargalo, e o gargalo em geral está fora do controle de quem escolhe o algoritmo. Trocar o algoritmo de congestionamento em produção é uma mudança cujo efeito só pode ser conhecido por medição de antes e depois, com atenção ao percentil e não à média, pelas razões da Seção 6 do Artigo 1.
8. QUIC: transporte no espaço de usuário
O QUIC não foi construído porque alguém achava o TCP feio. Ele foi construído porque o TCP havia se tornado, na prática, impossível de mudar.
O motivo é a ossificação. O cabeçalho do TCP é visível a todos os equipamentos do caminho, e vinte anos de intermediários, tradutores de endereço, firewalls e otimizadores passaram a inspecioná-lo, reescrevê-lo e rejeitar o que não reconhecem. Uma opção nova de TCP tem chance considerável de ser removida ou de fazer o pacote ser descartado por algum equipamento entre a origem e o destino. O Multipath TCP, padronizado na RFC 8684, levou uma década para ter implantação relevante em boa parte por isso.
A saída foi construir o transporte sobre UDP, em espaço de usuário, e cifrar quase todo o cabeçalho. Um intermediário vê datagramas UDP com carga opaca e não tem o que reescrever. A escolha do UDP não foi por suas qualidades: foi porque ele é o único espaço em branco que a Internet ainda oferece. E a escolha do espaço de usuário permite que uma correção de protocolo seja implantada com uma atualização de aplicação, e não com uma atualização de núcleo do sistema operacional em bilhões de dispositivos.
Feita a escolha, quatro coisas se tornaram possíveis, e é por elas que o protocolo vale a pena.
Streams independentes. Este é o ganho principal e merece cuidado, porque é frequentemente mal explicado. O HTTP/2 já multiplexava várias requisições em uma conexão TCP. O problema é que o TCP entrega bytes em ordem para a conexão inteira: se um segmento se perde, todos os streams multiplexados param, mesmo os que não tinham nada naquele segmento. É o bloqueio de cabeça de linha, e ele significa que a multiplexação do HTTP/2 resolve o problema no nível da aplicação e o reintroduz no nível do transporte.
O QUIC entrega em ordem por stream. A perda de um pacote atrasa apenas os streams que tinham dados nele. A Figura 3 mostra a diferença com quatro objetos concorrentes.
Figura 3: um único segmento perdido bloqueia quatro objetos em HTTP/2 sobre TCP e um objeto em HTTP/3 sobre QUIC. O atraso de recuperação é o mesmo; o que muda é quantos pagam por ele.
Criptografia integrada. O TLS deixa de ser uma camada sobre o transporte e passa a ser parte dele, o que produz o handshake de uma ida e volta da Seção 3 e impede que intermediários leiam ou alterem os metadados do transporte.
Migração de conexão. Uma conexão TCP é identificada pela quádrupla da Seção 1. Quando um telefone troca de rede sem fio para rede celular, o endereço de origem muda, a quádrupla muda e a conexão morre, obrigando a aplicação a refazer tudo, incluindo os $153$ ms de handshake. Uma conexão QUIC é identificada por um identificador de conexão transportado dentro do pacote cifrado, independente do endereço. A conexão sobrevive à troca de rede. Para quem escreve aplicações móveis, este costuma ser o argumento decisivo.
Evolução. Como o transporte vive no processo da aplicação, mudanças de protocolo se implantam na cadência de atualização da aplicação.
Falta a conta que o entusiasmo costuma pular. O QUIC não é mais eficiente em bytes. Comparando os envelopes por pacote:
| Pilha | Composição | Total |
|---|---|---|
| TCP + TLS 1.3 | IP $20$ + TCP $20$ + registro TLS $5$ + etiqueta $16$ | $61$ B |
| QUIC | IP $20$ + UDP $8$ + tipo $1$ + identificador $8$ + número $4$ + etiqueta $16$ | $57$ B |
Para um payload de $1200$ bytes, o custo relativo é de $4{,}84\%$ contra $4{,}53\%$. A diferença é de três décimos de ponto percentual, e é irrelevante. O ganho do QUIC não está nos bytes; está nas idas e voltas e no bloqueio de cabeça de linha. Quem justificar a migração pela economia de cabeçalho justificou pelo motivo errado.
E há um custo real, que precisa entrar na decisão. O processamento de QUIC acontece em espaço de usuário, sem o descarregamento em hardware que as placas de rede oferecem ao TCP há duas décadas: segmentação, soma de verificação e, cada vez mais, criptografia. Medições publicadas indicam consumo de CPU por byte substancialmente maior no QUIC, com a diferença encolhendo à medida que o descarregamento de UDP amadurece. Isso leva de volta ao critério da Seção 3: dentro do mesmo centro de dados, com $\text{RTT} = 0{,}5$ ms, economizar uma ida e volta economiza $1{,}25\%$ de uma requisição de $40$ ms, e o custo de CPU domina a conta. Para usuários a $76{,}65$ ms, a economia é de duas idas e voltas em conexões retomadas, e a conta se inverte.
8.1 Exercícios da Seção 8
1. Conte os objetos bloqueados por uma perda
Quatro objetos são multiplexados em uma conexão e um segmento se perde. Quantos objetos ficam bloqueados em HTTP/2 sobre TCP e em HTTP/3 sobre QUIC?
Solução: O TCP entrega bytes em ordem para a conexão inteira. O segmento perdido cria um buraco no fluxo, e nenhum byte posterior é entregue à aplicação até que a retransmissão chegue, uma ida e volta depois. Todos os quatro objetos ficam bloqueados, inclusive os três que não tinham um único byte no segmento perdido.
O QUIC mantém ordenação por stream. Apenas o stream que tinha dados no pacote perdido espera; os outros três continuam sendo entregues. Um objeto bloqueado.
O tempo de recuperação é idêntico nos dois casos, uma ida e volta, ou $76{,}65$ ms na nossa rota de referência. O que muda é quantos objetos pagam por ele, e o efeito sobre o tempo total da página segue a composição de caudas do exercício 4 da Seção 6 do Artigo 1: quanto maior o fan-out, maior a probabilidade de que ao menos um objeto seja atingido, e no TCP essa probabilidade se aplica a todos de uma vez.
2. Compare o custo de cabeçalho das duas pilhas
Calcule a sobrecarga relativa de TCP com TLS 1.3 e de QUIC para um payload de $1200$ bytes.
Solução: Com envelopes de $61$ e $57$ bytes:
\[\frac{61}{1200 + 61} = 4{,}84\%, \qquad \frac{57}{1200 + 57} = 4{,}53\%.\]A diferença é de $0{,}31$ ponto percentual a favor do QUIC. Em uma transferência de $10$ GB, isso corresponde a cerca de $31$ MB, ou $0{,}3\%$ do volume, o que não sustenta decisão nenhuma. O exercício existe para descartar um argumento que aparece com frequência e que não resiste a uma divisão.
3. Explique por que a troca de rede derruba TCP e não derruba QUIC
Um telefone passa de rede sem fio para rede celular. Descreva o que acontece com cada transporte.
Solução: A conexão TCP é identificada pela quádrupla endereço de origem, porta de origem, endereço de destino e porta de destino. A troca de rede muda o endereço de origem, logo a quádrupla muda e os pacotes seguintes não pertencem a nenhuma conexão conhecida no servidor. A conexão é encerrada e a aplicação precisa refazer o aperto de mão, pagando os $153{,}30$ ms da Seção 3 mais o custo de retomar o estado de aplicação.
A conexão QUIC é identificada por um identificador transportado dentro do pacote, escolhido pelo servidor e independente do endereço. Ao receber pacotes do novo endereço com o mesmo identificador e criptografia válida, o servidor valida o novo caminho e continua a conexão. Nenhum handshake é refeito, nenhum estado de aplicação é perdido, e a transferência em curso prossegue.
Registre a validação de caminho como parte necessária do mecanismo: sem ela, um atacante poderia redirecionar o fluxo de uma conexão alheia para um endereço de sua escolha apenas afirmando ter mudado de rede.
4. Determine quando o QUIC não compensa
Um serviço interno em um centro de dados tem $\text{RTT} = 0{,}5$ ms e requisições de $40$ ms. Justifique numericamente por que migrar para QUIC não traz ganho.
Solução: A economia de uma ida e volta no estabelecimento é de $0{,}5$ ms sobre uma requisição de $40$ ms, ou
\[\frac{0{,}5}{40} = 1{,}25\%.\]E mesmo esse valor só se aplica a conexões novas: um serviço interno bem construído mantém conexões persistentes, de modo que o handshake é amortizado por milhares de requisições e a economia real tende a zero.
Contra isso pesa o custo de CPU do processamento em espaço de usuário, que incide sobre todos os pacotes, e não apenas sobre o estabelecimento. O balanço é negativo. Os outros três benefícios também não se aplicam: a perda dentro de um centro de dados é baixa, o que reduz o valor dos streams independentes; nenhum serviço troca de endereço no meio de uma conexão, o que anula a migração; e o caminho não tem intermediários ossificados a contornar.
5. Calcule o ponto em que a migração se justifica
Para um serviço com conexões de vida curta, a partir de que RTT a economia de estabelecimento supera $5\%$ do tempo total de uma requisição de $40$ ms de processamento?
Solução: A economia é de uma ida e volta na primeira visita. Exigindo que ela exceda $5\%$ do tempo total, e observando que o tempo total inclui o próprio handshake:
\[\frac{\text{RTT}}{40 + 2\,\text{RTT}} > 0{,}05 \quad\Longrightarrow\quad \text{RTT} > 2 + 0{,}1\,\text{RTT} \quad\Longrightarrow\quad \text{RTT} > 2{,}22\ \text{ms}.\]O limiar é baixo, e a interpretação exige cuidado: ele diz que a economia é mensurável acima de $2{,}22$ ms, não que ela compense. Para decidir, é preciso somar o custo de CPU e o valor dos outros três benefícios. A conta de $5\%$ serve para descartar rapidamente os casos em que nem vale medir, que são exatamente os fluxos internos do exercício anterior.
9. Escolher o transporte é decidir a arquitetura
As seções anteriores dão os critérios; falta usá-los. A Tabela 5 resume a decisão com os quatro números que importam.
| Critério | UDP puro | TCP | QUIC |
|---|---|---|---|
| idas e voltas até o primeiro byte | $0$ | $2$ com TLS 1.3 | $1$, ou $0$ com retomada |
| estado por cliente | nenhum | bloco de controle no núcleo | estado no processo |
| perda de um pacote | ignorada, ou tratada pela aplicação | bloqueia a conexão | bloqueia um stream |
| troca de endereço do cliente | irrelevante | encerra a conexão | conexão sobrevive |
Tabela 5: os quatro critérios que decidem o transporte. Nenhuma linha vence sozinha; a decisão vem de qual delas dói mais na aplicação em questão.
Quatro casos concretos, cada um decidido por uma linha diferente da tabela.
Uma API interna entre microsserviços, com RTT de $0{,}5$ ms e conexões persistentes, escolhe TCP. A primeira linha não pesa, porque o handshake é amortizado; a terceira não pesa, porque a perda é rara; a quarta não pesa, porque endereços não mudam no meio de uma chamada. Resta a segunda, e o estado no núcleo, com descarregamento em hardware, é mais barato que o estado no processo. O Artigo 14 refina esta decisão ao comparar REST e gRPC.
Uma API pública consumida por navegadores em outro continente escolhe QUIC, e por duas linhas simultâneas: a primeira, porque a economia de uma a duas idas e voltas a $76{,}65$ ms é grande; e a terceira, porque uma página com dezenas de objetos multiplexados sofre desproporcionalmente com o bloqueio de cabeça de linha.
Uma chamada de voz escolhe UDP, decidida pela terceira linha, mas por uma razão oposta: para ela, um pacote atrasado é pior que um pacote perdido, e o mecanismo de recuperação do TCP é ativamente prejudicial. A aplicação implementa o que precisa, e precisa implementar também controle de congestionamento, sob pena de ser má cidadã.
Um aplicativo móvel com sessões longas escolhe QUIC pela quarta linha, isolada das outras três. A migração de conexão elimina uma classe de defeitos que, em TCP, aparece quando a troca de rede invalida a quádrupla e interrompe a sessão.
Uma última observação, que a atenta leitora provavelmente já formulou. A escolha do transporte quase nunca é feita diretamente: ela vem embutida na escolha do protocolo de aplicação. Escolher gRPC é escolher HTTP/2 sobre TCP; escolher HTTP/3 é escolher QUIC; escolher WebRTC é escolher UDP. O Artigo 14 vai tratar dessa camada, e vale chegar lá sabendo que a decisão de lá herda todas as consequências de cá.
10. Duas demonstrações em C++23
Duas coisas desta série de seções merecem ser vistas rodando, e não apenas lidas.
10.1 TCP é um fluxo de bytes
A afirmação da Seção 4, de que o número de sequência conta bytes e não segmentos, tem uma consequência que a leitora precisa sentir na pele: não existe correspondência entre chamadas a send e chamadas a recv. O programa abaixo envia mil mensagens de doze bytes com mil chamadas separadas, sobre uma conexão TCP em loopback, e conta quantas chamadas a recv o receptor precisou fazer e de que tamanhos.
O par de funções enviar_tudo e receber_tudo é, sozinho, o artefato mais útil deste artigo. Ignorar que send pode aceitar menos bytes do que se pediu, e que recv devolve o que houver e não o que se pediu, é o defeito mais comum de código de aplicação sobre TCP, e ele só se manifesta sob carga ou em rede real, nunca no ambiente de desenvolvimento.
// fronteiras.cpp -- TCP e um fluxo de bytes, nao uma fila de mensagens.
// MSVC 19.44: cl /std:c++latest /W4 /WX /EHsc /O2 fronteiras.cpp
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <expected>
#include <map>
#include <print>
#include <span>
#include <string>
#include <string_view>
#include <thread>
#include <tuple>
#include <vector>
namespace rede {
enum class Erro { conexao_encerrada, falha_de_socket };
constexpr std::string_view descrever(Erro e) {
switch (e) {
case Erro::conexao_encerrada: return "conexao encerrada pelo outro lado";
case Erro::falha_de_socket: return "falha na chamada de socket";
}
return "erro desconhecido";
}
// Escreve o span inteiro, tantas chamadas quantas forem necessarias.
// send() pode aceitar menos bytes do que se pediu, e ignorar isso e o
// defeito mais comum em codigo de aplicacao sobre TCP.
std::expected<void, Erro> enviar_tudo(SOCKET fd, std::span<const std::byte> dados) {
std::size_t escritos = 0;
while (escritos < dados.size()) {
const auto restantes = dados.size() - escritos;
const auto n = ::send(fd,
reinterpret_cast<const char*>(dados.data() + escritos),
static_cast<int>(restantes), 0);
if (n == 0) return std::unexpected(Erro::conexao_encerrada);
if (n == SOCKET_ERROR) return std::unexpected(Erro::falha_de_socket);
escritos += static_cast<std::size_t>(n);
}
return {};
}
// Le exatamente dados.size() bytes, tantas chamadas quantas forem necessarias.
// recv() devolve o que houver disponivel, que raramente e o que se pediu.
std::expected<void, Erro> receber_tudo(SOCKET fd, std::span<std::byte> dados) {
std::size_t lidos = 0;
while (lidos < dados.size()) {
const auto restantes = dados.size() - lidos;
const auto n = ::recv(fd,
reinterpret_cast<char*>(dados.data() + lidos),
static_cast<int>(restantes), 0);
if (n == 0) return std::unexpected(Erro::conexao_encerrada);
if (n == SOCKET_ERROR) return std::unexpected(Erro::falha_de_socket);
lidos += static_cast<std::size_t>(n);
}
return {};
}
} // namespace rede
namespace {
constexpr int MENSAGENS = 1000;
constexpr std::size_t TAM = 12; // bytes por mensagem da aplicacao
void servidor(int porta, bool sem_nagle) {
const SOCKET ouvinte = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
const char um = 1;
::setsockopt(ouvinte, SOL_SOCKET, SO_REUSEADDR, &um, sizeof(um));
sockaddr_in end{};
end.sin_family = AF_INET;
end.sin_addr.s_addr = ::htonl(INADDR_LOOPBACK);
end.sin_port = ::htons(static_cast<std::uint16_t>(porta));
::bind(ouvinte, reinterpret_cast<sockaddr*>(&end), sizeof(end));
::listen(ouvinte, 1);
const SOCKET c = ::accept(ouvinte, nullptr, nullptr);
if (sem_nagle) {
const char on = 1;
::setsockopt(c, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
}
std::byte msg[TAM];
std::memset(msg, 0x41, TAM);
for (int i = 0; i < MENSAGENS; ++i) {
if (!rede::enviar_tudo(c, std::span<const std::byte>(msg, TAM))) break;
}
::shutdown(c, SD_SEND);
::closesocket(c);
::closesocket(ouvinte);
}
// Le com recv() cru e registra o tamanho devolvido a cada chamada.
std::map<std::size_t, int> cliente_ingenuo(int porta) {
const SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in end{};
end.sin_family = AF_INET;
end.sin_addr.s_addr = ::htonl(INADDR_LOOPBACK);
end.sin_port = ::htons(static_cast<std::uint16_t>(porta));
while (::connect(s, reinterpret_cast<sockaddr*>(&end), sizeof(end)) != 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
std::map<std::size_t, int> tamanhos;
std::vector<std::byte> buf(4096);
std::size_t total = 0;
for (;;) {
const auto n = ::recv(s, reinterpret_cast<char*>(buf.data()),
static_cast<int>(buf.size()), 0);
if (n <= 0) break;
tamanhos[static_cast<std::size_t>(n)] += 1;
total += static_cast<std::size_t>(n);
}
::closesocket(s);
std::println(" bytes recebidos: {} (esperado {})", total, MENSAGENS * TAM);
return tamanhos;
}
void relatar(std::string_view titulo, const std::map<std::size_t, int>& t) {
int chamadas = 0;
for (const auto& [tam, n] : t) chamadas += n;
std::println("{}", titulo);
std::println(" chamadas a recv(): {} para {} mensagens da aplicacao", chamadas, MENSAGENS);
std::println(" tamanhos distintos devolvidos: {}", t.size());
int mostrados = 0;
for (const auto& [tam, n] : t) {
std::println(" recv devolveu {} B em {} chamada(s)", tam, n);
if (++mostrados == 4) {
if (t.size() > 4) std::println(" ...");
break;
}
}
}
} // namespace
int main() {
WSADATA dados_winsock{};
if (::WSAStartup(MAKEWORD(2, 2), &dados_winsock) != 0) return 1;
for (const auto& [porta, sem_nagle, rotulo] :
{std::tuple{45101, false, "Com o algoritmo de Nagle (padrao)"},
std::tuple{45102, true, "Com TCP_NODELAY, Nagle desligado"}}) {
std::thread t(servidor, porta, sem_nagle);
const auto tamanhos = cliente_ingenuo(porta);
t.join();
relatar(rotulo, tamanhos);
std::println("");
}
::WSACleanup();
return 0;
}
Compilado com MSVC 19.44 em /std:c++latest /W4 /WX /EHsc /O2, o programa produziu nesta execução:
bytes recebidos: 12000 (esperado 12000)
Com o algoritmo de Nagle (padrao)
chamadas a recv(): 981 para 1000 mensagens da aplicacao
tamanhos distintos devolvidos: 3
recv devolveu 12 B em 970 chamada(s)
recv devolveu 24 B em 3 chamada(s)
recv devolveu 36 B em 8 chamada(s)
...
bytes recebidos: 12000 (esperado 12000)
Com TCP_NODELAY, Nagle desligado
chamadas a recv(): 939 para 1000 mensagens da aplicacao
tamanhos distintos devolvidos: 6
recv devolveu 12 B em 919 chamada(s)
recv devolveu 24 B em 3 chamada(s)
recv devolveu 36 B em 4 chamada(s)
recv devolveu 48 B em 4 chamada(s)
...
Os números variam entre execuções porque o escalonador, os reconhecimentos e a drenagem do buffer mudam a segmentação observada. A propriedade estável é outra: mil chamadas a send não produziram mil mensagens no receptor. Mesmo em loopback, recv devolveu tanto mensagens isoladas de $12$ bytes quanto grupos de $24$, $36$ e $48$ bytes. Portanto, a aplicação precisa definir seu próprio enquadramento, e receber_tudo só pode ser usado quando o tamanho esperado já é conhecido.
O resultado também impede uma conclusão apressada sobre o algoritmo de Nagle. Nesta execução do Winsock, desligá-lo reduziu apenas de $981$ para $939$ as chamadas a recv; em outras cargas e sistemas operacionais, a direção e o tamanho da diferença podem mudar. O experimento demonstra fronteiras de leitura, não constitui um benchmark de Nagle.
10.2 A janela de congestionamento em uma simulação mínima
A segunda demonstração implementa o modelo da Seção 6. Ela é uma referência didática determinística, não uma reprodução completa de uma pilha TCP: uma iteração representa uma ida e volta, a perda é sorteada por pacote e a janela obedece ao teto do produto banda-atraso.
// congestionamento.cpp -- a maquina de congestionamento do TCP, por ida e volta.
// MSVC 19.44: cl /std:c++latest /W4 /WX /EHsc /O2 congestionamento.cpp
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <print>
#include <vector>
namespace rede {
// Gerador congruencial linear de 64 bits, o mesmo de Knuth em MMIX.
// Explicito no artigo para que a simulacao seja reproduzivel bit a bit.
class Gerador {
public:
explicit Gerador(std::uint64_t semente) : estado_(semente) {}
std::uint64_t proximo() {
estado_ = estado_ * 6364136223846793005ULL + 1442695040888963407ULL;
return estado_;
}
// Uniforme em [0, 1), com os 53 bits altos.
double uniforme() {
return static_cast<double>(proximo() >> 11) * (1.0 / 9007199254740992.0);
}
private:
std::uint64_t estado_;
};
struct Parametros {
double perda = 1e-3; // probabilidade de perda por pacote
int idas_e_voltas = 400; // duracao da simulacao
int aquecimento = 50; // idas e voltas descartadas
double cwnd_inicial = 10.0; // MSS
double ssthresh = 64.0; // MSS
double teto = 856.2; // MSS, o produto banda-atraso do enlace
std::uint64_t semente = 88172645463325252ULL;
};
struct Resultado {
double cwnd_media = 0.0;
double cwnd_maxima = 0.0;
int perdas = 0;
std::vector<double> historico;
};
// Um passo por ida e volta. A janela dobra na partida lenta, cresce de um MSS
// na prevencao de congestionamento e cai pela metade quando ha perda.
Resultado simular(const Parametros& p) {
Gerador g(p.semente);
double cwnd = p.cwnd_inicial;
double ssthresh = p.ssthresh;
Resultado r;
r.historico.reserve(static_cast<std::size_t>(p.idas_e_voltas));
double soma = 0.0;
int contadas = 0;
for (int i = 0; i < p.idas_e_voltas; ++i) {
r.historico.push_back(cwnd);
if (i >= p.aquecimento) {
soma += cwnd;
++contadas;
r.cwnd_maxima = std::max(r.cwnd_maxima, cwnd);
}
const auto pacotes = static_cast<int>(cwnd);
const double p_alguma = 1.0 - std::pow(1.0 - p.perda, pacotes);
const bool perdeu = p.perda > 0.0 && p_alguma > g.uniforme();
if (perdeu) {
++r.perdas;
ssthresh = std::max(2.0, cwnd / 2.0);
cwnd = ssthresh;
} else if (cwnd < ssthresh) {
cwnd = std::min(p.teto, cwnd * 2.0);
} else {
cwnd = std::min(p.teto, cwnd + 1.0);
}
}
r.cwnd_media = contadas > 0 ? soma / contadas : 0.0;
return r;
}
// Vazao media em bit/s a partir da janela media, em MSS, e da ida e volta.
double vazao(double cwnd_media, double mss_bytes, double rtt_s) {
return cwnd_media * mss_bytes * 8.0 / rtt_s;
}
// Teto de Mathis: BW <= (MSS/RTT) * sqrt(3/2) / sqrt(p).
double mathis(double mss_bytes, double rtt_s, double perda) {
return (mss_bytes * 8.0 / rtt_s) * std::sqrt(1.5) / std::sqrt(perda);
}
} // namespace rede
int main() {
constexpr double MSS = 1460.0; // bytes
constexpr double RTT = 0.100; // s
constexpr double R = 100e6; // bit/s
std::println("teto do enlace (BDP) = {:.1f} MSS\n", R * RTT / 8.0 / MSS);
std::println(" p cwnd med cwnd max perdas vazao Mathis razao");
for (const double p : {1e-2, 1e-3, 1e-4}) {
rede::Parametros par;
par.perda = p;
par.teto = R * RTT / 8.0 / MSS;
const auto r = rede::simular(par);
const double v = rede::vazao(r.cwnd_media, MSS, RTT) / 1e6;
const double m = rede::mathis(MSS, RTT, p) / 1e6;
std::println("{:>9.0e} {:>10.2f} {:>10.2f} {:>8} {:>8.2f} {:>8.2f} {:>7.2f}",
p, r.cwnd_media, r.cwnd_maxima, r.perdas, v, m, v / m);
}
return 0;
}
Compilado com MSVC 19.44 em /std:c++latest /W4 /WX /EHsc /O2, produz:
teto do enlace (BDP) = 856.2 MSS
p cwnd med cwnd max perdas vazao Mathis razao
1e-02 10.81 27.67 54 1.26 1.43 0.88
1e-03 39.85 89.50 17 4.65 4.52 1.03
1e-04 137.21 209.00 5 16.03 14.31 1.12
Trinta linhas de simulação concordam com a equação de Mathis dentro de $12\%$ ao longo de duas ordens de grandeza de taxa de perda. Não é uma coincidência agradável: é a confirmação de que a fórmula captura o mecanismo, e não apenas ajusta dados.
Dois detalhes do modelo merecem ser declarados, porque quem os ignora tira conclusões erradas de simulações como esta.
O primeiro é o teto. A janela é limitada pelo produto banda-atraso do enlace, aqui $856{,}2$ MSS para $100$ Mbit/s com $100$ ms. Sem esse limite, a simulação com $p = 10^{-4}$ produziria uma vazão maior que a capacidade do enlace, o que é fisicamente impossível. A equação de Mathis é um teto imposto pela perda; quando o enlace é o teto, ela não se aplica. O ponto de cruzamento, para este enlace, ocorre em $p = 2{,}05 \times 10^{-6}$: abaixo dessa taxa de perda, quem manda é a capacidade.
O segundo é o que o modelo não tem: não há temporizadores, não há recuperação rápida, não há reconhecimento seletivo, não há concorrência com outros fluxos. A concordância de $12\%$ é o que se pode esperar de um modelo com essas ausências, e prometer mais precisão do que isso seria vender uma simulação como se fosse uma medição.
O laboratório a seguir é este mesmo modelo, com os controles expostos. Vale começar pelos presets e depois procurar, movendo a taxa de perda, o ponto em que a curva deixa de encostar no teto do enlace e passa a ser governada pela equação de Mathis.
Vale também abrir o Laboratório 02 da suíte NetworkLabs e fazer um experimento que este artefato não faz: injetar $1\%$ de perda e comparar o goodput de TCP, UDP e QUIC com a mesma latência base. O UDP não vai desacelerar, e observar isso lado a lado com os outros dois ensina mais sobre controle de congestionamento do que qualquer parágrafo desta seção.
11. O que delegar à máquina e como conferir
O que um modelo de linguagem faz bem aqui. Explicar um traço de captura já reduzido; propor hipóteses a partir de um gráfico de janela de congestionamento; escrever a primeira versão de um cliente com enquadramento correto, se o esquema de enquadramento for informado; e traduzir entre nomes de opções de soquete em sistemas diferentes, que é um trabalho de memória em que a máquina é melhor que a pessoa.
A alucinação característica deste assunto. É a transformação de uma escolha de arquitetura em superstição. Perguntei a um assistente se valia migrar um serviço interno para QUIC e recebi, entre outras coisas, isto:
Como o QUIC roda sobre UDP, ele elimina o overhead de retransmissão e handshake do TCP, sendo consistentemente mais rápido. UDP é um protocolo mais leve e mais rápido que TCP, então a migração deve trazer ganhos de performance em qualquer cenário.
Cada frase contém um erro distinto, e vale separá-los porque cada um morre em uma camada diferente do verificador.
O QUIC não elimina a retransmissão: ele implementa recuperação de perda no espaço de usuário, conforme a RFC 9002. O QUIC também não elimina o handshake: em uma primeira conexão, integra transporte e TLS em uma ida e volta; dados em zero ida e volta dependem de estado anterior, aceitação do servidor e restrições contra repetição. Por fim, afirmar que UDP é mais rápido que TCP não nomeia uma grandeza verificável. UDP não contém controle de congestionamento; uma aplicação pode enviar mais agressivamente e, em um enlace congestionado, reduzir o próprio goodput ao provocar descartes.
O fecho da resposta promete ganhos de desempenho em qualquer cenário. Essa universalidade é a marca da alucinação: a resposta correta depende de uma tabela de compromissos. O exercício 4 da Seção 8 mostrou, com uma divisão, que o cenário perguntado é justamente aquele em que a migração não compensa.
O verificador, em três camadas.
A sintaxe, aqui, é a checagem de referente: toda afirmação de desempenho precisa nomear a grandeza medida, a unidade e a condição. Dizer apenas mais rápido não é verificável. Dizer que uma primeira conexão QUIC reduz em uma ida e volta o instante em que o servidor recebe a requisição, o que nesta rota equivale a $76{,}65$ ms, é. Uma frase de desempenho sem grandeza, unidade e condição pode ser devolvida para especificação antes de qualquer análise mais cara.
A semântica é a checagem contra os limites do transporte. Toda vazão proposta precisa respeitar $\min(R,\ W/\text{RTT})$; todo tempo de estabelecimento precisa ser um múltiplo inteiro do RTT mais o processamento; toda promessa de recuperação precisa ser compatível com o $\text{RTO}$ ou com a ida e volta da retransmissão rápida. Uma afirmação que viole qualquer um dos três é falsa independentemente de quão bem escrita esteja.
O raio de explosão é onde as recomendações de transporte machucam de verdade, porque quase todas elas são globais. Trocar o algoritmo de congestionamento de um servidor afeta todos os fluxos daquele servidor, inclusive os que estavam bem, e o efeito depende do buffer do gargalo, que não é observável de dentro. Desligar o algoritmo de Nagle altera a formação dos segmentos e precisa ser medido sob a carga real, como a Seção 10 mostrou. Habilitar zero ida e volta expõe operações à repetição, e o dano não é de desempenho. A regra de operação que sai daí: mudanças de transporte se implantam em fração do tráfego, com medição de percentil antes e depois, e com caminho de reversão testado.
Exercício de auditoria. Um assistente produziu o diagnóstico abaixo para uma transferência que obtém $5{,}2$ Mbit/s em um enlace de $1$ Gbit/s com $100$ ms de ida e volta, sem nenhuma perda observada. Encontre os três defeitos antes de ler a resposta.
A vazão de 5,2 Mbit/s indica congestionamento severo no caminho. Recomenda-se trocar o algoritmo de congestionamento para BBR, que é imune a perdas, e aumentar o buffer de recepção para 16 MB, o que deve elevar a vazão para próximo da capacidade do enlace.
O primeiro defeito é tratar congestionamento severo como conclusão, embora o diagnóstico não apresente perda, crescimento de RTT nem outro sinal que sustente essa causa. O valor de $5{,}2$ Mbit/s aponta para uma hipótese mais específica: $65\,535 \times 8 / 0{,}1 = 5{,}24$ Mbit/s é o teto de uma janela de $65\,535$ bytes com $100$ ms de ida e volta, calculado na Seção 5 do Artigo 1 e retomado neste artigo. A primeira verificação deve ser a negociação do escalonamento de janela.
O segundo defeito é atribuir ao BBR imunidade a perdas. Não usar a perda como sinal primário é diferente de ser imune a ela: pacotes perdidos continuam exigindo recuperação, e revisões do algoritmo incorporam a perda ao modelo. Se a janela do receptor for o limitante, trocar o controlador de congestionamento não remove esse teto.
O terceiro defeito é a recomendação de aumentar o buffer de recepção para $16$ MB. Ela está mais perto de certa do que as outras duas, e mesmo assim falha, porque o escalonamento de janela é negociado apenas no primeiro segmento da conexão. Aumentar o buffer afeta conexões futuras e não corrige a conexão em curso; e, se um intermediário no caminho estiver removendo a opção do SYN, não corrigirá nem as futuras. O teste que separa as duas hipóteses é olhar o SYN em uma captura, e a recomendação deveria ter vindo com ele.
Três defeitos, três camadas. O primeiro morre na semântica, porque $5{,}2$ Mbit/s coincide com um teto calculável que a hipótese não investiga. O segundo morre na sintaxe, porque a alegada imunidade não define o comportamento diante de descarte. O terceiro morre no raio de explosão, porque a ação proposta altera todas as conexões futuras para corrigir um problema cuja localização ainda não foi demonstrada.
12. Conclusão
O Artigo 1 mostrou que a física impõe um piso. Este artigo mostrou quanto o protocolo cobra acima dele, e a fatura é maior do que costuma aparecer nas apresentações.
A leitora agora conta idas e voltas antes de contar bytes, e sabe que TCP com TLS 1.3 gasta duas antes de o servidor receber a requisição, QUIC gasta uma e QUIC com dados antecipados pode enviá-la no primeiro voo, desde que aceite as restrições do 0-RTT. Sabe que o temporizador de retransmissão combina uma média suavizada com uma margem proporcional à variação observada. Sabe distinguir o controle que protege o receptor do que protege a rede, pois confundi-los produz diagnósticos dirigidos à metade errada do problema. Também sabe por que o crescimento do Reno depende das idas e voltas, por que o CUBIC usa o tempo e por que o BBR estima banda e atraso em vez de esperar que a perda revele o gargalo. Por fim, sabe que o QUIC não deve ser escolhido por uma suposta economia universal de bytes, mas pela integração do estabelecimento, pelos fluxos independentes e pela migração de conexão quando esses mecanismos importam para a aplicação.
Sabe, sobretudo, que TCP é um fluxo de bytes, porque viu mil chamadas a send perderem suas fronteiras antes de chegar a recv, com agrupamentos que variam entre execuções.
Falta uma peça antes de qualquer conexão existir. Todos os cálculos deste artigo pressupõem que o cliente já sabe com quem falar, e essa pergunta tem uma resposta distribuída, hierárquica e com memória curta, que troca consistência por disponibilidade e esconde a troca atrás de um número de aparência inofensiva. O próximo artigo entra no DNS, e mostra que o mesmo número que garante a resiliência do sistema é o que transforma uma falha de trinta segundos em cinco minutos de indisponibilidade.
Acrônimos e Abreviações neste artigo
| Acrônimo | Definição em Inglês | Tradução em Português |
|---|---|---|
ACK |
Acknowledgement | Reconhecimento |
AIMD |
Additive Increase, Multiplicative Decrease | Aumento Aditivo, Redução Multiplicativa |
BBR |
Bottleneck Bandwidth and Round-trip propagation time | Largura de Banda do Gargalo e Tempo de Propagação |
BDP |
Bandwidth-Delay Product | Produto Banda-Atraso |
CPU |
Central Processing Unit | Unidade Central de Processamento |
CUBIC |
CUBIC (nome próprio, da função cúbica de crescimento) | CUBIC |
DNS |
Domain Name System | Sistema de Nomes de Domínio |
IP |
Internet Protocol | Protocolo de Internet |
MSS |
Maximum Segment Size | Tamanho Máximo de Segmento |
MTU |
Maximum Transmission Unit | Unidade Máxima de Transmissão |
QUIC |
QUIC (nome próprio, originalmente Quick UDP Internet Connections) | QUIC |
RFC |
Request for Comments | Pedido de Comentários |
RTO |
Retransmission Timeout | Temporizador de Retransmissão |
RTT |
Round-Trip Time | Tempo de Ida e Volta |
SACK |
Selective Acknowledgement | Reconhecimento Seletivo |
SRTT |
Smoothed Round-Trip Time | Tempo de Ida e Volta Suavizado |
SYN |
Synchronize | Sincronizar |
TCP |
Transmission Control Protocol | Protocolo de Controle de Transmissão |
TLS |
Transport Layer Security | Segurança da Camada de Transporte |
UDP |
User Datagram Protocol | Protocolo de Datagrama de Usuário |
Referências
ALLMAN, M.; PAXSON, V.; BLANTON, E. RFC 5681: TCP Congestion Control. IETF, 2009. Disponível em: https://www.rfc-editor.org/rfc/rfc5681. Acesso em: 10 ago. 2026.
BISHOP, M. (ed.). RFC 9114: HTTP/3. IETF, 2022. Disponível em: https://www.rfc-editor.org/rfc/rfc9114. Acesso em: 10 ago. 2026.
BORMAN, D.; BRADEN, B.; JACOBSON, V.; SCHEFFENEGGER, R. (ed.). RFC 7323: TCP Extensions for High Performance. IETF, 2014. Disponível em: https://www.rfc-editor.org/rfc/rfc7323. Acesso em: 10 ago. 2026.
CARDWELL, N.; CHENG, Y.; GUNN, C. S.; YEGANEH, S. H.; JACOBSON, V. BBR: Congestion-Based Congestion Control. ACM Queue, v. 14, n. 5, p. 20–53, 2016. Disponível em: https://queue.acm.org/detail.cfm?id=3022184. Acesso em: 10 ago. 2026.
CHENG, Y.; CARDWELL, N.; DUKKIPATI, N.; JHA, P. RFC 9438: CUBIC for Fast and Long-Distance Networks. IETF, 2023. Disponível em: https://www.rfc-editor.org/rfc/rfc9438. Acesso em: 10 ago. 2026.
CHIU, D.-M.; JAIN, R. Analysis of the Increase and Decrease Algorithms for Congestion Avoidance in Computer Networks. Computer Networks and ISDN Systems, v. 17, n. 1, p. 1–14, 1989. DOI: 10.1016/0169-7552(89)90019-6. Disponível em: https://doi.org/10.1016/0169-7552(89)90019-6. Acesso em: 10 ago. 2026.
CHU, J.; DUKKIPATI, N.; CHENG, Y.; MATHIS, M. RFC 6928: Increasing TCP’s Initial Window. IETF, 2013. Disponível em: https://www.rfc-editor.org/rfc/rfc6928. Acesso em: 10 ago. 2026.
CLARK, D. D. RFC 813: Window and Acknowledgement Strategy in TCP. IETF, 1982. Disponível em: https://www.rfc-editor.org/rfc/rfc813. Acesso em: 10 ago. 2026.
CLOUDFLARE. The Road to QUIC. The Cloudflare Blog, 2018. Disponível em: https://blog.cloudflare.com/the-road-to-quic/. Acesso em: 10 ago. 2026.
EDDY, W. (ed.). RFC 9293: Transmission Control Protocol (TCP). IETF, 2022. Disponível em: https://www.rfc-editor.org/rfc/rfc9293. Acesso em: 10 ago. 2026.
FORD, A.; RAICIU, C.; HANDLEY, M.; BONAVENTURE, O.; PAASCH, C. RFC 8684: TCP Extensions for Multipath Operation with Multiple Addresses. IETF, 2020. Disponível em: https://www.rfc-editor.org/rfc/rfc8684. Acesso em: 10 ago. 2026.
GRIGORIK, I. High Performance Browser Networking. Sebastopol: O’Reilly, 2013. Disponível em: https://hpbn.co/. Acesso em: 10 ago. 2026.
HA, S.; RHEE, I.; XU, L. CUBIC: A New TCP-Friendly High-Speed TCP Variant. ACM SIGOPS Operating Systems Review, v. 42, n. 5, p. 64–74, 2008. DOI: 10.1145/1400097.1400105. Disponível em: https://doi.org/10.1145/1400097.1400105. Acesso em: 10 ago. 2026.
IYENGAR, J.; SWETT, I. (ed.). RFC 9002: QUIC Loss Detection and Congestion Control. IETF, 2021. Disponível em: https://www.rfc-editor.org/rfc/rfc9002. Acesso em: 10 ago. 2026.
IYENGAR, J.; THOMSON, M. (ed.). RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transport. IETF, 2021. Disponível em: https://www.rfc-editor.org/rfc/rfc9000. Acesso em: 10 ago. 2026.
JACOBSON, V. Congestion Avoidance and Control. ACM SIGCOMM Computer Communication Review, v. 18, n. 4, p. 314–329, 1988. DOI: 10.1145/52325.52356. Disponível em: https://doi.org/10.1145/52325.52356. Acesso em: 10 ago. 2026.
LANGLEY, A. et al. The QUIC Transport Protocol: Design and Internet-Scale Deployment. In: Proceedings of the ACM SIGCOMM 2017 Conference. p. 183–196. DOI: 10.1145/3098822.3098842. Disponível em: https://doi.org/10.1145/3098822.3098842. Acesso em: 10 ago. 2026.
MATHIS, M.; MAHDAVI, J.; FLOYD, S.; ROMANOW, A. RFC 2018: TCP Selective Acknowledgment Options. IETF, 1996. Disponível em: https://www.rfc-editor.org/rfc/rfc2018. Acesso em: 10 ago. 2026.
MATHIS, M.; SEMKE, J.; MAHDAVI, J.; OTT, T. The Macroscopic Behavior of the TCP Congestion Avoidance Algorithm. ACM SIGCOMM Computer Communication Review, v. 27, n. 3, p. 67–82, 1997. DOI: 10.1145/263932.264023. Disponível em: https://doi.org/10.1145/263932.264023. Acesso em: 10 ago. 2026.
PAXSON, V.; ALLMAN, M.; CHU, J.; SARGENT, M. RFC 6298: Computing TCP’s Retransmission Timer. IETF, 2011. Disponível em: https://www.rfc-editor.org/rfc/rfc6298. Acesso em: 10 ago. 2026.
POSTEL, J. RFC 768: User Datagram Protocol. IETF, 1980. Disponível em: https://www.rfc-editor.org/rfc/rfc768. Acesso em: 10 ago. 2026.
RESCORLA, E. RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3. IETF, 2018. Disponível em: https://www.rfc-editor.org/rfc/rfc8446. Acesso em: 10 ago. 2026.
RHEE, I.; XU, L.; HA, S.; ZIMMERMANN, A.; EGGERT, L.; SCHEFFENEGGER, R. RFC 8312: CUBIC for Fast Long-Distance Networks. IETF, 2018. Obsoleta pela RFC 9438. Disponível em: https://www.rfc-editor.org/rfc/rfc8312. Acesso em: 10 ago. 2026.
STANFORD UNIVERSITY. CS 144: Introduction to Computer Networking. Disponível em: https://cs144.github.io/. Acesso em: 10 ago. 2026.
THOMSON, M.; TURNER, S. (ed.). RFC 9001: Using TLS to Secure QUIC. IETF, 2021. Disponível em: https://www.rfc-editor.org/rfc/rfc9001. Acesso em: 10 ago. 2026.
Índice da Série: Redes para Engenharia de Software
- 1. A Pilha TCP/IP e a Física da Rede
- 2. Transporte: TCP, UDP e QUIC (Você está aqui)
(Updated: )