Anda di halaman 1dari 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 o 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 Message Envelope SMTPConnection A interface do usurio A mensagem de e-mail Envelope SMTP que envolve a mensagem 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 DATA HELO MAIL FROM QUIT RCPT TO Cdigo de resposta 354 250 250 221 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 Normal query using a given Server NS-query MX-query
nslookup host nslookup host server nslookup type=NS host 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 o 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 o cdigo de resposta do servidor o 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 o 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 modifique-o. 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 o compilador ver o 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(), rtinit3()

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

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 | timestamp synchronization source (SSRC) identifier contributing source (CSRC) identifiers .... sequence number | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

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

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 P X 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

Anda mungkin juga menyukai