Service thread architecture for critique

Started by
5 comments, last by hplus0603 18 years, 1 month ago
Hi, I've just finished implementing this and it seems to work ok, but I'm concerned it may be a bit unwieldy for others' purposes. This is designed for use in creating small-medium scale MMO games. This stuff is in the open-source section of the libs (as portions are based on the work of others) and so is not particularly elegant - it's intended as foundation work. All network stuff can be touched through a 'Net' singleton, which contains system-wide settings (maximum packet size, default encryption methods etc). A server type app will contain a number of 'Service' objects. These register with the Net singleton when initialised. The Net singleton must be used to start, stop, pause and resume the services. The initialisation of a service sets the port it will bind to, protocol to be used (TCP with a socket pool or default UDP) and it's name. The Net singleton will not allow services to contest the same port, etc. A service contains the following classes: A service thread - just updates the service depending on its state as set by the singleton. Also calls update functions for the authenticator, client manager and login manager to prune dead connections. The service's incoming message queue is examined and appropriate messages (depending on client state) are passed to the authenticator, login manager or client manager. An Authenticator - ensures that the incoming connection is of the correct client type, may perform D-H key negotiation, and sets 'pending' and 'authenticated' encryption methods, so authentication can be done with one algorithm and game traffic with another. The authenticator is also responsible for checking a banned IP list, should there be one. If there is a login manager, successful authentication passes the client to the login manager, otherwise it is added to the client manager. A Client Manager - performs network level client updates. Passes appropriate messages to the simulation layer. Performs keep-alive duties and linkdead client pruning. A service may additionally contain: A login manager - an additional authentication stage before going to the client manager. Username/password checks go here. So, you've got a number of threaded services, each of which can support a different client type and protocol and all of which will communicate with a separate simulation layer, operating as it's own thread. It should be noted that each thread's message pool is separate, and that transfer to the simulation layer's pool is prioritised and timed. It is intended that a TCP service type will be used for Master-slave server comms on internal LAN, and UDP for most client comms, except superclients where TCP will probably be used again. The UDP protocol implemented is 'semi-reliable' but does not guarantee packet order on arrival (making it potentially slower for very large transmissions than UDP, which has to order the packets on arrival). UDP resends are periodic, not immediate. For most client-related messages this is not a problem. Only superclients and servers which may need to send large chunks of gamestate would have a problem with the UDP protocol. No assumptions are made at this level as to the precise message format for anything other than authentication - the end user is responsible for providing their own client manager, service and login manager implementations. The vanilla authenticator works as is, but a derivation is suggested to confuse packet sniffers or those who expect the default format. Does this sound reasonable or too complex? Should I stick a factory-style interface in front of it or leave it as base classes to be derived from?
Winterdyne Solutions Ltd is recruiting - this thread for details!
Advertisement
Deriving from classes is almost never the right answer, unless those classes are pure abstract base classes (a k a "interfaces" in common parlance).

I don't like singletons, because that won't allow me to run one or more servers and one or more clients in the same process at the same time, with different configurations. Instead, I like the concept of "execution environments" -- it's like a singleton factory, which creates the service you ask for, the first time you ask for it, except each "singleton" is private to that environment.

Also, when you say you have a thread that "updates state based on ...," does this mean that the thread is polling (bad), or that you signal some event or condition variable when the state changes? If the latter, how do you wrap that so that the user doesn't have to remember to do it manually?
enum Bool { True, False, FileNotFound };
Yeah, they are pure, but vanilla implementations could be churned out by a factory. Derivations would give greater flexibility, but those that would use them over the default factory objects would probably have coded their own system anyway.

The system has threads for services, and clients. Each is independent in terms of protocol, precise implementation (services and clients are interfaces with pure virtual functions for application-specific tasks. These are created by the singleton, but can only be managed by it when they're initialised. The singleton I'm on about behaves very much like the environment factory you're describing.

The service thread (and indeed the client thread) are controlled by state variables. These are set from the Net singleton's startService, stopService, pauseService, resumeService functions. Note these do not necessarily terminate the thread - whilst paused some services may wish to finish sending any pending outbound data for example. startService (re)creates the thread, and stopService causes it to terminate.


Winterdyne Solutions Ltd is recruiting - this thread for details!
You might want to look at OpenBinder for a robust in-process and out-of-process service framework, which we started developing when I was still at Be (long time ago :-), and has been developed a lot further since then.
enum Bool { True, False, FileNotFound };
Quote:Original post by hplus0603
I don't like singletons, because that won't allow me to run one or more servers and one or more clients in the same process at the same time, with different configurations. Instead, I like the concept of "execution environments" -- it's like a singleton factory, which creates the service you ask for, the first time you ask for it, except each "singleton" is private to that environment.


I don't mean to steal the thread but could you explain that a bit more please? It sounds like a better approach which would work alot better in my app.
I think what hplus is describing is basically what I've got, only with perhaps an additional step of indirection to the actual service, to allow system level tweaks to be made in each environment (for each service or client).

To recap - a single manager for 'environments', which creates and assumedly tracks environment objects, in which you can create clients and/or services.

Hplus, had a quick gander at OpenBinder. Impressive, but far too complex for what I need to achieve. Wouldn't be worth the dependency to use. Haven't had a look at their codebase for anything potentially refactorable, but I suspect it's going to be complex.

Winterdyne Solutions Ltd is recruiting - this thread for details!
Smit: check my post in "General Programming" entitled "Singleton Environments".
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement