Você está na página 1de 57

Tarefa de Programao 1: Construindo um servidor Web multithreaded

Neste laboratrio, ser desenvolvido um servidor Web em duas etapas. No final, voc
ter construdo um servidor Web multithreaded, que ser capaz de processar mltiplas
requisies de servios simultneas em paralelo. Voc dever demonstrar que seu
servidor Web capaz de enviar sua home page ao browser Web.
Implementaremos a verso 1.0 do HTTP, definido na RFC-1945, onde requisies
HTTP separadas so enviadas para cada componente da pgina Web. Este servidor
dever manipular mltiplas requisies simultneas de servios em paralelo. Isso
significa que o servidor Web multithreaded. No thread principal, o servidor escuta
uma porta fixa. Quando recebe uma requisio de conexo TCP, ele ajusta a conexo
TCP atravs de outra porta e atende essa requisio em um thread separado. Para
simplificar esta tarefa de programao, desenvolveremos o cdigo em duas etapas. No
primeiro estgio, voc ir escrever um servidor multithreaded que simplesmente exibe o
contedo da mensagem de requisio HTTP recebida. Assim que esse programa
funcionar adequadamente, voc adicionar o cdigo necessrio para gerar a resposta
apropriada.
Enquanto voc desenvolve o cdigo, pode testar seu servidor a partir de um browser
Web. Mas lembre que o servidor no est atendendo pela porta padro 80; logo,
preciso especificar o nmero da porta junto URL que voc fornecer ao browser Web.
Por exemplo, se o nome da sua mquina host.someschool.edu, seu servidor est
escutando a porta 6789, e voc quer recuperar o arquivo index.html, ento deve
especificar ao browser a seguinte URL:
http://host.someschool.edu:6789/index.html

Se voc omitir :6789, o browser ir assumir a porta 80, que, provavelmente, no ter
nenhum servidor escuta.
Quando o servidor encontra um erro, ele envia uma mensagem de resposta com a fonte
HTML apropriada, de forma que a informao do erro seja exibida na janela do
browser.

Servidor Web em Java: Parte A


Nas etapas seguintes, veremos o cdigo para a primeira implementao do nosso
servidor Web. Sempre que voc vir o sinal ?, dever fornecer o detalhe que estiver
faltando.
Nossa primeira implementao do servidor Web ser multithreaded, e o processamento
de cada requisio de entrada ter um local dentro de um thread separado de execuo.
Isso permite ao servidor atender a mltiplos clientes em paralelo, ou desempenhar
mltiplas transferncias de arquivo a um nico cliente em paralelo. Quando criamos um
novo thread de execuo, precisamos passar ao construtor de threads uma instncia de
algumas classes que implementa a interface Runnable. Essa a razo de se definir uma
classe separada chamada HttpRequest. A estrutura do servidor Web mostrada a
seguir:
import java.io.* ;
import java.net.* ;
import java.util.* ;
public final class WebServer
{
public static void main(String arvg[]) throws Exception
{

}
}
final class HttpRequest implements Runnable
{

Normalmente, servidores Web processam requisies de servio recebidas atravs da


conhecida porta 80. Voc pode escolher qualquer porta acima de 1024, mas lembre-se
de usar o mesmo nmero de porta quando fizer requisies ao seu servidor Web a partir
do seu browser.
Public static void main(String arvg[]) throws Exception
{
// Ajustar o nmero da porta.
int port = 6789;

A seguir, abrimos um socket e esperamos por uma requisio de conexo TCP. Como
estaremos atendendo a mensagens de requisio indefinidamente, colocamos a operao
de escuta dentro de um lao infinito. Isso significa que precisaremos terminar o servidor
Web digitando ^C pelo teclado.
// Estabelecer o socket de escuta.
?
// Processar a requisio de servio HTTP em um lao infinito.
While (true)

// Escutar requisio de conexo TCP.


?

Quando uma requisio de conexo recebida, criamos um objeto HttpRequest,


passando ao seu construtor uma referncia para o objeto Socket que representa nossa
conexo estabelecida com o cliente.
//Construir um objeto para processar a mensagem de requisio HTTP.
HttpRequest request = new HttpRequest ( ? );
// Criar um novo thread para processar a requisio.
Thread thread = new Thread(request);
//Iniciar o thread.
Thread.start();

Para que o objeto HttpRequest manipule as requisies de servio HTTP de entrada em


um thread separado, criamos primeiro um novo objeto Thread, passando ao seu
construtor a referncia para o objeto HttpRequest, ento chamamos o mtodo start()
do thread.
Aps o novo thread ter sido criado e iniciado, a execuo no thread principal retorna
para o topo do loop de processamento da mensagem. O thread principal ir ento
bloquear, esperando por outra requisio de conexo TCP, enquanto o novo thread
continua rodando. Quando outra requisio de conexo TCP recebida, o thread

principal realiza o mesmo processo de criao de thread, a menos que o thread anterior
tenha terminado a execuo ou ainda esteja rodando.
Isso completa o cdigo em main(). Para o restante do laboratrio, falta o
desenvolvimento da classe HttpRequest.
Declaramos duas variveis para a classe HttpRequest: CRLF e socket. De acordo com a
especificao HTTP, precisamos terminar cada linha da mensagem de resposta do
servidor com um carriage return (CR) e um line feed (LF), assim definimos a CRLF de
forma conveniente. A varivel socket ser usada para armazenar uma referncia ao
socket de conexo. A estrutura da classe HttpRequest mostrada a seguir:
final class HttpRequest implements Runnable
{
final static String CRLF = \r\n;
Socket socket;
// Construtor
public HttpRequest(Socket socket) throws Exception
{
this.socket = socket;
}
// Implemente o mtodo run() da interface Runnable.
Public void run()
{

}
private void processRequest() throws Exception
{

}
}

Para passar uma instncia da classe HttpRequest para o construtor de Threads, a


HttpRequest

deve implementar a interface Runnable. Isso significa simplesmente que

devemos definir um mtodo pblico chamado run() que retorna void. A maior parte do
processamento ocorrer dentro do processRequest(), que chamado de dentro do
run().

At este ponto, apenas repassamos as excees em vez de apanh-las. Contudo, no


podemos repass-las a partir do run(), pois devemos aderir estritamente declarao
do run() na interface Runnable, a qual no repassa exceo alguma. Colocaremos todo
o cdigo de processamento no processRequest(), e a partir da repassaremos as
excees ao run(). Dentro do run(), explicitamente recolhemos e tratamos as excees
com um bloco try/catch.
// Implementar o mtodo run() da interface Runnable.
Public void run()
{
try {
processRequest();
} catch (Exception e) {
System.out.println(e);
}
}

Agora, vamos desenvolver o cdigo de dentro do processRequest(). Primeiro


obtemos referncias para os trechos de entrada e sada do socket. Ento colocamos os
filtros InputStreamReader e BufferedReader em torno do trecho de entrada. No
entanto, no colocamos nenhum filtro em torno do trecho de sada, pois estaremos
escrevendo bytes diretamente no trecho de sada.
Private void processRequest() throws Exception
{
// Obter uma referncia para os trechos de entrada e sada do
socket.
InputStream is = ?;
DataOutputStream os = ?;
// Ajustar os filtros do trecho de entrada.
?
BufferedReader br = ?;

Agora estamos preparados para capturar mensagens de requisio dos clientes, fazendo
a leitura dos trechos de entrada do socket. O mtodo readLine() da classe
5

BufferedReader

ir extrair caracteres do trecho de entrada at encontrar um caracter

fim-de-linha, ou em nosso caso, uma seqncia de caracter fim-de-linha CRLF.


O primeiro item disponvel no trecho de entrada ser a linha de requisio HTTP. (Veja
Seo 2.2 do livro-texto para a descrio disso e dos seguintes campos.)
// Obter a linha de requisio da mensagem de requisio HTTP.
String requestLine = ?;
//

Exibir a linha de requisio.

System.out.println();
System.out.println(requestLine);

Aps obter a linha de requisio do cabealho da mensagem, obteremos as linhas de


cabealho. Desde que no saibamos antecipadamente quantas linhas de cabealho o
cliente ir enviar, podemos obter essas linhas dentro de uma operao de looping.
// Obter e exibir as linhas de cabealho.
String headerLine = null;
While ((headerLine = br.readLine()).length() != 0) {
System.out.println(headerLine);
}

No precisamos das linhas de cabealho, a no ser para exibi-las na tela; portanto,


usamos uma varivel string temporria, headerLine, para manter uma referncia aos
seus valores. O loop termina quando a expresso
(headerLine = br.readLine()).length()

chegar a zero, ou seja, quando o headerLine tiver comprimento zero. Isso acontecer
quando a linha vazia ao final do cabealho for lida.
Na prxima etapa deste laboratrio, iremos adicionar o cdigo para analisar a
mensagem de requisio do cliente e enviar uma resposta. Mas, antes de fazer isso,
vamos tentar compilar nosso programa e test-lo com um browser. Adicione as linhas a
seguir ao cdigo para fechar as cadeias e conexo de socket.
// Feche as cadeias e socket.
os.close();
br.close();
socket.close();

Aps compilar o programa com sucesso, execute-o com um nmero de porta disponvel,
e tente contat-lo a partir de um browser. Para fazer isso, digite o endereo IP do seu
servidor em execuo na barra de endereos do seu browser. Por exemplo, se o nome da
sua mquina host.someschool.edu, e o seu servidor roda na porta 6789, ento voc
deve especificar a seguinte URL:
http://host.someschool.edu:6789/

O servidor dever exibir o contedo da mensagem de requisio HTTP. Cheque se ele


est de acordo com o formato de mensagem mostrado na figura do HTTP Request
Message da Seo 2.2 do livro-texto.

Servidor Web em Java: Parte B


Em vez de simplesmente encerrar a thread aps exibir a mensagem de requisio HTTP
do browser, analisaremos a requisio e enviaremos uma resposta apropriada. Iremos
ignorar a informao nas linhas de cabealho e usar apenas o nome do arquivo contido
na linha de requisio. De fato, vamos supor que a linha de requisio sempre especifica
o mtodo GET e ignorar o fato de que o cliente pode enviar algum outro tipo de
requisio, tal como HEAD o POST.
Extramos o nome do arquivo da linha de requisio com a ajuda da classe
StringTokenizer.

Primeiro, criamos um objeto StringTokenizer que contm a string

de caracteres da linha de requisio. Segundo, pulamos a especificao do mtodo, que


supusemos como sendo GET. Terceiro, extramos o nome do arquivo.
// Extrair o nome do arquivo a linha de requisio.
StringTokenizer tokens = new StringTokenizer(requestLine);
tokens.nextToken(); // pular o mtodo, que deve ser GET
String fileName = tokens.nextToken();
// Acrescente um . de modo que a requisio do arquivo esteja dentro
do diretrio atual.
fileName = . + fileName;

Como o browser precede o nome do arquivo com uma barra, usamos um ponto como
prefixo para que o nome do caminho resultante inicie dentro do diretrio atual.
Agora que temos o nome do arquivo, podemos abrir o arquivo como primeira etapa para
envi-lo ao cliente. Se o arquivo no existir, o construtor FileInputStream() ir
retornar a FileNotFoundException. Em vez de retornar esta possvel exceo e
encerrar a thread, usaremos uma construo try/catch para ajustar a varivel booleana
fileExists

para falsa. A seguir, no cdigo, usaremos este flag para construir uma

mensagem de resposta de erro, melhor do que tentar enviar um arquivo no existente.


// Abrir o arquivo requisitado.
FileInputStream fis = null;
Boolean fileExists = true;
try {
fis = new FileInputStream(fileName);
} catch (FileNotFoundException e) {
fileExists = false;
}

Existem trs partes para a mensagem de resposta: a linha de status, os cabealhos da


resposta e o corpo da entidade. A linha de status e os cabealhos da resposta so
terminados pela de seqncia de caracteres CRLF. Iremos responder com uma linha de
status, que armazenamos na varivel statusLine, e um nico cabealho de resposta,
que armazenamos na varivel contentTypeLine. No caso de uma requisio de um
arquivo no existente, retornamos 404 Not Found na linha de status da mensagem de
resposta e inclumos uma mensagem de erro no formato de um documento HTML no
corpo da entidade.
// Construir a mensagem de resposta.
String statusLine = null;
String contentTypeLine = null;
String entityBody = null;
If (fileExists) {
statusLine = ?;
contentTypeLine = Content-type: +
contentType( filename ) + CRLF;
} else {
statusLine = ?;

contentTypeLine = ?;
entityBody = <HTML> +
<HEAD><TITTLE>Not Found</TITTLE></HEAD> +
<BODY>Not Found</BODY></HTML>;

Quando o arquivo existe, precisamos determinar o tipo MIME do arquivo e enviar o


especificador do tipo MIME apropriado. Fazemos esta determinao num mtodo
privado separado chamado contentType(), que retorna uma string que podemos
incluir na linha de tipo de contedo que estamos construindo.
Agora podemos enviar a linha de status e nossa nica linha de cabealho para o browser
escrevendo na cadeia de sada do socket.
// Enviar a linha de status.
os.writeBytes(statusLine);
// Enviar a linha de tipo de contedo.
os.writebytes(?);
//

Enviar

uma

linha

em

branco

para

indicar

fim

das

linhas

de

cabealho.
os.writeBytes(CRLF);

Agora que a linha de status e a linha de cabealho com delimitador CRLF foram
colocadas dentro do trecho de sada no caminho para o browser, hora de fazermos o
mesmo com o corpo da entidade. Se o arquivo requisitado existir, chamamos um
mtodo separado para enviar o arquivo. Se o arquivo requisitado no existir, enviamos a
mensagem de erro codificada em HTML que preparamos.
// Enviar o corpo da entidade.
If (fileExists) {
sendBytes(fis, os);
fis.close();
} else {
os.writeBytes(?);
}

Aps enviar o corpo da entidade, o trabalho neste thread est terminado; ento fechamos
as cadeias e o socket antes de encerrarmos.

Ainda precisamos codificar os dois mtodos que referenciamos no cdigo acima, ou


seja, o mtodo que determina o tipo MIME, contentType() e o mtodo que escreve o
arquivo requisitado no trecho de sada do socket. Primeiro veremos o cdigo para enviar
o arquivo para o cliente.
private static void sendBytes(FileInputStream fis, OutputStream os)
throws Exception
{
// Construir um buffer de 1K para comportar os bytes no caminho
para o socket.
byte[] buffer = new byte[1024];
int bytes = 0;
// Copiar o arquivo requisitado dentro da cadeia de sada do
socket.
While((bytes = fis.read(buffer)) != -1 ) {
os.write(buffer, 0, bytes);
}
}

Ambos read() e write() repassam excees. Em vez de pegar essas excees e


manipul-las em nosso cdigo, iremos repass-las pelo mtodo de chamada.
A varivel, buffer, o nosso espao de armazenamento intermedirio para os bytes em
seu caminho desde o arquivo para a cadeia de sada. Quando lemos os bytes do
FileInputStream,

verificamos se read() retorna menos um (1), indicando que o final

do arquivo foi alcanado. Se o final do arquivo no foi alcanado, read() retorna o


nmero de bytes que foi colocado dentro do buffer. Usamos o mtodo write() da
classe OutputStream para colocar estes bytes na cadeia de sada, passando para ele o
nome do vetor dos bytes, buffer, o ponto inicial nesse vetor, 0, e o nmero de bytes no
vetor para escrita, bytes.
A parte final do cdigo necessria para completar o servidor Web um mtodo que
examina a extenso de um nome de arquivo e retorna uma string que representa o tipo
MIME. Se a extenso do arquivo for desconhecida, podemos retornar o tipo
application/octet-stream.
Private static String contentType(String fileName)
{
if(filename.endsWith(.htm) || fileName.endsWith(.html)) {

10

return text/html;
}
if(?) {
?;
}
of(?) {
?;
}
return application/octet-stream;
}

Est faltando um pedacinho deste mtodo. Por exemplo, nada retornado para arquivos
GIF ou JPEG. Voc mesmo poder adicionar os tipos de arquivo que esto faltando, de
forma que os componentes da sua home page sejam enviados com o tipo de contedo
corretamente especificado na linha de cabealho do tipo de contedo. Para GIFs, o tipo
MIME image/gif e, para JPEGs, image/jpeg.
Isso completa o cdigo para a segunda fase de desenvolvimento do nosso servidor Web.
Tente rodar o servidor a partir do diretrio onde sua home page est localizada e
visualizar os arquivos da home page com o browser. Lembre-se de incluir um
especificador de porta na URL, de forma que o browser no tente se conectar pela porta
80. Quando voc se conectar ao servidor Web em execuo, examine as requisies
GET de mensagens que o servidor Web receber do browser.

11

Tarefa de Programao 2: Agente usurio de correio em Java

Neste laboratrio, voc implementar um agente usurio de correio que envia e-mail
para outros usurios. Sua tarefa programar a interao SMTP entre o MUA e o
servidor SMTP local. O cliente prov uma interface grfica de usurio, a qual deve
conter campos para os endereos do remetente e do destinatrio, para o assunto da
mensagem e para a prpria mensagem. A interface de usurio deve ser parecida com:

Com essa interface, quando se quer enviar um e-mail, preciso preencher os endereos
completos do remetente e do destinatrio (exemplo: user@someschool.edu, e no
simplesmente user). Voc poder enviar e-mail para apenas um destinatrio. Tambm
ser necessrio informar o nome (ou endereo IP) do seu servidor de correio local. Veja
Querying the DNS abaixo para mais informaes sobre como obter o endereo do
servidor de correio local.
Quando tiver encerrado a composio do e-mail, pressione Send para envi-lo.

12

O cdigo
O programa consiste de quatro classes:
MailClient

A interface do usurio

Message

A mensagem de e-mail

Envelope

Envelope SMTP que envolve a mensagem

SMTPConnection

Conexo ao servidor SMTP

Voc precisar completar o cdigo na classe SMTPConnection de modo que no fim voc
tenha um programa capaz de enviar e-mail para qualquer destinatrio. O cdigo para a
classe SMTPConnection est no fim desta pgina. O cdigo para outras trs classes
fornecido nesta pgina.
Os locais onde voc dever completar o cdigo esto marcados com comentrios
/* Fill in */.

Cada um deles requer uma ou mais linhas de cdigo.

A classe MailClient prov a interface do usurio e chama as outras classes quando


necessrio. Quando o boto Send pressionado, a classe MailClient constri um
objeto de classe Message para comportar a mensagem de correio. O objeto Message
comporta os cabealhos e o corpo da mensagem atual. Ele constri o envelope SMTP
usando a classe Envelope. Essa classe compreende a informao SMTP do remetente e
do destinatrio, o servidor SMTP de domnio do remetente e o objeto Message. Ento o
objeto MailClient cria o objeto SMTPConnection, que abre uma conexo para o
servidor SMTP, e o objeto MailClient envia a mensagem atravs dessa conexo. O
envio do e-mail acontece em trs fases:
1. O objeto MailClient cria o objeto SMTPConnection e abre a conexo para o servidor
SMTP.
2. O objeto MailClient envia a mensagem usando a funo SMTPConnection.send().
3. O objeto MailClient fecha a conexo SMTP.
A classe Message contm a funo isValid(), que usada para verificar os endereos
do remetente e do destinatrio para se certificar de que h apenas um nico endereo e
que este contm o sinal @. O cdigo fornecido no realiza nenhum outro tipo de
verificao de erro.

13

Cdigos de resposta
Para o processo bsico de envio de mensagem, necessrio implementar apenas uma
parte do SMTP. Neste laboratrio, voc precisar implementar somente os seguintes
comandos SMTP:
Comando

Cdigo de resposta

DATA

354

HELO

250

MAIL FROM

250

QUIT

221

RCPT TO

250

A tabela acima tambm lista os cdigos de resposta aceitos para cada um dos comandos
SMTP que voc precisar implementar. Para simplificar, pode-se presumir que qualquer
outra resposta do servidor indica um erro fatal e aborta o envio da mensagem. Na
realidade, o SMTP distingue entre erros transientes (cdigos de resposta 4xx) e
permanentes (cdigos de resposta 5xx), e o remetente autorizado a repetir os
comandos que provocaram um erro transiente. Veja o Apndice E da RFC-821 para
mais detalhes.
Alm disso, quando voc abre uma conexo para o servidor, ele ir responder com o
cdigo 220.
Nota: A RFC-821 permite o cdigo 251 como resposta ao comando RCPT TO para
indicar que o destinatrio no um usurio local. Voc pode verificar manualmente com
o comando telnet o que o seu servidor SMTP local responde.

Dicas
A maior parte do cdigo que voc precisar completar similar ao cdigo que voc
escreveu no laboratrio de servidor Web. Voc pode us-lo aqui para ajud-lo.
Para facilitar o debug do seu programa, no inclua logo de incio o cdigo que abre o
socket, mas as seguintes definies: fromServer e toServer. Desse modo, seu
programa envia os comandos para o terminal. Atuando como um servidor SMTP, voc
precisar fornecer os cdigos de resposta correto. Quando seu programa funcionar,
adicione o cdigo que abre o socket para o servidor.
14

fromServer = new BufferedReader(new InputStreamReader(System.in));


toServer = System.out;

As linhas para abrir e fechar o socket, por exemplo, as linhas connection = ... no
construtor e a linha connection.close() na funo close(), foram excludas como
comentrios por default.
Comearemos completando a funo parseReply(), que ser necessria em vrios
lugares. Na funo parseReply(), voc deve usar a classe StringTokenizer para
analisar as strings de resposta. Voc pode converter uma string em um inteiro da
seguinte forma:
Int i = Integer.parseInt(argv[0]);

Na funo sendCommand(), voc deve usar a funo writeBytes() para escrever os


comandos para o servidor. A vantagem de usar writeBytes() em vez de write() que
a primeira converte automaticamente as strings em bytes, que so o que o servidor
espera. No se esquea de encerrar cada comando com a string CRLF.
Voc pode repassar excees assim:
throw new Exception();

Voc no precisa se preocupar com os detalhes, desde que as excees neste laboratrio
sejam usadas apenas para sinalizar um erro, e no para dar informao detalhada sobre o
que est errado.

Exerccios opcionais
Tente fazer os seguintes exerccios opcionais para tornar seu programa mais sofisticado.
Para estes exerccios, ser necessrio modificar outras classes tambm (MailClient,
Message, e Envelope).

Verificar o endereo do remetente. A classe System contm informaes sobre


o nome de usurio, e a classe InetAddress contm mtodos para encontrar o
nome do hospedeiro local. Use-as para construir o endereo do remetente para o
Envelope em vez de usar o valor fornecido pelo usurio no campo From do
cabealho.

15

Cabealhos adicionais. Os e-mails gerados possuem apenas quatro campos no


cabealho, From, To, Subject e Date. Adicione outros campos de cabealho de
acordo com a RFC-822, por exemplo, Message-ID, Keywords. Verifique a RFC
para definies dos diferentes campos.

Mltiplos destinatrios. At este ponto, o programa permite apenas enviar email a um nico destinatrio. Modifique a interface de usurio para incluir um
campo Cc e modifique o programa para enviar e-mail a dois destinatrios. Para
aumentar o desafio, modifique o programa para enviar e-mail a um nmero
arbitrrio de destinatrios.

Maior verificao de erros. O cdigo fornecido presume que todos os erros que
ocorrem durante a conexo SMTP so fatais. Adicione cdigo para distinguir
entre erros fatais e no fatais e adicione um mecanismo para sinaliz-los ao
usurio. Cheque o RFC para saber o significado dos diferentes cdigos de
resposta. Este exerccio pode requerer grandes modificaes nas funes
send( ), sendCommand( ) e parseReply( ).

Consultando o DNS
O DNS (Domain Name System) armazena informaes em registros de recursos. Os
nomes para mapeamentos de endereo IP so armazenados nos registros de recursos do
tipo A (Address). Os registros do tipo NS (NameServer) guardam informaes sobre
servidores de nomes e os registros do tipo MX (Mail eXchange) dizem qual servidor
est manipulando a entrega de correio no domnio.
O servidor que voc precisa encontrar o que est manipulando o correio para o
domnio da sua escola. Primeiramente, preciso encontrar o servidor de nomes da
escola e ento consult-lo pelo MX-hospedeiro. Supondo que voc est na Someschool
e que seu domnio someschool.edu, voc pode fazer o seguinte:
1. Encontrar o endereo de um servidor de nomes para o domnio do maior nvel .edu
(NS query)
2. Consultar o servidor de nomes de .edu , que buscar o servidor de nomes do domnio
someschool.edu

, para conseguir o endereo do servidor de nomes da Someschool.

(NS query)

16

3. Perguntar ao servidor de nomes da Someschool pelos registros-MX para o domnio


someschool.edu.

(MX query)

Pergunte ao administrador do seu sistema local sobre como realizar manualmente


perguntas ao DNS.
No UNIX, voc pode perguntar manualmente ao DNS com o comando nslookup. A
sintaxe desse comando encontra-se a seguir. Note que o argumento host pode ser um
domnio.
Normal query

nslookup host

Normal query using a given Server

nslookup host server

NS-query

nslookup type=NS host

MX-query

nslookup type=MX host

A resposta para o MX-query pode conter mltiplas contas de e-mail. Cada uma delas
precedida por um nmero que ser o valor preferencial para este servidor. Valores
menores de preferncia indicam servidores preferidos de modo que voc possa usar o
servidor com o valor mais baixo de preferncia.

SMTPConnection.java
Este o cdigo para a classe SMTPConnection que voc precisar completar. O cdigo
para as outras trs classes fornecido neste ponteiro.

Import java.net.*;
Import java.io.*;
Import java.util.*;
/**
* Abre uma conexo SMTP para o servidor de correio e envia um e-mail.
*
*/
public class SMTPConnection {
/* O socket para o servidor */
private Socket connection;

17

/* Trechos para leitura e escrita no socket */


private BufferedReader fromServer;
private DataOutputStream toServer;
private static final int SMTP_PORT = 25;
private static final String CRLF = \r\n;
/*

Estamos

conectados?

Usamos

close()

para

determinar

que

fazer.*/
private boolean isConnected = false;
/* Crie um objeto SMTPConnection. Crie os sockets e os
trechos associados. Inicie a conexo SMTP. */
public SMTPConnection(Envelope envelope) throws IOException {
// connection = /* Preencher */;
fromServer = /* Preencher */;
toServer =

/* Preencher */;

/* Preencher */
/* Ler uma linha do servidor e verificar se o cdigo de
resposta 220. Se no for, lance uma IOException. */
/* Preencher */
/* SMTP handshake. Precisamos do nome da mquina local.
Envie o comando handskhake do SMTP apropriado. */
String localhost = /* Preencher */;
sendCommand( /* Preencher*/ );
isConnected = true;
}
/* Envie a mensagem. Escreva os comandos SMTP corretos na ordem
correta. No verifique de erros, apenas lance-os ao chamador. */
public void send(Envelope envelope) throws IOException {
/* Preencher */
/* Envie todos os comandos necessrios para enviar a mensagem.
Chame o sendCommand() para fazer o trabalho sujo. No apanhe a
exceo lanada pelo sendCommand(). */
/* Preencher */
}
/* Feche a conexo. Primeiro, termine no nvel SMTP, ento feche o
socket. */

18

public void cloce() {


isConnected = false;
try {
sendCommand( /* Preencher */ );
// connection.close();
} catch (IOException e) {
System.out.println(Unable to lose connection: + e);
isConnected = true;
}
}
/* Envie um comando SMTP ao servidor. Cheque se o cdigo de resposta
est de acordo com o RFC 821. */
/* Preencher */
/* Escrever o comando do servidor e ler a resposta do servidor. */
/* Preencher */
/* Preencher */
/*

Cheque

se

cdigo

de

resposta

do

servidor

mesmo

do

parmetro rc. Se no, envie um IOException. */


/* Preencher */

/* Analise a linha de resposta do servidor. Retorne o cdigo de


resposta. */
private int parseReply(string reply) {
/* Preencher */
}
/* Destructor. Fecha a conexo se algo de ruim acontecer. */
protected void finalize() throws Throwable {
is(isConnected) {
close();
}
super.finalize();
}
}

19

Agente usurio de correio: verso simplificada


Este laboratrio est dividido em duas partes. Na primeira, voc dever usar o telnet
para enviar e-mail manualmente atravs de um servidor de correio SMTP. Na segunda,
voc ter de escrever o programa Java que realiza a mesma ao.

Parte 1: Enviando e-mail com telnet


Tente enviar um e-mail para voc mesmo. Isso significa que voc precisa saber o nome
do hospedeiro do servidor de correio do seu prprio domnio. Para encontrar essa
informao, voc pode consultar o DNS para buscar o registro MX que mantm
informaes sobre seu domnio de correio. Por exemplo, bob@someschool.edu possui o
domnio de correio someschool.edu. O comando a seguir consulta o DNS para encontrar
os servidores de correio responsveis pela entrega de correio neste domnio:

nslookup type=MX someschool.edu

Para a resposta a este comando, pode haver vrios servidores de correio que entregam email para as caixas de correio no domnio someschool.edu. Suponha que o nome de um
deles mx1.someschool.edu. Nesse caso, o seguinte comando estabelecer a conexo
TCP a este servidor de correio. (Note que a porta nmero 25 especificada na linha de
comando.)
telnet mx1.someschool.edu 25

Neste ponto, o programa telnet permitir a voc entrar com os comando SMTP e exibir
as respostas do servidor de correio. Por exemplo, a seqncia de comandos a seguir
envia um e-mail da Alice para o Bob.
HELO alice
MAIL FROM: <alice@crepes.fr>
RCPT TO: <bob@someschool.edu>
DATA
SUBJECT: hello
Hi Bob, Hows the weather? Alice.

20

.
QUIT

O protocolo SMTP foi originalmente projetado para permitir s pessoas interagirem


manualmente com os servidores de correio em modo de conversao. Por essa razo, se
voc digitar um comando com sintaxe incorreta, ou com argumentos inaceitveis, o
servidor retornar uma mensagem reportando isso e permitir que voc tente
novamente.
Para completar esta parte do laboratrio, voc deve enviar uma mensagem de e-mail
para voc mesmo e verificar se ela chegou.

Parte 2: Enviando e-mail com Java


A Java fornece uma API para interagir com o sistema de correio da Internet, que
chamado JavaMail. No entando, no usaremos esta API, pois ela esconde os detalhes do
SMTP e da programao de sockets. Em vez disso, voc escrever um programa Java
que estabelece uma conexo TCP com um servidor de correio atravs da interface de
socket e enviar uma mensagem de e-mail.
Voc pode colocar todo seu cdigo dentro do mtodo principal de uma classe chamada
EmailSender. Execute seu programa com o simples comando a seguir:
java EmailSender

Isso significa que voc incluir em seu cdigo os detalhes da mensagem de e-mail que
voc est tentando enviar.
Aqui est um esqueleto do cdigo que voc precisar para escrever:

import java.io.*;
import java.net.*;
public class EmailSender
{
public static void main(String() args) throws Exception
{
// Estabelecer uma conexo TCP com o servidor de correio.

21

// Criar um BufferedReader para ler a linha atual.


InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
// Ler os cumprimentos do servidor.
String response = br.readLine();
System.out.println(response);
if (!response.startsWith(220)) {
Throw new Exception(220 reply not received from server.);
}
// Pegar uma referncia para o trecho de sada do socket.
OutputStream os = socket.getOuputStream();
// Enviar o comando HELO e pegar a resposta do servidor.
String comand = Helo Alice\r\n;
System.out.println(command);
os.write(command.getBytes(US-ASCII));
response = br.readLine();
System.out.println(response);
if (!response.startsWith(250)) {
throw new Exception(250 reply not received from server.);
}
// Enviar o comando MAIL FROM.
// Enviar o comando RECP TO.
// Enviar o comando DATA.
// Enviar o dados da mensagem.
// Terminar com uma linha de um nico perodo.
// Enviar o comando QUIT.
}
}

22

Tarefa de Programao 3: Laboratrio de UDP pinger

Neste laboratrio, voc ir estudar um simples servidor de Ping da Internet escrito em


linguagem Java e implementar um cliente correspondente. A funcionalidade provida por
esses programas similar dos programas de Ping padro disponveis nos sistemas
operacionais modernos, exceto aqueles que usam o UDP em vez do ICMP (Internet
Control Message Protocol) para se comunicar. (Java no prov meios diretos para
interagir com o ICMP.)
O protocolo Ping permite a uma mquina cliente enviar um pacote de dados para uma
mquina remota, a qual retornar o dado para o cliente sem modificaes (uma ao
conhecida como eco). Entre outros usurios, o protocolo Ping permite aos hospedeiros
determinarem o tempo de resposta de outras mquinas.
Complete o cdigo para o servidor de Ping abaixo. Seu trabalho ser escrever o cliente
Ping.

Cdigo do servidor
O cdigo a seguir implementa por completo o servidor de Ping. Voc precisar compilar
e executar este cdigo. Estude-o cuidadosamente, pois ele ir ajud-lo a escrever seu
cliente de Ping.
import.java.io.*;
import.java.net.*;
import.java.util.*;
/*
* Servidor para processar as requisies de Ping sobre UDP.
*/
public class PingServer
{
private static final doubl LOSS_RATE = 0.3;
private static final int AVERAGE_DELAY = 100; //milliseconds
public static void main(String[]args) throws Exception
{
// Obter o argumento da linha de comando.

23

if (args.length !=1) {
System.out.pritln(Required arguments: port);
return;
}
int port = Integer.parseInt(args[0]);
// Criar um gerador de nmeros aleatrios para uso em simulao
de perda de pacotes e atrasos na rede.
Random random = new Random();
// Criar um socket de datagrama para receber e enviar pacotes UDP
atravs da porta especificada na linha de comando.
DatagramSocket socket = new DatagramSocket(port);
// Loop de processamento.
while(true);
//

Criar

um

pacote

de

datagrama

para

comportar

pacote

UDP // de chegada.
DatagramPacket request = new DatagramPacket(new
byte[1024],1024);
// Bloquear at que o hospedeiro receba o pacote UDP.
Socket.receive(request);
// Imprimir os dados recebidos.
printData(request);
// Decidir se responde, ou simula perda de pacotes.
if(random.nextDouble() < LOSS_RATE) {
System.out.println(Reply not sent.);
Continue;
}
// Simular o atraso da rede.
Thread.sleep((int)(random.nextDouble) * 2 * AVERAGE_DELAY));
// Enviar resposta.
InetAddress clientHost = request.getAddress();
Int clientPort = request.getPort();
Byte[]buf = request.getData();
DatagramPacket reply = new DatagramPacket(buf, buf.length,
clientHost, clientPort);

24

Socket.send(reply);
System.out.println(Reply sent.);
}
}
/*
* Imprimir o dado de Ping para o trecho de sada padro.
*/
private static void printData(DatagramPacket request) throws
Exception
{
// Obter referncias para a ordem de pacotes de bytes.
byte[] buf = request.getData();
// Envolver os bytes numa cadeia de entrada vetor de bytes, de
modo que voc possa ler os dados como uma cadeia de bytes.
ByteArrayInputStream bais = new ByteArrayInputstream(buf);
// Envolver a cadeia de sada do vetor bytes num leitor de
cadeia de entrada, de modo que voc possa ler os dados como uma
cadeia de caracteres.
InputStreamReader isr = new InputStreamReader(bais);
// Envolver o leitor de cadeia de entrada num leitor com
armazenagem, de modo que voc possa ler os dados de caracteres
linha a linha. (A linha uma seqncia de caracteres
terminados por alguma combinao de \r e \n.)
BufferedReader br = new BufferedReader(isr);
// O dado da mensagem est contido numa nica linha, ento leia
esta linha.
String line = br.readLine();
// Imprimir o endereo do hospedeiro e o dado recebido dele.
System.out.println(
Received from +
request.getAddress().getHostAddress()+
: +
new String(line));

25

}
}

O servidor fica num loop infinito de escuta pela chegada de pacotes UDP. Quando um
pacote chega, o servidor simplesmente envia o dado encapsulado de volta para o cliente.

Perda de pacotes
O UDP prov aplicaes com servio de transporte no confivel, pois as mensagens
podem se perder pela rede devido a um overflow na fila do roteador ou por outras
razes. Em contraste a isso, o TCP fornece aplicaes com um servio de transporte
confivel, e cada pacote perdido retransmitido at que ele seja recebido com sucesso.
Aplicaes que usam o UDP para comunicao precisam implementar alguma
segurana separadamente no nvel de aplicao (cada aplicao pode implementar uma
poltica diferente, de acordo com necessidades especficas).
Devido ao fato de a perda de pacotes ser rara, ou at mesmo inexistente, em uma rede
tpica, o servidor neste laboratrio injeta perda artificial para simular os efeitos da perda
de pacotes na rede. O servidor possui um parmetro LOSS_RATE, que determina qual a
porcentagem de pacotes deve ser perdida.
O servidor tambm possui outro parmetro, AVERAGE_DELAY, que usado para
simular o atraso de transmisso ao enviar um pacote pela Internet. Voc deve ajustar o
AVERAGE_DELAY com um valor positivo quando o cliente e o servidor forem estar
na mesma mquina, ou quando as mquinas estiverem muito perto fisicamente na rede.
Voc pode ajustar o AVERAGE_DELAY em 0 (zero) para encontrar o tempo de
transmisso verdadeiro dos seus pacotes.

Compilando e executando o servidor


Para compilar o servidor, faa o seguinte:
javac PingServer.java

Para executar o servidor, faa o seguinte:


java PingServer port

26

onde port o nmero da porta que o servidor escuta. Lembre que voc deve usar um
nmero de porta maior do que 1024, pois apenas os processos executando no modo root
(administrador) possuem privilgio de usar portas menores que 1024.
Nota: Se voc obtiver um erro de classe no encontrada quando executar o comando
acima, voc precisar dizer para o Java olhar no diretrio atual para resolver as
referncias de classe. Nesse caso, os comandos so:
java classpath . PingServer port

Sua tarefa: o cliente


Voc deve escrever o cliente de modo que ele envie 10 requisies de Ping para o
servidor, separadas por aproximadamente 1 segundo. Cada mensagem contm uma
carga til de dados que inclui a palavra PING, um nmero de seqncia, e uma marca
de tempo. Aps enviar cada pacote, o cliente espera um segundo para receber a resposta.
Se um segundo se passar sem uma resposta do servidor, ento o cliente pode supor que
esse pacote ou o pacote de resposta do servidor se perdeu pela rede.
Dica: Copie e cole o PingServer, renomeie o cdigo para PingClient e ento modifiqueo.
Voc deve escrever o cliente de modo que ele inicie com o seguinte comando:
java PingClient host port

onde hospedeiro o nome do computador em que o servidor est sendo executado e


port o nmero da porta que ele est escutando. Note que voc pode executar o cliente
e o servidor em diferentes mquinas ou na mesma.
O cliente deve enviar 10 Pings para o servidor. Como o UDP um protocolo no
confivel, alguns dos pacotes enviados pelo cliente ou pelo servidor podem ser
perdidos. Por essa razo, o cliente no pode esperar indefinidamente pela resposta a
uma mensagem de Ping. Voc deve fazer com que o cliente espere at um segundo por
uma resposta; se nenhuma resposta for recebida, ele presume que o pacote foi perdido
durante a transmisso. Voc precisar pesquisar a API para o DatagramSocket de modo
a descobrir como se ajusta o valor de tempo de expirao num socket de datagrama.
Ao desenvolver seu cdigo, voc deve executar o servidor de Ping em sua mquina e
testar seu cliente enviando pacotes para o hospedeiro local (ou, 127.0.0.1). Aps o
27

completo debug do seu cdigo, voc deve ver como sua aplicao se comunica atravs
da rede com um servidor de Ping sendo executado por um outro membro da classe.

Formato das mensagens


As mensagens de Ping neste laboratrio so formatadas de modo simples. Cada
mensagem contm uma seqncia de caracteres terminados por um caracter de retorno
(r) e um carter de mudana de linha (n). A mensagem contm a seguinte string:
PING sequence_number time CRLF

onde sequence_number comea em 0 (zero) e progride at 9, para cada mensagem


sucessiva de Ping enviada pelo cliente; time o tempo do momento em que o cliente
enviou a mensagem e CRLF representa o retorno e linha de caracteres que finalizam a
linha.

Exerccios opcionais
Quando voc terminar de escrever seu cdigo, pode tentar resolver os seguintes
exerccios.
1) No ponto atual, o programa calcula o tempo de transmisso de cada pacote e os
imprime individualmente. Modifique isso para corresponder ao modo de funcionamento
do programas de Ping padres. Voc dever informar os RTTs mnimo, mximo e
mdio. (fcil)
2) O programa bsico envia um novo Ping imediatamente quando recebe uma resposta.
Modifique-o de modo que ele envie exatamente 1 Ping por segundo, similar ao modo
como programas de Ping padres funcionam. Dica: Use as classes Timer e TimerTask
em java.util. (difcil)
3) Desenvolva duas novas classes, ReliableUdpSender e ReliableUdpReceiver, que
sero usadas para enviar e receber dados de maneira confivel sobre UDP. Para fazer
isso, voc precisar projetar um protocolo (como o TCP) em que o destinatrio dos
dados envia acknowledgements de volta ao remetente para indicar que o dado foi
recebido. Voc pode simplificar o problema apenas provendo um transporte
unidirecional de dados de aplicao do remetente ao destinatrio. Como seus

28

experimentos podem ser feitos em um ambiente com pouca ou nenhuma perda de


pacotes IP, voc dever simular perda de pacotes. (difcil)

29

Tarefa de Programao 4: Proxy cache

Neste laboratrio, voc desenvolver um pequeno servidor Web proxy que tambm ser
capaz de fazer cache de pginas Web. Este ser um servidor proxy bem simples, que
apenas entender requisies GET simples, mas ser capaz de manipular todos os tipos
de objetos, no apenas pginas HTML, mas tambm imagens.

Cdigo
O cdigo est dividido em trs classes:
ProxyCache

compreende o cdigo de inicializao do proxy e o cdigo para tratar

as requisies.
HttpRequest

contm as rotinas para analisar e processar as mensagens que chegam

do cliente.
HttpResponse

encarregada de ler as respostas vindas do servidor e process-las.

Seu trabalho ser completar o proxy de modo que ele seja capaz de receber requisies,
encaminh-las, ler as respostas e retorn-las aos clientes. Voc precisar completar a
classes ProxyCache, HttpRequest, e HttpResponse. Os lugares onde voc precisar
preencher o cdigo estaro marcados com /* Preencher */. Cada lugar pode exigir uma
ou mais linhas de cdigo.
Nota: Conforme ser explicado abaixo, o proxy utiliza DataOutputStreams para
processar as respostas dos servidores. Isso ocorre porque as respostas so uma mistura
de texto e dados binrios, e a nica cadeia de entrada em Java que permite trabalhar
com ambos ao mesmo tempo a DataOutputStream. Para obter o cdigo a ser
compilado, voc deve usar o argumento deprecation para o compilador, conforme
segue:
javac deprecation *.java

Se voc no usar o flag deprecation, o compilador ir se recusar a compilar seu cdigo.

30

Executando o proxy
Para executar o proxy faa o seguinte:
java ProxyCache port

onde port o nmero da porta que voc quer que o proxy escute pela chegada de
conexes dos clientes.

Configurando seu browser


Voc tambm vai precisar configurar seu browser Web para usar o proxy. Isso depende
do seu browser Web. No Internet Explorer, voc pode ajustar o proxy em Internet
Options na barra Connection em LAN settings. No Netscape (e browsers derivados,
como Mozilla), voc pode ajustar o proxy em Edit Preferences e ento selecionar
Advanced e Proxies.
Em ambos os casos, voc precisar informar o endereo do proxy e o nmero da porta
que voc atribuiu a ele quando foi inicializado. Voc pode executar o proxy e o browser
na mesma mquina sem nenhum problema.

Funcionalidades do proxy
O proxy funciona da seguinte forma:
1. O proxy escuta por requisies dos clientes.
2. Quando h uma requisio, o proxy gera um novo thread para tratar a requisio e
cria um objeto HttpRequest que contm a requisio.
3. O novo thread envia a requisio para o servidor e l a resposta do servidor dentro
de um objeto HttpResponse.
4. O thread envia a resposta de volta ao cliente requisitante.
Sua tarefa completar o cdigo que manipula o processo acima. A maior parte dos erros
de manipulao em proxy muito simples, e o erro no informado ao cliente. Quando
ocorrem erros, o proxy simplesmente pra de processar a requisio e o cliente
eventualmente recebe uma resposta por causa do esgotamento da temporizao.

31

Alguns browsers tambm enviam uma requisio por vez, sem usar conexes paralelas.
Especialmente em pginas com vrias imagens, pode haver muita lentido no
carregamento delas.

Caching
Realizar o caching das respostas no proxy fica como um exerccio opcional, pois isso
demanda uma quantidade significativa de trabalho adicional. O funcionamento bsico
do caching descrito a seguir:
1. Quando o proxy obtm uma requisio, ele verifica se o objeto requisitado est no
cache; se estiver, ele retorna o objeto do cach, sem contatar o servidor.
2. Se o objeto no estiver no cache, o proxy traz o objeto do servidor, retorna-o ao
cliente e guarda uma cpia no cache para requisies futuras.
Na prtica, o proxy precisa verificar se as respostas que possui no cache ainda so
vlidas e se so a resposta correta requisio do cliente. Voc pode ler mais sobre
caching e como ele tratado em HTTP na RFC-2068. Para este laboratrio, isto
suficiente para implementar a simples poltica acima.

Dicas de programao
A maior parte do cdigo que voc precisar escrever est relacionada ao processamento
de requisies e respostas HTTP, bem como o tratamento de sockets Java.
Um ponto notvel o processamento das respostas do servidor. Em uma resposta HTTP,
os cabealhos so enviados como linhas ASCII, separadas por seqncias de caracteres
CRLF. Os cabealhos so seguidos por uma linha vazia e pelo corpo da resposta, que
pode ser dados binrios no caso de imagens por exemplo.
O Java separa as cadeias de entrada conforme elas sejam texto ou binrio. Isso apresenta
um pequeno problema neste caso. Apenas DataInputstreams so capazes de tratar texto
e binrio simultaneamente; todas as outras cadeias so puro texto (exemplo:
BufferedReader), ou puro binrio (exemplo: BufferedInputStream), e mistur-los no
mesmo socket geralmente no funciona.

32

O DataInputstream possui um pequeno defeito, pois ele no capaz de garantir que o


dado lido possa ser corretamente convertido para os caracteres corretos em todas as
plataformas (funo DataInputstream.readLine() ). No caso deste laboratrio, a
converso

geralmente

funciona,

mas

compilador

ver

mtodo

DataInputstream.readLine() como deprecated (obsoleto) e se recusar a compil-lo


sem o flag deprecation.
altamente recomendvel que voc utilize o DataInputStream para ler as respostas.

Exerccios opcionais
Quando voc terminar os exerccios bsicos, tente resolver os seguintes exerccios
opcionais.
1. Melhor tratamento de erros. No ponto atual, o proxy no possui nenhum tratamento
de erro. Isso pode ser um problema especialmente quando o cliente requisita um objeto
que no est disponvel, desde que a resposta usual 404 Not Found no contenha
corpo de resposta e o proxy presuma que existe um corpo e tente l-lo.
2. Suporte para o mtodo POST. O proxy simples suporta apenas o mtodo GET.
Adicione o suporte para POST incluindo o corpo de requisio enviado na requisio
POST.
3. Adicione caching. Adicione o funcionamento simples de caching descrito acima. No
preciso implementar nenhuma poltica de substituio ou validao. Sua
implementao deve ser capaz de escrever respostas ao disco (exemplo: o cache) e
traz-las do disco quando voc obtiver um encontro no cache. Para isso, voc precisa
implementar alguma estrutura interna de dados no proxy para registrar quais objeto
esto no cache e onde eles se encontram no disco. Voc pode colocar essa estrutura de
dados na memria principal; no necessrio faz-la persistir aps processos de
desligamento.

33

Tarefa de Programao 5: Implementando um protocolo de transporte confivel

Viso geral
Neste laboratrio, voc escrever o cdigo de nvel de transporte de envio e recepo,
implementando um simples protocolo de transferncia de dados confivel. H duas
verses deste laboratrio: a verso protocolo bit alternante e a verso Go-Back-N. O
laboratrio ser divertido, visto que sua implementao ir diferir um pouco do que
seria exigido numa situao do mundo real.
Como voc provavelmente no possui mquinas standalone (com um OS que pode ser
modificado), seu cdigo precisar ser executado num ambiente de hardware/software
simulado. No entanto, a interface de programao fornecida para suas rotinas, por
exemplo, o cdigo que poderia chamar suas entidades de cima e de baixo bem
prximo do que feito num ambiente UNIX atual. (Na verdade, as interfaces de
software descritas neste exerccio so muito mais realistas do que remetentes e
destinatrios de loop infinito que muitos textos descrevem.) Interrupo/acionamento de
temporizadores sero tambm simulados, e interrupes feitas pelo temporizador faro
com que seu temporizador manipule rotinas a serem ativadas.

Rotinas que voc ir escrever


Os procedimentos que voc escrever so para a entidade de envio (A) e para a entidade
de recepo (B). Apenas transferncia unidirecional de dados (de A para B) exigida.
Naturalmente, o lado B dever enviar pacotes ao lado A para confirmar (positiva ou
negativamente) a recepo do dado. Suas rotinas devem ser implementadas na forma
dos procedimentos descritos abaixo. Esses procedimentos sero chamados pelos (e
chamaro) procedimentos que escrevi com um ambiente de rede emulado. Toda a
estrutura do ambiente mostrada na Figura Lab.3-1 (estrutura do ambiente emulado).
A unidade de dados que trafega entre as camadas superiores e seus protocolos uma
mensagem, que declarada como:
Struct msg {
Char data[20];
};

34

Essa declarao, e todas as outras estruturas de dados e rotinas de emulador, bem como
rotinas de ponta (exemplo: aquelas que voc precisa completar) esto no arquivo
prog2.c, descrito mais tarde. Sua entidade de envio receber dados em blocos de 20bytes da camada 5; sua entidade de recepo dever entregar blocos de 20 bytes de
dados recebidos corretamente para a camada 5 do lado destinatrio.
A unidade de dados que trafega entre suas rotinas e a camada de rede o pacote, que
declarado como:
struct pkt {
int seqnum;
int acknum;
int checksum;
char payload[20];
};

Suas rotinas iro preencher o campo carga til do dado da mensagem vinda da camada
5. Os outros campos da mensagem sero usados por seus protocolos para assegurar
entrega confivel.
As rotinas que voc escrever esto detalhadas abaixo. Conforme dito acima, tais
procedimentos no mundo real seriam parte do sistema operacional e chamados por
outros procedimentos no sistema operational.
A_output(message), onde message a estrutura do tipo msg, contendo dados que
sero enviados para o lado B. Esta rotina ser chamada sempre que a camada superior
do lado (A) tenha uma mensagem a ser enviada. Este o trabalho do seu protocolo para
assegurar que os dados em tal mensagem sejam entregues em ordem, e corretamente,
para a camada superior do lado destinatrio.
A_input(packet), onde packet a estrutura do tipo pkt. Esta rotina ser chamada
sempre que um pacote enviado pelo lado B (exemplo: como resultado de um
tolayer3()

feito por um procedimento do lado B) chega ao lado A. packet o pacote

(possivelmente corrompido) enviado pelo lado B.


A_timeinterrupt() Esta rotina ser chamada quando o temporizador de A expirar
(gerando uma interrupo no temporizador). Voc provavelmente usar esta rotina para

35

controlar a retransmisso dos pacotes. Veja starttimer() e stoptimer() abaixo para


ver como o temporizador acionado e interrompido.
A_init() Esta rotina ser chamada apenas uma vez, antes de qualquer outra rotina do
lado A ser chamada. Ela pode ser usada para fazer qualquer inicializao requerida.
B_input(packet), onde packet a estrutura do tipo pkt. Esta rotina ser chamada
sempre que um pacote enviado pelo lado A (exemplo: como resultado de um
tolayer3()

feito por um procedimento do lado A) chegar ao lado A. packet o pacote

(possivelmente corrompido) enviado pelo lado A.


B_init() Esta rotina ser chamada apenas uma vez, antes de qualquer outra rotina do
lado B ser chamada. Ela pode ser usada para fazer qualquer inicializao requerida.

Interfaces de software
Os procedimentos descritos acima so aqueles que voc ir escrever. As rotinas a seguir
foram escritas e podem ser chamadas pelas suas rotinas:
starttimer(calling_entity, increment), em que calling_entity ser 0 (para
acionar o temporizador do lado A) ou 1 (para acionar o temporizador do lado B), e
increment

um valor flutuante que indica a quantidade de tempo que se passou antes

de o temporizador ser interrompido. O temporizador de A dever ser acionado (ou


interrompido) apenas pelas rotinas do lado A, similarmente para o temporizador do lado
B. Para dar uma idia do valor apropriado de incremento a ser usado, um pacote
enviado dentro de uma rede leva em mdia 5 unidades de tempo para chegar ao outro
lado quando no h outras mensagens no meio.
stoptimer(calling_entity), onde calling_entity ser 0 (para acionar o
temporizador do lado A) ou 1 (para acionar o temporizador do lado B).
tolayer3(calling_entity,packet), onde calling_entity ser 0 (para envio do lado
A) ou 1 (para envio do lado B), e packet a estrutura do tipo pkt. Ao chamar esta
rotina, um pacote ser enviado pela rede, destinado a outra entidade.
tolayer5(calling_entity,packet), onde calling_entity ser 0 (entrega do lado A
para a camada 5) ou 1 (entrega do lado B para a camada 5), e message a estrutura
do tipo msg. Em transferncia de dados unidirecional, voc poderia apenas ser chamado

36

com calling_entity igual a 1 (entrega para o lado B). Ao chamar esta rotina, os
dados passaro para a camada 5.

O ambiente de rede simulado


Uma chamada ao procedimento tolayer3() envia pacotes para o meio (exemplo:
dentro da camada de redes). Seus procedimentos A_input() e B_input() so
chamados quando um pacote deve ser entregue do meio para a sua camada de protocolo.
O meio capaz de corromper e perder pacotes. Ele no ir reordenar os pacotes.
Quando voc compilar seus procedimentos com os que so fornecidos aqui e executar o
programa resultante, ser necessrio especificar os valores de acordo com o ambiente de
rede simulado:
Nmero de mensagens para simular. Meu emulador (e suas rotinas) ir parar
quando esse nmero de mensagens tiver sido passado para a camada 5, no importando
se todas as mensagens foram, ou no, entregues corretamente. Ento, voc no precisa
se preocupar com mensagens no entregues ou no confirmadas que ainda estiverem no
seu remetente quando o emulador parar. Note que se voc ajustar esse valor para 1,
seu programa ir terminar imediatamente, antes de as mensagens serem entregues ao
outro lado. Logo, esse valor deve ser sempre maior do que 1.
Perdas. Voc dever especificar a probabilidade de perda de pacotes. Um valor de 0,1
significa que um em cada dez pacotes (na mdia) ser perdido.
Corrompimento. Voc dever especificar a probabilidade de perda de pacotes. Um
valor de 0,2 significa que um em cada cinco pacotes (na mdia) ser corrompido. Note
que os campos de contedo da carga til, seqncia, ack, ou checksum podem ser
corrompidos. Seu checksum dever ento incluir os campos de dados, seqncia e ack.
Tracing. Ajustar um valor de tracing de 1 ou 2 imprimir informaes teis sobre
o que est acontecendo dentro da emulao (exemplo: o que ocorre com pacotes e
temporizadores). Um valor de tracing de 0 desliga esta opo. Um valor maior do que
2 exibir todos os tipos de mensagens especiais que so prprias para a depurao do
meu emulador. O valor 2 pode ser til para voc fazer o debug do seu cdigo.
Mantenha em mente que implementaes reais no provem tais informaes sobre o
que est acontecendo com seus pacotes.

37

Mdia de tempo entre mensagens do remetente da camada 5. Voc pode ajustar


este valor para qualquer valor positivo diferente de zero. Note que quanto menor o valor
escolhido, mais rpido os pacotes chegaro ao seu remetente.

Verso protocolo bit alternante deste laboratrio


Voc escrever os procedimentos A_output(), A_input(), A_timerinterrupt(),
A_init(), B_input()

e B_init() que, juntos, implementaro uma transferncia

pare-e-espere (exemplo: o protocolo bit alternante referido como rdt3.0 no texto)


unidirecional de dados do lado A para o lado B. Seu protocolo dever usar mensagens
ACK e NACK.
Escolha um valor bem alto para a mdia de tempo entre mensagens da camada 5 do
remetente, assim ele nunca ser chamado enquanto ainda tiver pendncias, isto ,
mensagens no confirmadas que ele esteja tentando enviar ao destinatrio. Sugiro que se
escolha um valor de 1.000. Realize tambm uma verificao em seu remetente para ter
certeza de que quando A_output() chamado, no haja nenhuma mensagem em
trnsito. Se houver, voc pode simplesmente ignorar (descartar) os dados que esto
sendo passados para a rotina A_output().
Coloque seus procedimentos em um arquivo chamado prog2.c. Voc precisar da verso
inicial deste arquivo, contendo as rotinas de emulao que escrevemos para voc, e as
chamadas

para

seus

procedimentos.

Para

obter

este

programa,

acesse

http://gaia.cs.umass.edu/kurose/transport/prog2.c.
Este laboratrio pode ser feito em qualquer mquina com suporte a C. Ele no faz uso
de caractersticas UNIX. (Voc pode simplesmente copiar o arquivo prog2.c em
qualquer mquina e sistema operacional de sua escolha).
Recomendamos-lhe que tenha em mos uma listagem do cdigo, um documento de
projeto e uma sada de amostra. Para sua sada de amostra, seus procedimentos podem
imprimir uma mensagem sempre que um evento ocorrer no seu remetente ou
destinatrio (a chegada de uma mensagem/pacote, ou uma interrupo de temporizador)
bem como qualquer ao tomada como resposta. Voc pode querer ter uma sada para
uma execuo at o ponto (aproximadamente) quando 10 mensagens tiverem sido
confirmadas (ACK) corretamente pelo destinatrio, uma probabilidade de perda de 0,1,

38

e uma probabilidade de corrupo de 0,3, e um nvel de trace de 2. Anote sua impresso


com uma caneta colorida mostrando como seu protocolo recuperou corretamente uma
perda ou corrupo de pacote.
No deixe de ler as dicas teis para este laboratrio logo aps a descrio da verso
Go_Back-N.

Verso Go-Back-N deste laboratrio


Voc escrever os procedimentos A_output(), A_input(), A_timerinterrupt(),
A_init(), B_input()

e B_init() e juntamente implementar uma transferncia

unidirecional de dados do lado A para o lado B, com tamanho de janela igual a 8. Seu
protocolo dever usar mensagens ACK e NACK. Consulte a verso protocolo bit
alternante acima para informaes sobre como obter e emulador de rede.
altamente recomendvel que voc implemente primeiro o laboratrio mais fcil (bit
alternante) e ento estenda seu cdigo para implementar o mais difcil (Go-Back-N).
Acredite, isso no ser perda de tempo! No entanto, algumas novas consideraes para
o seu cdigo Go-Back-N (que no se aplicam ao protocolo bit alternante) so:
A_output(message), onde message uma estrutura do tipo msg contendo dados para
serem enviados ao lado B.
Agora sua rotina A_output() ser chamada algumas vezes quando houver pendncias,
mensagens no confirmadas no meio implicando a necessidade de voc ter um buffer
para mensagens mltiplas em seu remetente. Voc tambm precisar do buffer devido
natureza do Go-Back-N: algumas vezes seu remetente ser chamado mas no ser capaz
de enviar a nova mensagem porque ela cai fora da janela.
Ento voc dever se preocupar em armazenar um nmero arbitrrio de mensagens.
Voc pode ter um valor finito para o nmero mximo de buffers disponveis em seu
remetente (para 50 mensagens) e pode simplesmente abortar (desistir e sair) se todos os
50 buffers estiverem em uso de uma vez (Nota: usando os valores acima, isso nunca
dever acontecer). No mundo real, naturalmente, deveria haver uma soluo mais
elegante para o problema de buffer finito.
A_timerinterrupt() Esta rotina ser chamada quando o temporizador de A expirar
(gerando uma interrupo de temporizador). Lembre que voc possui apenas um
39

temporizador, e pode ter muitas pendncias e pacotes no confirmados no meio; ento,


pense um pouco em como usar este nico temporizador.
Consulte a verso protocolo bit alternante para uma descrio geral do que voc precisa
ter em mos. Voc pode querer ter uma sada para uma execuo que seja grande o
bastante de modo que pelo menos 20 mensagens sejam transferidas com sucesso do
remetente ao destinatrio (exemplo: o remetente recebe o ACK para estas mensagens),
uma probabilidade de perda de 0,2, e uma probabilidade de corrupo de 0,2, e um nvel
de trace de 2, e um tempo mdio entre chegadas de 10. Voc pode anotar sua impresso
com uma caneta colorida mostrando como seu protocolo recuperou corretamente uma
perda ou corrupo de pacote.
Para crdito extra, voc pode implementar transferncia bidirecional de mensagens.
Nesse caso, as entidades A e B operam tanto como remetente quanto como destinatrio.
Voc tambm pode sobrepor confirmaes nos pacotes de dados (ou pode escolher no
faz-lo). Para fazer meu emulador entregar mensagens da camada 5 para sua rotina
B_output(), preciso trocar o valor declarado de BIDIRECTIONAL de 0 para 1.

Dicas teis
Soma de verificao. Voc pode usar qualquer soluo de soma de verificao que
quiser. Lembre que o nmero de seqncia e o campo ack tambm podem ser
corrompidos. Sugerimos uma soma de verificao como o do TCP, que consiste na
soma dos valores (inteiro) dos campos de seqncia e do campo ack adicionada soma
caracter por caracter do campo carga til do pacote (exemplo: trate cada caracter como
se ele fosse um inteiro de 8 bits e apenas adicione-os juntos).
Note que qualquer estado compartilhado entre suas rotinas deve estar na forma de
variveis globais. Note tambm que qualquer informao que seus procedimentos
precisam salvar de uma invocao para a prxima tambm deve ser uma varivel global
(ou esttica). Por exemplo, suas rotinas precisam manter uma cpia de um pacote para
uma possvel retransmisso. Nesse caso, seria provavelmente uma boa idia uma
estrutura de dados ser uma varivel global no seu cdigo. Note, no entanto, que se uma
das suas variveis usada pelo seu lado remetente, essa varivel no deve ser acessada
pela entidade do lado destinatrio, pois, no mundo real, a comunicao entre entidades
conectadas apenas por um canal de comunicao no compartilha variveis.
40

H uma varivel global flutuante chamada time, que voc pode acessar de dentro do
seu cdigo para auxili-lo com diagnsticos de mensagens.
COMECE SIMPLES. Ajuste as probabilidades de perda e corrupo para zero e
teste suas rotinas. Melhor ainda, projete e implemente seus procedimentos para o caso
de nenhuma perda ou corrupo. Primeiro, faa-as funcionar; ento trate o caso de uma
dessas probabilidades no ser zero e, finalmente, de ambas no serem zero.
Debugging. Recomendamos que voc ajuste o nvel de trace para 2 e coloque vrios
printf em seu cdigo enquanto faz a depurao de seus procedimentos.
Nmeros aleatrios. O emulador gera perda de pacotes e erros usando um gerador de
nmeros aleatrios. Nossa experincia passada que geradores de nmeros aleatrios
podem variar bastante de uma mquina para outra. Voc pode precisar modificar o
cdigo do gerador de nmeros aleatrios no emulador que estamos fornecendo. Nossas
rotinas do emulador possuem um teste para ver se o gerador de nmeros aleatrios em
sua mquina funcionar com o nosso cdigo. Se voc obtiver uma mensagem de erro:
provvel que o gerador de nmeros aleatrios em sua mquina seja diferente
daquele que este emulador espera. Favor verificar a rotina jimsrand() no cdigo
do emulador. Desculpe.
ento voc saber que precisa olhar como os nmeros aleatrios so gerados na rotina
jimsrand(); veja os comentrios nessa rotina.

Q&A
Quando pensamos neste laboratrio em nosso curso de introduo de redes, os
estudantes enviaram vrias questes. Se voc estiver interessado em v-las, acesse:
http://gaia.cs.umass.edu/kurose/transport/programming_assignment_QA.htm.

41

Tarefa de Programao 6: Implementando um algoritmo

Viso geral
Neste laboratrio, voc escrever um conjunto distribudo de procedimentos que
implementam um roteamento de vetor de distncias assncrono distribudo para a rede
mostrada na Figura Lab.4-1.

Figura Lab.4-1: Topologia de rede e custos dos enlaces para o laboratrio de


roteamento de vetor de distncias
Tarefa bsica
Rotinas que voc ir escrever. Para a parte bsica da tarefa, voc escrever as
seguintes rotinas que executaro assincronamente dentro do ambiente emulado que
escrevemos para esta tarefa.
Para o n 0, voc escrever as rotinas:
rtinit0() Esta rotina ser chamada uma vez no incio da emulao. rtinit0() no
possui argumentos. Ela dever inicializar a tabela de distncias no n 0 para refletir os
custos 1, 3 e 7 para os ns 1, 2 e 3, respectivamente. Na Figura 1, todos os links so
bidirecionais, e os custos em ambas as direes so idnticos. Aps inicializar a tabela
de distncia, e qualquer outra estrutura de dados necessria para sua rotina do n 0,
ela dever ento enviar aos vizinhos diretamente conectados (neste caso, 1, 2, e 3) o
custo dos seus caminhos de menor custo para todos os outros ns da rede. Esta

42

informao de custo mnimo enviada para os ns vizinhos em um pacote de rotina


atravs da chamada rotina tolayer2(), conforme descrita abaixo. O formato do pacote
de rotina descrito abaixo tambm.
rtupdate0()(struct rtpkt *rcvdpkt). Esta rotina ser chamada quando o n 0
receber um pacote de rotina enviado por um de seus vizinhos diretamente conectados. O
parmetro *rcvdpkt um ponteiro para o pacote que foi recebido.
rtupdate0()

o corao do algoritmo de vetor de distncias. O valor que ele recebe

num pacote de rotina vindo de algum outro n i contm o custo do caminho mais curto
de i para todos os outros ns da rede. rtupdate0() usa estes valores recebidos para
atualizar sua prpria tabela de distncia (como especificado pelo algoritmo de vetor de
distncias). Se o seu prprio custo mnimo para outro n mudar como resultado da
atualizao, o n 0 informa a todos os vizinhos diretamente conectados sobre essa
mudana em custo mnimo enviando para eles um pacote de rotina. Ressaltamos que no
algoritmo de vetor de distncias, apenas os ns diretamente conectados iro trocar
pacotes de rotina. Portanto, os ns 1 e 2 se comunicam entre si, mas os ns 1 e 3 no.
Conforme vimos em aula, a tabela de distncia dentro de cada n a principal estrutura
de dados usada pelo algoritmo de vetor de distncias. Voc achar conveniente declarar
a tabela de distncia como uma disposio 4-por-4 de ints, onde a entrada [i,j] na
tabela de distncia no n 0 o custo atual do n 0 para o n i pelo vizinho direto j.
Se 0 no estiver diretamente conectado ao j, voc pode ignorar essa entrada.
Usaremos a conveno de que o valor inteiro 999 infinito.
A Figura Lab.4-2 fornece uma viso conceitual do relacionamento dos procedimentos
dentro do n 0.
Rotinas similares so definidas para os ns 1, 2 e 3. Portanto, voc escrever 8
procedimentos ao todo: rtinit0(), rtinit1(), rtinit2(), rtinit3(),
rtupdate0(), rtupdate1(), rtupdate2(), rtupdate3()

43

Lab.4-2: Relao entre procedimentos dentro do n 0.


Interfaces de software
Os procedimentos descritos acima so os que voc ir escrever. Escrevemos as
seguintes rotinas que podem ser chamadas pelas suas rotinas:
tolayer2(struct rtpkt pkt2send)

onde rtpkt a seguinte estrutura, que j est declarada para voc. O


procedimento tolayer2() definido no arquivo prog3.c
extern struct rtpkt {
int sourceid; /* id do n que est enviando o pkt, 0, 1, 2 ou
3 */
int destid /* id do roteador para o qual o pkt est sendo
enviado (deve ser um vizinho imediato) */
int mincost[4]; /* custo mnimo para o n 0 ... 3 */
};

Note que tolayer2() passado como estrutura, no como um ponteiro para


uma estrutura.
printdt0()

imprimir a tabela de distncia para o n 0. Ela passada como um ponteiro


para uma estrutura do tipo distance_table. printdt0() e a declarao da estrutura
para a tabela de distncia do n 0 est declarada no arquivo node0.c. Rotinas de

44

impresso similares esto definidas para voc nos arquivos node1.c, node2.c e
node3.c.

O ambiente de rede simulado


Seus

procedimentos

rtinit0(),

rtinit1(),

rtinit2(),

rtupdate0(), rtupdate1(), rtupdate2(), rtupdate3()

rtinit3()

enviam pacotes de rotina

(cujo formato est descrito acima) dentro do meio. O meio entregar os pacotes em
ordem e sem perda para o destinatrio especfico. Apenas ns diretamente conectados
podem se comunicar. O atraso entre o remetente e o destinatrio varivel (e
desconhecido).
Quando voc compilar seus procedimentos e meus procedimentos juntos e executar o
programa resultante, dever especificar apenas um valor relativo ao ambiente de rede
simulado:
Tracing. Ajustar um valor de tracing de 1 ou 2 imprimir informaes teis sobre o
que est acontecendo dentro da emulao (exemplo: o que ocorre com pacotes e
temporizadores). Um valor de tracing de 0 desliga esta opo. Um valor maior do que
2 exibir todos os tipos de mensagens de uso prprio para depurao do meu emulador.
Um valor igual a 2 pode ser til para voc fazer o debug do seu cdigo. Mantenha em
mente que implementaes reais no provem tais informaes sobre o que est
acontecendo com seus pacotes.

Tarefa bsica
Voc escrever os procedimentos rtinit0(), rtinit1(), rtinit2(), rtinit3()
e

rtupdate0(),

rtupdate1(),

rtupdate2(),

rtupdate3()

que

juntos

implementaro uma computao distribuda assncrona das tabelas de distncia para a


topologia e os custos mostrados na Figura 1.
Voc dever colocar seus procedimentos para os ns 0 at 3 em arquivos chamados
node0.c,

... node3.c. No permitido declarar nenhuma varivel global que seja

visvel fora de um dado arquivo C (exemplo: qualquer varivel global que voc definir
em node0.c. pode ser acessada somente dentro do node0.c). Isso o forar a cumprir
convenes que deveriam ser adotadas se voc fosse executar os procedimentos em
45

quatro ns distintos. Para compilar suas rotinas: cc prog3.c node0.c node1.c


node2.c node3.

Verses de prottipos desses arquivos encontram-se em: node0.c,

node1.c, node2.c, node3.c. Voc pode adquirir uma cpia do arquivo prog3.c em:
http://gaia.cs.umass.edu/kurose/network/prog3.c.
Esta tarefa pode ser feita em qualquer mquina que tenha suporte a C. Ela no utiliza
caractersticas UNIX.
Como sempre, a maior parte dos instrutores espera que voc possua uma lista de
cdigos, um documento de projeto e uma sada de amostra.
Para sua sada de amostra, seus procedimentos devem imprimir uma mensagem sempre
que seus procedimentos rtinit0(), rtinit1(), rtinit2(), rtinit3() ou
rtupdate0(),

rtupdate1(),

rtupdate2(),

rtupdate3()

forem chamados,

fornecendo o tempo (disponvel pela minha varivel global clocktime). Para


rtupdate0(), rtupdate1(), rtupdate2() e rtupdate3(),

voc deve imprimir a

identidade do remetente do pacote de rotina que est sendo passado para sua rotina, se a
tabela de distncia for, ou no, atualizada, o contedo da tabela de distncia (voc pode
usar minha rotina de impresso) e uma descrio de todas as mensagens enviadas para
os ns vizinhos como resultado de cada atualizao da tabela de distncia.
A sada de amostra deve ser uma listagem de sada com um valor de TRACE igual a 2.
Destaque a tabela de distncia final produzida em cada n. Seu programa ser executado
at que no haja mais nenhum pacote de rotina em trnsito na rede. Nesse ponto, o
emulador terminar.

Tarefa avanada
Voc escrever dois procedimentos, rtlinkhandler0(int linkid, int newcost) e
rtlinkhandler1(int linkid, int newcost),

que sero chamados se (e quando) o

custo do link entre 0 e 1 mudar. Essas rotinas devem ser definidas nos arquivos node0.c
e node1.c, respectivamente. As rotinas levaro o nome (id) do n vizinho no outro lado
do link cujo custo foi mudado, e o novo custo do link. Note que quando o custo de um
link muda, suas rotinas devero atualizar a tabela de distncia e podem (ou no) precisar
enviar pacotes de roteamento atualizados aos ns vizinhos.

46

Para completar a parte avanada da tarefa, voc precisar mudar o valor da constante
LINKCHANGES (linha 3 no prog3.c) para 1. Para sua informao, o custo do link
ser alterado de 1 para 20 no tempo 10000 e ento retornar para 1 no tempo 20000.
Suas rotinas sero invocadas nesses instantes.
altamente recomendvel que voc implemente primeiro a tarefa bsica e ento estenda
seu cdigo para implementar a tarefa avanada.

Q&A
Quando pensamos neste laboratrio em nosso curso de introduo de redes, os
estudantes enviaram vrias questes. Se voc estiver interessado em v-las, acesse:
http://gaia.cs.umass.edu/kurose/network/programming_assignment_QA.htm.

47

Tarefa de Programao 7: Streaming vdeo com RTSP e RTP

O cdigo
Neste laboratrio, voc implementar um servidor de streaming vdeo e cliente que se
comunica usando o protocolo de fluxo contnuo em tempo real (RTSP) e envia dados
usando o protocolo de tempo real (RTP). Sua tarefa implementar o protocolo RTSP no
cliente e implementar o empacotamento RTP no servidor.
Forneceremos o cdigo que implementa o protocolo RSTP no servidor, o
desempacotamento RTP no cliente e trataremos de exibir o vdeo transmitido. Voc no
precisa mexer neste cdigo.

Classes
Existem quatro classes nesta tarefa.
Client
Esta classe implementa o cliente e a interface de usurio que voc usar para
enviar comandos RTSP e que ser utilizada para exibir o vdeo. Abaixo vemos
como a interface. Voc dever implementar as aes que so tomadas quando
os botes so pressionados.
Server
Esta classe implementa o servidor que responde s requisies RTSP e
encaminha o vdeo de volta. A interao RTSP j est implementada e o servidor
chama as rotinas na classe RTPpacket para empacotar os dados de vdeo. Voc
no precisa mexer nesta classe.
RTPpacket
Esta classe usada para manipular os pacotes RTP. Ela possui rotinas separadas
para tratar os pacotes recebidos no lado cliente que j dado e voc no precisa
modific-lo (mas veja os Exerccios opcionais). Voc dever completar o
primeiro construtor desta classe para implementar o empacotamento RTP dos
dados de vdeo. O segundo construtor usado pelo cliente para desempacotar os
dados. Voc no precisa modific-lo tambm.
48

VideoStream
Esta classe usada para ler os dados de vdeo do arquivo em disco. Voc no
precisa modificar esta classe.

Executando o cdigo
Aps completar o cdigo, voc pode execut-lo da seguinte forma:
Primeiro, inicie o servidor com o comando:
java Server server_port

onde server_port a porta onde seu servidor escuta as conexes RTSP que chegam. A
porta RTSP padro a 554, mas voc deve escolher um nmero de porta maior que
1024.
Ento, inicie o cliente com o comando:
java Client server_name server_port video_file

onde server_name o nome da mquina onde o servidor est sendo executado,


server_port

a porta que o servidor est escutando, e video_file o nome do

arquivo que voc quer requisitar (fornecemos um arquivo de exemplo movie.Mjpeg). O


formato do arquivo est descrito no Apndice.
O cliente abre uma conexo com o servidor e abre uma janela como esta:

49

Voc pode enviar comandos RTSP para o servidor pressionando os botes. Uma
interao normal RTSP acontece assim:
1. O cliente envia SETUP. Esse comando usado para ajustar os parmetros de
sesso e de transporte.
2. O cliente envia PLAY. Isso inicia a reproduo.
3. O cliente pode enviar PAUSE se ele quiser pausar durante a reproduo.
4. O cliente envia TEARDOWN. Isso termina a sesso e fecha a conexo.
O servidor sempre responde a todas as mensagens que o cliente envia. Os cdigos de
resposta so exatamente os mesmos do HTTP. O cdigo 200 indica que a requisio foi
bem-sucedida. Neste laboratrio, voc no precisa implementar nenhum outro cdigo de
resposta. Para mais informaes sobre o RTSP, veja a RFC-2326.

1. Cliente
Sua primeira tarefa implementar o RTSP do lado cliente. Para fazer isso, voc deve
completar as funes que so chamadas quando o usurio clica nos botes da interface

50

de usurio. Para cada boto na interface, h uma funo manipuladora do cdigo. Voc
deve implementar as seguintes aes em cada funo manipuladora.
Quando o cliente iniciado, ele tambm abre o socket RTSP para o servidor. Use este
socket para enviar todas as requisies RTSP.
SETUP
Crie um socket para receber os dados RTP e ajustar o tempo de expirao no
socket para 5 milissegundos.
Envie uma requisio SETUP para o servidor. Voc deve inserir o cabealho de
Transporte onde voc especificar a porta para o socket de dados RTP que voc
criou.
Leia a resposta do servidor e analise o cabealho de Sesso na resposta para obter
o ID da sesso.
PLAY
Envie uma requisio PLAY. Voc deve inserir o cabealho de sesso e usar o ID
de sesso fornecido na resposta ao SETUP. No coloque cabealho de Transporte
nesta requisio.
Leia a resposta do servidor.
PAUSE
Envie uma requisio PAUSE. Voc deve inserir o cabealho de Sesso e usar o ID
de sesso fornecido na resposta ao SETUP. No coloque cabealho de Transporte
nesta requisio.
Leia a resposta do servidor.
TEARDOWN
Envie uma requisio TEARDOWN. Voc deve inserir o cabealho de Sesso e
usar o ID de sesso fornecido na resposta ao SETUP. No preciso colocar
cabealho de Transporte neste requisio.
Leia a resposta do servidor.

51

Nota: Voc deve inserir o cabealho CSeq em cada requisio que voc enviar. O valor
do cabealho CSeq um numero que ser incrementado de 1 a cada requisio que voc
enviar.

Exemplo
Aqui est uma interao de amostra entre cliente e servidor. As requisies dos clientes
so marcadas com C: e as respostas dos servidores com S:. Neste laboratrio, tanto o
cliente quanto o servidor so bem simples. Eles no precisam ter rotinas de anlise
sofisticadas e esperam sempre encontrar os campos do cabealho na ordem que voc
ver abaixo, ou seja, numa requisio, o primeiro cabealho o CSeq, e o segundo ou
o de Transporte (para SETUP) ou o de Sesso (para todas as outras requisies). Na
resposta, CSeq novamente o primeiro e de Sesso o segundo.
C: SETUP movie.Mjpeg RTSP/1.0
C: CSeq: 1
C: Transport: RTP/UDP; client_port= 25000
S: RTSP/1.0 200 OK
S: CSeq: 1
S: Session: 123456
C: PLAY movie.Mjpeg RTSP/1.0
C: CSeq: 2
C: Session: 123456
S: RTSP/1.0 200 OK
S: CSeq: 2
S: Session: 123456
C: PAUSE movie.Mjpeg RTSP/1.0
C: CSeq: 3
C: Session: 123456
S: RTSP/1.0 200 OK
S: CSeq: 3
S: Session: 123456
C: PLAY movie.Mjpeg RTSP/1.0

52

C: CSeq: 4
C: Session: 123456
S: RTSP/1.0 200 OK
S: CSeq: 4
S: Session: 123456
C: TEARDOWN movie.Mjpeg RTSP/1.0
C: CSeq: 5
C: Session: 123456
S: RTSP/1.0 200 OK
S: CSeq: 5
S: Session: 123456

Estado do cliente
Uma das diferenas entre HTTP e RTSP que no RTSP cada sesso possui um estado.
Neste laboratrio, voc precisar manter o estado do cliente atualizado. O cliente muda
de estado quando ele recebe uma resposta do servidor de acordo com o seguinte
diagrama.

2. Servidor
No servidor, voc precisar implementar o empacotamento dos dados de vdeo em
pacotes RTP. Para isso, ser necessrio criar o pacote, ajustar os campos no cabealho
do pacote e copiar a carga til (exemplo: um quadro de vdeo) dentro do pacote.
Quando o servidor recebe a requisio PLAY do cliente, ele aciona um temporizador
que ativado a cada 100ms. Nesses tempos, o servidor ler um quadro de vdeo do

53

arquivo e o enviar para o cliente. O servidor cria um objeto RTPpacket, que o


encapsulamento RTP do quadro de vdeo.
O servidor chama o primeiro construtor da classe RTPpacket para realizar o
encapsulamento. Sua tarefa escrever essa funo. Voc precisar fazer o seguinte: (as
letras em parnteses se referem aos campos no formato do pacote RTP abaixo)
1. Ajuste o campo RTP-version (V). Voc deve ajust-lo para 2.
2. Ajuste os campos padding (P), extension (X), number of contributing sources
(CC), e marker (M). Todos eles so ajustados em zero neste laboratrio.
3. Ajuste o campo carga til (PT). Neste laboratrio, usamos MJPEG e o tipo para
ele 26.
4. Ajuste o nmero de seqncia. O servidor fornece esse nmero de seqncia
como argumento Framenb para o construtor.
5. Ajuste a marca de tempo. O servidor fornece este nmero como argumento Time
para o construtor.
6. Ajuste o identificador da fonte (SSRC). Este campo identifica o servidor. Voc
pode usar o valor inteiro que desejar.
Como no temos nenhuma outra fonte de contribuio (campo CC == 0), o campo
CSRC no existe. O comprimento do cabealho do pacote de 12 bytes, ou as trs
primeiras linhas do diagrama abaixo.

54

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X|

CC

|M|

PT

sequence number

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|

timestamp

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|

synchronization source (SSRC) identifier

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|

contributing source (CSRC) identifiers

....

|
|

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Voc deve preencher o cabealho na disposio header da classe RTPpacket. Voc


tambm precisar copiar a carga til (fornecida como argumento data) para a varivel
payload.

O comprimento da carga til dado no argumento data_length.

O diagrama acima est na ordem de byte de rede (tambm conhecido como big-endian).
A Java Virtual Machine usa a mesma ordem de byte, ento voc no precisa transformar
seu cabealho de pacote na ordem de byte de rede.
Para mais detalhes sobre RTP, veja a RFC-1889.

Manipulando os bits
Aqui esto alguns exemplos de como ajustar e verificar bits individuais ou grupo de
bits. Note que no formato do cabealho do pacote RTP, os menores nmeros de bit se
referem a maiores ordens de bits, ou seja, o bit nmero 0 de um byte 2^7, e o bit
nmero 7 1 (ou 2^0). Nos exemplos abaixo, os nmeros de bit se referem aos nmeros
no diagrama acima.
Como o campo header da classe RTPpacket um vetor do tipo byte, voc precisar
ajustar o cabealho de um byte por vez, que um grupo de 8 bits. O primeiro byte
possui os bits 0-7, o segundo byte possui os bits 8-15, e assim por diante. Em Java, um
int tem 32 bits ou 4 bytes.
Para ajustar o nmero n na varivel mybyte do tipo byte:
mybyte = mybyte | 1 << (7 n);

55

Para ajustar os bits n e n+1 para o valor de foo na varivel mybyte:


mybyte = mybyte | foo << (7 n);

Note que foo deve ter um valor que possa ser expresso com 2 bits, ou seja, 0, 1, 2 ou 3.
Para copiar um foo inteiro de 16-bits em 2-bytes, b1 e b2:
b1 = foo >> 8;
b2 = foo & 0xFF;

Aps fazer isso, b1 ter 8 bits de maior ordem de foo e b2 ter 8 bits de menor ordem
de foo.
Voc pode copiar um inteiro de 32-bits em 4-bytes de maneira similar.
Se voc no se sente confortvel com o ajuste de bits, pode encontrar mais informaes
no Java Tutorial.

Exemplo de bit
Suponha que desejamos preencher o primeiro byte do cabealho do pacote RTP com os
seguintes valores:
V=2
P=0
X=0
CC = 3
Em binrio, isso poderia ser representado como
1 0 | 0 | 0 | 0 0 1 1
V=2

CC = 3

2^7 . . . . . . . 2^0

Exerccios opcionais
Em vez do servidor normal fornecido a voc, use a classe chamada FunkyServer
(faa tambm o download da classe FunkyServer$1.class), por exemplo, execute-o com
java FunkyServer server_port.

O que acontece no cliente? Explique o porqu.


56

Calcule as estatsticas sobre a sesso. Voc precisar calcular a taxa de perda de


pacotes RTP, a taxa de dados de vdeo (em bits ou bytes por segundo) e qualquer outra
estatstica interessante que voc conseguir encontrar.
A interface de usurio no cliente possui 4 botes para as 4 aes. Se voc compar-la a
um transdutor padro, tal como RealPlayer ou transdutor Windows, voc ver que eles
possuem apenas 3 botes para as mesmas aes, chamadas, PLAY, PAUSE e STOP
(correspondendo exatamente ao TEARDOWN). No h nenhum boto de SETUP
disponvel para o usurio. Dado que o SETUP mandatrio numa interao RTSP,
como voc implementaria isso? apropriado enviar TEARDOWN quando o usurio
clica no boto stop?
At aqui, o cliente e o servidor implementam apenas o mnimo necessrio de
interaes RTSP e PAUSE. Implemente o mtodo DESCRIBE, que usado para passar
informaes sobre o media stream. Quando o servidor recebe uma requisio
DESCRIBE, ele envia de volta um arquivo de descrio de sesso que diz ao cliente que
tipos de streams esto na sesso e quais codificaes esto sendo utilizadas.

Apndice
Formato do MJPEG (Motion JPEG) proprietrio do laboratrio.
Neste laboratrio, o servidor encaminha um vdeo codificado com um formato de
arquivo proprietrio MJPEG. Este formato armazena o vdeo como concatenao de
imagens codificadas em JPEG, com cada imagem sendo precedida por um cabealho de
5-bytes que indica o tamanho em bits da imagem. O servidor analisa o bitstream do
arquivo MJPEG para extrair as imagens JPEG no caminho. O servidor envia as imagens
ao cliente em intervalos peridicos. O cliente ento exibe as imagens JPEG individuais
conforme elas vo chegando do servidor.

57