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.

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.

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.

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.