Departamento de Informática Faculdade de Ciências e Tecnologia UNIVERSIDADE NOVA DE LISBOA Licenciatura em Engenharia Informática PROVA DE TESTE PRÁTICO Sistemas Distribuídos I 2º Semestre, 2005/2006 NOTAS: Leia com atenção cada questão antes de responder. A interpretação do enunciado de cada pergunta é um factor de avaliação do teste. O teste é SEM consulta. A duração do teste é de 2h00. O enunciado contém 10 páginas que devem ser entregues com a resposta ao teste. NOME: NÚMERO.: 1) Suponha que se pretende criar um sistema de caching cooperativo com as funcionalidades base do sistema desenvolvido no trabalho prático. Suponha que, quando em funcionamento, o sistema inclui, em cada instituição, um servidor de cache único e um proxy individual em cada máquina dos clientes. O servidor de cache mantém uma cache com recursos (ficheiros) obtidos directamente da Internet e a partir de outros servidores de cache. A interface dos servidores de cache é acessível usando RMI. O objecto remoto que implementa a interface do servidor de cache regista-se no rmiregistry com o nome CacheServer. O proxy individual apenas serve de intermediário entre os clientes e o servidor de cache. Os clientes (browsers) obtêm os recursos pretendidos pelos utilizadores usando o protocolo HTTP a partir do proxy individual que corre na mesma máquina. No âmbito deste sistema, responda às seguintes questões. 1. Pretende-se criar um programa que verifique se um ficheiro existe no servidor de cache duma instituição. Este programa recebe dois parâmetros: o nome da máquina em que executa o servidor de cache e o URL do recurso de que se pretende verificar a existência. Em caso de erro, o programa deve apresentar imediatamente uma mensagem apropriada à situação de erro surgida. Complete o anexo A com o código apropriado. 2. Complete o anexo B com o código da classe FileToTransfer, usada para transferir o conteúdo de um recurso armazenado num ficheiro entre dois servidores de cache. 3. Complete o anexo C com o código do servidor de cache. Ao completar o código da função getresource pode utilizar apenas as funções definidas neste anexo leia atentamente a sua funcionalidade e o modo como devem ser invocadas nos comentários do código. (No código apresentado não deve ser implementada nenhuma funcionalidade de recuperação de erros.) 4. A classe MulticastRegistry, apresentada no Anexo D, pode ser usada para obter referências para objectos remotos sem saber a sua localização exacta. Para tal, é usada comunicação UDP em modo multi-ponto. A classe inclui tanto a parte servidor, responsável por atender os pedidos de lookup e bind, bem como a parte cliente que faz esses pedidos enviando, repetidamente, uma mensagem para um grupo multi-ponto pré-acordado. Complete o anexo D de modo a que na função lookup os pedidos sejam reenviados repetidamente, com intervalos de aproximadamente 1 segundo, caso ainda não tenha sido obtida uma resposta e até se esgotar o timeout especificado. 2) Para cada pergunta, assinale como V[erdadeira] ou F[alsa] cada uma das afirmações. As respostas erradas descontam. 1. Um servidor implementado em Java RMI: i. Apenas está disponível para receber invocações remotas após ter sido registado no rmiregistry (i.e., após ter sido feito Naming.rebind(...)). ii. Só pode implementar uma e uma só interface remota. iii. Pode terminar a sua execução se o servidor de registo (rmiregistry) local terminar a sua execução. iv. Um servidor RMI que defina um método remoto (void f( A a)), pode receber como parâmtero da função um objecto do tipo B (i.e., uma instância da classe B). 1
2. Suponha que um programa mantém uma referência para um objecto remoto. i. É possível dois threads desse programa usarem a mesma referência remota para fazer diferentes pedidos ao servidor. ii. O programa pode passar essa referência como parâmetro de uma invocação remota de um método definido noutro servidor. iii. Enquanto o programa mantiver essa referência, o servidor por ela referido não termina, a menos que a máquina em que o servidor executa falhe (assumindo que não existem problemas nas comunicações). iv. A referência para o objecto remoto inclui informação sobre a localização do objecto remoto (incluindo o nome/ip da máquina em que o servidor executa). 3. Considere o mecanismo de serialização do Java: i. O mecanismo de serialização automática serializa todos os campos definidos numa classe com excepção dos membros definidos como transient e incluindo os membros estáticos da classe. ii. Ao serializar um objecto, serializa-se não só o valor dos membros do objecto, mas também o código da classe que o objecto implementa. iii. Ao ler uma referência remota (referência para um objecto remoto) gravada previamente num ficheiro usando o mecanismo de serialização automática, é possível usá-la para efectuar uma invocação remota. iv. Ao definir um par de funções para efectuar a serialização não automática, é sempre necessário considerar o estado completo do objecto, incluindo os membros definidos em toda a hierarquia de classes (por exemplo, ao definir as funções de serialização não automática numa classe A que estende B, os membros de B devem ser considerados nas funções definidas). 4. Considere o mecanismo que permite definir fábricas de sockets específicas para cada objecto remoto. i. Uma referência remota inclui informação sobre a fábrica do cliente. ii. Todos os servidores de um mesmo tipo (i.e. instâncias da mesma classe) devem usar a mesma fábrica de sockets. iii. É possível garantir que o conteúdo das invocações RMI não pode ser lido por um atacante utilizando uma fábrica de sockets cujos sockets cifram os dados com um chave secreta criada no momento da criação das fábricas e guardada como membro das fábricas. 3) No contexto do trabalho prático, suponha que um servidor de cache pretende verificar se, para um dado recurso, o conteúdo da sua cache é idêntico ao conteúdo da cache de outro servidor. 1. Explique como poderia implementar esta funcionalidade de forma eficiente em todas as situações, incluindo os recursos relativos a servidores HTTP anteriores à versão 0.9, i.e., a servidores cujas respostas não incluem cabeçalhos. Não se esqueça de indicar as operações que são necessárias estar disponíveis no servidor de cache interrogado e o processamento efectuado no servidor de cache que inicia a verificação. 2. Explique brevemente como poderia implementar esta funcionalidade para recursos relativos a servidores HTTP 1.0. 2
4) Indique, justificando em ambos os casos, se as seguintes afirmações são verdadeiras ou falsas. 1. No âmbito do trabalho prático, quando um servidor de cache remove da sua cache uma página HTML, deixa de haver interesse em manter na cache as figuras que são referidas nessa página, assim, estas figuras podem ser igualmente removidas imediatamente). É Verdadeiro / Falso porque: 2. No contexto do trabalho prático, suponha que o proxy individual descobre o servidor de cache por multicast, i.e., enviando uma mensagem multicast que será respondida pelo servidor de cache. Suponha que nesta interacção o servidor de cache (S) envia ao proxy individual (P) a seguinte mensagem: Servidor de cache -> proxy individual : stub, {KsKpubP, {MD5(stub+Ks)KprivS Ignorando os problema de replaying, o proxy individual pode comunicar de forma segura com o servidor de cache, tendo a certeza que está a comunicar com o servidor de cache, cifrando todas as mensagens trocadas com a chave Ks. É Verdadeiro / Falso porque: 5) Relativamente ao trabalho prático, responda a DUAS das seguintes perguntas (nota: caso responda a mais de duas perguntas, apenas as duas primeiras serão corrigidas). 1. Um requisito do trabalho prático impõe que os recursos de uma instituição devem ser usados de forma prioritária para servir os seus próprios utilizadores. O facto de em Java RMI cada invocação remota ser atendida no servidor por um thread separado pode levantar problemas na implementação desse requisito. Uma possível solução pode basear-se no uso do semáforo como primitiva de sincronização como forma de limitar directamente o número de pedidos externos concorrentes. Outra forma alternativa de atender ao mesmo requisito consiste em depositar os pedidos numa fila de espera, servida por um número de threads fixo e pré-determinado, os quais processam concorrentemente os pedidos e entregam os resultados aos respectivos requerentes através de callback RMIs. Indique uma vantagem e um defeito de cada uma destas técnicas. 2. No âmbito do trabalho prático, suponha que um servidor de cahe envia, periodicamente, para todos os outros servidores de cache uma mensagem com um resumo (Bloom filter) das páginas que mantém em cache. Este resumo é construído de forma a ocupar muito pouco espaço, podendo, no entanto, indicar que uma página se encontra em cache quando tal não acontece (com baixa probabilidade). Caso o resumo indique que a página não se encontra em cache, essa informação é correcta. Indique uma vantagem e uma desvantagem desta aproximação quando comparada com uma solução em que um servidor de cache envia uma mensagem a todos os outros servidores sempre que cada recurso é adicionado ou removido da sua cache. 3
3. Não sendo possível utilizar comunicação multi-ponto para localizar servidores de outras instituições, uma possível alternativa é a seguinte. Cada servidor mantém a lista de todos os outros servidores a executar. Quando um novo thread inicia a execução comunica com um dos servidores já em execução (o qual deve ser dado como parâmtero ao iniciar o servidor) para obter a lista dos outros servidores. Ao receber a lista de servidores já a executar envia uma mensagem a cada um dos servidores da lista a informar a sua presença. O novo servidor adiciona-se localmente à lista obtida. Supondo que a comunicação é efectuada de forma fiável e ignorando os problemas da remoção de servidores da lista de servidores conhecidos, indique se esta aproximação permite garantir que todos os servidores do sistema ficarão a conhecer todos os outros. Justifique. 4. Considere uma forma de filtragem de conteúdos consistindo em procurar ocorrências de palavras banidas em todos os recursos não binários. Caso uma palavra proibida seja encontrada, o servidor de cache recusa-se a servir e fazer caching desses conteúdos. Supondo que o objectivo é filtrar conteúdos de um dado tipo (por exemplo, os conteúdos relacionados com futebol poderiam ser filtrados usando um conjunto de palavras, entre as quais: futebol, bola, estádio, etc.), seria esta estratégia de censura eficaz, coerente e inteligente? Justifique. 5. Relativamente à opção de antecipação proposta no trabalho, considere a seguinte estratégia. O servidor tem um conjunto de threads responsáveis por obter os recursos a partir dos servidores de origem. Estes servidores processam continuamente uma lista de pedidos relativos a recursos a obter. Para cada recurso, obtêm o recurso a partir do servidor final, colocam-no na cache local e, se for uma página HTML, inserem na lista de recursos a obter todos os URLs (imagens e ligações a outras páginas) existentes nessa página. Indique qual o problema desta estratégia e sugira uma alternativa. 4
ANEXO A /** Interface do servidor de cache public interface CacheServerInterface extends java.rmi.remote { /** * Devolve verdadeiro caso o servidor tenha uma cópia do recurso com o URL indicado public boolean existsresource( String url) throws RemoteException;... // outros métodos public class CachedURL { public static void main(string[] args) { if( args.length!= 2) { System.out.println( "Use: java CachedURL host url"); System.exit(0); String hostname = args[0]; String url = args[1]; CacheServerInterface server = (CacheServerInterface) Naming.lookup ( "//" + hostname + "/dirserver" ) ; if( server.existsresource( url ) ) System.out.println( "URL " + url + " disponível na cache" ); else System.out.println( "URL " + url + " não disponível na cache " ); catch( Exception e) { System.out.println( "Erro na comunicação : " + e.getmessage()) ; 5
ANEXO B /** Classe usada para transferir um ficheiro entre dois servidores public class FileToTransfer implements java.io.serializable { String filename; String url; public FileToTransfer(... ) throws... {... /** função que grava estado do objecto, incluindo o conteúdo do ficheiro... /** função que lê estado do objecto, gravando o conteúdo do ficheiro * para um ficheiro com o mesmo nome private void readobject( ObjectInputStream ois ) throws IOException, ClassNotFoundException { filename = ois.readutf() ; // des-serializa o nome do ficheiro url = ois.readutf() ; // des-serializa url //expires = ois.readobject(); long remaining = ois.readlong() ; // des-serializa o tamanho do ficheiro // copia o conteúdo do ficheiro do canal de entrada para o disco byte[] buffer = new byte[1024] ; FileOutputStream fos = new FileOutputStream(filename + ".backup"); while( remaining > 0 ) { int n = ois.read( buffer, 0, (int) Math.min( buffer.length, remaining )) ; fos.write( buffer, 0, n ) ; remaining -= n ; fos.close() ; 6
ANEXO C /** Classe de escepção usada para indicar a inexistência de um dado ficheiro public class URLNotFoundException extends Exception { public URLNotFoundException() { /** Interface do servidor de cache public interface CacheServerInterface... { /** * Método usado por um proxy individual para obter o recurso indicado. O servidor deve * tentar obter o recurso a partir dos servidores de cache e da Internet public FileToTransfer getresource( String url) throws..., URLNotFoundException; /** * Método usado por um servidor de cache para obter o recurso indicado a partir da * da cache de outro servidor de cache. public FileToTransfer getcachedresource( String url) throws..., URLNotFoundException;... // outros métodos public class CacheServer extends UnicastRemoteObject implements CacheServerInterface { public CacheServer() throws RemoteException { super(); /** Devolve array de referências remota para servidores de cache private CacheServerInterface [] getcacheservers () {... /** Obtém recurso associado a URL acedendo ao servidor indicado no URL. Se não é * possível obter o recurso devolve null private FileToTransfer getresourcefrominternet( String url) {... /** Obtém cópia local do recurso associado ao URL indicado. Se não existe cópia * local do recurso, devolve null private FileToTransfer getresourcefromlocalcache ( String url) {... /** * Método usado por um servidor de cache para obter o recurso indicado a partir da * da cache de outro servidor de cache. public FileToTransfer getcachedresource( String url) throws..., URLNotFoundException {... 7
/** * Método usado por um proxy individual para obter o recurso indicado. O servidor deve * tentar obter o recurso a partir dos servidores de cache e da Internet public FileToTransfer getresource( String url) throws..., URLNotFoundException { FileToTransfer r = getresourcefromlocalcache( url); If( r!= null) Return r; CacheServerInterface[] servers = getcacheservers(); for( int i = 0; i < servers.length; i++) { FileToTransfer r = servers[i].getcachedresource( url); Return r; catch( Exception e) { // do nothing r = getresourcefrominternet( url); if( r == null) throw new URLNotFoundException(); else return r; public static void main( String args[]){ System.getProperties().put( "java.security.policy", "policy.all"); if( System.getSecurityManager() == null) { System.setSecurityManager( new RMISecurityManager()); LocateRegistry.createRegistry ( 1099); catch( RemoteException e) { // do nothing Naming.rebind ( "CacheServer, new CacheServer() ); catch( Exception e) { e.printstacktrace(); 8
ANEXO D /*========== Classes usadas para codificar o pedido efectuado ============== class Request implements java.io.serializable { class RegisterRequest extends Request implements java.io.serializable { String name; Remote stub; class LookupRequest extends Request implements java.io.serializable { String name; /* ====== Servidor de registo acessível por multicast ======================= public class MulticastRegistry implements Runnable { private static final String GROUP_ADDR = "239.239.239.239"; private static final int GROUP_PORT = 23923 ; private Hashtable<String, DatagramPacket> stubs ; private MulticastSocket ss ; // ====================== funções do servidor =============================== /** Inicializa o estado do objecto e lança o thread de atendimento. MulticastRegistry() throws IOException { InetAddress group_addr = InetAddress.getByName( GROUP_ADDR ) ; ss = new MulticastSocket( GROUP_PORT ) ; ss.joingroup( group_addr ) ; stubs = new Hashtable<String, DatagramPacket>(); new Thread( this ).start() ; /* Atende os pedidos de lookup e bind. Para isso, espera que chegue um datagrama com * um objecto do tipo RegisterRequest ou LookupRequest. No caso do lookup responde * enviando de volta o stub do objecto remoto serializado. public void run() { for(;;) { DatagramPacket request = new DatagramPacket( new byte[65536], 65536) ; ss.receive(request ) ; Request req = (Request)DatagramToObject( request ); if( req instanceof RegisterRequest) { stubs.put( ((RegisterRequest)req).name, ObjectToDatagram(((RegisterRequest)req).stub ) ) ;... else if( req instanceof LookupRequest ) { DatagramPacket reply = stubs.get( ((LookupRequest)req).name ) ; if( reply!= null ) { reply.setport( request.getport() ) ; reply.setaddress( request.getaddress() ) ; ss.send( reply ) ; catch( Exception x ) {... 9
// ====================== funções auxiliares =============================== /* * Dá o tempo corrente em milissegundos. private static long now(){... /** Serializa o objecto dado para um DatagramPacket. private static DatagramPacket ObjectToDatagram(Object o) {... /** Des-serializa o objecto contido no datagrama dado. private static Object DatagramToObject(DatagramPacket datagram) {... // ====================== funções do cliente =============================== /** Regista um objecto remoto com o nome dado no servidor. public static void rebind(string name, Remote stub) throws IOException {... /* * Implementa o lookup: procura encontrar o stub do objecto identificado por name, * gastando até um máximo de timeout milissegundos. * * Continua a repetir o pedido a cada segundo, caso não tenha recebido * uma resposta (até ao timeout)... public static Remote lookup(string name, int timeout) { long deadline = now() + timeout ; while( now() < deadline ) { catch( SocketTimeoutException x ) { 10