Question

I'm trying to write a simple Echo client in Twisted that sends keyboard input to the server, and is terminated by the user entering 'q' on it's own. In short, I'm just trying to modify the simple echo client (and variants) found on this page. Nothing sexy at all, just the basics.

I'm struggling with the very basic event loop. It looks like I can't start/stop the reactor within the loop as a stopped reactor cannot be restarted. If I don't stop the reactor, then I'll never get to the next line that gets keyboard input.

Any help in getting my echo client working would be much appreciated.

from twisted.internet.protocol import ClientFactory
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor

class EchoClient(LineReceiver):
    end="Bye-bye!"
    def connectionMade(self):
        #only write and end transmission if the message isn't empty
        if len(self.factory.message) > 0:
            self.sendLine(self.factory.message)
            self.sendLine(self.end)
        else:
        #Else just terminate the connection
            self.transport.loseConnection()

    def lineReceived(self, line):
        print "receive:", line
        if line==self.end:
            self.transport.loseConnection()

class EchoClientFactory(ClientFactory):
    message = ""

    def buildProtocol(self, address):
        p = EchoClient()
        p.factory = self
        return p

    def clientConnectionFailed(self, connector, reason):
        reactor.stop()

    def clientConnectionLost(self, connector, reason):
        reactor.stop()

def main():

    s = raw_input('Text to send (''q'' to terminate): ')
    while s != 'q':
        factory = EchoClientFactory()
        factory.message = s
        reactor.connectTCP('localhost', 8000, factory)

        #This is bad because reactor cannot be restarted once it's been stopped
        reactor.run()

        s = raw_input('Text to send(''q'' to terminate): ')

if __name__ == '__main__':
    main()
Was it helpful?

Solution

As a rule of a thumb - there are very rare instances where you would want to restart or stop reactor, unless you are terminating your program alltogeather. If your encounter a piece of code which will cause block e.g. database access, long computation or in your case raw_input, you have to either: find a twisted alternative (twisted.enterprise.adabi in case of database) or make it twisted compatible. The easiest way to 'deblock' your code is move the blocking bits into thread by utilizing deferToThread from twisted.internet.threads. Consider this example:

from twisted.internet.threads import deferToThread as __deferToThread
from twisted.internet import reactor

def mmprint(s):
    print(s)

class TwistedRAWInput(object):
    def start(self,callable,terminator):
        self.callable=callable
        self.terminator=terminator
        self.startReceiving()
    def startReceiving(self,s=''):
        if s!=self.terminator:
            self.callable(s)
            __deferToThread(raw_input,':').addCallback(self.startReceiving)


tri = TwistedRAWInput()
reactor.callWhenRunning(tri.start,mmprint,'q')
reactor.run()

You would never have to stop reactor, as raw_input would happen in an outside thread, callbacking deferred on every new line.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top