Tail -f inicia sesión en el servidor, procesa los datos y luego los entrega al cliente mediante trenzado

Objetivo: Mostrar datos del servidor en la GUI de wxPython en el cliente

Recién llegado a Twisted. Tengo una GUI wxPython que se ejecuta en un cliente de Windows 7 y tengo un progtwig que se ejecuta en un servidor Ubuntu que produce un registro. Mi bash actual es seguir de cerca el registro, canalizar la salida a un servidor torcido y luego entregar al cliente cualquier información que cumpla con mis condiciones de expresión regular. Ya tengo un túnel abierto, así que no necesito complicar las cosas con SSH. He ejecutado el siguiente bloque de código, 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 al 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 juntos. También he intentado varios otros métodos utilizando 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 al primer comentario, también intenté leer el registro desde Python, el progtwig se bloquea. El código sigue:

 #! /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() 

Tienes unos cuantos objectives diferentes fácilmente separados que estás tratando de lograr aquí. Primero, voy a hablar de ver el archivo de registro.

Su generador tiene un par de problemas. Uno de ellos es grande: llama time.sleep(0.1) . La función de sleep bloquea para la cantidad de tiempo que se le pasa. Mientras está bloqueando, el hilo que lo llamó no puede hacer nada más (eso es más o menos lo que significa “bloquear”, después de todo). Está iterando sobre el generador en el mismo hilo que se llama a LogProtocol.connectionMade (ya connectionMade follow llamadas a connectionMade ). LogProtocol.connectionMade se llama en el mismo subproceso en el que se ejecuta el reactor Twisted, porque Twisted es más o menos un solo subproceso.

Entonces, estás bloqueando el reactor con las llamadas del sleep . Mientras el sueño esté bloqueando el reactor, el reactor no puede hacer nada, como enviar bytes a través de sockets. El locking es transitivo, por cierto. Entonces, LogProtocol.connectionMade es un problema aún mayor: itera indefinidamente, durmiendo y leyendo. Así que bloquea el reactor indefinidamente.

Necesitas 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 reposo. Utilice reactor.callLater para progtwigr futuras lecturas del archivo:

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

También puede dejar que LoopingCall ocupe de la parte que hace que esto sea un bucle que se ejecuta para siempre:

 def follow(fObj): line = fObj.readline() from twisted.internet.task import LoopingCall loop = LoopingCall(follow, open(filename)) loop.start(0.1) 

Cualquiera de estos le permitirá leer nuevas 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 reactjsr ante la aparición de una nueva línea en el archivo. Presumiblemente quieres escribirlo a tu conexión. Esto no es demasiado difícil: “reactjsr” 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 que LogProtocol configure el registro siguiente y proporcione un objeto de callback para manejar las líneas cuando aparezcan. Considere este ligero ajuste a la función de 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 sin lockings un archivo de registro en busca de nuevas líneas y saber cuándo se ha presentado uno. 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 probablemente desee dejar de ver el archivo cuando se pierde la conexión:

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

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