Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
1. Introdução ao UDP
O conjunto de protocolos da Internet admite um protocolo de transporte sem conexões, o
UDP (User Datagram Protocol). O UDP oferece um meio para as aplicações enviarem datagramas IP
encapsulados sem que seja necessário estabelecer uma conexão. O UDP transmite segmentos que
consistem em um cabeçalho de 8 bytes, seguido pela carga útil. O cabeçalho é mostrado na Figura
6.23. As duas portas servem para identificar os pontos extremos nas máquinas de origem e destino.
Quando um pacote UDP chega, sua carga útil é entregue ao processo associado a porta de destino.
Essa associação ocorre quando a primitiva BIND ou algo semelhante é usado. De fato, o principal
valor de se ter o UDP em relação ao uso do IP bruto é a adição das portas de origem e destino. Sem os
campos de portas, a camada de transporte não saberia o que fazer com o pacote. Com eles, a camada
entrega segmentos corretamente.
A porta de origem é usada principalmente quando uma resposta dever ser devolvida a
origem. Copiando o campo Source port do segmento de entrada no campo Destination port do
segmento de saída, o processo que transmite a resposta pode especificar qual processo na máquina
transmissora deve recebe-lo.
O campo UDP length inclui o cabeçalho de 8 bytes e os dados. O campo UDP checksum é
opcional e armazenado como 0 se não for calculado (um valor 0 verdadeiro calculado é armazenado
com todos os bits iguais a 1). É tolice desativa-lo, a menos que a qualidade dos dados não tenha
importância (por exemplo, no caso de voz digitalizada).
Vale a pena mencionar explicitamente algumas ações que o UDP não realiza. Ele não realiza
controle de fluxo, controle de erros ou retransmissão após a recepção de um segmento incorreto.
Tudo isso cabe aos processos do usuário. O que ele faz é fornecer uma interface para o protocolo IP
com o recurso adicional de demultiplexação de vários processos que utilizam as portas. Isso é tudo
que ele faz. Para aplicações que precisam ter controle preciso sobre o fluxo de pacotes, o controle de
erros ou a sincronização, o UDP fornece apenas aquilo que é determinado.
Uma área na qual o UDP é especialmente útil é a de situações cliente/servidor. Com
frequência, o cliente envia uma pequena solicitação ao servidor e espera uma pequena resposta de
volta. Se a solicitação ou a resposta se perder, o cliente simplesmente chegara ao timeout e tentará
de novo. Não só o código é simples, mas é necessário um número menor de mensagens (uma em cada
sentido) do que no caso de um protocolo que exige uma configuração inicial.
Uma aplicação que utiliza o UDP desse modo é o DNS (Domain Name System), protocolo da
camada de aplicação. Em resumo, um programa que precisa pesquisar o endereço IP de algum nome
de host — por exemplo, www.cs.berkeley.edu — pode enviar um pacote UDP contendo o nome do
host a um servidor DNS. O servidor responde com um pacote UDP que contém o endereço IP. Não é
necessária nenhuma configuração antecipada e também nenhum encerramento posterior. Basta
enviar duas mensagens pela rede.
Figura 6.24: Etapas na criação de uma chamada de procedimento remoto. Os stubs estão
sombreados
Apesar da elegância conceitual da RPC, existem alguns períodos ocultos. Um deles é o uso de
parâmetros de ponteiros. Normalmente, a passagem de um ponteiro a um procedimento não e
problema. O procedimento chamado pode usar um ponteiro do mesmo modo que o chamador o
utiliza, porque ambos os procedimentos convivem no mesmo espaço de endereço virtuais. Com a
RPC, a passagem de ponteiros é impossível, porque o cliente e o servidor estão em espaços de
endereços diferentes. Em alguns casos, podem ser usados artifícios para torna possível a passagem
de ponteiros.
Um segundo problema é que, em linguagens com tipificação fraca como C, é perfeitamente
valido escrever um procedimento que calcula o produto interno de dois vetores (arrays), sem
especificar o tamanho de cada vetor. Cada um deles poderia terminar com um valor especial
conhecido apenas pelo procedimento de chamada e pelo procedimento chamado. Sob essas
circunstancias, é essencialmente impossível para o stub do cliente empacotar os parâmetros: ele não
tem como determinar o tamanho desses parâmetros.
Um terceiro problema é que nem sempre é possível deduzir os tipos dos parâmetros, nem
mesmo a partir de uma especificação formal ou do próprio código. Um exemplo é printf, que pode ter
qualquer número de parâmetros (pelo menos um), e os parâmetros podem ser uma mistura
arbitraria de números inteiros, curtos, longos, de caracteres, de strings, de números em ponto
flutuante de diversos tamanhos e de outros tipos. Tentar chamar prinif como um procedimento
remoto seria praticamente impossível, porque C é uma linguagem muito permissiva. Porém, uma
regra estabelecendo que a RPC pode ser usada desde que você não programe em C (ou em C++) não
seria muito popular.
Um quarto problema se relaciona ao uso de variáveis globais. Normalmente, o procedimento
Chamador e o procedimento chamado podem se comunicar usando variáveis globais, além de
parâmetros. Se o procedimento chamado for agora deslocado para uma máquina remota, o código
falhara, porque as variáveis globais não serão mais compartilhadas.
Esses problemas não pretendem sugerir que a RPC é impossível. De fato, ela é amplamente
utilizada, mas são necessárias algumas restrições para faze-la funcionar bem na pratica. É claro que
a RPC não precisa usar pacotes UDP, mas RPC e UDP se adaptam bem, e o UDP é comumente utilizado
para RPC. Porém, quando os parâmetros ou resultados são maiores que o pacote máximo do UDP, ou
quando a operação solicitada não é idempotente (isto e, não pode ser repetida com segurança, como
ocorre quando se incrementa um contador), pode ser necessário instalar uma conexão TCP e enviar
a solicitação por ela, em vez de usar o UDP.
O TCP (Transmission Control Protocol) foi projetado especificamente para oferecer um fluxo
de bytes fim a fim confiável em uma inter-rede não confiável. Uma inter-rede é diferente de uma
única rede porque suas diversas partes podem ter topologias, larguras de banda, retardos, tamanhos
de pacote e outros parâmetros completamente diferentes. O TCP foi projetado para se adaptar
dinamicamente as propriedades da inter-rede e ser robusto diante dos muitos tipos de falhas que
podem ocorrer.
Cada máquina compatível com o TCP tem uma entidade de transporte TCP, que pode ser um
procedimento de biblioteca, um processo do usuário ou parte do núcleo. Em todos os casos, ele
gerencia fluxos e interfaces TCP para a camada IP. Uma entidade TCP aceita fluxos de dados do
usuário provenientes de processos locais, divide-os em partes de no máximo 64 Kb (na pratica, com
frequência temos 1460 bytes de dados, para que ele possa caber em um único quadro Ethernet com
os cabeçalhos IP e TCP) e envia cada parte em um datagrama IP distinto. Quando os datagramas IP
que contém dados TCP chegam a uma máquina, eles são enviados a entidade TCP, que restaura os
fluxos de bytes originais. Para simplificar, as vezes utilizamos apenas "TCP", a fim de fazer referência
tanto a entidade de transporte TCP (um software) quanto ao protocolo TCP (um conjunto de regras).
Pelo contexto, ficara claro a qual deles estaremos nos referindo. Por exemplo, em "O usuário envia os
dados para TCP", está claro que estamos nos referindo a entidade de transporte TCP.
A camada IP não oferece qualquer garantia de que os datagramas serão entregues da forma
apropriada; portanto, cabe ao TCP administrar os timers e retransmiti-los sempre que necessário. Os
datagramas também podem chegar fora de ordem; o TCP também terá de reorganiza-los em
mensagens na sequência correta. Resumindo, o TCP deve fornecer a confiabilidade que a maioria dos
usuários deseja, mas que o IP não oferece.
Figura 6.28: (a) Quatro segmentos de 512 bytes enviados como datagramas IP separados. (b) Os
2048 bytes de dados entregues a aplicação em uma única chamada READ
No UNIX, os arquivos também têm essa propriedade. O leitor de um arquivo não é capaz de
distinguir se ele foi gravado um bloco por vez, um byte por vez ou todo de uma vez. A exemplo do
que acontece com um arquivo UNIX, o software TCP não tem ideia do significado dos bytes, e também
não está interessado em descobri-lo. Um byte é apenas um byte.
Quando uma aplicação repassa dados para a entidade TCP, ela pode envia-los imediatamente
ou armazena-los em um buffer (para aguardar outros dados e enviar um volume maior de uma só
vez), de acordo com suas necessidades. Entretanto, há ocasiões em que a aplicação realmente quer
que os dados sejam enviados de imediato. Por exemplo, suponha que um usuário tenha se conectado
a uma máquina remota. Depois que uma linha de comandos é preenchida e a tecla Enter (ou Carriage
Return) é pressionada, é essencial que a linha seja transportada à máquina remota imediatamente, e
não guardada no buffer até a chegada da próxima linha. Para forçar a saída dos dados, as aplicações
podem usar o flag PUSH, que informa ao serviço TCP para não retardar a transmissão.
Algumas aplicações antigas utilizavam o flag PUSH como um tipo de marcador para assinalar
os limites das mensagens. Ainda que esse artificio as vezes funcione, ele pode falhar, pois nem todas
as implementações do TCP repassam o flag PUSH para a aplicação no lado receptor. Além disso, se
outros flags PUSH chegarem antes da transmissão do primeiro (por exemplo, porque a linha de saída
está ocupada), o serviço TCP terá a liberdade de agrupar todos os dados que contiverem o flag PUSH
em um único datagrama IP, sem qualquer separação entre as várias partes.
Um último recurso do serviço TCP que vale a pena mencionar é o dos dados urgentes.
Quando um usuário interativo pressiona a tecla DEL ou as teclas CTRL- C para interromper um
processo remoto já iniciado, a aplicação transmissora adiciona algumas informações de controle ao
fluxo de dados e o entrega ao TCP juntamente com um flag URGENT. Isso faz com que o serviço TCP
pare de acumular dados e transmita tudo imediatamente. Quando os dados urgentes são recebidos
no destino, a aplicação receptor a é interrompida (na terminologia UNIX, ela recebe um sinal) e para
tudo o que estiver fazendo para ler o fluxo de dados e encontrar os dados urgentes. O final dos dados
urgentes é marcado para que a aplicação saiba quando eles terminarem. O início dos dados urgentes
não é marcado, e a aplicação deve saber identifica-lo. Basicamente, esse esquema oferece um
mecanismo de sinalização pouco sofisticado, deixando a maior parte do trabalho para a aplicação.