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:
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).





