2011-11-16 10 views
9

Objetivo: Mostrar datos del servidor en wxPython GUI en el clientetail-f iniciar sesión en el servidor, los datos de proceso, a continuación, sirven para el cliente a través de retorcidos

Newcomer en Twisted. Tengo una GUI de wxPython ejecutándose en un cliente de Windows 7, y tengo un programa ejecutándose en un servidor de Ubuntu que produce un registro. Mi intento actual es rastrear el registro, canalizar la salida a un servidor trenzado, y luego entregar al cliente cualquier dato que cumpla con mis condiciones regex. Ya tengo un túnel abierto, así que no necesito complicar las cosas con SSH. Obtuve el siguiente bloque de código en ejecución, pero solo sirve la primera línea en la entrada. Sé que necesito seguir revisando la entrada de una nueva línea y luego escribirla en el transporte, pero no estoy seguro de cómo hacerlo sin romper la conexión.

No he podido encontrar suficiente información para parchar una solución completa. También he intentado varios otros métodos usando sockets y archivos IO, pero creo que Twisted parece ser una buena herramienta para este problema. ¿Estoy en el camino correcto? Cualquier recomendación apreciada. Gracias

#! /usr/bin/python 

import optparse, os, sys 

from twisted.internet.protocol import ServerFactory, Protocol 

def parse_args(): 
    usage = """usage: %prog [options] 
""" 

    parser = optparse.OptionParser(usage) 

    help = "The port to listen on. Default to a random available port." 
    parser.add_option('--port', type='int', help=help) 

    help = "The interface to listen on. Default is localhost." 
    parser.add_option('--iface', help=help, default='localhost') 

    options =parser.parse_args() 

    return options#, log_file 

class LogProtocol(Protocol): 
    def connectionMade(self): 
     for line in self.factory.log: 
      self.transport.write(line) 

class LogFactory(ServerFactory): 
    protocol = LogProtocol 

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

def main(): 
    log = sys.stdin.readline() 
    options, log_file = parse_args() 

    factory = LogFactory(log) 

    from twisted.internet import reactor 

    port = reactor.listenTCP(options.port or 0, factory, 
          interface=options.iface) 

    print 'Serving %s on %s.' % (log_file, port.getHost()) 

    reactor.run() 


if __name__ == '__main__': 
    main() 

Para responder a la primera observación, también he tratado de simplemente leer el registro desde Python, el programa se cuelga. El código es el siguiente:

#! /usr/bin/python 

import optparse, os, sys, time 
from twisted.internet.protocol import ServerFactory, Protocol 

def parse_args(): 
    usage = """ usage: %prog [options]""" 

    parser = optparse.OptionParser(usage) 

    help = "The port to listen on. Default to a random available port" 
    parser.add_option('--port', type='int', help=help, dest="port") 

    help = "The logfile to tail and write" 
    parser.add_option('--file', help=help, default='log/testgen01.log',dest="logfile") 

    options = parser.parse_args() 
    return options 

class LogProtocol(Protocol): 
    def connectionMade(self): 
     for line in self.follow(): 
      self.transport.write(line) 
     self.transport.loseConnection() 

    def follow(self): 
     while True: 
      line = self.factory.log.readline() 
      if not line: 
       time.sleep(0.1) 
       continue 
      yield line 

class LogFactory(ServerFactory): 
    protocol = LogProtocol 

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

def main(): 
    options, log_file = parse_args() 
    log = open(options.logfile) 
    factory = LogFactory(log) 

    from twisted.internet import reactor 

    port = reactor.listenTCP(options.port or 0, factory) #,interface=options.iface) 

    print 'Serving %s on %s.' % (options.logfile, port.getHost()) 

    reactor.run() 


if __name__ == '__main__': 
    main() 
+0

¿Ha considerado leer el registro desde dentro de python, en lugar de conectar el resultado de 'tail'? – Velociraptors

+0

He intentado resolver esto también usando un generador, el programa se cuelga. Me parece que el transporte y el generador están esperando que el otro termine. Código mostrado arriba. ¿Hay una mejor manera de hacer esto que usar un generador? – jsucsy

Respuesta

7

Aquí tienes varios objetivos diferentes que puedes lograr fácilmente. Primero, hablaré sobre ver el archivo de registro.

Su generador tiene un par de problemas. Uno de ellos es grande: llama al time.sleep(0.1). Los bloques de función sleep para la cantidad de tiempo que se le pasa. Mientras está bloqueando, el hilo que lo llamó no puede hacer otra cosa (eso es más o menos lo que significa "bloqueo", después de todo). Está iterando sobre el generador en el mismo hilo que se llama al LogProtocol.connectionMade (desde connectionMade llama al follow). LogProtocol.connectionMade se llama en el mismo subproceso que el reactor Twisted, porque Twisted tiene aproximadamente un solo subproceso.

Por lo tanto, está bloqueando el reactor con las llamadas sleep. Mientras el sueño bloquee el reactor, el reactor no puede hacer nada, como enviar bytes a través de tomas. El bloqueo es transitivo, por cierto. Entonces, LogProtocol.connectionMade es un problema aún mayor: itera indefinidamente, durmiendo y leyendo. Entonces bloquea el reactor indefinidamente.

Necesita leer las líneas del archivo sin bloquear. Puede hacer esto mediante sondeo, que es efectivamente el enfoque que está tomando ahora, pero evitando la llamada de espera. Utilice reactor.callLater para programar el futuro lee del archivo:

def follow(fObj): 
    line = fObj.readline() 
    reactor.callLater(0.1, follow, fObj) 

follow(open(filename)) 

También puede dejar LoopingCall acuerdo con la parte que hace de este un bucle que se ejecuta siempre:

def follow(fObj): 
    line = fObj.readline() 

from twisted.internet.task import LoopingCall 

loop = LoopingCall(follow, open(filename)) 
loop.start(0.1) 

Cualquiera de ellas le permitirá leer nueva líneas del archivo a lo largo del tiempo sin bloquear el reactor. Por supuesto, ambos simplemente dejan caer la línea en el suelo después de leerla. Esto me lleva al segundo problema ...

Debe reaccionar a la aparición de una nueva línea en el archivo. Presumiblemente, desea escribirlo en su conexión. Esto no es demasiado difícil: "reaccionar" es bastante fácil, por lo general solo significa llamar a una función o un método. En este caso, es más fácil tener el LogProtocol configurar el registro siguiente y proporcionar un objeto de devolución de llamada para manejar las líneas cuando aparecen.Considere este ligero ajuste a la función follow desde arriba:

def follow(fObj, gotLine): 
    line = fObj.readline() 
    if line: 
     gotLine(line) 

def printLine(line): 
    print line 

loop = LoopingCall(follow, open(filename), printLine) 
loop.start(0.1) 

Ahora puede sondear no blockingly un archivo de registro para las nuevas líneas y aprender cuando uno ha mostrado realmente para arriba. Esto es fácil de integrar con LogProtocol ...

class LogProtocol(Protocol): 
    def connectionMade(self): 
     self.loop = LoopingCall(follow, open(filename), self._sendLogLine) 
     self.loop.start() 

    def _sendLogLine(self, line): 
     self.transport.write(line) 

Un último detalle es que es probable que desee dejar de ver el archivo cuando la conexión se pierde:

def connectionLost(self, reason): 
     self.loop.stop() 

Por lo tanto, esta solución evita el bloqueo por usando LoopingCall en lugar de time.sleep y empuja las líneas al protocolo cuando se encuentran usando simples llamadas a métodos.

+0

Excelente explicación, gracias. Funciona maravillosamente – jsucsy

Cuestiones relacionadas