Lab5 Bruno Cesar

aluno: Bruno César Alves
ano/sem: 2008/2o.
data do laboratório (13º semana) : 22/12/2008 (2)

Introdução

Este laboratório trata do desenvolvimento final de um servidor de um bolão virtual de jogos esportivos. Na construção do servidor foi aplicado o que foi aprendido no curso sobre threads, io, e os padrões decorator e builder.

Desenvolvimento

A partir de um código base, a construção do servidor foi dividida nas seguintes partes:

- Decorators: Responsáveis pela adição de decorações nas listas de usuários e partidas.
- Builders: Usados na criação de listas de partidas e usuarios
- Método principal: Usado para gerenciar as atividades do servidor

Decorators

Os decorators são utilizados com o objetivo de proteger as listas de partidas e usuários em relação à requisições multithread, persistencia em disco e log.
A decoração de log não precisou ser implementada, pois já estava feita no desenvolvimento inicial.
Segue a implementação da decoração de persistência:

MatchPersistanceDecorator
require 'decorators/decorator'
module Decorators
  class MatchPersistanceDecorator < Decorator
    def initialize(obj,match_list_reference)
      super(obj)
      @match_list_reference = match_list_reference
    end
    def make_bet(score, user)
        var_ret = @decorated.make_bet(score,user)
        @match_list_reference.save
        var_ret
    end
    def finalize(final_score)
        var_ret = @decorated.finalize(final_score)
        @match_list_reference.save
        var_ret 
    end
  end
end
MatchListPersistanceDecorator
require 'decorators/decorator'
require 'decorators/list_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
  end
end

Segue a implementação da decoração thread safe:
MatchThreadSafeDecorator
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
MatchListThreadSafeDecorator
require 'monitor'
require 'decorators/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(decorated)
    end
 
    def add(name, end_time)
      synchronize do
        @decorated.add(name, end_time)
      end
    end
  end
end

Builders

O padrão Builder foi usado para auxiliar na criação de listas de partidas e listas de usuários com suas respectivas decorações.

MatchListBuilder
class MatchListBuilder 
  def initialize
    @match_list =  MatchList.new
    @added_persistance = false
  end
  def add_persistance(filename=$matchListFileName)
    @match_list = ListPersistanceDecorator.new(@match_list,filename)
    @added_persistance=true
  end
  def add_logging
    @match_list = MatchListLoggerDecorator.new(@match_list)
  end
  def add_thread_safety
    @match_list = ListThreadSafeDecorator.new(@match_list)
  end
  def add_all
    add_persistance
    add_logging
    add_thread_safety 
  end
  def match_list
    @match_list.load_list if @added_persistance
    @match_list
  end
end
UserListBuilder
class UserListBuilder 
  def initialize
    @user_list = UserList.new
    @added_persistance = false
  end
  def add_persistance(filename=$userListFileName)
    @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 add_all
    add_persistance
    add_logging
    add_thread_safety 
  end
  def user_list
    @user_list.load_list if @added_persistance
    @user_list
  end
end

Método main

O método main tem a função de, a partir de dois parâmetros (entrada e saída), realizar a leitura de requisições pelo parâmetro de entrada e responder ao parâmetro de saída as alterações feitas no sistema do bolão virtual.

def main( in_file, out_file)
  rr = RequestReader.new( in_file, out_file)
  while(!rr.end_connection)
  rr.operate  
  end
  in_file.close if(in_file!=nil && (! in_file.kind_of?TCPSocket)) 
  out_file.close if(out_file!=nil && (! out_file.kind_of?TCPSocket))
end

Para a realização da leitura de requisições e resposta, são utilizados dois módulos: Printers e Requests.
O módulo Requests é um conjunto de classes que trata separadamente de cada requisição feita. A classe RequestReader orienta o tipo de tratamento a ser realizado.

require 'yaml'
module Requests
  class RequestReader
    attr_accessor :in_file
    attr_accessor :out_file
    attr_accessor :match_list
    attr_accessor :current_user
    attr_accessor :user_list
    attr_accessor :request
    attr_accessor :end_connection
 
    def initialize(in_file = $in_file,out_file=$out_file)
      @in_file=in_file
      @out_file=out_file
      @current_user=nil
      @match_list=$match_list
      @user_list=$user_list
      @end_connnection = false
    end
    def operate
      @request = read(@in_file)
      return false if(@request == [])
      case @request['request']['operation']
        when "create_user"
          CreateUserRequest.new(self)
        when "authenticate_user"
          @currentUser=AuthenticateUserRequest.new(self)
        when "get_current_matches"
          CurrentMatchesRequest.new(self)
        when "get_finalized_matches"
          FinalizedMatchesRequest.new(self)
        when "get_match"
          MatchRequest.new(self)
        when "get_bets"
          BetsRequest.new(self)
        when "make_bet"
          MakeBetRequest.new(self)
        when "end_connection"
          EndConnectionRequest.new(self)
        else
          YamlMessagePrinter.new("Operacao sem tratamento",@out_file)
      end
    end
    def read(in_file)
 
      loop do
       string = in_file.gets
        break if string && string.include?("INICIO")
      end
      yaml = in_file.gets("\nFIM")
      yaml = yaml.chomp("FIM") 
      YAML.load(yaml)
      end
  end
end

As classes de tratamento de requisição estão em anexo no projeto completo.

O módulo Printers realiza as respostas ao parâmetro de saída. Suas classes já estavam implementadas.

Leitura de arquivo

O primeiro teste do servidor foi feito com a leitura de um arquivo do tipo yaml. Este arquivo contem requisições que abrangem todos os tipos realizados pelo servidor.

Arquivo de requisição:
INICIO
---
request:  
 operation: create_user  
 parameters:  
  username: Joao  
  password: 123456  
FIM
INICIO
---
request:  
 operation: create_user  
 parameters:  
  username: Joao  
  password: 123456  
FIM
INICIO
---
request:  
 operation: authenticate_user
 parameters:  
  username: Joao  
  password: 123456  
FIM
INICIO
---
request:  
 operation: authenticate_user
 parameters:  
  username: Joao  
  password: 1234  
FIM
INICIO
---
request:  
 operation: get_current_matches
FIM
INICIO
---
request:  
 operation: get_finalized_matches
FIM
INICIO
---
request:  
 operation: get_match
 parameters:  
  match_name: TimaoXBambis 
FIM
INICIO
---
request:  
 operation: get_bets
FIM
INICIO
---
request:  
 operation: make_bet
 parameters:  
  match_name: TimaoXBambis 
  score: 4x0
FIM
INICIO
---
request:  
 operation: end_connection
FIM
Arquivo de resposta
INICIO
--- 
:response: 
  :message: Usuario inserido com sucesso
FIM
INICIO
--- 
:response: 
  :message: Usuario existente
FIM
INICIO
--- 
:response: 
  :message: Usuario e Senha validos
FIM
INICIO
--- 
:response: 
  :message: Usuario e Senha invalidos
FIM
INICIO
--- 
:response: []
FIM
INICIO
--- 
:response: []
FIM
INICIO
--- 
:response: 
  :message: Tentativa de acessar jogo inexistente
FIM
INICIO
--- 
:response: 
  :message: Impossivel acessar apostas. Logue um usuario
FIM
INICIO
--- 
:response: 
  :message: Partida nao encontrada
FIM
INICIO
--- 
:response: 
  :message: Conexao finalizada
FIM

Servidor Multithread

Agora, é realizada de fato a função de servidor. O método server oferece acesso a conexão externas pela porta 9945.

Servidor

def server
  server = TCPServer.new(9945)  # 4344 é a porta de conexão de rede usada
  while ( io_socket = server.accept ) #aguarda até que conexão seja recebida, retornando um TCPSocket com a conexão.
    # dispare uma nova thread e utilize io_socket para efetuar a entrada e saída do método main, de dentro desta thread
      Thread.new do
      io_socket.puts "conexao estabelecida"
      main(io_socket,io_socket)
      io_socket.puts "conexao finalizada"
    end
 
    #io_socket deve ser utilizado tanto para saída quanto para entrada, portanto é passado como parâmetro in e parâmetro out de main.
    #main(io_socket,io_socket)
    #io_socket.close
  end
end

Para a teste do servidor, foi criado um cliente que o acessa e faz as requisições realizadas anteriormente pelo arquivo.

Cliente
require 'socket'
 
client = TCPSocket.new('localhost', 9945)
 
puts client.gets
client.puts('INICIO')
client.puts('---')
client.puts('request:  ')
client.puts(' operation: create_user')
client.puts(' parameters:  ')
client.puts('  username: Joao  ')
client.puts('  password: 123456  ')
client.puts('FIM')
  text = client.gets
  text = text.chomp
  while (text!='FIM')
  puts text
  text = client.gets
  text = text.chomp
end
puts text
client.puts('INICIO')
client.puts('---')
client.puts('request:  ')
client.puts(' operation: create_user  ')
client.puts(' parameters:  ')
client.puts('  username: Joao  ')
client.puts('  password: 123456  ')
client.puts('FIM')
  text = client.gets
  text = text.chomp
  while (text!='FIM')
  puts text
  text = client.gets
  text = text.chomp
end
puts text
client.puts('INICIO')
client.puts('---')
client.puts('request:  ')
client.puts(' operation: get_current_matches')
client.puts('FIM')
text = client.gets
  text = text.chomp
  while (text!='FIM')
  puts text
  text = client.gets
  text = text.chomp
end
puts text
client.puts('INICIO')
client.puts('---')
client.puts('request:  ')
client.puts(' operation: get_finalized_matches')
client.puts('FIM')
text = client.gets
  text = text.chomp
  while (text!='FIM')
  puts text
  text = client.gets
  text = text.chomp
end
puts text
client.puts('INICIO')
client.puts('---')
client.puts('request:  ')
client.puts(' operation: make_bet')
client.puts(' parameters:  ')
client.puts('  match_name: TimaoXBambis ')
client.puts('  score: 4x0')
client.puts('FIM')
text = client.gets
  text = text.chomp
  while (text!='FIM')
  puts text
  text = client.gets
  text = text.chomp
end
puts text
client.puts('INICIO')
client.puts('---')
client.puts('request:  ')
client.puts(' operation: authenticate_user')
client.puts(' parameters:  ')
client.puts('  username: Joao  ')
client.puts('  password: 123456  ')
client.puts('FIM')
text = client.gets
  text = text.chomp
  while (text!='FIM')
  puts text
  text = client.gets
  text = text.chomp
end
puts text
client.puts('INICIO')
client.puts('---')
client.puts('request:  ')
client.puts(' operation: authenticate_user')
client.puts(' parameters:  ')
client.puts('  username: Joao  ')
client.puts('  password: 1234  ')
client.puts('FIM')
text = client.gets
  text = text.chomp
  while (text!='FIM')
  puts text
  text = client.gets
  text = text.chomp
end
puts text
client.puts('INICIO')
client.puts('---')
client.puts('request:  ')
client.puts(' operation: get_match')
client.puts(' parameters:  ')
client.puts('  match_name: TimaoXBambis ')
client.puts('FIM')
text = client.gets
  text = text.chomp
  while (text!='FIM')
  puts text
  text = client.gets
  text = text.chomp
end
puts text
client.puts('INICIO')
client.puts('---')
client.puts('request:  ')
client.puts(' operation: get_bets')
client.puts('FIM')
text = client.gets
  text = text.chomp
  while (text!='FIM')
  puts text
  text = client.gets
  text = text.chomp
end
puts text
client.puts('INICIO')
client.puts('---')
client.puts('request:  ')
client.puts(' operation: end_connection')
client.puts('FIM')
text = client.gets
  text = text.chomp
  while (text!='FIM')
  puts text
  text = client.gets
  text = text.chomp
end
puts text
puts(client.gets)

Para testar a possibilidade de várias threads acessarem o servidor, foram executados grupos de arquivos cliente.

ebbxx0.jpg

O teste rake também foi realizado com sucesso:

qs61d3.jpg

Projeto NetBeans

Projeto

Conclusão

Embora tenha sido extremamente trabalhoso, principalmente no entendimento do modulo Decorators, o laboratório foi de grande aprendizado. Além do amadurecimento na linguagem Ruby, o aprendizado nos padrões Decorator e Builder foram concretizados. O uso de um cliente que faz requisições ao servidor (bolão virtual) foi muito interessante e seu aprendizado oferece muitas aplicações.

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