[Twisted-Python] Keeping a list of connected PB clients
Phil Christensen
phil at bubblehouse.org
Fri Nov 24 16:46:26 MST 2006
On Nov 24, 2006, at 5:16 PM, Yi Qiang wrote:
> On 11/19/06, Phil Christensen <phil at bubblehouse.org> wrote:
> On Nov 19, 2006, at 7:00 PM, Yi Qiang wrote:
> > There does not seem to be a corresponding clientConnectionLost
> > method for the Factory class. Any suggestions would be appreciated.
>
> My approach to this was to put any post-connection cleanup in the
> Avatar's "logout" method.
>
> Hi Phil,
> Do you happen to be able to share the code you've written to
> accomplish just this? Where do you store your list of connected
> clients?
I'm CC'ing the list for posterity, but this is actually pretty
straightforward. I'm guessing the issue is that you're over-thinking
this.
First of all, here's some code from your original post:
On Nov 19, 2006, at 7:00 PM, Yi Qiang wrote:
> from twisted.spread import pb
>
> class DSagePBServerFactory(pb.PBServerFactory):
[snip snip snip]
> def clientConnectionMade(self, broker):
> self.clients.append((broker.transport,
> broker.transport.getPeer().host,
> broker.transport.getPeer().port))
> print self.clients
First thing, there's not necessarily any need to subclass the
PBServerFactory if this is all you're doing. In the interest of
forward-compatibility (always a good idea with Twisted ;-), I try to
avoid making subclasses of essential framework classes unless I have
a very good reason (or a very bad one ;-).
The place where you want to do all this is really in your Realm
instance. I think it's possible to create a PB server that doesn't
require login, but I've never had any need for it, so you're kind of
on your own there.
> class MyRealm:
> """
> Holds a reference to the main service object.
> """
> __implements__ = portal.IRealm
>
> def __init__(self, service):
> """
> Create a realm with the given service.
> """
> self.service = service
>
> def requestAvatar(self, avatarId, mind, *interfaces):
> """
> This function is called after the user has verified their
> identity. It returns an object that represents the user
> in the system, i.e., an avatar.
> """
> if pb.IPerspective in interfaces:
> avatar = self._getPBAvatar(avatarId, mind)
> ####################### <-- RIGHT HERE
> return pb.IPerspective, avatar, avatar.logout
> else:
> raise NotImplementedError("no interface")
This is where you want to maintain your client list. The hardest part
is deciding on a place to put it that will be accessible from all
your code; I usually use a singleton pattern of some kind, where I
can import a module that holds onto client references and provides
functions to manipulate/retrieve that list.
So, that point where I put all those hashes is a pretty good place to
add clients your client queue. The 'mind' argument is usually a
reference to the client on the other end, assuming your client passes
it properly. My client does this:
> # self.client is a Referenceable on the client side.
> defer = self.factory.login(credentials.UsernamePassword
> (username, password), self.client)
> defer.addCallback(connected_callback, self)
This way, you can find all the necessary info you might need to
identify the user on the other end in the properties of this object.
You'll also see that right after the hashes, I return a three-tuplet
that has avatar.logout as its third item. This is a method defined on
my avatar object, but any callable object can be returned here, and
that callable, of course, can remove clients from your list. So one
approach to keeping track of client connections would be:
> class MyRealm(pb.Avatar):
> # ... snip
> # ... snip
> def requestAvatar(self, avatarId, mind, *interfaces):
> """
> This function is called after the user has verified their
> identity. It returns an object that represents the user
> in the system, i.e., an avatar.
> """
> if pb.IPerspective in interfaces:
> avatar = MyAvatar()
>
> import clienttracker
> clienttracker.append(avatar)
>
> return pb.IPerspective, avatar, avatar.logout
> else:
> raise NotImplementedError("no interface")
>
> class MyAvatar(pb.Avatar):
> # ... snip
> # ... snip
> def logout(self):
> import clienttracker
> clienttracker.remove(self)
There's a lot of flexibility there. You could move the
clienttracker.append call right inside your Avatar constructor -- the
only reason I didn't is because my server supports a number of
protocols besides PB, and I wanted to deal with them universally.
One thing this approach doesn't do is keep track of clients that
connect, but fail authentication. To do that, you'd need to subclass
Broker, but that's a tough one. I looked into this once before, and
it certainly appears doable -- in the end, a Broker is still a
Protocol, which means it has a transport property you can get client
addresses out of.
Of course, if a user fails authentication, they are immediately
disconnected, so this would only be a problem if you are concerned
about malevolent users. It would be possible to write a client that
connects but never authenticates; enough of those could cause some
discomfort on the server side, but if that happens, you likely have
more problems than keeping track of a client list.
Hope this helps...
-phil
More information about the Twisted-Python
mailing list