2013-07-17 13 views
5

Ho implementato un client SOCKS4 di base con socket, ma la mia traduzione di Twisted non sta andando troppo bene. Ecco il mio codice corrente:Uso corretto di un client e differito con Twisted

import struct 
import socket 

from twisted.python.failure import Failure 

from twisted.internet import reactor 
from twisted.internet.defer import Deferred 
from twisted.internet.protocol import Protocol, ClientFactory 

class Socks4Client(Protocol): 
    VERSION = 4 
    HOST = "0.0.0.0" 
    PORT = 80 

    REQUESTS = { 
     "CONNECT": 1, 
     "BIND": 2 
    } 

    RESPONSES = { 
     90: "request granted", 
     91: "request rejected or failed", 
     92: "request rejected because SOCKS server cannot connect to identd on the client", 
     93: "request rejected because the client program and identd report different user-ids" 
    } 

    def __init__(self): 
     self.buffer = "" 

    def connectionMade(self): 
     self.connect(self.HOST, self.PORT) 

    def dataReceived(self, data): 
     self.buffer += data 

     if len(self.buffer) == 8: 
      self.validateResponse(self.buffer) 

    def connect(self, host, port): 
     data = struct.pack("!BBH", self.VERSION, self.REQUESTS["CONNECT"], port) 
     data += socket.inet_aton(host) 
     data += "\x00" 

     self.transport.write(data) 

    def validateResponse(self, data): 
     version, result_code = struct.unpack("!BB", data[1:3]) 

     if version != 4: 
      self.factory.protocolError(Exception("invalid version")) 
     elif result_code == 90: 
      self.factory.deferred.callback(self.responses[result_code]) 
     elif result_code in self.RESPONSES: 
      self.factory.protocolError(Exception(self.responses[result_code])) 
     else: 
      self.factory.protocolError(Exception()) 

     self.transport.abortConnection() 

class Socks4Factory(ClientFactory): 
    protocol = Socks4Client 

    def __init__(self, deferred): 
     self.deferred = deferred 

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

    def clientConnectionLost(self, connector, reason): 
     print "Connection lost:", reason 

    def protocolError(self, reason): 
     self.deferred.errback(reason) 

def result(result): 
    print "Success:", result 

def error(reason): 
    print "Error:", reason 

if __name__ == "__main__": 
    d = Deferred() 
    d.addCallbacks(result, error) 

    factory = Socks4Factory(d) 
    reactor.connectTCP('127.0.0.1', 1080, factory) 

    reactor.run() 
  1. Ho la sensazione che sto abusando Deferred. È questo il modo giusto per inviare risultati dal mio cliente?
  2. Ho letto alcuni tutorial, ho esaminato la documentazione e letto la maggior parte dei protocolli in bundle con Twisted, ma non riesco ancora a capirlo: cos'è esattamente un ClientFactory? Lo sto usando nel modo giusto?
  3. clientConnectionLost s viene attivato molto. A volte perdo la connessione e ottengo una risposta positiva. Com'è così? Cosa significa questo, e dovrei trattarlo come un errore?
  4. Come faccio ad accertarmi che la mia chiamata differita abbia solo un callback/errback?

Qualsiasi consiglio è apprezzato.

risposta

5

Ho la sensazione che sto abusando di Deferred. È questo il modo giusto per inviare risultati dal mio cliente?

Non è l'ideale, ma non è esattamente sbagliato neanche. In generale, si dovrebbe cercare di mantenere il codice che istanzia un Deferred il più vicino possibile al codice che chiama Deferred.callback o Deferred.errback su quello Deferred. In questo caso, quei pezzi di codice sono abbastanza distanti: il primo è in __main__ mentre il secondo è in una classe creata da una classe creata dal codice in __main__. Questo è un po 'come la legge di Demetra - più passaggi tra queste due cose, il software più strettamente unito, inflessibile e fragile.

Considera di fornire Socks4Client un metodo che crea e restituisce questa istanza Deferred. Quindi, provare a utilizzare un endpoint per configurare la connessione in modo da poter più facilmente chiamare questo metodo:

endpoint = TCP4StreamClientEndpoint(reactor, "127.0.0.1", 1080) 
d = endpoint.connect(factory) 
def connected(protocol): 
    return protocol.waitForWhatever() 
d.addCallback(connected) 
d.addCallbacks(result, error) 

Una cosa da notare qui è che l'utilizzo di un endpoint, non saranno chiamati clientConnectionFailed e clientConnectionLost metodi di vostra fabbrica . L'endpoint riprende la responsabilità precedente (non quest'ultima però).

Ho letto un paio di tutorial, guardato la documentazione, e leggere attraverso la maggior parte dei protocolli in bundle con Twisted, ma ancora non riesco a capire: che cosa è esattamente un ClientFactory per? Lo sto usando nel modo giusto?

È solo per quello che stai facendo. :) Crea istanze di protocollo da utilizzare con le connessioni. È necessaria una factory perché è possibile creare connessioni a molti server (o molte connessioni a un server). Tuttavia, molte persone hanno problemi con ClientFactory quindi le API Twisted introdotte più di recente non si basano su di esse. Ad esempio, si potrebbe anche fare l'impostazione di collegamento come:

endpoint = TCP4StreamClientEndpoint(reactor, "127.0.0.1", 1080) 
d = connectProtocol(endpoint, Socks4Client()) 
... 

ClientFactory è ora fuori dal quadro.

clientConnectionLosts viene attivato molto.A volte perdo la connessione e ottengo una risposta positiva. Com'è così? Cosa significa questo, e dovrei trattarlo come un errore?

Ogni connessione va persa. Devi decidere da solo se si tratta di un errore o meno. Se hai finito tutto ciò che volevi fare e chiami loseConnection, probabilmente non è un errore. Prendi in considerazione una connessione a un server HTTP. Se hai inviato la tua richiesta e hai ricevuto la tua risposta, perdere la connessione probabilmente non è un grosso problema. Ma se hai ricevuto solo metà della risposta, questo è un problema.

Come faccio ad accertarmi che la mia chiamata differita abbia solo un callback/errback?

Se si struttura il codice come descritto in risposta alla prima domanda di cui sopra, diventa più facile farlo. Quando il codice che utilizza il callback/errback su un differito viene distribuito su ampie parti del programma, diventa più difficile farlo correttamente.

Tuttavia, si tratta solo di un corretto monitoraggio dello stato. Una volta dato un risultato differito, devi fare in modo di sapere che non dovresti dargliene un altro. Un idioma comune per questo è di lasciare il riferimento al Differito. Ad esempio, se lo si salva come valore di un attributo su un'istanza di protocollo, impostare tale attributo su None quando il risultato è stato rinviato.