On Jan 28, 2007, at 2:22 PM, Steve wrote:

Hi everyone,
As a coding excersize just to "see if I could do it" I decided to make
a chat server using UDP.

Up to this point, you're doing pretty good! Coding things just to see if you can do it is excellent practice and lots of fun, to boot.

A major part of my design is the ability to scale up without slowing
down much, as such I decided to break my server design into 3 major
component objects.
Listener, Sender, Core.

This isn't too bad of a goal, but it is often wise to make a fairly simple prototype that performs the core features as simply as possible and see if it works well enough for you. If it slows down too much as you perform scale testing, you can see exactly why it happens and know precisely what needs to change.


The listener is pretty simple we just create a non blocking listener
on a port and poll it periodically.


Now we're really off in the weeds. Periodic polling of a non- blocking port is almost never what you want; at least not polling by hand. If the listener is in its own thread, just block on your read call. If you need to do other things in the thread while waiting for input, there's always the select() or poll() system calls, which will block until input arrives on any of the file descriptors you tell them to watch or a timeout of your choice occurs.

In fact, by building your application out of an event dispatch loop centered on a select() call, you can avoid dealing with pthreads altogether. As far as I'm concerned, users of C and C++ should avoid threading as often as it is feasible to, because threading introduces nondeterminism to your code and opens the door to all sorts of hard- to-find errors, many of which won't appear until you really heavily load the application.

The Core server design handles processing of information coming in
from the listener, i.e. reading the buffer, and creating new sender
objects if the client has never been seen before, as well as cleaning
up sender objects if the client has gone too long without a response.

The Sender(s) are where I'm having difficulty here, but it seems to me
this shouldn't be so hard.  Basically a sender is a self contained
"machine", it needs its own thread because it runs in an infinite loop
checking the main chat buffer in the Core, if anything has changed it
sends those changes to the client, and then sleeps for 250ms.


Let me get this straight here. You want to write a scalable application, and you are assigning a thread to each client? Those are seriously conflicting design features. Each new thread (assuming you're using Linux) allocates a new process structure in the kernel that has a new chunk of memory for stack space allocated to it and a pointer to the same heap as the process it belongs to. This is not a particularly cheap data structure when compared to non-threaded alternatives. Start getting into the hundreds or thousands of concurrent connections, or get a DOS attack of hundreds of thousands of incoming 'new users', and your server will fall right over.

Now I know in a typical implementation, that all clients are contained
in a list and when the buffer has changed then the server iterates
through all the clients and sends out the changes.   But I don't
really like that design, the whole point of my design is to do it
without iterating through a list.


That implementation is typical because 1) it is easy, and 2) it is efficient. What's not to like about it? If you want to dress it up, call it the Listener Pattern and create the appropriate objects.

So as I was saying basically the sender class has a public method
called "void run()", this method is the function that wakes up, checks
the buffer, sends if needed and then goes back to sleep.


... [ pthread and C++ stuff snipped ] ...

And it works, but it feels very wrong to me.  Having to cast the
object to void, then recast back to it's original form, seems like a
lot of overhead as well as being dangerous.  And it has to occur every
250 ms, which seems like alot of recasting to me.


Well, it feels pretty wrong to me, too, but just about every combination of C/C++ and pthreads feels wrong to me. Casting to and from void isn't particularly dangerous if you always know exactly what you're casting, and it certainly doesn't add any overhead. It looks ugly, but considering the lousy type system C has, it's sometimes necessary. It's just subverting the type system, after all, not actually *doing* anything. What's got a lot of overhead is waking up every 250ms (causing context switching and interrupting something else) whether there's any reason to or not. Slave threads like that really should stay blocked waiting for an event, not polling.


Thoughts?  Ideas? Concerns?
Thanks in advance!

Well, it's cool that you're trying to build a scalable system as a learning project, and I think this is a reasonable sort of project to start with. I think you're getting a bit ahead of yourself, though, and that you ought to take a couple of steps back and start with something simpler. If you *really* want to use threads, I would suggest reading about them in a bit more depth before trying another design, because your current one is fundamentally broken. If you just want to build a scalable system, I suggest avoiding threads altogether and building on top of a select()-based event loop.

                        --Levi

/*
PLUG: http://plug.org, #utah on irc.freenode.net
Unsubscribe: http://plug.org/mailman/options/plug
Don't fear the penguin.
*/

Reply via email to