Posted on 2008-04-07 10:11
鱼儿 阅读(276)
评论(0) 编辑 收藏 引用 所属分类:
boost.asio
[From]http://blog.cplusplus-soup.com/2006/12/boost.html
Wednesday, December 06, 2006
Boost.Asio and Patterns
The past few months my previous team and I were working on a networked messaging application which uses Boost.Asio for the networking code. Because of this, we were able to come up with a one day server for logging, and we have been able to concentrate on optimizing and testing the server according to our requirements. Needless to say, Boost.Asio allowed us to develop network servers and clients very easily compared to having to do it with the ACE framework or the native OS' C Networking API. While Boost.Asio has saved us a lot of headaches, we have discovered a few recurring usage patterns that may seem useful. I'll list down a few and come up with a few more as the days go by and I get to organize my thoughts.
Active Objects are now easier to create thanks to the Boost.Asio IO Service.
Reader-Writer Objects seem easier to manage.
Protocol Handlers can now be as generic as possible.
Generic Servers can easily be used for pretty much anything.
I will deal with each usage term on a per section basis, and I'll give some example code along the way.
Active Objects
An Active Object is a design pattern which is summarized as:
The Active Object pattern, which decouples method execution from method invocation in order to simplify synchronized access to an object that resides in its own thread of control. The Active Object pattern allows one or more independent threads of execution to interleave their access to data modeled as a single object. A broad class of producer/consumer and reader/writer applications are well suited to this model of concurrency.
http://citeseer.ist.psu.edu/lavender96active.html
by R. Greg Lavender, Douglas C. Schmidt
The paper goes well beyond what you actually need in the real world usually, where what's mostly required is to invoke asynchronous operations which may or may not have returned values. Simply put, the idea is that there's a different thread of execution for operations that are supposed to be run asynchronously. We can make our own active object by using the Boost.Asio IO Service class and a few other components in the Boost library (like Boost.Date_Time). An example is the following code listing:
#include
#include
#include
#include
#include
#include
struct active_object : boost::noncopyable {
active_object() :
timer(scheduler, boost::posix_time::seconds(15))
{
timer.async_wait( boost::bind(&active_object::timer_expired, this, boost
::asio::placeholders::error) );
execution_thread.reset(new boost::thread(
boost::bind(&boost::asio::io_service::run, &scheduler)));
};
virtual ~active_object() {
scheduler.interrupt();
execution_thread->join();
};
void some_operation() {
scheduler.post( boost::bind(&active_object::some_operation_impl, this) );
};
protected:
boost::asio::io_service scheduler;
private:
boost::asio::deadline_timer timer;
boost::shared_ptr execution_thread;
void some_operation_impl () {
// do something here...
std::cout << "Do something." << std::endl;
};
void timer_expired(boost::asio::error const & error) {
timer.expires_at (timer.expires_at() + boost::posix_time::seconds(15));
if (!error)
timer.async_wait( boost::bind(&active_object::timer_expired,
this,
boost::asio::placeholders::error)
);
};
};
The above code snippet shows a very bare Active Object implementation. If you check "void some_operation_impl()", it contains just a simple output line. This is scheduled for future execution using the boost::asio::io_service's post method. This is very convenient especially if you notice that the io_service's run method is invoked in a different thread. It's a very useful active object implementation especially if you have operations that may be delayed for later execution and that do not return values for later use.
If your operations do make use of return values, then you'll have to implement the Futures pattern, or use an existing Futures implementation.
Reader/Writer Objects
Using the above Active Object implementation as a guide, a multi-threaded writer can use a single handle to perform a write -- and in the same object, a read method can be made to return a Future object. This makes resource management way easier than it used to be especially for multi-threaded applications. Although this approach may be too much,
the alternatives seem a bit hairier than the proposed approach.
We've been able to use the Active Object implementation all over our messaging system because processing messages received is usually a dead-end operation which can be scheduled asynchronously. Same thing with Writers, because the write operations can be scheduled for later execution -- readers on the other hand may be scheduled in a different execution thread but the results can be accessed only when the read operations are done.
Truly Generic Protocol Handlers
Perhaps by using the patterns such as CRTP (Curiously Recurring Template Pattern) and PBCP (Parametric Base Class Pattern) techniques, protocol handlers can be tested using a mock implementation for the networking operations. This means all the networking related code which touch the Boost.Asio library can very well be implemented as a specialization of a protocol handler implementation. This means the exceptional conditions can be implemented either in the derived type (CRTP) or the base type (PBCP). Examples of how that would look like in practice are shown below:
// Using CRTP
// -------------
template
struct my_protocol_handler {
// ...
void handle_timeout(boost::asio::error const & errror) {
// Do the protocol required stuff here then
// call the derived type's handle_timeout_impl()
static_cast(this)->handle_timeout_impl();
};
// ...
};
struct http_protocol_handler : my_protocol_handler {
// ...
void handle_timeout_impl() {
// handle the timeout here, which may call methods already
// defined in the my_protocol_handler template type
};
// ...
};
// -------------
// Using PBCP
// -------------
template
struct handler : BaseType {
// ...
void handle_timeout (boost::asio::error const & error) {
// Since the handler derives from the supplied type,
// it can call the correct implementation directly.
BaseType::handle_timout_impl();
};
// ...
};
struct http_handler_impl {
// ...
void handle_timout_impl() {
// implement how to handle the timeout here
};
// ...
};
typedef handler http_handler;
// -------------
The above samples will greatly help especially if you are building a lot of protocol implementations, and would be able to refactor the most common parts out into a template, and just leverage the above techniques to make them generic. The trick here is to be able to leverage the C++ language features to make your life easier, as it has enabled us to do. Although definitely there are many ways to skin a cat, the above approach seems the most flexible as far as our implementation and experience is concerned.
Generic Servers
By leveraging on the Boost C++ Libraries along with the Boost.Asio networking powers, it can allow you to write truly generic servers and delay the handler implementations to functors or function objects. This approach is very popular with programmers who have previously used the STL, and is not very hard to use especially with the Boost C++ libraries. Thanks to Boost.Bind and Boost.Function, these wrappers and function generators allows us to delay the handler implementation further and moving the handling logic away from the actual server applications. A rough example would be to implement your server, and just take an argument on the initializer which signifies your functor handlers.
struct my_server {
explicit my_server ( boost::function const & handler )
: _handler(handler) { };
boost::function _handler;
// ...
void handle_read (boost::asio::error const & error, size_t bytes_read) {
// Do the other stuff that need to be done, then call the handler
_handler(input_str);
// Then do the other stuff that also need to be done
};
// ...
};
The good thing with the above sample is that you can make the handler a boost::bind result of an active object's facade -- that means, if we go back to the active_object type in the first section of this article, you can do a "boost::bind(&active_object::some_function_that_takes_a_string, &instance, _1)" when you construct an instance of my_server. You can do a lot more complex things with these combinations, and it will also allow you to create more generic components for your future requirements.
Summary
I really prefer well-designed systems and if you talk to some of my previous teammates, they will pretty much concur that I am a bit anal about design especially if we've already been able to fulfill the requirements. My mantra of "solve the problem first then figure out design later" has yielded quite a number of usage patterns as far as Boost.Asio is concerned. The good thing about it is, that I'm able to share with others these patterns through this blog. I certainly hope to be able to write more about other things in the coming days, as the schedule permits.
The author is still a partner at Orange and Bronze Software Labs, Ltd. Co. though only in an advisory function. The author is currently Linux Lead Developer at CitizensSoft, Inc. -- this article was written over a span of three days stemming from a request by Caroline Middlebrook. You may still contact the author through dean@orangeandbronze.com ; comments and reactions most appreciated.
by Dean Michael at Wednesday, December 06, 2006