Lab5 Igor Oliveira

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

Introdução

Neste laboratório, partiu-se de um código Ruby com o "esqueleto" de uma aplicacão de um Bolão Virtual e incrementou-se esse código. Foram feitos decorators para persistência, criação de log e execução de mais de uma thread simultaneamente. Após isso, foi criado um protocolo para transmissão de dados entre um cliente e um servidor. Foi criado também um arquivo server.rb que roda o servidor. O acesso a esse servidor se dá manualmente, através do Telnet.

Desenvolvimento

Decorators

Baseado no exemplo de MatchListLoggerDecorator, foram criados os demais decorators. A seguir, estão os códigos de MatchThreadSafeDecorator, MatchListThreadSafeDecorator, MatchPersistanceDecorator e MatchListPersistanceDecorator.

MatchThreadSafeDecorator

require 'monitor'
module Decorators
  class MatchThreadSafeDecorator < Decorator
    include MonitorMixin
 
    def make_bet(*args)
      synchronize do
        @decorated.make_bet(*args)
      end
    end
 
    def finalize(*args)
      synchronize do
        @decorated.finalize(*args)
      end
    end
 
  end  
end

MatchListThreadSafeDecorator

require 'monitor'
module Decorators
  class MatchListThreadSafeDecorator < Decorator
    include MonitorMixin
 
    def initialize(decorated)
      super
      old_match_decorator = self.decorated.item_decorator
      self.decorated.item_decorator = lambda do |match|
        MatchThreadSafeDecorator.new( old_match_decorator.call(match) )
      end
    end
 
    def add(*args)
      synchronize do
        @decorated.add(*args)
      end
    end
 
  end
end

MatchPersistanceDecorator

require 'yaml'
module Decorators 
 
  class MatchPersistanceDecorator < Decorator
 
    def initialize(match, match_list)
        super(match)
        @match_list = match_list
    end
 
    def make_bet(*args)      
      @decorated.make_bet(*args)
      @match_list.save 
    end
 
    def finalize(*args)      
      @decorated.finalize(*args)
      @match_list.save 
    end      
  end
end

MatchListPersistanceDecorator

require 'yaml'
module Decorators  
 
  class MatchListPersistanceDecorator < ListPersistanceDecorator
 
    def initialize(decorated, filename)
      super(decorated, filename)
      old_match_decorator = self.decorated.item_decorator
      self.decorated.item_decorator = lambda do |match|
        MatchPersistanceDecorator.new(old_match_decorator.call(match), self)
      end      
    end
 
  end  
end

Foram alterados alguns métodos das classes de teste (test_thread_decorated_integration e test_persistance_advanced) para que a criação dos novos decorators seja usada nos testes. O resultado da saída dos testes está mostrado logo abaixo.

teste.jpg

Builders

Foram criados 2 builders, um para UserList e outro para MatchList. Vale lembrar que o decorator de ThreadSafe é chamado por último em add_all por, dessa forma, tudo o que já foi decorado será capaz de suportar mais de uma thread simultaneamente.

MatchListBuilder

module Builders
  class MatchListBuilder
    def initialize
      @match_list = MatchList.new
      @added_persistance = false
      @match_list
    end
 
    def add_persistance
      @added_persistance = true
      @match_list = MatchListPersistanceDecorator.new(@match_list, "match_builder.yml")
    end
 
    def add_logging
      @match_list = MatchListLoggerDecorator.new(@match_list)
    end
 
    def add_thread_safety
      @match_list = MatchListThreadSafeDecorator.new(@match_list)
    end
 
    def add_all
      @match_list = MatchListThreadSafeDecorator.new(MatchListLoggerDecorator.new(ListPersistanceDecorator.new(@match_list, "match_builder.yml")))
    end
 
    def match_list
      @match_list.load_list if @added_persistance
      @match_list
    end
  end
end

UserListBuilder

module Builders
  class UserListBuilder
 
    def initialize
      @user_list = UserList.new
      @added_persistance = false
      @user_list
    end
 
    def add_persistance
      @added_persistance = true
      @user_list = ListPersistanceDecorator.new(@user_list, "user_builder.yml")
    end
 
    def add_logging
      @user_list = UserListLoggerDecorator.new(@user_list)
    end
 
    def add_thread_safety
      @user_list = ListThreadSafeDecorator.new(@user_list)
    end
 
    def add_all
      @user_list = ListThreadSafeDecorator.new(UserListLoggerDecorator.new(ListPersistanceDecorator.new(@user_list, "user_builder.yml")))
    end
 
    def user_list
      @user_list.load_list if @added_persistance
      @user_list
    end
 
  end
end

Protocolo

O protocolo foi a parte mais demorada e trabalhosa deste laboratório. Como sua realização ocorreu após a segunda semana de exames, os integrantes da dupla desenvolveram praticamente todo o código independentemente um do outro.

Foi utilizada a biblioteca Yaml para formatação dos dados de entrada/saída. Criou-se o módulo Printers, que contém as 3 classes utilizadas para padronização das requisições: YamlBetPrinter, YamlMatchListPrinter e YamlUserPrinter. Essas classes são chamadas por RequestReader, que interpreta o yaml de entrada e realiza o processamento. A seguir, estão os códigos as classes citadas.

YamlBetPrinter

require 'yaml'
module Printers
 
  class YamlBetPrinter
    def initialize(io = $stdout)
      @io = io
    end
 
    def printer_make_bet(nome_da_partida, placar)
      puts $user.username
      partida = $match_list.find_by_name(nome_da_partida)
      partida.make_bet(placar, $user)
      @io.puts "INICIO"
      @io.puts(
        {
          :response =>
            {
              :resposta => "aposta realizada.",
              :name => nome_da_partida,
              :score => placar
            }
       }.to_yaml
      )
      @io.puts "FIM"
    end
 
    def printer_my_bets()      
      finalizadas = $user.finalized_bets
      em_andamento = $user.current_bets
      vencedoras = $user.winning_bets
      @io.puts "INICIO"
      @io.puts(
        {
          :response => 
            {
              :name => $user.username,                       
              :finalized => finalizadas.map.join(", "),
              :current => em_andamento.map.join(", "),
              :winners => vencedoras.map.join(", ")
            }          
       }.to_yaml
      )
      @io.puts "FIM"
    end
 
  end
end

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",
              :winners => (m.winners) ? m.winners.map{|u|u.username}.join(", ") : "no winners"
            }
          end
       }.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(", ") : "no winners"
            }
          end
       }.to_yaml
      )
      @io.puts "FIM"
    end
 
    def print_specific_match(nome_da_partida)
      #partida = @match_list.find {|match| match.name == nome_da_partida}
      partida = @match_list.find_by_name(nome_da_partida)
      @io.puts "INICIO"
      @io.puts(
        {
          :response =>
            {
              :name => partida.name,
              :end_time => partida.end_time.strftime("%d/%m/%Y %H:%M"),
              :finalized => "#{(partida.finalized?) ? '' : 'not ' }finalized",
              :final_score => partida.final_score || "_ x _",
              :bet_count => "#{partida.bet_count} bets",
              :winners => (partida.winners) ? partida.winners.map{|u|u.username}.join(", ") : "no winners"
            }
       }.to_yaml
      )
      @io.puts "FIM"
    end  
 
  end
end

YamlUserPrinter

require 'yaml'
module Printers
 
  class YamlUserPrinter
    def initialize(name, password, io = $stdout)
        @io = io
        @name = name
        @password = password
        @status = "erro! tente novamente"
 
    end
 
    def create_user
      begin
        $user_list.add(@name, @password)
        @status = "usuario criado."
      rescue
        puts $!
      end
      @io.puts "INICIO"
      @io.puts(
        {
          :response =>
            {
              :resposta => @status
            }
       }.to_yaml
      )
      @io.puts "FIM"
    end
 
    def authenticate_user
      begin
        $user = $user_list.authenticate(@name, @password)        
        @status = "usuario logado." if (!!$user)
      rescue
        puts $!
      end
      @io.puts "INICIO"
      @io.puts(
        {
          :response =>
            {
              :resposta => @status
            }
       }.to_yaml
      )
      @io.puts "FIM"
    end   
 
  end
end

RequestReader

require 'yaml'
require 'printers\yaml_match_list_printer'
 
include Printers 
 
class RequestReader
  def initialize(input,output)
      @input = input
      @out = output
      $close_connection = false
  end
 
  def captura_requisicao
    loop do
      string = @input.gets
      break if string && string.include?("INICIO")
    end
    yaml = @input.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 processa_requisicao
    if (@requisicao['request']['operation']=="create_user")
      processar = YamlUserPrinter.new(@requisicao['request']['parameters'][0], @requisicao['request']['parameters'][1], @out)
      processar.create_user
    elsif (@requisicao['request']['operation']=="authenticate_user")
      processar = YamlUserPrinter.new(@requisicao['request']['parameters'][0], @requisicao['request']['parameters'][1], @out)
      processar.authenticate_user
    elsif (@requisicao['request']['operation']=="current_matches")
      processar = YamlMatchListPrinter.new($match_list, @out)
      processar.print_current_matches
    elsif (@requisicao['request']['operation']=="finalized_matches")
      processar = YamlMatchListPrinter.new($match_list, @out)
      processar.print_finalized_matches
    elsif (@requisicao['request']['operation']=="match_info")
      processar = YamlMatchListPrinter.new($match_list, @out)
      processar.print_specific_match(@requisicao['request']['parameters'][0])
    elsif (@requisicao['request']['operation']=="make_bet")
      processar = YamlBetPrinter.new(@out)
      processar.printer_make_bet(@requisicao['request']['parameters'][0], @requisicao['request']['parameters'][1])
    elsif (@requisicao['request']['operation']=="my_bets")
      processar = YamlBetPrinter.new(@out)
      processar.printer_my_bets
    elsif (@requisicao['request']['operation']=="close_connection")
      $close_connection = true
    end
  end
end

Main

Inicialmente, antes da criação de server.rb, o arquivo main.rb era responsável por chamar a si mesmo, para rodar a aplicação. Essa função foi passada para server.rb posteriormente. Porém, o arquivo main.rb ainda continuou responsável por criar as listas de usuários e partidas ($user_list e $match_list), que são variáveis globais e guardam as informações da aplicação, e também por chamar continuamente os métodos captura_requisicao e processa_requisicao, de RequestReader, que recebe e executa o comando digitado pelo usuário no telnet. Foram criadas duas partidas de teste para que os usúarios pudessem fazer apostas e o programa ser testado. O código de main.rb segue abaixo.

require 'initialize'
require 'request_reader'
require 'builders\match_list_builder'
require 'builders\user_list_builder'
$:.unshift File.join(File.dirname(__FILE__),'..','lib')
 
include Builders
include Decorators
include Printers 
 
$match_list = MatchListBuilder.new()
$match_list = $match_list.add_all()
 
$user_list = UserListBuilder.new()
$user_list = $user_list.add_all
 
m1 = $match_list.add("fla x flu", Time.now + 100000 )
m2 = $match_list.add("ceara x fortaleza", Time.now + 100010 )
 
def main(input=$stdin, out=$stdout)
 
  request = RequestReader.new(input,out)
  loop do
    #utiliza RequestReader para esperar a chegada de uma requisicao
    request.captura_requisicao
    request.processa_requisicao
    break if $close_connection
  end
 
end

Server

Por último, foi criado o arquivo server.rb, que recebe a conexão do telnet. Basicamente, ele cria uma nova thread para cada nova conexão através do telnet na porta 4344. Os puts servem para mostrar o desenvolvimento da execução.

Ao estranho que aconteceu foi que, ao declarar o trecho de código

$logger = Logger.new("bolao.log")
$logger.level = Logger::INFO

em initialize.rb, aparecia o seguinte erro na execução do server:
C:\Users\Igor Aquino\Desktop\CES 22\Labs\lab5_Dec-19th-2008\lab5_Dec-19th-2008\lib/decorators\match_list_logger_decorator.rb:17:in `add': undefined method `info' for nil:NilClass (NoMethodError)

Pôde-se perceber que, coincidentemente, outro aluno também passou pelo mesmo problema e, como solução, declarou no próprio server.rb o trecho de código que tratava do $logger. Adotando a mesma solução, o servidor rodou normalmente e os testes estão mostrados nas figuras a seguir.
telnet.jpg

Essas são duas telas do telnet, que estão conectadas simultaneamente. Numa delas, é criado o usuário Joao e na outro o usuário Igor. Ambos fazem aposta e verificam a situação da partida, sem comprometimento na execução por haver duas pessoas conectadas.

saida1.jpg

Essa é a saída no netBeans para o arquivo server.rb. O arquivo server.rb foi executado diretamente no netBeans (através do comando shift+F6) e, em seguida, foram conectados os dois telnet através do comando telnet localhost 4344, na linha de comando do windows. Observe que, quando um segundo usuário se conecta, aparecem as linhas "entrou no while do server" e "entrou no thread", mostrando que outra pessoa acabou de abrir umanova conexão. O nome dos usuários aparece quando eles fazem uma aposta e a frase "terminou de executar a main" aparece quando um dos usuário pede para terminar sua conexão.

Conclusão

Esse laborátorio foi bastante trabalhoso, pois algumas partes do código foram difíceis de enteder mas, principalmente, a maior dificuldade foi o entendimento de como funcionava essa questão de servidor/protocolo/cliente. A maior parte do tempo da realização do laboratório foi gasta no protocolo, em saber como ele funcionava, como seriam solicitadas as operações, como elas deveriam ser processadas e como mostrar a saída para o usuário. Uma dificuldade adicional foi que, como o laboratório foi realizado após a 2a. semana de exames, já não havia mais os colegas de turma para explicar alguma coisa em que estivesse "travado". Isso tomou bastante tempo e, às vezes, eram coisas simples.

Por outro lado, fica um certo aprendizado de como funciona uma aplicação com servidor (recebendo conexões), protocolo (padronizando pedidos e respostas) e cliente (solicitando e recebendo informações). Creio que essa foi a maior lição deste laboratório. Além dela, também foram postos em prática os padrões Builder e Decorator, que havia sido vistos somente em teoria.

O arquivo rarzado com o projeto netBeans do lab se encontra aqui.

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