Sistemas Distribuídos 4º Ano LEIC
Trabalho Prático nº 1: Serviço de Tempo com sockets
UDP em Java


1  Introdução

1.1  Objectivo

Este trabalho prático tem por objectivo principal a consolidação dos conceitos fundamentais requeridos para a programação de aplicações distribuídas directamente sobre protocolos de comunicação.

Pretende-se também que ganhem alguma familiaridade com a interface de programação baseada em sockets, a qual foi adoptada por quase todos os sistemas operativos disponíveis no mercado.

1.2  Método

Há um provérbio que diz (cito de memória):
Ouço, esqueço. Vejo, memorizo. Faço, aprendo.
Assim, espero que cada um de vocês (ou no máximo, em pares) tente resolver por si só o problema proposto neste TP. Obviamente poderão discutir com os vossos colegas os problemas, bem como possíveis soluções. Também podem, e devem, consultar a documentação Java disponibilizada pela Sun.

1.3  Siga as instruções com rigor

Os enunciados deste TP são uma especificação do vosso trabalho. Como engenheiros, uma parte significativa do vosso trabalho requererá que sigam especificações, elaboradas por vocês ou por outros: uma solução para um problema errado não é correcta. Habituem-se a seguir as especificações dos problemas que têm que resolver. (No entanto, mantenham uma atitude crítica para detectar eventuais inconsistências ou erros.)

Neste TP é pedido que escrevam dois programas, um cliente e um servidor, usando, se assim o desejarem, uma classe que vos é fornecida. Cabe-vos definir e conceber as outras funções, bem como o seu agrupamento em ficheiros. No entanto, deverão criar uma Makefile que permita gerar os vossos programas usando dois simples comandos, a saber:
make DateClient, para gerar o cliente;
make DateServer, para gerar o servidor.
Apesar de ter tentado o meu melhor no que diz respeito à clareza e ao rigor deste guião, é inevitável que não tenhamos eliminado todas as ambiguidades. Assim, em caso de dúvida, não hesitem em enviar-me email.

2  A aplicação

Neste trabalho prático vão escrever uma aplicação cliente-servidor para determinar (e acertar) a hora.

A ideia é usar um processo servidor que ``tem'' um relógio e fornece a hora a processos clientes que pretendam saber a hora. A utilização dum único processo servidor permite que os diferentes clientes usem uma base de tempo comum.

De facto, embora cada computador tenha um relógio é muito provável que o valor dos relógios dos diferentes computadores dum sistema distribuído sejam distintos. Algumas razões para os múltiplos valores incluem: 1) os relógios foram inicializados com horas distintas, 2) as frequências reais dos relógios são diferentes das nominais, conduzindo a atrasos/avanços, 3) a pilha da motherboard está gasta e consequentemente, o relógio é reinicializado (em 1 de Janeiro de 1980, no caso dos PCs) cada vez que o computador é ligado.

Obs.- Em princípio, os computadores da FEUP executam uma aplicação de sincronização de relógios, que permite manter os relógios sincronizados com diferenças de poucos ms.

3  Protocolo de comunicação cliente-servidor

Numa aplicação cliente-servidor, o cliente envia pedidos ao servidor. Este último processa os pedidos, retornando os resultados, se alguns, aos clientes.

Numa aplicação baseada em sockets quer os pedidos quer os resultados são enviados em mensagens. A mensagem enviada pelo cliente deverá incluir o pedido em si bem como qualquer parâmetro. É responsabilidade do cliente construir a mensagem pedido. Por outro lado, o servidor terá que identificar o pedido e extrair qualquer parâmetro de cada mensagem que recebe dum cliente, e invocar a função que processa o pedido. Do mesmo modo, cabe ao servidor construir a mensagem de resposta, se alguma, possivelmente com os resultados do processamento do pedido. O cliente por sua vez terá que interpretar a mensagem resposta, se alguma.

O formato e o significado das mensagens trocadas entre o cliente e o servidor, bem como as regras que governam a troca dessas mensagens constituem o protocolo de comunicação entre o cliente e o servidor.

Neste TP, o servidor deverá ser capaz de responder a 2 pedidos: Obs.- Não nos preocuparemos com problemas de controlo de acesso: numa aplicação a sério, não deveria ser possível que qualquer utilizador alterasse a hora. (Por exemplo, em Unix só o root tem permissões para alterar a hora do relógio local.)

Um possível formato das mensagens pedido é:
``GETTIME''
para ler as horas;
``ADJTIME delta''
para acertar as horas, onde ``delta'' é uma string representando um inteiro com sinal que indica o número de segundos a somar à hora actual;
Um possível formato para as mensagens resposta a qualquer destes pedidos é:
``data''
uma string com a data e hora, no formato que acharem apropriado.
Notem que todas as mensagens do protocolo proposto são strings legíveis. Este facto, simplifica o debugging da aplicação. Outra vantagem é que, normalmente, não há necessidade de fazer conversão de formatos dos conteúdos das mensagens. No entanto, há também alguns problemas com este tipo de protocolos. Por exemplo, conduzem a mensagens mais longas. No caso da aplicação deste TP, atendendo a que há apenas 2 tipos de pedidos, bastar-nos-ia usar um bit, para indicar o tipo da mensagem (seria sensato usar mais bits, para o caso de se decidir acrescentar mais mensagens). Outro inconveniente é que o processamento de protocolos baseados em strings é mais elaborado. P.ex., ao fazer a desmultiplexação das mensagens, em vez de usar uma simples instrução switch tem-se que usar funções de comparação de strings.

A maioria dos protocolos da camada aplicação, p.ex. FTP e HTTP, usam protocolos baseados em strings. Pelo contrário, a generalidade dos protocolos de níveis mais baixos usam protocolos em que o tipo de mensagem é codificado em alguns bytes.

4  Comunicação com Sockets do Tipo DGRAM

Para implementar a aplicação acima descrita deverão desenvolver duas classes (DateClient) e (DateServer) usando a classe java.net.DatagramSocket.

A vossa aplicação poderá usar a classe:
 public class MyClock {
   static String getTime();
   static String adjTime(double adj);
 } 
      
que fornece os métodos getTime() e adjTime(), para lêr e acertar a hora, respectivamente, e que se encontra no ficheiro MyClock.java.

Servidor

O programa servidor deverá ser invocado da seguinte maneira:
java DateServer <port_number>
onde
<port_number> é o número do porto que o servidor deverá usar.
O programa servidor deverá imprimir mensagens na consola indicando as acções executadas, nomeadamente os pedidos recebidos e a sua fonte (endereço IP e número do porto do cliente), bem como as respostas enviadas.

Cliente

O programa cliente deverá ser invocado da seguinte maneira:
java DateClient <host_name> <port_number>
onde
<host_name> é o nome DNS do computador onde executa o servidor;
<port_number> é o número do porto do servidor.
O processo cliente deverá criar um socket e executar um ciclo infinito no qual:
  1. Envia uma mensagem por segundo.
  2. Por cada 3 mensagens GETTIME deve enviar uma mensagem ADJTIME com um valor à vossa escolha.
Após submeter um pedido, o cliente fica à espera até receber uma resposta.

O programa cliente deverá imprimir mensagens na consola indicando as acções executadas, nomeadamente os pedidos enviados ao servidor bem como as respostas respectivas.

Para pensar:

O que acontece se houver perda de mensagens, ou se o servidor não estiver a funcionar? Como poderiam tornar o vosso cliente robusto contra este tipo de eventos? Se estiverem interessados e tiverem tempo, tentem implementar uma solução.
This document was translated from LATEX by HEVEA.