Broadcast server

Michael ms at cerenity.org
Sun Sep 3 13:22:44 EDT 2006


swell at netcourrier.com wrote:

>   It sounds very interesting and i will definitely take a deeper look
> but i was more considering something simpler as a learning exercise
> more that finding a package that should already do what i want to
> achieve.

The bulk of this post dissects (roughly) how Kamaelia handles this
internally since I figure that might also be useful if you're after
this as a learning exercise :-) (It should hopefully be clear enough
for this to be useful) At minimum it might go some way to explain why
people generally tend to suggest using an existing framework :-)

> I want to find the basic guidelines to write that kind of client/server
> architecture and make some tests of different solutions(socket+select
> or socket+thread or mix of them)

I think if you're doing socket & select it sounds easy/simple until you
start dealing with all the cases you need to to do it properly. You're
probably best looking at the python cookbook for a good initial starting
point.

> Your project seems very great and will try to read in to learn.

Kamaelia *might* still be useful here in some respects, since despite being
*effectively* event based, due to the use of generators code tends to look
linear.

The low level structure of the server breaks down as follows:

(I'm spending a bit of time here since it might be of general interest,
sorry if it isn't)

   * Kamaelia.Internet.TCPServer is a simple component that sits waiting for
     a user to connect. 

     It makes the server port, adds any user socket options, makes it non
     blocking, binds it to the requested (if any) host and either to a user
     defined or random port number. (latter is for testing)

     It then registers the socket with the piece of code that's taking care
     of select handling, and sits to wait for any notifications of activity
     on the socket. (Which would mean for something acting as a server a new
     connection).

     It then performs a socket accept, and creates an object to handle the
     raw (now connected) socket - a connected socket adaptor. It then
     registers that socket and object with the piece of code that's taking
     care of select handling. (So that the select handling can tell the
     piece of code handling the raw socket to do some work on the socket)

     code: http://tinyurl.com/pocm8

   * Kamaelia.Internet.ConnectedSocketAdaptor is actually more complicated
     than you might expect. The reason is because the socket is
     non-blocking, and where-ever you would normally block you have
     to maintain some sort of buffering in case the action fails.
     This is more a symptom of non-blocking behaviour than it is about
     Kamaelia.

     Anyhow the basics of this is to take any data that is to be sent to the
     socket, and add that to a sending queue.  (this data will generally
     come from the code handling the protocol)

     Then if the select handling code has said that we can send/should data,
     we take data from the send queue and send as much as we can until our
     sending fails (after all, we're non-blocking, so we expect it to fail).

     Then if the select handling code has said that there's data ready to
     receive the component reads from the socket until an error (again we
     expect an error due to non-blocking behaviour). This data is then
     passed on to a location where a protocol handler can expect to take the
     data and do what it likes with it.

     The bulk of the code for the CSA actually revolves around resends, and
     the necessary error handling. (It's also one of the oldest bits of code
     in Kamaelia so possibly a little horrid to look at!)

     code: http://tinyurl.com/nj8lk

   * Kamaelia.Internet.Selector is also slightly more complex than you might
     think. This is because whilst you tell select "let me know whenever any
     of these sockets is ready for (reading|writing|exceptional)", it
     actually cares about what you do *after* it's given you an answer. 

     For example if you don't read from all the things that are ready to
     read, or don't write to all the things that are ready to write or
     (worse) try asking it to do things with regard to closed or invalid
     sockets, then you're just asking for trouble.

     There's lots of ways round this and you can do something like:
     looping:
         (readables, writeables, excepts) = select( ...)
         for readable in readables:
             read from readable and do something
         for writable in writeables:
             write to writeables and do something
         for exceptional in excepts:
             handle exceptional

     However then you add new problems - what about if you want to do a
     database request as result of one? Render a image for another? Wait for
     keyboard press? etc.

     As a result that's why we use explicit message passing, though many
     people use a reactor (or sometimes a pro-actor) pattern to handle
     events. (if you're familiar with simulation it's very similar to an
     event wheel approach).

     For us we simply have a service (a selector) that sits an waits to be
     told "send me a message when this is ready to do X". Since the thing
     handling select can't know (in our system because we don't want any
     accidental serialisation) whether the event (socket read to ready,
     socket ready to write) has been handled, our selector removes the
     socket from the read/write/exceptional set when it's been found to be
     ready to do something.

     This means that when the thing using has read from the socket it tells
     the selector "can I have more please", and if it's written to the
     socket has to tell the selector "I want to do more, but it bust - can
     you tell me when it's working again".

     This means the code managing the select loop and the socket sets the
     select call operates on has to deal with all these issues. (You'd have
     to anyway, but in some respects it's more explicit at the socket-level
     in Kamaelia)

     As a result you'll see explicit code adding sockets to "readers",
     "writers", and "exceptionals", and for removing sockets from them when
     an event's happened. This puts responsibility for making this work with
     the code that cares - the code handling the socket activity directly.
     The rationale for this is that it then also means that the same code
     that's used for sockets can be used for file handles. (one of the
     points of select on unix after all)

     code: http://tinyurl.com/mfqyr

For completeness:
   * Kamaelia.Chassis.ConnectedServer.SimpleServer takes the above
     components and packages them up in an easy to use way. (meaning you can
     avoid all the lowlevel hassle). 

     It's called a Chassis because like you add components onto a car
     chassis (wheels, seats, doors, engine) in order to make a car, you need
     to add someting to the SimpleServer to make a server. Specifically you
     need to give it something that can create components to handle
     connections.

     As a result, the way SimpleServer works is whenever a new connection
     comes in, it gets told about it by being passed a connected socket
     adaptor by the tcp server. It then creates a protocol handler to talk
     to the connected socket adaptor. As a result, any information that
     comes in from the socket gets passed to the protocol handler. Any
     information the protocol handler generates gets sent out the socket.

     As a result the logic for the SimpleServer is:
         * Create the TCP Server
         * Loop
            * When the TCP Server gives us a connected socket adaptor,
              create a protocol handler (from the given function), and wire
              the two together.
            * If the connection dies (due to the protocol handler saying
              "shutdown" or due to the socket dying) the everyone interested
              finds out and propogates the change. (Meaning you don't get
              dead file descriptors in the select call)
     code: http://tinyurl.com/pts72

You'll note that the explanation of select itself is by far the longest
here! You may also find "man select_tut" interesting and useful if you're
doing this as a learning exercise.

If you're looking to do this more generally, you should really consider
using SocketServer or asyncore that come with python, or Twisted or Kamelia
if you want to run a production system. 

Twisted is more well known and more deployed and as a result more battle
tested (meaning seasoned programmers would sensibly trust it more!), and
follows a more common approach for coding all this. (Boils down to something
similar though) As a result if hiring people to work on code is something
to think about Twisted operates is probably a better choice. Kamaelia is
however designed to be easy to pick up.

Finally nipping back to your example here:

> 5 clients that get time updated from the server

The code to handle on the server side is trivial. 

----(start)----
import time
from Axon.ThreadedComponent import threadedcomponent
from Kamaelia.Chassis.Pipeline import Pipeline
from Kamaelia.Chassis.ConnectedServer import SimpleServer
from Kamaelia.Util.Stringify import Stringify
from Kamaelia.Util.Backplane import *

class periodictime(threadedcomponent):
    def main(self):
        while 1:
            time.sleep(0.1)
            self.send(time.time(), "outbox")

Backplane("periodictime").activate()
Pipeline(periodictime(),
         Stringify(), # Make suitable for network
         PublishTo("periodictime")).activate()

def getTimeProtocol():
    return SubscribeTo("periodictime")

SimpleServer(protocol=getTimeProtocol, port=1600).run()
----(finish)----

Telnet to 127.0.0.1 1600 to see the result here.

If you want to have the data source something from a client (eg event info
coming in on port 1599) with clients of the event source coming in from
elsewhere, this would change the server as follows:

----(start)----
from Kamaelia.Chassis.Pipeline import Pipeline
from Kamaelia.Chassis.ConnectedServer import SimpleServer
from Kamaelia.Util.Backplane import *

Backplane("periodictime").activate()

def publishTime():     return PublishTo("periodictime")
def getTimeProtocol(): return SubscribeTo("periodictime")

SimpleServer(protocol=publishTime, port=1599).run()
SimpleServer(protocol=getTimeProtocol, port=1600).run()
----(finish)----

And the time (event) source would look like this:

----(start)----
import time
from Kamaelia.Internet.TCPClient import TCPClient
from Axon.ThreadedComponent import threadedcomponent
from Kamaelia.Chassis.Pipeline import Pipeline
from Kamaelia.Util.Stringify import Stringify

class periodictime(threadedcomponent):
    def main(self):
        while 1:
            time.sleep(0.1)
            self.send(time.time(), "outbox")

perdiodictimeserver = "127.0.0.1"
Pipeline(periodictime(),
         Stringify(), # Make suitable for network
         TCPClient(perdiodictimeserver, 1599)).activate()
----(finish)----

Anyway, I hope the explanation of what's going on inside the core is useful
since in many respects if you're writing your own select handling loop
(which I would encourage you to do if you're learning about this!), the
basics of what you have to do stay the same. (check activity, clear, when
errors happen ask again, buffer data which needs to get sent, and decouple
everything as best as makes sense whilst trying to avoid accidental
serialisations).

The reason for the examples in the end is merely for completeness. (I'll
probably add these to our SVN distribution since the question does seem to
crop up fairly often generally speaking!)

If you're looking to do this in a production environment I'm personally an
advocate of learning what's going on in the core and then using an existing
library.

(The reason Kamaelia exists is because I wondered if there was an
alternative, potentially clearer way of writing these things, most
people would quite sensibly just use Twisted - especially given you
can buy a book on it! I personally think Kamaelia is cleaner, but then
I would think that :)

Have fun!


Michael




More information about the Python-list mailing list