terça-feira, 29 de junho de 2010

twisted - tutorial basico sobre reactor e Deferreds

twisted é uma event-driven framework, isso significa que ao invés de ter as funções dos programas chamados em uma sequência especificada pelo programa logicamente, eles são chamados em resposta a ações externas ou eventos.

por exemplo:
um programa feito em gtk , pode ter um código para responder um evento de 'button pressed' o desenvolvedor desse programa não vai saber exatamente quando tal evento irá ocorrer, mas ele escreve uma função para responder a esse evento sempre que isto ocorrer, essa função é conhecida como event-handler, toda framework orientada a eventos , engloba uma função especial chamada por um event loop , Uma vez iniciado, um event loop executa indefinidamente, enquanto ele está rodando, ele espera por eventos e Quando um evento ocorre, o event loop inicia event handler apropiado.

Usar um event loop requer uma certa habilidade de compreensão por parte do programador
pois é bem diferente da forma tradicional que é uma programação sequencial.
Uma vez que vc inicia um event loop, vc não vai ter o controle diretamente de instruir seu programa de o que fazer, seu programa só vai pode executar ações em resposta a eventos. Por isso , quando você estiver projetando seu programa, vc precisa pensar em termos de eventos e event handler.
No twisted, existe um objeto especial que implementa o event loop,  este objeto é chamado de "reactor" e vc pode imaginar o "reactor" como o sistema nervoso central de uma aplicação twisted, sendo o responsável pelo event loop.
Quando vc roda o reactor com reactor.run() e ele fica esperando algo acontecer, e quando acontece ele chama uma função.

exemplo sobre o objeto reactor:

from twisted.internet import reactor

def tempo(segundos):
    print 'Passaram-se %s segundos...' % segundos

def programa():
    print 'comecou'
    # chama a funcao tempo daqui a 2s, passando como parametro a string 'dois':
    reactor.callLater(2, tempo, 'dois') 

    # chama a funcao tempo daqui a 3s, passando como parametro a string 'tres':
    reactor.callLater(3, tempo, 'tres') 

    # termina o programa daqui a 4s:
    reactor.callLater(4, reactor.stop)

protocol "twisted.internet.protocol" é uma conexão, uma instância da class protocol pode ser instânciada por conexões sobre demanada e podem terminar quando a conexão estiver finalizada.
isso significa que a configuração persistente não é salva em Protocol.

twisted.internet.protocol.Factory
a configuração persistente é mantida na class Factory, no qual geralmente é herdada de
twisted.internet.protocol.Factory.
A class factory padrão instâncía cada Protocol, e então é definido um atributo chamado factory qual é apontado para sí mesmo.
As factories ou fábricas são somente objetos que criam protocolos, a cada conexão, a fábrica cria uma instância separada do protocolo ,isso geralmente é útil por ser capaz de oferecer os mesmo serviços em multiplas portas ou endereços de rede.

Exemplo de uso de factory:
from twisted.internet import reactor, protocol

class ConnectProtocol(protocol.Protocol):
    def connectionMade(self):
        """
        Esta funcao sera chamada automaticamente quando a conexao for 
        estabelecida
        """
        
        print 'Conectei!'
        self.transport.loseConnection() # desconecta
        reactor.stop() # para o loop principal, terminando o programa
        
class ConnectFactory(protocol.ClientFactory):
    protocol = ConnectProtocol
    def clientConnectionFailed(self, connector, reason):
        """
        Esta funcao sera chamada automaticamente se a conexao falhar
        """
        print 'Conexao falhou! Motivo: ', reason.getErrorMessage()
        reactor.stop()
    
f = ConnectFactory()
reactor.connectTCP('google.com', 80, f)

# inicia o programa
reactor.run()

uma breve explicação sobre deferred:
depois de toda essa explicação sobre o reactor, vamos falar sobre deferred.
Deferreds ou como podemos dizer: uma "promessa" de uma resposta, são provavelmente os mais importantes objetos usado no twisted, você ira trabalhar com Deferreds frequentemente em aplicações feitas com twisted, então, é essencial que vc compreenda como isso funciona.
Deferreds pode parecer um pouco confuso de compreender, mas o seu propósito é bem simples, manter o rastro ou melhor dizendo manter uma trilha de ações assíncronas, e receber os resultados assim que forem completados.

Deferreds podem ser ilustrados dessa forma: vc vai ao restaurante, onde vc vai esperar por uma mesa, e imagine que esse restaurante tenha um sistema que possa te informar quando sua mesa estiver disponível, algo como um sistema de pager, então vc terá a liberdade de sair do restaurante e fazer qualquer coisa até que vc receba um beep informando que sua mesa está disponivel, então vc volta para o restaurante e se senta.

Deferred é como um sistema de pager. ele da a seu programa uma forma de encontrar quando algumas tarefas assincronas são finalizadas, em propósito de ter a liberdade de fazer outras coisas durante esse meio tempo. Quando uma função retorna um objeto Deffered, ela está dizendo que vai existir um tempo de espera antes do resultado final de uma função se tornar disponível.
Para se controlar o que acontece quando o resultado se torna disponivel, vc pode determinar event handlers para o Deferred.
mas como seria isso?
seria, escrevendo uma função que inicia uma ação assíncrona que retorna um objeto Deferred.

por exemplo:
se vc dá um getPage pra baixar uma página na internet ao invés de te retornar a página, getPage() te retorna uma instância de Deferred significa que a página ainda não chegou, mas quando chegar, essa "promessa" será cumprida, daí vc adiciona callbacks ao deferred ,  callbacks significa: quando a promessa rolar, chame a função x,  d.addCallback(trataPagina) significa que trataPagina() será chamado quando chegar a página.


um programa exemplo:
from twisted.internet import reactor, defer, protocol

class CallbackAndDisconnectProtocol(protocol.Protocol):

    def connectionMade(self):
        self.factory.deferred.callback("Connected!")
        self.transport.loseConnection( )


class ConnectionTestFactory(protocol.ClientFactory):
    protocol = CallbackAndDisconnectProtocol

    def __init__(self):
        self.deferred = defer.Deferred( )

    def clientConnectionFailed(self, connector, reason):
        self.deferred.errback(reason)

def testConnect(host, port):
    testFactory = ConnectionTestFactory( )
    reactor.connectTCP(host, port, testFactory)
    return testFactory.deferred

def handleSuccess(result, port):
    print "Connected to port %i" % port
    reactor.stop( )

def handleFailure(failure, port):
    print "Error connecting to port %i: %s" % (
        port, failure.getErrorMessage( ))
    reactor.stop( )

if __name__ == "__main__":
    import sys
    if not len(sys.argv) == 3:
        print "Usage: connectiontest.py host port"
        sys.exit(1)

    host = sys.argv[1]
    port = int(sys.argv[2])
    connecting = testConnect(host, port)
    connecting.addCallback(handleSuccess, port)
    connecting.addErrback(handleFailure, port)
    reactor.run( )

$ python foo.py google.com 80

Connection to port 80.




como já foi dito, cada deferred é uma "promessa" de uma resposta, vc pode usar a palavra "promessa".
vc pode adicionar quantos callbacks quiser a um deferred, todos serão chamados na ordem
o que um callback retornar passa pro próximo callback , mas só começa a sequencia de chamadas quando a promessa se cumprir.
no entanto, se você mesmo criar o deferred exemplo: com defer.Deferred() aí é vc que está prometendo
logo quando acontecer o que for que você estiver promentendo, vc tem que iniciar a chamada da sequência de callbacks
meuDeferred = defer.Deferred() ; e na hora certa... meuDeferred.callback('dados')
isso vai chamar o primeiro callback que foi adicionado a meuDeferred, passando como parâmetro "dados"
uma instância de deferred é uma promessa de algo que vai acontecer
é um objeto que representa a promessa, nele vc pode adicionar callbacks,
que são o que vc quer que aconteça quando a promessa for cumprida.

em
def __init__(self):
self.deferred = defer.Deferred( )

deferred é criado

em
self.factory.deferred.callback("Connected!")
o deferred é definido como atributo da factory
quando a factory cria o protocolo, ela se coloca como atributo do protocolo automaticamente
então, em self.factory.deferred.callback("Connected!"), self.factory é a factory
self.factory.deferred é o self.deferred da factory

em class ConnectionTestFactory(protocol.ClientFactory):
no corpo da fábrica o programa define protocol = CallbackAndDisconnectProtocol
então significa que o protocolo que essa fábrica vai criar será o CallbackAndDisconnectProtocol
significa que cada vez que essa fábrica conectar, uma instância de CallbackAndDisconnectProtocol será criada para gerenciar esta conexão

no __main__
em
função testConnect
essa função cria uma instância da fábrica
testFactory = ConnectionTestFactory()
isso chama o __init__ da fábrica
no __init__ da fábrica, é criado um deferred, uma promessa
self.deferred = defer.Deferred()
como já foi dito, esse deferred é armazenado como atributo da fábrica

em seguida, a função testConnect, manda o reactor fazer uma conexão via TCP/IP
usando essa fábrica para gerenciar as conexões
ou seja, quando o reactor conseguir conectar, ele vai mandar a fábrica criar um protocolo
a fábrica então vai criar o protocolo CallbackAndDisconnectProtocol
que foi o protocolo definido na definição da fábrica
vc passa a fábrica pro reactor
quando conectar, a fábrica vai criar um protocolo, pode demorar a conexão, o programa não para esperando.

em
connecting = testConnect(host, port) , o testConnect retorna o deferred que foi criado na fabrica

em
connecting.addCallback(handleSuccess, port) adiciona um callback chamado handleSuccess a este deferred
o primeiro parâmetro aí, é a função a ser chamada quando o deferred for "disparado" o resto dos parâmetros, são parâmetros extra que serão passados a essa função  e vc pode colocar quantos parâmetros quiser.
toda deferred quando dispara, recebe 1 parâmetro, o restante são parâmetros definidos quando vc adicionou o callback ao deferred, quando ele dispara o deferredself.factory.deferred.callback("Connected!") isso está disparando o deferred
em seguida chama o handleSuccess, o parâmetro result de handleSuccess (primeiro parâmetro) recebe o "Connected!"

em
connecting.addErrback(handleFailure, port) adiciona um Errback caso haja uma falha, óbivio.

e por fim quando este deferred for cumprido, vai chamar esta função handleSuccess
em seguida, ele roda o reactor, reactor.run() nessa hora, o reactor vai tentar conectar
o programa para e o reactor fica tentando conectar quando o reactor conseguir conectar, vai solicitar que a fábrica crie um protocolo.
A fábrica vai criar uma instância de CallbackAndDisconnectProtocol e em seguida o reactor vai chamar connectionMade no protocolo criado para indicar que a conexão foi estabelecida.
a função connectionMade,ela cumpre a promessa do deferred, ela diz assim: beleza, consegui conectar.
esse parâmetro "Connected!" é passado para os callbacks dessa deferred , no caso ,  ele vai cair no parametro "result" de handleSuccess o handleSuccess não faz nada com ele , todo callback de deferred recebe um parâmetro qualquer, poderia ser None mas no exemplo foi passado uma string, poderia ser qualquer objeto.

Um comentário:

  1. Bom dia.
    Achei bem legal seu post, tirou muitas dúvidas minhas.
    Entretanto surgiram novas dúvidas. Você poderia passar teu e-mail para conversarmos sobre Defferd?
    Obrigado.

    ResponderExcluir