Lab5 Misael Alexandre

aluno: Misael Alexandre
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 10/12/2008 (11)

Introdução

Este laboratório visou uma melhoria na versão inicial de uma aplicação cliente-servidor previamente dada, aplicando conhecimentos adquiridos em aula como Threads, IO e a implementação do Padrão de Projeto Decorator. A linguagem usada neste laboratório foi Ruby. A fim de desenvolver também Java, a aplicação do sexto laboratório, que visava conectar com a parte do servidor desenvolvida no presente laboratório, foi feita em Java.

Desenvolvimento

A aplicação a ser desenvolvida neste e no próximo laboratórios será um "Bolão Virtual", no qual apostadores poderão fazer apostas em um conjunto de jogos que os servidor mostra. Neste laboratório será desenvolvida a parte servidor da aplicação (Backend).

As ações feitas visando melhorar o código foram:

Mudanças nos Decorators de Thread Safety

Aqui o código foi melhorado com a criação de dois Decorators: MatchThreadSafeDecorator e MatchListThreadSafeDecorator.

No primeiro, tal Decorator foi implementado visto a necessidade de alguns métodos da classe Match (make_bet e finalize) de estarem livres de condições de corrida, já que alteravam o conteúdo de suas instâncias. Assim, foi criado o Decorator MatchThreadSafeDecorator, o qual utilizou Threads para tais métodos fossem executados unitariamente. O código da classe pode ser visto abaixo:

require 'monitor'
require 'decorators/decorator'
module Decorators
  class MatchThreadSafeDecorator < Decorator
    include MonitorMixin
 
    def make_bet(score, user)
      synchronize do
        @decorated.make_bet(score, user)
      end
    end
 
    def finalize(final_score)
      synchronize do
        @decorated.finalize(final_score)
      end
    end      
 
  end
end

O segundo Decorator foi criado a partir da necessidade de que o método add criasse uma lista de Matches decorados, e por sua vez tal adição de Matches fosse "Thread Safe", foi criado o Decorator MatchListThreadSafeDecorator, cujo código pode ser visto abaixo:

require 'decorators/decorator'
require 'decorators/match_thread_safe_decorator'
module Decorators
  class MatchListThreadSafeDecorator < Decorator
  include MonitorMixin
 
    def initialize(decorated)
        old_match_decorator = decorated.item_decorator
        decorated.item_decorator = lambda do |match|
          MatchThreadSafeDecorator.new( old_match_decorator.call(match) )
        end
        super
    end
 
    def add(name, end_time)
      synchronize do
        match = @decorated.add(name, end_time)
        $logger.info "Match '#{name}' was added successfully."
        match
      end
    end
 
  end
end

Mudanças nos Decorators de Persistência

Aqui, foram criados dois Decorators: MatchPersistanceDecorator e MatchListPersistanceDecorator

Dada a necessidade de que alguns métodos da classe Match, os quais alteram objetos, pudessem salvar em disco as alterações feitas, criou-se o Decorator MatchPersistanceDecorator. Era necessário haver uma referência a lista que a partida estaria inclusa, de forma a salvar as alterações da partida na lista. Assim, resolveu-se criar uma viriável @match_list, que na criação do objeto já recebia a lista que o objeto fazia parte. O código foi:

require 'decorators/decorator'
module Decorators
  class MatchPersistanceDecorator < Decorator
 
    def initialize(object , match_list)
      @match_list=match_list
      super(object)
    end
 
    def make_bet(score, user)
      aposta=@decorated.make_bet(score, user)
      @match_list.save
      aposta
    end
 
    def finalize(final_score)
      placar=@decorated.finalize(final_score)
      @match_list.save
      placar
    end
 
  end
end

Foi também criada a classe MatchListPersistanceDecorator, a qual fazia com que o método add criasse matches decorados com MatchPersistanceDecorator. O código da classe foi:

require 'decorators/decorator'
require 'decorators/list_persistance_decorator'
require 'decorators/match_persistance_decorator'
 
module Decorators
  class MatchListPersistanceDecorator < ListPersistanceDecorator
 
    def initialize(decorated,filename)
      old_match_decorator = decorated.item_decorator
      decorated.item_decorator = lambda do |match|
        MatchPersistanceDecorator.new( old_match_decorator.call(match), self )
      end
      super(decorated,filename)
    end
 
    def add(name, end_time)
      match = @decorated.add(name, end_time)
      $logger.info "Match '#{name}' was added successfully."
      save
      match
    end
 
  end
end

Criação de Builders para UserList e MatchList com as decorações

Para treinar o uso do padrão Builder, assim como pra facilitar a criação das Listas Decoradas, foram criadas duas classes (Builders) que davam suporte aos três tipos de decorações (Logger, Thread Safe e Persistance), podendo as mesmas serem acrescentadas individualmente ou todas de uma vez. Também, quando se retornava o objeto da List (User ou Match), a lista era carregada (load_list) se o Decorator de persistência era usado.

O código de MatchListBuilder pode ser visto abaixo:

require 'match_list'
require 'decorators/match_list_thread_safe_decorator'
require 'decorators/match_list_persistance_decorator'
require 'decorators/match_list_logger_decorator'
 
class MatchListBuilder
 
  include Decorators
 
  def initialize
    @match_list=MatchList.new
    @filename="match_list_builder_file.yml"
  end
 
  def match_list
    @match_list.load_list if @added_persistance
    @added_persistance=false
    @match_list
  end
 
  def add_all
    add_persistance
    add_logging
    add_thread_safety
    @added_persistance=true
  end
 
  def add_persistance
    @match_list=MatchListPersistanceDecorator.new(@match_list,@filename)
    @added_persistance=true
  end
 
  def add_logging
    @match_list=MatchListLoggerDecorator.new(@match_list)
  end
 
  def add_thread_safety
    @match_list=MatchListThreadSafeDecorator.new(@match_list)
  end
 
  def filename=(filename)
    @filename=filename
  end  
 
end

O código de UserListBuilder foi:

require 'user_list'
require 'decorators/list_thread_safe_decorator'
require 'decorators/list_persistance_decorator'
require 'decorators/user_list_logger_decorator'
 
class UserListBuilder
  include Decorators
 
  def initialize
    @user_list=UserList.new
    @filename="user_list_builder_file.yml"
  end
 
  def user_list
    @user_list.load_list if @added_persistance
    @added_persistance=false
    @user_list
  end
 
  def add_all
    @user_list=ListThreadSafeDecorator.new(UserListLoggerDecorator.new(ListPersistanceDecorator.new(@user_list, @filename)))
    @added_persistance=true
  end
 
  def add_persistance
    @user_list=ListPersistanceDecorator.new(@user_list,@filename)
    @added_persistance=true
  end
 
  def add_logging
    @user_list=UserListLoggerDecorator.new(@user_list)
  end
 
  def add_thread_safety
    @user_list=ListThreadSafeDecorator.new(@user_list)
  end
 
  def filename=(filename)
    @filename=filename
  end
end

Criação de um método main que interprete um protocolo de entrada e saída de dados do backend

O protocolo escolhido para a troca de dados foi o Yaml. O Backend teve que ser capaz de realizar as seguintes operações:

  • Criar Usuario
  • Autenticar Usuario

Deve ser feita antes de qualquer uma das operações a seguir. Se for mal sucedida, encera a aplicação. Bem sucedida, define qual o usuário para o qual as operações seguintes serão realizadas

  • Listar partidas atuais (retornando uma lista de nomes de partidas com algumas informações básicas a respeito de cada uma).
  • Listar partidas encerradas (semelhante a anterior)
  • Informações detalhadas de uma partida (apostadores, ganhadores)
  • Listar informações das minhas apostas (vencedoras, em andamento, finalizadas)
  • Fazer aposta em uma partida
  • Encerrar a conexão

Para fazer isso, inicialmente foi criado o método main. Este método recebe dois ios (sockets) de entrada e saída, e através deles efetua a troca de informações. Em seguida, cria uma instância da classe RequestReader (explicada posteriormente), fazendo que a mesma leia continuamente a entrada (dentro do loop) e processe os pedidos feitos, mandando a resposta. A saída deste loop só é realizada quando a instância de RequestReader diz que a conexão foi fechada. Neste método também temos a criação das variáveis globais $match_list e $user_list, as quais implementam os três Decorators. A parte comentada do código era usada pra criar partidas e finalizá-las a fim de fazer testes. O código do método main pode ser visto abaixo:

require 'initialize'
require 'request_reader'
 
$match_list_builder=MatchListBuilder.new
$user_list_builder=UserListBuilder.new
$match_list_builder.add_all
$user_list_builder.add_all
$match_list=$match_list_builder.match_list
$user_list=$user_list_builder.user_list
 
#$match_list.to_a.map do |m|
#  m.finalize("3-8")
#end
 
#$match_list.add "atletico x cruzeiro", Time.now + 100000
#$match_list.add "atletico x sao", Time.now + 100000
#$match_list.add "atletico x fortaleza", Time.now + 100000
#$match_list.add "atletico x vasco", Time.now + 100000
#$match_list.add "atletico x flamengo", Time.now + 100000
 
  def main(input=$stdin, out=$stdout)
 
    r=RequestReader.new(input, out)
 
    loop do
      r.wait_and_make_hash
      r.process_request
      break if r.conected==false
    end
 
  end

Logo após, foi criada a classe RequestReader, que na sua criação recebia os sockets de io. A classe tinha dois métodos: wait_and_make_hash e process_request.

Método wait_and_make_hash

Este método, inicialmente, lia continuamente a entrada em um loop e saía deste loop se a string "INICIO" fosse lida. Em seguida, tudo era lido até que se encontrasse a palavra "FIM" e através do método load de YAML, a requisição era transformada em um hash.

Método process_request

Este método, através de um laço de if-elsif…else, analisava o hash obtido em wait_and_make_hash e, dependendo da operação solicitava, a processava e mandava a resposta.

O código da classe RequestReader pode ser visto abaixo:

require 'match_list_builder'
require 'user_list_builder'
require 'printers/yaml_user_printer'
require 'printers/yaml_user_list_printer'
require 'printers/yaml_match_printer'
require 'printers/yaml_match_list_printer'
require 'printers/yaml_connection_printer'
require 'yaml'
#require 'C:/ruby/src/ruby-1.8.6-p111/io.c'
 
class RequestReader
  include Printers
 
  def initialize(input=$stdin, out=$stdout)
    @in=input
    @out=out
    @user=nil
    @conected=true
  end
 
  attr_reader :conected
 
  def wait_and_make_hash
    #lê a entrada linha a linha até acharmos um "INICIO"
    loop do
      string = @in.gets
      break if string && string.include?("INICIO")
    end
    yaml = @in.gets("\nFIM") #depois do INICIO, lê tudo até a palavra FIM
    yaml = yaml.chomp("FIM") #tira o FIM do final do string que foi lido do io
    @requisicao = YAML.load(yaml) # aqui já temos o hash com a requisicao processada
  end
 
  def process_request
 
    if(@requisicao['request']['operation']=="create_user")
      begin
        name=@requisicao['request']['parameters'][0]
        pass=@requisicao['request']['parameters'][1]
        user=$user_list.add(name, pass)
        output=YamlUserPrinter.new(user,@out)
        output.print_user_created
      rescue
        output=YamlUserPrinter.new(user,@out)
        output.print_user_not_created
      end
    elsif(@requisicao['request']['operation']=="autenticate_user") 
      begin
        name=@requisicao['request']['parameters'][0]
        pass=@requisicao['request']['parameters'][1]
        @user=$user_list.authenticate(name, pass)
        output=YamlUserPrinter.new(user,@out)
        output.print_user_not_autenticated if @user==nil
        output.print_user_autenticated if @user!=nil
      rescue
        output=YamlUserPrinter.new(user,@out)
        output.print_user_not_autenticated
      end
    elsif(@requisicao['request']['operation']=="current_matches"&&@user) 
      begin
        output=YamlMatchListPrinter.new($match_list,@out)
        output.print_current_matches
      rescue
        puts "Houve um problema com a listagem de partidas atuais"
      end
    elsif(@requisicao['request']['operation']=="finalized_matches"&&@user) 
      begin
        output=YamlMatchListPrinter.new($match_list,@out)
        output.print_finalized_matches
      rescue
        puts "Houve um problema com a listagem de partidas finalizadas"
      end
    elsif(@requisicao['request']['operation']=="match_information"&&@user) 
      begin
        name_match=@requisicao['request']['parameters'][0]
        match=$match_list.find_by_name(name_match)
        if (match!=nil)
          output=YamlMatchPrinter.new(match,@out)
          output.print_match_information
        else
          output=YamlMatchPrinter.new(match,@out)
          output.print_match_void_information
        end
      rescue
        puts "Houve um problema com a listagem da informacao da partida"
      end
    elsif(@requisicao['request']['operation']=="own_bets_information"&&@user) 
      begin
        match_list=[]
        $match_list.to_a.map do |m|
          m.bets.each do |bet|
            bet.holders.each do |holder|
              if (holder.username==@user.username)
                match_list << m
              end
            end
          end
        end
 
        if (match_list!=nil)
          output=YamlMatchListPrinter.new(match_list,@out)
          output.print_matches
        else
          output=YamlMatchListPrinter.new(match_list,@out)
          output.print_void_matches
        end
      rescue
        puts "Houve um problema com a listagem das apostas"
      end  
    elsif(@requisicao['request']['operation']=="make_bet"&&@user) 
      begin
        name_match=@requisicao['request']['parameters'][0]
        score=@requisicao['request']['parameters'][1]
        score=score.to_score
        match=$match_list.find_by_name(name_match)
        if (match!=nil)
          match.make_bet(score, @user) if @user != nil
          output=YamlUserPrinter.new(@user,@out)
          output.print_match_bet_made  
        else
          output=YamlUserPrinter.new(@user,@out)
          output.print_match_bet_not_made
        end
      rescue
        output=YamlUserPrinter.new(@user,@out)
        output.print_match_bet_not_made
      end
    elsif(@requisicao['request']['operation']=="close_connection") 
      begin
        @conected=false
        output=YamlConnectionPrinter.new
        output.print_connection_closed
 
      rescue
        puts "Houve um erro no fechamento da conexao"
      end
    else
      puts "Operacao invalida ou nao permitida"
    end
 
  end
 
end

No código acima pode ser visto que, para escrever as respostas no socket de saída, eram chamados determinadas classes que terminavam em Printer. Tais classes faziam parte de uma módulo denominado Printers, o qual conteve um conjunto de classes que escreviam no socket de saída a resposta devida respectiva a cada request. Como exemplo de código do módulo Printers, abaixo pode-se ver o código da classe YamlMatchListPrinter.

require 'yaml'
module Printers
  class YamlMatchListPrinter
    def initialize(match_list, io = $stdout)
      @match_list, @io = match_list, io
    end
 
    def print_matches
      @io.puts "INICIO"
      @io.puts(
        {
          :response => @match_list.to_a.map do |m|
            {
              :name => m.name,
              :end_time => m.end_time.strftime("%d/%m/%Y %H:%M"),
              :finalized => "#{(m.finalized?) ? '' : 'not ' }finalized",
              :final_score => m.final_score || "_ x _",
              :bet_count => "#{m.bet_count} bets",
              :holders => (m.holders) ? m.holders.map{|u|u.username}.join(" e ") : "no holders",
              :winners => (m.winners) ? m.winners.map{|u|u.username}.join(" e ") : "no winners"
            }
          end
       }.to_yaml
      )
      @io.puts "FIM"
    end
 
    def print_void_matches
      @io.puts "INICIO"
      @io.puts(
        {
          :response => []
       }.to_yaml
      )
      @io.puts "FIM"
    end
 
    def print_current_matches
      @io.puts "INICIO"
      @io.puts(
        {
          :response => @match_list.list_current.map do |m|
            {
              :name => m.name,
              :end_time => m.end_time.strftime("%d/%m/%Y %H:%M"),
              :bet_count => "#{m.bet_count} bets",
            }
          end
       }.to_yaml
      )
      @io.puts "FIM"
    end
 
    def print_finalized_matches
      @io.puts "INICIO"
      @io.puts(
        {
          :response => @match_list.list_finalized.map do |m|
            {
              :name => m.name,
              :end_time => m.end_time.strftime("%d/%m/%Y %H:%M"),
              :finalized => "#{(m.finalized?) ? '' : 'not ' }finalized",
              :final_score => m.final_score || "_ x _",
              :bet_count => "#{m.bet_count} bets",
              :winners => (m.winners) ? m.winners.map{|u|u.username}.join(" e ") : "no winners"
            }
          end
       }.to_yaml
      )
      @io.puts "FIM"
    end
 
  end
end

Criação do servidor multithread

Por fim, foi criado um arquivo chamado server.rb, o qual ficou responsável pelo estabelecimento da conexão. Inicialmente é criado um TCPServer. Depois, dentro de um loop, escuta-se se alguma conexão nova quer ser feita. A cada novo conexão, é criada uma nova Thread e nela é chamado o método main passando-se como parâmetros de entrada e saída o socket criado ao aceitar-se a conexão. O código do arquivo pode ser visto abaixo:

$server = TCPServer.new(4344)
 
require 'main'
 
loop do
 
  while (io_socket=$server.accept)
    Thread.new do
      $stdout.puts "Criando conexao"
      main(io_socket,io_socket)
      $stdout.puts "Conexao fechada!"
      io_socket.close
    end
  end
end

O código fonte do projeto do NetBeans criado foi:

Laboratório 5

Conclusão

A execução deste laboratório mostrou-se muito útil em diversos aspectos. Além de exercitar os conhecimentos adquiridos em aula, como os padrões Builder e Decorator, Threads e IO, este laboratório ajudou a aproximar ainda mais o aluno da linguagem de programação Ruby, sendo necessário bastante conhecimento da mesma para se saber o que fazer (diferentemente do quarto laboratório, o qual foi mais um exercício de cópia, apesar de ter dado um pontapé inicial no conhecimento de Ruby).
Quanto à dificuldade na execução do laboratório, a mesma se mostrou maior que nos laboratórios anteriores. Isto talvez se deva a maior complexidade das operações executadas neste laboratório, muitas das quais envolviam detalhes não previamente conhecidos (como detalhes da própria linguagem e de Yaml em si).

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