2

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()
CadentOrange
  • 3,263
  • 1
  • 34
  • 52
  • possible duplicate of [python twisted stdio multiple connections to a server with a command prompt for interaction](http://stackoverflow.com/questions/2311844/python-twisted-stdio-multiple-connections-to-a-server-with-a-command-prompt-for) – Jean-Paul Calderone Apr 28 '12 at 12:41
  • It's not really a duplicate, and the answer that jbreicis provided is far better than the answer provided over there. – CadentOrange Apr 28 '12 at 13:47
  • My answer is not better for the specific task. With my answer I was trying more to illustrate how to make legacy blocking code compatible with twisted.However - deferToThread is the easiest and the ugliest way. Usualy you should be able to find "native" twisted alternatives. As regards line input, much better twisted native approach is provided in twisted examples section http://twistedmatrix.com/trac/browser/trunk/doc/core/examples/stdin.py – jbreicis Apr 28 '12 at 13:59
  • Your answer is better because it contains information, unlike the answer in the other question which merely links to a page that no longer exists. Feel free to disagree, but I found your post helpful anyway :) – CadentOrange Apr 28 '12 at 17:53
  • Whether the answer here is better or worse, the question is still a duplicate. – Jean-Paul Calderone Apr 29 '12 at 10:12

1 Answers1

3

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.

jbreicis
  • 588
  • 2
  • 8
  • Thanks for your answer. It looks like it works, and I'm starting to see why people state that the learning curve for Twisted is really steep! There's no way I'd have been able to figure that out on my own. – CadentOrange Apr 28 '12 at 12:47
  • It is not that it is steep. It basicly is just one very steep bump to get the concept; after that it is the best framework there is. – jbreicis Apr 28 '12 at 13:21