Lab5 Ricardo Tominaga

aluno: Nome do Aluno
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 01/12/2008 (10)

Introdução

No laboratório 5 foi desenvolvido um servidor de um bolão virtual. A sua confecção envolveu os seguintes temas:

  • Padrão Decorator
  • Padrão Builder
  • IO em ruby
  • Multithreading
  • Safe threads
  • Sockets

Ao final, pôde-se acessar o servidor remotamente e simultâneamente através de "telnet".

Desenvolvimento

Padrão Decorator

O padrão Decorator permite a inclusão de comportamentos em tempo de execução de forma aditiva, ou seja, o código base não é alterado, ele é englobado pelo Decorator que adiciona "modificações" aos métodos já existentes.

Thread safety

No caso de thread safety, o padrão Decorator foi utilizado para adicionar monitores a métodos que seriam acessados por multiplas threads, de forma que quando uma thread estivesse acessando o método outra não poderia acessá-lo ao mesmo tempo, evitando assim condições de corrida.

def make_bet(score,user)
      synchronize do
        @decorated.make_bet(score, user)
      end
    end

"Decoração" do método make_bet com thread safety

Persistence

Como se trata de um bolão virtual, usuários, apostas e partidas devem ser armazenadas de forma persistente, isto é, num pequeno banco de dados. Para evitar que qualquer informação fosse perdida, foi criado um decorador de persistencia que adicionava aos métodos relativos a dados persistentes a característica de salvar num arquivo os referidos dados.

def make_bet(score,user)
      bet = @decorated.make_bet(score, user)
      @matchlist.save
      bet
    end

"Decoração" do método make_bet com persistencia

Padrão builder

O padrão builder, como o nome sugere, é usado para construção. Ele encapsula em uma classe todo o processo de instanciação de determinado objeto complexo. No caso do laboratório, ele foi utilizado para criar matchlists e userlists e adicionar a elas, i.e. decorá-las com, thread safety, logging e persistance.

def add_all
      self.add_persistance
      self.add_logging
      self.add_thread_safety
    end

Método add_all que constrói uma matchlist com thread safety, logging e persistencia

O servidor

Os clientes se conectam ao servidor e utilizam um protocolo (yaml+próprio) para comunicação.

INICIO
---
request:
 operation: <operation>
 parameters:
  - <parameter1>
  - <parameter2>
  - ...
FIM

Exemplo de comunicação no protocolo do servidor

E através desse protocolo o servidor realiza as seguintes operações:

User

  • Create user
  • Login
  • List active matches
  • List finalized matches
  • Retrieve single match information
  • List bets (user's bets)
  • Make bet
  • Logoff
  • Disconnect

Admin

  • Create match
  • Finalize match
ex1.jpg

Exemplo de acesso múltiplo ao servidor (esq. user, dir. admin)

O código

Primeiramente será descrita a conexão com o servidor.

$server = TCPServer.new(4555)
 
loop do
  puts "Ouvindo conexao..."
  while (io_socket = $server.accept)
    Thread.new do
      $stdout.puts "Estabelecendo conexao..."
      io_socket.puts "Conexao estabelecida...\n\r"
      main(io_socket,io_socket)
      $stdout.puts "Conexao encerrada...\n\r"
      io_socket.close
    end
  end
end

Pedaço de código que espera pedidos de conexão

O código acima espera pedidos de conexão, e quando ocorre um pedido o "stream" é armazenado em io_socket. É, então, criada uma thread para essa conexão, e é passado io_socket para o método main que concentra todo o tratamento de pedidos.

Passando a parte fácil, temos o método main.

def main(inp = $stdin,outp = $stdout)
 
  req_reader = RequestReader.new(inp)
  req_handler = RequestHandler.new($match_list, $user_list, outp)
 
  loop do
    begin  
      req_reader.wait_for_request
      req_handler.handle_request(req_reader.requisicao)
    rescue
      ErrorMsgPrinter.new(outp).print_msg("Problemas com a requisicao")
    end
    break if req_handler.disconnect
  end
end

O método main

O trabalho do método main foi dividido entre dois outros métodos, req_reader e req_handler. O método req_reader fica "escutando" o input stream a espera de comandos no formato do protocolo descrito acima. Req_reader "limpa" o protocolo e passa uma requisição pré moldada para req_handler, o método mais complexo.

A função do método req_handler (request handler) é tratar uma requisição. A sua estrutura é composta por um case-when ("switch-case") o qual seleciona a operação designada por "operation" e chama o método correspondente ou dispara uma mensagem de erro. A maioria dos métodos chamados no case-when provêm do modulo printers.

Os printers formatam uma resposta e a enviam ao output stream.

Sticking point

Um erro tomou longas horas do autor e será descrito a seguir.

Todas as vezes que se tentava executar a requisição make_bet o arquivo das matches era apagado misteriosamente e era lançado o seguinte erro "can't dump anonymous class Class". Sabiamente, o autor supôs que o método problemático era o próprio make_bet.

Analisou-se, então, o código do tal método problemático. Através do comentário estratégico de algumas linhas descobriu-se que o problema estava mais algumas camadas para dentro do programa, no método add_holder.

Já dentro do add_holder, e usando a mesma técnica de comentários, decobriu-se que quem causava a exceção era a seguinte linha de código:

@holders << holder

Código "do mal"

Infelizmente, a inexperiência do autor não permitiu verificar diretamente qual era o problema. Então foram realizados testes aleatórios, e um deles forneceu a luz no fim do túnel. No próprio método req_handler, foi adicionada a impressão da variável @user. O que resultou na informação de que @user (como era esperado) estava decorado, i.e. não é um "plain object". Quando essa referência chegava no código "do mal" ele lançava o erro, que, por sua vez, ocorria antes da diretiva "save", apagando o arquivo das matches.

A solução foi utilizar o método reformado get_plain_object.

@user = get_plain_object(@user)

Solução milagrosa

Conclusão

À primeira vista, o laboratório parecia, de certa forma, um "abuso". No entanto, permitiu o aprendizado de diversas ferramentas e o resultado saiu bem "bonito". Algo que deixou o autor muto safisfeito, apesar de compor apenas pequena parte do lab, foi a possibilidade de acessar o programa por socket, de outro computador.

O yaml foi muito bom para a comunicação cliente-servidor, no entanto na direção servidor-cliente ele estava dando vários problemas de formatação, a resposta estava "feia". Foi utilizado, alternativamente, a impressão direta no output stream usando o método puts.

Add a New Comment
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License