Transporte: TCP, UDP e QUIC
por Frank de Alcantara em 10/08/2026
O Artigo 1 mostrou que a física fixa o piso; o Artigo 3 acompanhou o pacote até o próximo salto. Agora precisamos cobrar o que acontece nas pontas, quando uma aplicação exige ordem, confiabilidade e controle da taxa de envio.
Índice da Série: Redes para Engenharia de Software
- 1. A Pilha TCP/IP e a Física da Rede
- 2. Da Rede Local à Web: Dispositivos, Topologias e Serviços
- 3. Camada IP: Endereçamento, Sub-redes e Encaminhamento
- 4. 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 10 ela será a chave de agregação de fluxos; no Artigo 11 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 cabeçalho QUIC variável |
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 5 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 17 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.
Vamos fixar a grandeza antes de calcular. Mediremos o tempo entre o primeiro pacote de estabelecimento e o instante em que o cliente pode enviar o primeiro byte de aplicação protegido. Depois que esse byte sai, ele ainda precisa de uma propagação de ida para alcançar o servidor. A Figura 1 marca exatamente o instante de saída no cliente, o que impede misturar custo de estabelecimento com tempo de entrega.
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 cliente poder enviar a requisição protegida. Com o TLS 1.2, que exigia duas idas e voltas de negociação criptográfica, seriam três no total, ou $229{,}95$ ms. Para saber quando o servidor recebe o primeiro byte, acrescentamos a propagação de ida do caminho.
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: o cliente não espera o handshake para enviar, embora o pacote ainda leve o tempo de propagação até o servidor.
A Figura 1 mostra as três linhas do tempo lado a lado, com o mesmo RTT.
Figura 1: TCP mais TLS 1.3 espera 153,30 ms antes de o cliente enviar o primeiro byte útil; QUIC espera 76,65 ms; QUIC com retomada não espera o estabelecimento. Em todos os casos, ainda há uma propagação de ida até o servidor.
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 leitora deve guardar, porque ele governa quando vale a pena medir: a economia de estabelecimento 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. Em um enlace com $\text{RTT} = 200$ ms, economiza $200$ ms. O primeiro valor pode desaparecer diante do processamento e da variação do serviço; o segundo pode dominar o caminho crítico. A distância aumenta o benefício possível, mas não substitui a medição dos demais custos.
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 de estabelecimento e o instante em que o cliente pode enviar a requisição protegida? Quanto ainda falta para o primeiro byte alcançar o servidor, supondo caminho simétrico?
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.
Em caminho simétrico, a propagação de ida vale metade do RTT:
\[T_{\text{servidor}} = 153{,}30 + \frac{76{,}65}{2} = 191{,}625\ \text{ms}.\]Portanto, $153{,}30$ ms mede a espera para enviar; $191{,}625$ ms mede o instante aproximado em que o servidor recebe esse primeiro byte.
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 seis estabelecimentos sequenciais é de trinta milissegundos. Só uma medição da variação do serviço dirá se esse valor aparece na cauda. A $200$ ms, a economia chega a $1{,}2$ segundo no cenário sequencial e merece prioridade na investigação. A regra de decisão que sai daí é mais precisa: a distância define o teto do ganho de estabelecimento; CPU, reutilização de conexões e carga definem se o ganho permanece no sistema completo.
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.
A Figura 2 transforma essa limitação em estado visível. Quando os segmentos $5$ e $12$ se perdem, o ACK cumulativo permanece parado no primeiro buraco. O reconhecimento seletivo, SACK, conserva esse ACK e acrescenta os blocos que chegaram fora de ordem. O remetente passa a saber, na mesma ida e volta, que precisa reparar dois segmentos específicos.
Figura 2: o ACK cumulativo localiza o primeiro byte ausente; o SACK acrescenta os blocos que já chegaram e revela todos os buracos representáveis na opção.
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\vert\text{SRTT} - R'\bigr\vert,\] \[\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 será então dado por
\[\text{RTO} = \text{SRTT} + \max\bigl(G,\ 4\,\text{RTTVAR}\bigr),\]na qual $G$ é a granularidade do relógio. A variável $\text{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 diferença entre a recomendação da norma e algumas pilhas implantadas, porque ela explica um comportamento que confunde. A RFC 6298 recomenda arredondar para um segundo qualquer $\text{RTO}$ calculado abaixo desse valor. Com os números do exercício 1, o estimador produz $195$ ms e a aplicação literal do piso produz $1000$ ms. Há pilhas que adotam limites inferiores diferentes. A escolha depende da implementação e da versão, portanto não devemos transformar um valor observado em regra universal. Vamos medir a pilha em uso e registrar o piso junto com o sistema operacional, o algoritmo e a versão.
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 recomendado pela RFC
A RFC 6298 recomenda arredondar para um segundo o $\text{RTO}$ calculado abaixo desse valor. Qual seria o temporizador efetivo nas quatro medições do exercício anterior? O que precisamos medir antes de comparar esse resultado com uma conexão real?
Solução: Todos os quatro valores calculados ficam abaixo de $1000$ ms. Com o piso recomendado, o $\text{RTO}$ efetivo seria $1000$ ms nas quatro linhas.
| Medição | $\text{RTO}$ calculado | Com piso de $1000$ ms |
|---|---|---|
| 1 | $228{,}000$ ms | $1000$ ms |
| 2 | $196{,}750$ ms | $1000$ ms |
| 3 | $169{,}156$ ms | $1000$ ms |
| 4 | $195{,}012$ ms | $1000$ ms |
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.
Uma pilha pode adotar outro limite inferior e produzir um tempo menor. Antes de chamar a diferença de defeito, precisamos identificar a implementação, a versão e o valor mínimo configurado ou compilado nela. O piso não pode ser reduzido por intuição: um $\text{RTO}$ menor que a variação real do caminho produz retransmissões espúrias, consome banda e pode acionar o controle de congestionamento sem que um pacote tenha se perdido.
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 revela
Uma janela de $20$ segmentos sofre a perda dos segmentos $5$ e $12$, como na Figura 2. Compare a informação disponível com ACK cumulativo e com SACK. Em uma recuperação NewReno sem SACK, quantas idas e voltas podem ser necessárias para reparar os dois buracos? E com um algoritmo SACK que programa as duas retransmissões na mesma janela de recuperação?
Solução: O ACK cumulativo informa apenas que o próximo segmento esperado é o $5$. Ele não diz que $6$ a $11$ chegaram, nem que existe outro buraco no $12$, nem que $13$ a $20$ também chegaram. O remetente descobre o segundo buraco quando a retransmissão do $5$ é reconhecida parcialmente. No modelo de recuperação do NewReno, essa descoberta serial pode custar uma ida e volta por buraco. Para duas perdas, a recuperação pode ocupar aproximadamente $2\,\text{RTT}$.
O SACK mantém o ACK cumulativo no $5$ e acrescenta dois blocos recebidos, de $6$ a $11$ e de $13$ a $20$. O remetente infere imediatamente que os únicos buracos são $5$ e $12$. Se a janela de congestionamento permitir as duas retransmissões no mesmo período de recuperação, o custo cai para aproximadamente $1\,\text{RTT}$.
| Recuperação | Informação disponível de imediato | Buracos localizados | Custo aproximado no modelo |
|---|---|---|---|
| NewReno sem SACK | próximo segmento esperado, $5$ | um por etapa | $2\,\text{RTT}$ |
| com SACK | ACK $5$, blocos $6$ a $11$ e $13$ a $20$ | $5$ e $12$ | $1\,\text{RTT}$ |
Em ambos os casos, um algoritmo correto pode retransmitir apenas os dois segmentos perdidos. O ganho do SACK não é uma promessa de retransmitir catorze segmentos a menos; é informação suficiente para localizar múltiplas perdas sem descobri-las uma por ida e volta. A RFC 6675 especifica uma estratégia conservadora de recuperação baseada nessa informaçã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 será dada pelo 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} = 65\,535$ bytes, aproximadamente $64$ KiB, $\text{cwnd} = 200$ KiB e $\text{RTT} = 50$ ms, a janela efetiva é de $65\,535$ bytes e a vazão será dada por
\[\frac{65\,535 \times 8}{0{,}050} = 10{,}49\ \text{Mbit/s},\]enquanto a janela de congestionamento 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: o limite está na janela e no tempo de ida e volta, não na capacidade nominal do enlace.
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} = 65\,535$ bytes 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 observado é o receptor, não a janela de congestionamento: o remetente poderia manter $200$ KiB em voo segundo $\text{cwnd}$, mas o outro lado anuncia espaço para apenas $65\,535$ bytes.
O próximo teste é observar a ocupação do buffer de recepção. Se ele permanece cheio, precisamos corrigir a aplicação que não o drena ou dimensionar sua capacidade. Se permanece vazio e a janela continua pequena, precisamos revisar a configuração e a negociação de escalonamento. Aumentar a capacidade do enlace ou trocar o algoritmo de congestionamento não toca a variável que limita este exemplo.
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 $\text{cwnd}$ 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.
| Fator $s$ | Multiplicador $2^s$ | Janela máxima | Atende a $2{,}5$ MB? |
|---|---|---|---|
| $5$ | $32$ | $65\,535\times32=2\,097\,120$ bytes | Não |
| $6$ | $64$ | $65\,535\times64=4\,194\,240$ bytes | Sim |
Portanto, $s=6$ é o primeiro fator que satisfaz a desigualdade; $s=5$ ainda deixa a janela $402\,880$ bytes abaixo do necessário.
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 será dada por
\[\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 será dada por
\[\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. A ausência de retransmissões elimina a evidência de perda recente, mas não revela o valor de $\text{cwnd}$ e não prova que o transporte deixou de limitar. A janela pode estar em $180$ KB por causa do histórico da conexão, de controle por atraso, de ritmo de envio ou de outro limite interno.
Precisamos observar pelo menos $\text{cwnd}$, bytes em voo, fila do soquete e ritmo da aplicação no mesmo intervalo. Se o buffer de envio fica vazio, a aplicação não oferece dados suficientes. Se fica cheio e $\text{cwnd}$ permanece perto de $180$ KB, o transporte limita. Se fica cheio, $\text{cwnd}$ é maior e os bytes em voo não crescem, procuramos limitação por ritmo, agendamento ou configuração. Uma captura isolada descarta $\text{rwnd}$, mas não escolhe sozinha entre as hipóteses restantes.
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 3.
Figura 3: 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 estimativas de ordem de grandeza. O resultado completo é $22{,}5\%$ maior que o resultado sem $C$; olhando no sentido inverso, o resultado sem $C$ fica $18{,}4\%$ abaixo do completo. A Tabela 3 avalia a expressão para $\text{MSS} = 1460$ bytes e $\text{RTT} = 100$ ms e mantém explícito qual denominador usamos em cada porcentagem.
| $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. Uma arquitetura que só atinge o objetivo quando o caminho sustenta essa taxa de perda está apoiada em uma condição excepcionalmente exigente. É por essa razão, e não por moda, que algoritmos mais escaláveis substituíram Reno em muitas pilhas, 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 será dado pela 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 $919{,}8$ kB, 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 uma 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 será dado por
\[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 16 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 \to 60.\]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: Primeiro vamos calcular o fator comum:
\[\frac{\text{MSS}\cdot 8}{\text{RTT}} = \frac{1460\times 8}{0{,}100} = 116\,800\ \text{bit/s}.\]Para $p=10^{-2}$, por exemplo, $\sqrt{p}=0{,}1$. Com a constante, a vazão será $116\,800\times1{,}2247/0{,}1=1{,}43$ Mbit/s. Sem a constante, será $116\,800/0{,}1=1{,}17$ Mbit/s. Repetindo a substituição para as outras duas taxas:
| $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. A conta já impõe um requisito extremo sem precisarmos atribuir toda perda a uma tecnologia de enlace específica. Erros de transmissão, descartes em filas e mudanças de rota entram juntos no $p$ que o remetente observa.
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 trata a perda como sinal de fila cheia, uma aproximação adequada ao problema de colapso de congestionamento que orientou o projeto em 1988. A suposição envelheceu por dois motivos. Primeiro, enlaces sem fio também perdem pacotes por interferência, sem que haja congestionamento, e Reno reduz a janela em resposta ao sinal errado. 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 será dada, em função do tempo, por
\[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, a função cúbica usa tempo em segundos e fica menos diretamente dependente do RTT fora da região de compatibilidade com Reno. O algoritmo completo ainda é movido por ACKs, distingue fluxos limitados pela aplicação e inclui uma região Reno-friendly. A RFC 9438 formula a propriedade com esse recorte, e é ele que devemos preservar ao comparar fluxos.
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: precisamos de um modelo do caminho. A primeira versão, publicada pelo Google em 2016, tornou centrais duas estimativas: a largura de banda do gargalo, $\text{BtlBw}$, medida pela taxa de entrega, e o atraso de propagação de ida e volta, $\text{RTprop}$, aproximado pelo menor RTT observado. O ponto de partida será dado por
\[\text{BDP} = \text{BtlBw} \times \text{RTprop},\]e o algoritmo usa ganhos de ritmo e de dados em voo para operar em torno desse produto e sondar mudanças de capacidade.
A consequência procurada é evitar que uma fila persistentemente cheia seja o ponto normal de operação. Isso não autoriza prometer fila zero nem a mesma vazão em todo caminho. A versão atual em discussão no IETF também usa perda, marcação explícita de congestionamento e limites de dados em voo para restringir o modelo quando a estimativa se mostra otimista. Dizer apenas que BBR ignora perdas descreve a motivação histórica, não o algoritmo atual.
Um fluxo BBR e um fluxo CUBIC no mesmo gargalo podem repartir a capacidade de modo desigual. O resultado depende do tamanho do buffer, dos RTTs, das versões dos dois algoritmos, da marcação ECN e da carga concorrente. A leitora que trocar de algoritmo em produção precisa registrar a versão e medir no próprio caminho, em vez de transportar para sua rede uma conclusão 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}.\]| Algoritmo | Janela após a perda | Regra de recuperação | Tempo para retomar $2000$ MSS |
|---|---|---|---|
| Reno | $1000$ MSS | acrescenta $1$ MSS por RTT | $100{,}0$ s |
| CUBIC | $1400$ MSS | segue a função cúbica até $t=K$ | $11{,}45$ s |
A razão será $100{,}0/11{,}45 = 8{,}74$. Nesse cenário, o CUBIC retoma a janela anterior cerca de $8{,}74$ vezes mais depressa. 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. Em uma configuração didática com ganho de janela igual a $2$, calcule também o limite de dados em voo.
Solução: O BDP será dado por
\[\text{BDP} = \frac{100 \times 10^{6} \times 0{,}040}{8} = 500\ \text{kB}.\]Com ganho de janela igual a $2$, o limite didático será
\[2\times 500 = 1000\ \text{kB}.\]Esse megabyte é um limite da configuração proposta, não uma promessa de ocupação nem uma constante comum a todas as versões. O ritmo de envio, as fases de sondagem e os limites aprendidos no caminho determinam quantos bytes ficam realmente em voo. Para comparar latência com CUBIC, precisamos medir a fila e a vazão; o BDP isolado não prova que uma fila ficará vazia nem que a outra ficará cheia.
4. Quantifique um ciclo histórico de sondagem do BBR
O ciclo ProbeBW do BBRv1 usava oito fases: uma com ganho de ritmo $1{,}25$, uma com ganho $0{,}75$ e seis com ganho $1{,}0$. Qual é o ganho médio? O que mudaria se a fase de drenagem fosse omitida?
Solução: Vamos somar as oito fases explicitamente:
\[\bar{g} = \frac{1{,}25 + 0{,}75 + 6\times 1{,}0}{8} = \frac{8}{8} = 1{,}0.\]Sem a fase de ganho $0{,}75$, teríamos uma fase a $1{,}25$ e sete a $1{,}0$. O ganho médio passaria a
\[\frac{1{,}25 + 7\times1{,}0}{8} = 1{,}03125,\]ou $3{,}125\%$ acima da taxa estimada. A conta descreve o BBRv1 e continua útil para entender sondagem e drenagem. Ela não deve ser atribuída sem rótulo à versão atual: o rascunho do IETF publicado em 2026 usa uma máquina de estados mais rica e, na fase de redução, adota ganho $0{,}90$ em vez desse ciclo fixo de oito idas e voltas.
5. Discuta a equidade entre BBR e CUBIC
Um fluxo BBR e um fluxo CUBIC compartilham um gargalo. Podemos prever a repartição usando apenas o tamanho do buffer?
Solução: Não. O tamanho do buffer altera quando CUBIC encontra perda e quanto tempo a fila pode permanecer ocupada, mas não determina sozinho a divisão. Também precisamos das versões dos algoritmos, dos RTTs, da marcação ECN, dos limites aprendidos pelo BBR, do ritmo das aplicações e dos demais fluxos.
O experimento mínimo registra, antes e depois da mudança, a vazão de cada fluxo, o RTT mínimo, a mediana, o percentil 95, as perdas, as marcações ECN e a ocupação da fila. Devemos repetir com buffers rasos e profundos e com mais de uma razão entre RTTs. Se uma conclusão muda quando trocamos a versão do BBR ou a disciplina de fila, ela não é propriedade de BBR contra CUBIC; é propriedade daquele ensaio.
A conclusão operacional é mais forte quando admite esse limite: não existe resposta independente do gargalo e da implementação. Trocar o algoritmo em produção exige medição de antes e depois, com a versão registrada e atenção à cauda de latência.
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 4 mostra a diferença com quatro objetos concorrentes.
Figura 4: 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 ganha automaticamente em bytes. A comparação abaixo fixa um caso ilustrativo: IPv4 sem opções, TCP sem opções, uma etiqueta de autenticação de $16$ bytes e um cabeçalho QUIC curto com identificador de $8$ bytes e número de pacote de $4$ bytes. Outros caminhos e outras fases da conexão produzem tamanhos diferentes.
| 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, nessas hipóteses, o custo relativo é de $4{,}84\%$ contra $4{,}53\%$. A diferença é de três décimos de ponto percentual. O ganho arquitetural do QUIC está nas idas e voltas, nos streams independentes, na migração e na capacidade de evolução, não em uma economia universal de quatro bytes por pacote.
Há também um custo que precisa ser medido. O QUIC costuma ser implementado no processo e encontra um conjunto de recursos de descarregamento diferente daquele disponível ao TCP em cada combinação de sistema operacional, biblioteca e placa de rede. Não podemos converter essa diferença em um multiplicador universal de CPU. Dentro do mesmo centro de dados, com $\text{RTT} = 0{,}5$ ms, economizar uma ida e volta representa apenas $1{,}25\%$ de uma requisição de $40$ ms. Em uma rota de $76{,}65$ ms, a mesma ida e volta domina. A decisão pede duas medições separadas: tempo até o primeiro byte e CPU por byte na plataforma real.
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
Sob as hipóteses de cabeçalho da tabela desta seção, 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\%.\]| Pilha | Cabeçalho | Total transmitido por pacote | Sobrecarga relativa |
|---|---|---|---|
| TCP com TLS 1.3 | $61$ bytes | $1261$ bytes | $4{,}84\%$ |
| QUIC | $57$ bytes | $1257$ bytes | $4{,}53\%$ |
A diferença é de $0{,}31$ ponto percentual neste modelo. Em uma transferência com $10$ GB de carga útil dividida em pacotes idênticos, a ordem de grandeza da diferença fica perto de $31$ MB. Esse resultado não se transfere automaticamente para IPv6, opções TCP, cabeçalhos QUIC longos, números de pacote menores ou cargas de outro tamanho. O exercício serve para mostrar que a comparação precisa declarar o envelope usado.
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 o que a conta de estabelecimento consegue provar
Um serviço interno em um centro de dados tem $\text{RTT} = 0{,}5$ ms, requisições de $40$ ms e conexões persistentes. O que a conta de estabelecimento permite concluir sobre uma migração para QUIC?
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.
A conta permite concluir apenas que a economia de estabelecimento é pequena e quase desaparece quando a conexão é reutilizada. Ela não prova que o balanço é negativo, porque ainda não medimos CPU, perdas, bloqueio entre streams, mudanças de caminho nem facilidade de evolução.
O próximo passo é um ensaio na plataforma real. Vamos medir CPU por byte, vazão e percentis de latência com a mesma carga e o mesmo número de conexões. Se a aplicação não precisa de migração, não sofre bloqueio relevante e não ganha com evolução no espaço de usuário, a economia de $0{,}5$ ms dificilmente justificará a mudança. Essa é uma conclusão condicional e verificável, não uma propriedade de todo centro de dados.
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 16 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 16 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 materializa uma das fronteiras mais importantes 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, produz um defeito traiçoeiro: testes locais pequenos podem escondê-lo, enquanto carga, escalonamento e rede real tendem a expô-lo.
// fronteiras.cpp -- TCP e um fluxo de bytes, nao uma fila de mensagens.
// MSVC 19.51: cl /std:c++latest /W4 /WX /EHsc /O2 fronteiras.cpp
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <expected>
#include <limits>
#include <map>
#include <print>
#include <semaphore>
#include <span>
#include <string>
#include <string_view>
#include <thread>
#include <tuple>
#include <vector>
namespace rede {
enum class Erro { conexao_encerrada, falha_de_socket, falha_de_conexao };
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";
case Erro::falha_de_conexao: return "nao foi possivel conectar ao servidor";
}
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 bloco = std::min(restantes,
static_cast<std::size_t>(std::numeric_limits<int>::max()));
const auto n = ::send(fd,
reinterpret_cast<const char*>(dados.data() + escritos),
static_cast<int>(bloco), 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 bloco = std::min(restantes,
static_cast<std::size_t>(std::numeric_limits<int>::max()));
const auto n = ::recv(fd,
reinterpret_cast<char*>(dados.data() + lidos),
static_cast<int>(bloco), 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, std::binary_semaphore& pronto) {
const SOCKET ouvinte = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ouvinte == INVALID_SOCKET) {
pronto.release();
return;
}
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));
if (::bind(ouvinte, reinterpret_cast<sockaddr*>(&end), sizeof(end)) == SOCKET_ERROR ||
::listen(ouvinte, 1) == SOCKET_ERROR) {
pronto.release();
::closesocket(ouvinte);
return;
}
pronto.release();
const SOCKET c = ::accept(ouvinte, nullptr, nullptr);
if (c == INVALID_SOCKET) {
::closesocket(ouvinte);
return;
}
if (sem_nagle) {
const char on = 1;
if (::setsockopt(c, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == SOCKET_ERROR) {
::closesocket(c);
::closesocket(ouvinte);
return;
}
}
std::array<std::byte, TAM> msg{};
std::ranges::fill(msg, std::byte{0x41});
for (int i = 0; i < MENSAGENS; ++i) {
if (!rede::enviar_tudo(c, msg)) break;
}
::shutdown(c, SD_SEND);
::closesocket(c);
::closesocket(ouvinte);
}
// Le com recv() cru e registra o tamanho devolvido a cada chamada.
std::expected<std::map<std::size_t, int>, rede::Erro> cliente_ingenuo(int porta) {
const SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET) return std::unexpected(rede::Erro::falha_de_socket);
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));
if (::connect(s, reinterpret_cast<sockaddr*>(&end), sizeof(end)) == SOCKET_ERROR) {
::closesocket(s);
return std::unexpected(rede::Erro::falha_de_conexao);
}
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;
if (n == SOCKET_ERROR) {
::closesocket(s);
return std::unexpected(rede::Erro::falha_de_socket);
}
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::binary_semaphore pronto{0};
std::thread t([&] { servidor(porta, sem_nagle, pronto); });
pronto.acquire();
const auto tamanhos = cliente_ingenuo(porta);
t.join();
if (!tamanhos) {
std::println("falha: {}", rede::descrever(tamanhos.error()));
::WSACleanup();
return 1;
}
relatar(rotulo, *tamanhos);
std::println("");
}
::WSACleanup();
return 0;
}
Compilado com MSVC 19.51 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(): 985 para 1000 mensagens da aplicacao
tamanhos distintos devolvidos: 4
recv devolveu 12 B em 977 chamada(s)
recv devolveu 24 B em 3 chamada(s)
recv devolveu 36 B em 3 chamada(s)
recv devolveu 48 B em 2 chamada(s)
bytes recebidos: 12000 (esperado 12000)
Com TCP_NODELAY, Nagle desligado
chamadas a recv(): 1000 para 1000 mensagens da aplicacao
tamanhos distintos devolvidos: 1
recv devolveu 12 B em 1000 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 aumentou de $985$ para $1000$ as chamadas a recv e preservou cada grupo de doze bytes. 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 uma medição de desempenho do algoritmo 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.51: 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 = 100'000; // duracao da simulacao
int aquecimento = 5'000; // 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<std::size_t>(cwnd);
const double p_alguma = p.perda > 0.0
? -std::expm1(static_cast<double>(pacotes) * std::log1p(-p.perda))
: 0.0;
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.51 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 12.85 42.49 11692 1.50 1.43 1.05
1e-03 41.15 124.63 3985 4.81 4.52 1.06
1e-04 132.74 387.05 1268 15.50 14.31 1.08
O modelo curto aproxima a equação de Mathis ao longo de duas ordens de grandeza da taxa de perda. A comparação não valida uma pilha TCP; mostra apenas que a dinâmica AIMD implementada é a mesma dinâmica que a expressão resume.
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. Uma razão próxima de um entre a simulação e Mathis verifica consistência interna, não precisão sobre uma rede real. Prometer mais 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 pelas configurações predefinidas 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. Como auditar recomendações de transporte
Uma recomendação de transporte precisa transformar comparações genéricas em hipóteses mensuráveis. Considere a afirmação recorrente:
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 é detectado em uma camada diferente da verificação.
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.
A última frase promete ganhos de desempenho em qualquer cenário. Essa universalidade denuncia o problema: a decisão depende de uma tabela de compromissos. O exercício 4 da Seção 8 mostrou, com uma divisão, que a economia de estabelecimento é pequena no cenário proposto e não basta para decidir. Ainda faltam as medições de CPU, cauda de latência e carga real.
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 a espera até o cliente poder enviar a requisição protegida, 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. Considere 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 espera duas antes de o cliente enviar a requisição protegida, QUIC espera 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 sem tratar a perda como único sinal. 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 ajuda o sistema a continuar respondendo também pode transformar 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.
BLANTON, E. et al. RFC 6675: A Conservative Loss Recovery Algorithm Based on Selective Acknowledgment (SACK) for TCP. IETF, 2012. Disponível em: https://www.rfc-editor.org/rfc/rfc6675. Acesso em: 18 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.
CARDWELL, N. et al. BBR Congestion Control. Internet-Draft draft-ietf-ccwg-bbr-05. IETF, 2026. Disponível em: https://datatracker.ietf.org/doc/draft-ietf-ccwg-bbr/. Acesso em: 18 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. Da Rede Local à Web: Dispositivos, Topologias e Serviços
- 3. Camada IP: Endereçamento, Sub-redes e Encaminhamento
- 4. Transporte: TCP, UDP e QUIC (Você está aqui)
(Updated: )