Exiting Python Program after closing connection in Twisted

2.5k views Asked by At

So I have this little side project which uses websockets in python from autobahn twisted. The connection between client and server and data transfer works perfectly but I'm having trouble gracefully exiting the program after the TCP connection closes. Hers is the code :

class MyClientProtocol(WebSocketClientProtocol):

    def onConnect(self, response):
        print("Server connected: {0}".format(response.peer))

    def onOpen(self):
        print("WebSocket connection open.")

        def hello():
            from twisted.internet import reactor
            self.sendMessage(u"Hello, world!".encode('utf8'))
            self.sendMessage(b"\x00\x01\x03\x04", isBinary=True)

            #self.factory.reactor.callLater(1, hello)
            #self.reactor.callFromThread(reactor.stop)
            #reactor.callFromThread(reactor.stop)
            #self.factory.reactor.callFromThread(reactor.stop)
        # start sending messages every second ..
        hello()
        return

    def onMessage(self, payload, isBinary):
        if isBinary:
            print("Binary message received: {0} bytes".format(len(payload)))
        else:
            print("Text message received: {0}".format(payload.decode('utf8')))

    def onClose(self, wasClean, code, reason):
        print("WebSocket connection closed: {0}".format(reason))


def WebCon():
    import sys

    from twisted.python import log
    from twisted.internet import reactor

    log.startLogging(sys.stdout)

    factory = WebSocketClientFactory("ws://localhost:8080", debug=False)
    factory.protocol = MyClientProtocol

    reactor.connectTCP("127.0.0.1", 8080, factory)

    reactor.run()
    reactor.stop()
    print("Should exit")
    return
1

There are 1 answers

0
Glyph On

reactor.run() runs forever. So the two lines

reactor.run()
reactor.stop()

mean:

Run forever. After "forever" is done, stop running.

There's a clue in the way you asked your question: you said you want to "gracefully [exit] the program after the TCP connection closes". As your program is written right now you are gracefully exiting the program after the program is ready to exit.

A connection closing is an event. In Twisted you are notified of events by methods being called on your objects. Luckily you've already got the object, and you've already even implemented the appropriate method! You just need to change

def onClose(self, wasClean, code, reason):
    print("WebSocket connection closed: {0}".format(reason))

to

def onClose(self, wasClean, code, reason):
    print("WebSocket connection closed: {0}".format(reason))
    reactor.stop()

For a more idiomatic solution, you should really be using endpoints, not connectTCP, to make your outgoing connection, and react, not reactor.run(), to run your main loop. It looks like you want to write a function that reads more like "connect, then do stuff on my connection, then wait for the connection to close, then exit" rather than hard-coding onClose to stop the whole reactor.

That would look something more like this: import sys from somewhere.i.dont.know import (WebSocketClientProtocol, WebSocketClientFactory)

from twisted.internet.defer import Deferred, inlineCallbacks
from twisted.internet.task import react
from twisted.internet.endpoints import clientFromString

from twisted.logger import globalLogBeginner, textFileLogObserver

class MyClientProtocol(WebSocketClientProtocol):

    def __init__(self):
        self.finished = Deferred()

    def onConnect(self, response):
        print("Server connected: {0}".format(response.peer))

    def onOpen(self):
        print("WebSocket connection open.")
        self.sendMessage(u"Hello, world!".encode('utf8'))
        self.sendMessage(b"\x00\x01\x03\x04", isBinary=True)

    def onMessage(self, payload, isBinary):
        if isBinary:
            print("Binary message received: {0} bytes".format(len(payload)))
        else:
            print("Text message received: {0}".format(payload.decode('utf8')))

    def onClose(self, wasClean, code, reason):
        print("WebSocket connection closed: {0}".format(reason))
        self.finished.callback(None)


@inlineCallbacks
def WebCon(reactor, endpoint="tcp:127.0.0.1:80"):
    globalLogBeginner.beginLoggingTo(textFileLogObserver(sys.stdout))

    factory = WebSocketClientFactory("ws://localhost:8080", debug=False)
    factory.protocol = MyClientProtocol

    endpoint = clientFromString(reactor, endpoint)
    protocol = yield endpoint.connect(factory)
    yield protocol.finished

react(WebCon, sys.argv[1:])

Hope this helps!