Você está na página 1de 6

UNIVERSIDADE DO MINHO

LICENCIATURA EM ENGENHARIA E GESTÃO DE SISTEMAS DE INFORMAÇÃO


PROJETO 2022/2023:
FUNDAMENTOS DE SISTEMAS DISTRIBUÍDOS
PL1 - Grupo 4

1ª ENTREGA
Relatório do Projeto

A97468 A95144 A95822

Guilherme José de Sousa Inês Capa de Barros Maria Beatriz Garcez Morais
Barbosa

1. Implementação:
PresencesServer

Esta é a classe principal do Servidor.


Esta classe contém uma lista static que vai guardar todas as instancias da class
AtendePedidoTCP criadas para nos permitir enviar e receber, pedidos e respostas, ao cliente.
Uma outra lista de strings é criada contendo as dez mensagens mais recentes.

Começamos por pedir a porta na qual o servidor deseja operar para que qualquer
conexão seja estabelecida. Pedimos também que seja indicado um valor para o parâmetro
SESSION_TIMEOUT, inicializamos as Presences passando como parâmetro o valor inserido e
inicializamos o servidor com a porta que ele previamente selecionou.
O servidor está sempre aberto a aceitar ligações vindas de clientes. Cada vez que temos
uma nova ligação de um cliente ao servidor, criamos e adicionamos uma instância do
atendePedidoTCP (passamos como parâmetro a ligação socket e o Servidor que lhe deu origem)
à lista de utilizadores.
De modo que o servidor esteja atualizado e atualize os clientes em relação ao número
de conectados, vamos ter um temporizador que a cada segundo compara os utilizadores que
estão ativos com os utilizadores que estão presentes na lista static que contém todos os
objetos do tipo atendePedidoTCP.
Percorremos a lista static e por cada utilizador que estiver nessa lista, mas não estiver
na dos ativos, adicionamos à lista dos utilizadores inativos.
Por fim, para cada elemento da lista dos inativos enviamos um “SESSION_TIME_OUT”,
fechamos a ligação com o servidor e removemos da lista static de utilizadores.

AtendePedidoTCP

Esta classe gere a ligação de cada cliente com o servidor e é instanciada no


PresencesServer cada vez que uma nova ligação é efetuada.

Cada AtendePedidoTCP tem um nickname do respetivo cliente.

Quando o cliente envia um pedido, este está contido numa só string que contem o tipo de
pedido e o respetivo conteúdo separada por “:”, que é lida pelo atendePedidoTCP o qual os vai
separar. Consoante o cabeçalho da mensagem que pode ser "SESSION_UPDATE_REQUEST" ou
"AGENT_POST" o servidor terá um comportamento diferente:

• Se for do tipo "AGENT_POST", significa que foi uma mensagem que o cliente enviou e
vai guardá-la nas 10 mensagens mais recentes.
• Se for do tipo "SESSION_UPDATE_REQUEST" e o AtendePedidoTCP não tiver nickname,
ou seja, se ainda não se tiver conectado, então este passa a ser igual ao nickname
enviado no pedido do cliente e este é adicionado à lista de utilizadores.

Em ambos os tipos de pedido, o servidor atualiza todos os clientes com o


“SESSION_UPDATE” e é reiniciado o cronometro para o utilizador que realizou o pedido.
No método sessionUpdate() começamos por ir buscar o número de utilizadores que estão
presentes no servidor. Enviamos esse número para o Cliente com o intuito de informar
quantos nicknames irá receber e posteriormente enviamos cada nickname presente na lista de
utilizadores. De seguida, fazemos o mesmo procedimento para enviar as mensagens mais
recentes, ou seja, enviamos a quantidade e o conteúdo das mensagens, sendo que o número
de mensagens nunca pode ser maior que dez.

IPInfo

Esta classe serve para guardar a informação relativa ao cliente: IP, nickname e regista
quando foi a última vez que este efetuou um pedido ao servidor. O método TimeOutPassed()
consiste em verificar a diferença do tempo atual e da última vez que o cliente foi visto. Se este
tempo for maior do que o tempo estipulado para o “TIME_OUT”, então o cliente será visto
como inativo.

Presences

Criamos uma hashtable para incluir os dados dos clientes, sendo a chave o IP e para
cada chave tem associado um objeto da classe IPInfo. Estes dados contém a informação
relativa aos IP’s, os respetivos nicknames, e a data mais recente na qual o cliente foi visto, ou
seja, a última vez que efetuou um pedido ao servidor.

Posteriormente apresentamos um método que nos irá retornar os clientes presentes.


Se se tratar da primeira vez que um cliente se tenta conectar com o servidor, criamos um novo
objeto IPInfo e adicionamo-lo à HashTable presentIPs e definimos o tempo do último pedido
como sendo o tempo de pedido de conexão. Caso o cliente já se encontre conectado, então
iremos somente atualizar o tempo em que este foi visto pela última vez. Este método será
executado cada vez que um cliente solicitar um pedido do tipo "SESSION_UPDATE_REQUEST"
ou "AGENT_POST" ao servidor.

No método getNickNameList() começamos por percorrer a lista de objetos IPInfo e


para cada elemento vamos verificar se dentro do tempo estipulado como tempo de
“TIME_OUT” o cliente realizou algum pedido, se não tiver realizado não é adicionado
novamente à lista, caso contrário o nome dele é adicionado novamente e o cronometro é
reiniciado.

ConfiguracoesCliente

Esta interface será usada com o intuito de receber informações sobre como será
estabelecida a conexão e com quem. Para isso pedimos ao cliente que insira o endereço ao
qual se pretende conectar e a porta do respetivo endereço. No caso do cliente não inserir nem
o endereço nem a porta aos quais se deseja conectar, será utilizada a informação default. De
seguida inicializamos a interface cliente.

InterfaceCliente
Começamos por inicializar a interface do cliente. Decidimos o modelo para cada uma
das tabelas e criamos o painel que representa toda a nossa interface. É nesta interface que o
cliente se pode conectar com o servidor, para isso criamos um botão “Conectar” que estabelece
a ligação com o servidor através do método iniciar(). Este método cria o Socket, o canal de
comunicação com o servidor, que serve para entrada (BufferedReader) e saída (PrintWriter) de
dados. O cliente deve inserir um nickname válido, ou seja, que não seja nulo e, posteriormente,
iniciamos o método iniciarLoop() que nos irá permitirá realizar um
"SESSION_UPDATE_REQUEST” a cada 120 segundos.
Ao clicar no botão “Enviar”, o método enviarMensagem(String mensagemEnviar) é
chamado e enviamos para o servidor um pedido do tipo “AGENT_POST” que contém a
mensagem em questão.
Para recebermos as mensagens que o servidor envia, criamos uma thread que está
sempre em execução enquanto houver ligação entre o cliente e o servidor. Esta thread é
responsável por ver se a mensagem recebida é do tipo "SESSION_UPDATE”. Caso seja, prepara
o cliente para este receber a lista dos utilizadores presentes e as 10 mensagens mais recentes.
A ordem pela qual é recebida a informação é a mesma pela qual é enviada pelo servidor.
Já no método receberSessionUpDate() vamos guardar as informações das presenças e
das mensagens em dois arrays distintos. Inicialmente percorremos o array das presenças para
que possamos listar os clientes presentes na interface do cliente, adicionando assim a
quantidade de linhas na tabela correspondente ao número de utilizadores ativos. Realizamos o
mesmo processo para as mensagens: para cada elemento contido no array vamos adicionar uma
linha na tabela das mensagens com o respetivo conteúdo (nickname: post). Após isto alteramos
a visibilidade do botão “Conectar”, metendo este como não visível.

2. Respostas às Questões Específicas do Enunciado

Q1.

O nosso sistema não está implementado usando formatos externos de representação


de dados. Se se pretender adicionar um novo agente de utilizador, será necessário utilizar a
linguagem de programação JAVA, uma vez que esta já conta com a serialização que transforma
os dados em conjuntos de bytes.

Em primeiro lugar, será necessário cada agente de utilizador possuir um nickname.

Sempre que o agente de utilizador quiser enviar um “sessionUpdateRequest”, terá de o


fazer enviando para o servidor no seguinte formato: "SESSION_UPDATE_REQUEST:" + nickname.

Sempre que o agente de utilizador quiser enviar uma mensagem para publicar no feed,
terá de o fazer enviando para o servidor no seguinte formato: "AGENT_POST:" +
mensagemEnviar.

Para receber mensagens vindas do servidor, o cliente precisa de ter uma thread a correr
enquanto a ligação com o cliente estiver ativa, para que este possa receber as mensagens vindas
do servidor. Existem dois tipos de mensagem: “SESSION_UPDATE” e “SESSION_TIME_OUT”.

O primeiro tipo de mensagem é recebido sempre que um cliente envia uma mensagem
para o servidor seja ela "SESSION_UPDATE_REQUEST" ou "AGENT_POST". Existe uma ordem
para receber informação vinda do servidor sempre que recebemos uma mensagem do tipo
“SESSION_UPDATE”. Em primeiro lugar, recebemos o número de utilizadores presentes na lista
de presenças. O agente de utilizador ficando a saber quantos números de utilizadores estão
presentes, receberá essa quantidade em nicknames. De seguida, recebe o número de
mensagens mais recentes e fica a saber quantas mensagens vai receber a seguir. Após isso, o
conteúdo de cada mensagem é recebido.

Se for do tipo “SESSION_TIME_OUT”, significa que o tempo estipulado foi excedido sem
o cliente ter feito nenhum pedido e a ligação é encerrada.

Q2.

O nosso sistema não está totalmente preparado para lidar com falhas de forma
autónoma. Testamos o nosso programa e corrigimos todas as que encontramos e incluímos
exceptions para prevenir potenciais erros, porém isto não significa que não tenhamos que
enfrentar falhas.

Q3.

Algumas das limitações que encontramos no nosso projeto foram: a ausência de um


relógio global, muitos pedidos de diferentes clientes podem encher o buffer e o servidor não é
capaz de responder, a falta de segurança, não termos autenticação, o envio de mensagens ser
em string e não do objeto todo, a confidencialidade e a integridade dos dados.

No que diz respeito ao relógio global sentimo-nos limitados quando tentamos acertar
o cronómetro de cada um dos temporizadores (SESSION_TIME_OUT e o
SESSION_UPDATE_REQUEST). Isto ser-nos-ia útil para tanto o cliente como o servidor terem as
informações o mais atualizadas e sincronizadas possível.

A falta de segurança para já é uma limitação porque não está a ser tratada nesta fase,
no entanto, iremos lidar com a mesma numa próxima entrega.

Uma outra limitação é o envio das mensagens ser em String e não ser possível enviar o
objeto todo, como por exemplo um ArrayList.

Ainda não sabemos se

Q4.

Existem diversos fatores que tornam uma solução mais adequada que a outra, assim
como também existem outros fatores que a tornam menos adequada. Devemos debruçar-nos
sobre esta questão e analisar o sistema e as suas necessidades para entendermos qual das
soluções é a mais indicada.

É evidente que se a solução escolhida for o servidor a tomar a iniciativa de notificar


todos os clientes sempre que ocorrer uma nova alteração, então tornar-se-á um fator
vantajoso na medida em que os clientes podem contar com informações verídicas a qualquer
momento.

No entanto, para isto ser possível, iremos sobrecarregar o servidor e tornar mais
complexo o nosso sistema, tornando esta solução menos lapidada.

Se a solução for os clientes a tomarem regularmente a iniciativa de contactar o


servidor para saber se existe nova informação, então as vantagens seriam uma simplificação
do lado do servidor, uma vez que, este só teria que responder a pedidos de clientes se os
tivesse, e não como na outra solução, em que além de termos de responder a pedidos dos dois
tipos (SESSION_UPDATE-REQUEST e AGENT_POST), teríamos também de enviar regularmente
mensagens do tipo SESSION_UPDATE.

Por outro lado, esta solução apresenta algumas desvantagens. Sempre que houver
alguma alteração no sistema, o cliente não será informado, a não ser que requisite ao servidor,
correndo o risco de se fiar erroneamente na visualização desatualizada do sistema.

Concluindo, ambas as soluções apresentam vantagens e desvantagens, a melhor


solução irá depender da preferência de satisfação dos diferentes requisitos.

Q5.

O projeto que temos vindo a desenvolver recai sobre a comunicação entre clientes
ligados através de um servidor. Para que esta comunicação seja eficaz e seja estabelecida
corretamente, é necessário que as mensagens sejam entregues por ordem ao cliente e sem
perdas de informação.

Se usarmos Sockets TCP para a realização do projeto, todos os casos anteriormente


abordados serão cobertos e garantidos, isto porque a pilha TCP trata dos dados que são
enviados para a rede e entregues ao recetor. Após isso permite retransmiti-los até que estes
sejam reconhecidos pelo recetor. Como já mencionado, uma das vantagens da utilização do
TCP é a garantia de que o recetor recebe os dados na ordem correta e uma única vez. Outra
das vantagens é o facto de ocorrer controlo de fluxo, este ocorre transmitindo os dados a uma
taxa de transmissão adequada para a conexão de rede e para o recetor.

Já com a utilização de Sockets UDP, o programador é que trata de gerir a transmissão


diretamente para a rede, tendo também de arranjar uma maneira de lidar com as perdas de
pacotes de dados, com a ordem a que estes chegam ao destinatário. Nesta maneira de
execução é também necessário que o programador efetue o controlo de fluxo e a
fragmentação de dados, de modo a que os pacotes possam ser transmitidos pela conexão de
rede.

No nosso ponto de vista, o protocolo UDP poderiam ser a melhor solução se o seguinte
fator fosse prioritário: A rapidez da conexão; se necessitássemos de uma conexão rápida,
como é necessário numa chamada de voz ou jogos, mesmo podendo sofrer algum tipo de
perda de dados, diríamos que o protocolo UDP cobre melhor essa funcionalidade.

Concluindo, o projeto poderia ser implementado através de Sockets UDP em vez de


TCP, contudo não existem os benefícios necessários nesse protocolo ao ponto dele se sobrepor
ao protocolo TCP. Como a rapidez da conexão não é tão tida em conta neste contexto e todos
os outros fatores referidos do protocolo TCP são muito mais prioritários, podemos dizer que a
melhor escolha de implementação seria através de Sockets TCP (relembrando também de
todos os outros pontos "negativos" referidos previamente sobre o protocolo UDP).

Você também pode gostar