10

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.

vsekhar
  • 5,090
  • 5
  • 22
  • 23

3 Answers3

21

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)
vsekhar
  • 5,090
  • 5
  • 22
  • 23
Glyph
  • 31,152
  • 11
  • 87
  • 129
1

FWIW if you need to do this with endpoints here is my implementation with a slight tweak for my local setup (the callback option would work well here too):

class PortReporter(StreamServerEndpointService, object):
    def __init__(self, endpoint, factory):
        StreamServerEndpointService.__init__(self, endpoint, factory)
    self._reportedPort = None

def privilegedStartService(self):
    r = super(PortReporter, self).privilegedStartService()
    self._waitingForPort.addCallback(self.port_cb)
    return r

def port_cb(self, port):
    self._reportedPort = port.getHost().port
    return port

def getReportedPort(self):
    return self._reportedPort
dpn
  • 592
  • 3
  • 9
  • Of course, this only works if the endpoint is for a transport type that has a port. If you tried to use `PortReporter` with a UNIX server, for example, it would fail with an `AttributeError`. This might seem obvious, but if you're using endpoints to give your users full control over the network configuration of your application then you don't know in advance if they're going to type in a UNIX address or not... – Jean-Paul Calderone Jul 03 '14 at 10:25
  • Thanks exarkun - definitely one to be aware of. In my case I'm using it in the context of service discovery, where each service is known to be HTTP and reports host + port combo back to the service broker. Hadn't really found anything similar in tx land yet.. (suggestions welcome) – dpn Jul 04 '14 at 11:02
0

You can access the port bind to your server like so if you didn't start the server yet (didn't call startService yet):

>>> serv._getPort()._realPortNumber

Else you can also do:

>>> serv._port._realPortNumber
mouad
  • 67,571
  • 18
  • 114
  • 106
  • 1
    These are private APIs; please don't call APIs in Twisted which begin with "_". – Glyph Aug 15 '11 at 20:17
  • @Glyph : The OP have stated a problem, i gived him a solution that may not be the *correct* one, so if you know another way that don't involve calling a __internal method__ please post it, if not well after all __we are all adult here__ . – mouad Aug 15 '11 at 20:25
  • My solution gives the required method. And there was nothing un-adult about Glyphs comment. – Jakob Bowyer Aug 15 '11 at 20:43
  • @Jakob: ahhh sorry about any miss understanding, When i said "we are all adult here" i was quoting the python slogan about accessing attribute __We're all consenting adults here__ i didn't mean by that anything bad, i'm terribly sorry :( beside this your method has one problem when you close your temp socket the port that you bind will be free again and it's possible (even with small probability) that this OS will assign it to another process before you had the change to bind it to your server . – mouad Aug 15 '11 at 20:50
  • @mouad - the point of that slogan is that Python doesn't need `private` access qualifiers, because sensible adults will respect the coding convention that methods beginning with an underscore should not be called from outside the class in which they are defined. – detly Aug 16 '11 at 01:35
  • 1
    @detly: I dunno, the "consenting" part always led me to interpret it as, we are adults and don't need to be protected from ourselves by access modifiers, we know when and how to break the rules. – Adrian Petrescu Aug 16 '11 at 04:28
  • @Adrian: +1, for "when and how to break the rules" , IMO a when can be when the API that you're using don't give you what you want as a public function. – mouad Aug 16 '11 at 08:53
  • @detly: In most cases, but what about when you don't have what you want as public ? IMO you have to options: 1- write a code that call a `private` function that put a warning comment in your code, 2- well ask for the feature to be added in the API that you are using and postpone the task that you're doing until the function that you want to use will be available as public. correct me if i'm wrong :) – mouad Aug 16 '11 at 08:57