01 - Orientado ao Fluxo
video: https://youtu.be/... no frontmatter da nota.O conceito Orientado ao Fluxo (Stream-Oriented) é a característica fundamental do serviço de Circuito Virtual (especificamente o TCP) que define como os dados são vistos e transportados. Neste modelo, a Camada de Transporte trata os dados não como uma sequência de blocos ou mensagens independentes, mas como um fluxo contínuo e ininterrupto de bytes.
Para a aplicação que envia os dados, é como se ela estivesse escrevendo em uma extremidade de um longo tubo, e a aplicação receptora estivesse lendo na outra ponta. Não existem marcas naturais, fronteiras ou divisórias dentro desse tubo; os dados fluem como a água em um cano.
1. A Diferença Fundamental: Fluxo vs. Mensagem
Para entender o modelo orientado ao fluxo, é essencial contrastá-lo com o modelo orientado a mensagens (Datagrama/UDP):
- Modelo Orientado a Mensagem (UDP): Preserva os limites. Se a aplicação enviar duas escritas distintas — primeiro um bloco de 100 bytes e depois um bloco de 200 bytes — o receptor receberá exatamente dois pacotes: um de 100 bytes e um de 200 bytes. A aplicação receptora sabe onde uma mensagem termina e a próxima começa.
- Modelo Orientado ao Fluxo (TCP): Não preserva os limites. Se a aplicação enviar um bloco de 100 bytes e depois outro de 200 bytes, o TCP simplesmente os enxerga como 300 bytes consecutivos de dados. O receptor pode receber tudo de uma vez (um único bloco de 300 bytes), ou em pedaços fragmentados (ex: 50 bytes, depois 150, depois 100), ou byte por byte.
2. O Processo Técnico: Segmentação e Remontagem
O TCP precisa mover esse fluxo contínuo sobre uma rede de pacotes (IP) que é fragmentada. Para resolver isso, o TCP gerencia a quebra e a montagem transparentemente para a aplicação.
A. No Lado do Remetente (Segmentação)
- Buffer de Envio: A aplicação executa um comando de escrita (ex:
send()ouwrite()). Os dados não vão imediatamente para a rede; eles vão para um buffer de memória no sistema operacional (socket send buffer, tipicamente 16-64 KiB ajustável viaSO_SNDBUF). - Agrupamento (Nagle's Algorithm): O TCP pode aguardar brevemente para acumular mais dados, criando segmentos maiores e mais eficientes (MSS - Maximum Segment Size). O algoritmo de Nagle (RFC 896) diz: "se houver dados não confirmados em trânsito, segure pequenos segmentos até acumular um MSS completo".
- Corte em Segmentos: O TCP corta o fluxo de bytes em pedaços (segmentos) que cabem no MTU da Camada de Rede. A esses pedaços são anexados cabeçalhos TCP com números de sequência. Nota-se que o corte é baseado na capacidade da rede, não nas fronteiras de escrita da aplicação.
B. No Lado do Receptor (Remontagem)
- Chegada Desordenada: Os segmentos chegam via IP, possivelmente fora de ordem.
- Buffer de Recepção: O TCP coloca os bytes desses segmentos em um buffer de reordenamento, usando os números de sequência para colocá-los na ordem correta perfeita.
- Entrega como Fluxo: A aplicação chama um comando de leitura (ex:
recv()ouread()). O TCP entrega os bytes do buffer para a aplicação na ordem correta, exatamente como foram escritos originalmente, mas sem informar onde estavam as fronteiras dos segmentos originais.
3. Demonstração: o Efeito Prático
Cliente envia em três chamadas send() consecutivas: "HELLO" (5 bytes), " " (1 byte), "WORLD" (5 bytes) — total 11 bytes, SEQ inicial 1000.
Possíveis cenários no receptor com uma única chamada recv() de buffer 100:
Cenário 1: recv() retorna "HELLO WORLD" (11 bytes de uma vez)
Cenário 2: recv() retorna "HELLO" (5 bytes; segunda chamada traz o resto)
Cenário 3: recv() retorna "HELL" (4 bytes; próximo recv traz "O WOR", etc.)
Cenário 4: recv() retorna "O WORLD" (6 bytes; o "HELL" veio em recv anterior)
A aplicação não pode assumir nada sobre o tamanho do recv() — deve sempre concatenar chunks até ter o dado completo (ou até encontrar um delimitador).
4. O Problema da Aplicação: Definição de Limites
Como o transporte não diz à aplicação onde uma "mensagem" termina e a próxima começa, é a aplicação que deve definir seu próprio protocolo para interpretar o fluxo de bytes. A aplicação deve implementar lógica para "frasear" o fluxo.
Existem três estratégias comuns para isso:
- Delimitadores (Separadores): A aplicação insere um caractere especial (como
\nnewline ou\r\n) para indicar o fim de uma mensagem. O cliente lê o fluxo byte a byte até encontrar o delimitador. (Ex: cabeçalhos HTTP, SMTP, FTP de controle, IRC.) - Prefixo de Comprimento (Length Prefixing): Antes de enviar os dados, a aplicação envia um cabeçalho fixo (ex: 4 bytes em big-endian) indicando quantos bytes compõem a mensagem. O receptor lê os 4 bytes, sabe que a mensagem tem X bytes, e lê exatamente X bytes do fluxo. (Ex: protocolos binários como TLS record, WebSocket frame, gRPC/HTTP2 frames.)
- Conexão por Mensagem: Cada mensagem abre uma conexão TCP nova (custa 1 RTT de handshake) e o
FINindica o fim. Simples mas ineficiente. (Ex: HTTP/1.0 sem Keep-Alive.)
5. Vantagens do Modelo Orientado ao Fluxo
A escolha pelo modelo de fluxo oferece vantagens significativas de eficiência e controle:
- Adaptação Dinâmica: O TCP pode decidir dividir o fluxo em segmentos de tamanhos variáveis dependendo das condições da rede (Congestion Control). Se a rede estiver congestionada, ele pode enviar segmentos pequenos; se a rede estiver limpa, ele pode enviar segmentos grandes. Isso seria impossível se a aplicação exigisse que cada "mensagem" fosse um pacote IP.
- Transparência de Tamanho: A aplicação não precisa se preocupar com o MTU (Maximum Transmission Unit) da rede subjacente. Ela pode escrever 1 MB de dados de uma só vez se quiser, e o TCP se encarregará de quebrar isso em pedaços pequenos o suficiente para atravessar a rede.
- Full-Duplex: O fluxo é bidirecional e independente. Os dados podem fluir em ambas as direções simultaneamente no mesmo "tubo", sem colisão lógica.
- Pipelining: Como a ordem é garantida pela numeração de bytes, o remetente pode transmitir múltiplos segmentos "na pipa" sem esperar ACKs individuais — o que multiplicaria a taxa efetiva pela janela.
6. Desvantagens e Cuidados
- Sem preservação de record: Para protocolos orientados a mensagens (ex: RPC), é preciso camada extra de framing.
- Head-of-line blocking: Se um byte no meio do fluxo for perdido, todos os bytes posteriores — mesmo já recebidos no buffer — ficam retidos até a retransmissão preencher a lacuna. Em tempo real (vídeo, jogos), isso é prejudicial; é um dos motivos do QUIC rodar sobre UDP.
- Complexidade no receptor: A aplicação precisa implementar lógica de "ler até delimitador" ou "ler N bytes", muitas vezes com buffers parciais.
7. Comparação Rápida: Fluxo (TCP) x Mensagem (UDP)
| Aspecto | TCP (Stream) | UDP (Message) |
|---|---|---|
| Fronteiras de mensagem | Não preserva | Preserva |
| Unidade visível à aplicação | Fluxo de bytes | Datagrama |
| Tamanho máximo do envio | Ilimitado (segmentado) | Limitado a 65507 bytes |
| Ordem de chegada | Garantida | Não garantida |
send()/recv() semântica |
Streaming | Datagram |
| API típica | read()/write() |
sendto()/recvfrom() |
8. Implementação Prática: como ler uma "mensagem" do TCP
Para ilustrar como a aplicação constrói framing sobre o fluxo de bytes, veja o padrão length-prefix em C:
// Remetente: envia frame [length:4 bytes][payload:length bytes]
uint32_t len_n = htonl(payload_len);
send(sock, &len_n, 4, 0);
send(sock, payload_buf, payload_len, 0);
// Receptor: lê 4 bytes do length, depois o payload exato
uint32_t len_n, len;
recv_full(sock, &len_n, 4); // wrapper que faz loop até 4 bytes
len = ntohl(len_n);
char *buf = malloc(len);
recv_full(sock, buf, len);
A função recv_full precisa fazer loop em recv() porque ele pode retornar menos bytes do que o pedido (o "tubo" pode entregar em pedaços). Isso não acontece em UDP — um recvfrom() devolve sempre um datagrama completo.
9. Head-of-Line Blocking: a desvantagem oculta
O modelo orientado ao fluxo tem uma desvantagem sutil mas séria em certos cenários: se um byte no meio do fluxo for perdido, todos os bytes seguintes — mesmo já recebidos no buffer — ficam retidos até a retransmissão preencher a lacuna.
Exemplo: remetente envia bytes 1000-1999, 2000-2999, 3000-3999. O segmento 2000-2999 se perde. No receptor:
- Bytes 1000-1999: entregues à aplicação.
- Bytes 2000-2999: faltam; TCP guarda 3000-3999 no buffer e não entrega à aplicação.
- Aplicação fica bloqueada mesmo tendo bytes "sãos" disponíveis.
Em tempo real (vídeo, jogos), isso é prejudicial; é um dos motivos do QUIC (HTTP/3) rodar sobre UDP e implementar streams independentes, evitando o bloqueio de uma stream afetar as outras.
10. Resumo
O modelo orientado ao fluxo é a espinha dorsal das aplicações que transferem grandes volumes de dados sem estrutura natural de mensagem (transferência de arquivos, terminal interativo, web). Ao abrir mão de fronteiras explícitas, o TCP ganha a liberdade de otimizar o tamanho e o ritmo dos segmentos, adaptando-se dinamicamente à rede subjacente. A contrapartida — implementar framing — é pequena perto do ganho em throughput e simplicidade para a maioria das aplicações.
A função recv() POSIX suporta o flag MSG_WAITALL que bloqueia até preencher todo o buffer requisitado — útil para ler um frame de tamanho conhecido de uma vez só, abstraindo parcialmente o modelo de fluxo.