[Twisted-Python] Keeping a list of connected PB clients

Phil Christensen phil at bubblehouse.org
Fri Nov 24 18:46:26 EST 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