Search code examples
pythontcptwistedp2p

twisted - get OS-chosen listen port


I am writing a twisted P2P client using the application framework. The listen port for incoming connections will be on a random (OS-determined) port. However, I need a way to determine what that port is after creating it:

import twisted... etc.

application = service.Application('vmesh')
peerservice = MyPeerService()
servicecollection = service.IServiceCollection(application)
factory = MyPeerFactory(peerservice)
server = internet.TCPServer(0, factory) # listen on random port
listen_port = server.getHost().port # ??? doesn't work...
server.setServiceParent(servicecollection)

I can't find anything in the docs about querying the port created by internet.TCPServer() or by reactor.listenTCP() which it forwards to. I can't simply wait for a connection to occur since the client has to announce its port in order for those connections to ever happen.


Solution

  • listenTCP returns an IListeningPort, which has a getHost() method that gives back an object with a port. For example:

    >>> from twisted.internet import reactor
    >>> from twisted.internet.protocol import Factory
    >>> port = reactor.listenTCP(0, Factory())
    >>> port.getHost().port
    55791
    

    However, TCPServer doesn't call listenTCP until it is started with privilegedStartService. Plus, the IListeningPort isn't actually exposed via a public API. So, you will need to write your own Service. Luckily, it's quite easy to do this; TCPServer doesn't do very much. You just need to write one that reports its port somewhere as soon as it starts listening. Here's an example:

    from twisted.internet import reactor
    from twisted.application.service import Service
    
    class PortReporter(Service, object):
        def __init__(self, factory, reportPort):
            self.factory = factory
            self.reportPort = reportPort
    
        def privilegedStartService(self):
            self.listeningPort = reactor.listenTCP(0, self.factory)
            self.reportPort(self.listeningPort.getHost().port)
            return super(PortReporter, self).privilegedStartService()
    
        def stopService(self):
            self.listeningPort.stopListening()
            return super(PortReporter, self).stopService()
    

    You can then use this in a tac file, like so:

    from twisted.internet.protocol import Factory
    from twisted.application.service import Application
    application = Application("test")
    def showPortNumber(n):
        print("The port number is: %d" % (n,))
    PortReporter(Factory(), showPortNumber).setServiceParent(application)