Você está na página 1de 5

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

2ª 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, respetivo
IP e aceitação de RMI irá receber. Posteriormente enviamos cada nickname, respetivo IP e aceitação de
rmi ou não, presentes 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, regista quando foi a
última vez que este efetuou um pedido ao servidor e regista também se este deseja comunicar
remotamente(RMI) . 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, a data mais recente na qual o cliente foi visto, ou seja, a última vez que efetuou
um pedido ao servidor e também se este deseja comunicar remotamente(RMI).

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.

No método getNicknameAndIPList() 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
nickname, o IP e o RMI 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, a
porta do respetivo endereço e, se este pretender comunicar remotamente(RMI), deve selecionar a
checkbox. 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. E se não selecionar a checkbox, o cliente não irá comunicar remotamente
com outros. 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.

No método criarRegisto() criamos o registo RMI na porta 1099 e criamos um objeto remoto. Para
esse registo, em localhost, precisa de criar um serviço com o nome “PrivateMessaging” que depois é
associado ao objeto remoto.

No método separarNicknameIpRmi (int linha, int coluna) vamos buscar a linha selecionada da
tabela das Presenças e separamos o nickname do Ip da informação relativa ao RMI(true/false). Fazemos
verificações não permitindo que o cliente envie mensagens para si próprio e obrigando-o a selecionar um
dos clientes que pretende comunicar remotamente.

No método enviarMensagemPrivada (String nicknameParaQuemVamosEnviar,String ip,String


mensagemEnviar) adicionamos à tabela das mensagens privadas uma nova linha com a seguinte
informação: “Enviou para:” + nicknameParaQuemVamosEnviar + ": " + mensagemEnviar. De seguida,
acedemos ao registo do IP ao qual vamos enviar a mensagem e fazemos um lookup com o SERVICE_NAME
(“PrivateMessaging”) e obtemos o objeto remoto. Através deste escrevemos a mensagem que
pretendemos enviar.

EnviarMensagem

Nesta classe vamos guardar o nickname e a DefaultTableModel, para posteriormente podermos


escrever na tabela.

No método sendMessage (String name, String message) adicionamos uma linha à tabela das
mensagens privadas com o nome do cliente que a envia e o respetivo conteúdo. Retornamos o nickname
de quem recebe a mensagem como pedido no enunciado.

PrivateMessaging

Esta interface serve unicamente para declarar o método sendMessage, podendo ser executado
pelo stub.
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.

Para tal, no “SESSION_UPDATE_REQUEST” temos de enviar uma String com o nickname e, através
do uso de booleans, indicar se o cliente deseja comunicar remotamente (RMI). No seguinte formato:

Se o boolean rmi for true, significa que o cliente deseja comunicar com outros através de
mensagens privadas beneficiando da comunicação remota. Caso contrário, ou seja, se o boolean for false
significa que o cliente não deseja comunicar com outros através de mensagens privadas nem receber as
mesmas, logo não beneficia da comunicação remota.

Se o cliente desejar comunicar remotamente, terá de ser criado um registo na porta 1099. Para
esse registo, precisa de criar um serviço com o nome “PrivateMessaging” que depois é associado a um
objeto remoto.

Quando o cliente desejar enviar uma mensagem privada, deve especificar o ip do destinatário da
mensagem e o nome do serviço(“PrivateMessaging”) para ser possível procurar no registo o objeto remoto
e enviar a mensagem.

Q2.

Para a implementação desta solução o servidor necessita de ter acesso a todos os objetos remotos
(stubs) dos clientes. Para tal, o cliente ao fazer o SESSION_UPDATE_REQUEST, este deve incluir o stub na
mensagem que envia.

Tendo acesso a esses objetos remotos, o servidor, sempre que quisesse notificar algum cliente,
seja mensagens privadas, mensagens gerais ou novas presenças, conseguiria executar os métodos dos
objetos remotos. Isto torna-se numa vantagem pois auxilia o servidor, não o sobrecarregando.

Q3.

O servidor tem acesso a todos os canais de comunicação (de entrada e saída de informação), de
todos os clientes ativos. Uma possível solução, era sempre que um cliente quisesse enviar uma
mensagem privada a outro cliente, efetuava um pedido ao servidor. O pedido teria de ser composto por,
no mínimo, dois parâmetros. No cabeçalho da mensagem, o cliente deve indicar se pretende enviar
mensagem privada ou não, bem como o conteúdo da mensagem e qual o recetor (nickname ou IP) da
mesma. Quando o servidor receber um pedido do cliente, deve analisar o cabeçalho e se se tratar de
uma mensagem privada, tem apenas de a encaminhar ao cliente destinatário, usando o “printWriter”.

Esta solução deixa muito a desejar relativamente a questões de segurança. Como o servidor
serve de intermediário entre o cliente emissor e recetor da mensagem, tem acesso ao conteúdo da
mesma, desrespeitando a privacidade entre clientes. Em termos de escalabilidade, se vários clientes
quisessem enviar qualquer tipo de mensagem para o servidor, este fica sobrecarregado, uma vez que
pode não ter capacidade para gerir todos os pedidos do cliente, aumentando a complexidade do sistema.

Você também pode gostar