Boost ASIO攻破!!!(★firecat推荐★)

时间:2022-09-08 23:31:03

文章来源:http://www.cppblog.com/shanoa/archive/2009/06/26/88606.aspx

注意事项:

1、研究asio的源码,需要多多关注\boost\asio\detail\impl\*.ipp系列文件。里面封装了许多windows底层API函数。例如IOCP相关的函数,CreateIoCompletionPort()可以在win_iocp_io_service.ipp文件找到。文件\boost\asio\detail\socket_ops.hpp和\boost\asio\detail\impl\socket_ops.ipp涉及较多底层操作。

2、 一般来说,*.h里面只有声明,没有实现,而*.hpp里声明实现都可以有,后者可以减少.cpp的数量。.hpp文件,其实质就是将.cpp的实现代码混入.h头文件当中,定义与实现都包含在同一文件,则该类的调用者只需要includecpp文件即可,无需再将cpp加入到project中进行编译。而实现代码将直接编译到调用者的obj文件中,不再生成单独的obj,采用hpp将大幅度减少调用project中的cpp文件数与编译次数,也不用再发布烦人的libdll,因此非常适合用来编写公用的开源库。

3、.ipp文件,奇葩,只有boost独有。boost asio的C++源文件。


转载正文:

花了足足3天时间,外加1天心情休整,终于在第5天编写出了一个能运行的基于asio和thread_group的框架,差点没气晕过去,把源码都看懂了才感觉会用了。
测试了一下,debug下一万次回应耗时800+毫秒,release下是200+毫秒,机器配置双核2.5G英特尔,4个线程并行工作,无错的感觉真好,再也不用担心iocp出一些奇怪的问题啦,因为是巨人们写的实现,呵呵。

进入正题,简要说一下asio的实现原理吧。在win32平台上,asio是基于IOCP技术实现的,我以前也用过IOCP,却没想到居然能扩展成这样,真是神奇!在其他平台下还会有别的方法去实现,具体见io_service类下面这部分的源码:

Boost ASIO攻破!!!(★firecat推荐★)  // The type of the platform-specific implementation.
Boost ASIO攻破!!!(★firecat推荐★)
#if defined(BOOST_ASIO_HAS_IOCP)
Boost ASIO攻破!!!(★firecat推荐★)  typedef detail::win_iocp_io_service impl_type;
Boost ASIO攻破!!!(★firecat推荐★)  friend class detail::win_iocp_overlapped_ptr;
Boost ASIO攻破!!!(★firecat推荐★)#elif defined(BOOST_ASIO_HAS_EPOLL)
Boost ASIO攻破!!!(★firecat推荐★)  typedef detail::task_io_service<detail::epoll_reactor<false> > impl_type;
Boost ASIO攻破!!!(★firecat推荐★)#elif defined(BOOST_ASIO_HAS_KQUEUE)
Boost ASIO攻破!!!(★firecat推荐★)  typedef detail::task_io_service<detail::kqueue_reactor<false> > impl_type;
Boost ASIO攻破!!!(★firecat推荐★)#elif defined(BOOST_ASIO_HAS_DEV_POLL)
Boost ASIO攻破!!!(★firecat推荐★)  typedef detail::task_io_service<detail::dev_poll_reactor<false> > impl_type;
Boost ASIO攻破!!!(★firecat推荐★)#else
Boost ASIO攻破!!!(★firecat推荐★)  typedef detail::task_io_service<detail::select_reactor<false> > impl_type;
Boost ASIO攻破!!!(★firecat推荐★)#endif
Boost ASIO攻破!!!(★firecat推荐★)
这部分代码其实就在boost::asio::io_service类声明中的最前面几行,可以看见在不同平台下,io_service类的实现将会不同。很显然,windows平台下当然是win_iocp_io_service类为实现了(不过我一开始还以为win_iocp_io_service是直接拿出来用的呢,还在疑惑这样怎么有移植性呢?官方文档也对该类只字不提,其实我卡壳就是卡在这里了,差点就直接用这个类了^_^!)。

那么就分析一下win_iocp_io_service的代码(\boost\asio\detail\win_iocp_io_service.hpp和\boost\asio\detail\impl\win_iocp_io_service.ipp)吧,这里完全是用IOCP来路由各种任务,大家使用post来委托任务,内部调用的其实是IOCP的PostQueuedCompletionStatus函数,然后线程们用run来接受任务,内部其实是阻塞在IOCP的GetQueuedCompletionStatus函数上,一旦有了任务就立即返回,执行完后再一个循环,继续阻塞在这里等待下一个任务的到来,这种设计思想堪称神奇,对线程、服务以及任务完全解耦,灵活度达到了如此高度,不愧为boost库的东西!我只能有拜的份了...

说一下总体的设计思想,其实io_service就像是劳工中介所,而一个线程就是一个劳工,而调用post的模块相当于富人们,他们去中介所委托任务,而劳工们就听候中介所的调遣去执行这些任务,任务的内容就写在富人们给你的handler上,也就是函数指针,指针指向具体实现就是任务的实质内容。其实在整个过程中,富人们都不知道是哪个劳工帮他们做的工作,只知道是中介所负责完成这些就可以了。这使得逻辑上的耦合降到了最低。不过这样的比喻也有个不恰当的地方,如果硬要这样比喻的话,我只能说:其实劳工里面也有很多富人的^o^! 。很多劳工在完成任务的过程中自己也托给中介所一些任务,然后这些任务很可能还是自己去完成。这也难怪,运行代码的总是这些线程,那么调用post的肯定也会有这些线程了,不过不管怎么说,如此循环往复可以解决问题就行,比喻不见得就得恰当,任何事物之间都不可能完全相同,只要能阐述思想就行。

最后还要说明的一点就是:委托的任务其实可以设定执行的时间的,很不错的设定,内部实现则是通过定时器原理,GetQueuedCompletionStatus有一个等待时间的参数似乎被用在这方面,还有源码中的定时器线程我并没有过多的去理解,总之大体原理已基本掌握,剩下的就是使劲的用它了!!!

另外为了方便人交流,在这里插入一些代码可能更容易让人理解吧,
下面这个是启动服务时的代码:
Boost ASIO攻破!!!(★firecat推荐★)void ServerFramework::run()
Boost ASIO攻破!!!(★firecat推荐★){
Boost ASIO攻破!!!(★firecat推荐★)    boost::thread_group workers;
Boost ASIO攻破!!!(★firecat推荐★)    for (uint32 i = 0; i < mWorkerCount; ++i)
Boost ASIO攻破!!!(★firecat推荐★)        workers.create_thread(
Boost ASIO攻破!!!(★firecat推荐★)            boost::bind(&boost::asio::io_service::run, &mIoService));
Boost ASIO攻破!!!(★firecat推荐★)    workers.join_all();
Boost ASIO攻破!!!(★firecat推荐★)}

在打开前就得分配好任务,否则线程们运行起来就退出了,阻塞不住,任务的分配就交给open函数了,它是分配了监听端口的任务,一旦有了连接就会抛出一个任务,其中一个线程就会开始行动啦。
Boost ASIO攻破!!!(★firecat推荐★)void ServerFramework::open(const String& address, const String& port, uint32 nWorkers /*= DEFAULT_WORKER_COUNT*/)
Boost ASIO攻破!!!(★firecat推荐★){
Boost ASIO攻破!!!(★firecat推荐★)    // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
Boost ASIO攻破!!!(★firecat推荐★)
    boost::asio::ip::tcp::resolver resolver(mIoService);
Boost ASIO攻破!!!(★firecat推荐★)    boost::asio::ip::tcp::resolver::query query(address, port);
Boost ASIO攻破!!!(★firecat推荐★)    boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
Boost ASIO攻破!!!(★firecat推荐★)
Boost ASIO攻破!!!(★firecat推荐★)    mAcceptor.open(endpoint.protocol());
Boost ASIO攻破!!!(★firecat推荐★)    mAcceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
Boost ASIO攻破!!!(★firecat推荐★)    mAcceptor.bind(endpoint);
Boost ASIO攻破!!!(★firecat推荐★)    mAcceptor.listen();
Boost ASIO攻破!!!(★firecat推荐★)
Boost ASIO攻破!!!(★firecat推荐★)    mNextConnection = new Connection(this);
Boost ASIO攻破!!!(★firecat推荐★)    mAcceptor.async_accept(mNextConnection->getSocket(),
Boost ASIO攻破!!!(★firecat推荐★)        boost::bind(&ServerFramework::__onConnect, this,
Boost ASIO攻破!!!(★firecat推荐★)        boost::asio::placeholders::error));
Boost ASIO攻破!!!(★firecat推荐★)
Boost ASIO攻破!!!(★firecat推荐★)    mWorkerCount = nWorkers;
Boost ASIO攻破!!!(★firecat推荐★)    if (mWorkerCount == DEFAULT_WORKER_COUNT)
Boost ASIO攻破!!!(★firecat推荐★)    {
Boost ASIO攻破!!!(★firecat推荐★)        mWorkerCount = 4;
Boost ASIO攻破!!!(★firecat推荐★)    }

Boost ASIO攻破!!!(★firecat推荐★)}

open函数中给io_service的一个任务就是在有链接访问服务器端口的情况下执行ServerFramework::__onConnect函数,有一点需要格外注意的,io_service必须时刻都有任务存在,否则线程io_service::run函数将返回,于是线程都会结束并销毁,程序将退出,所以,你必须保证无论何时都有任务存在,这样线程们即使空闲了也还是会继续等待,不会销毁。所以,我在ServerFramework::__onConnect函数中又一次给了io_service相同的任务,即:继续监听端口,有链接了还是调用ServerFramework::__onConnect函数。如果你在ServerFramework::__onConnect执行完了还没有给io_service任务的话,那么一切都晚了...... 代码如下:
Boost ASIO攻破!!!(★firecat推荐★)void ServerFramework::__onConnect(const BoostSysErr& e)
Boost ASIO攻破!!!(★firecat推荐★){
Boost ASIO攻破!!!(★firecat推荐★)    if (e)
Boost ASIO攻破!!!(★firecat推荐★)    {
Boost ASIO攻破!!!(★firecat推荐★)        MOELOG_DETAIL_WARN(e.message().c_str());
Boost ASIO攻破!!!(★firecat推荐★)    }

Boost ASIO攻破!!!(★firecat推荐★)
Boost ASIO攻破!!!(★firecat推荐★)    Connection* p = mNextConnection;
Boost ASIO攻破!!!(★firecat推荐★)    mNextConnection = new Connection(this);
Boost ASIO攻破!!!(★firecat推荐★)
Boost ASIO攻破!!!(★firecat推荐★)    // 再次进入监听状态
Boost ASIO攻破!!!(★firecat推荐★)
    mAcceptor.async_accept(mNextConnection->getSocket(),
Boost ASIO攻破!!!(★firecat推荐★)        boost::bind(&ServerFramework::__onConnect, this,
Boost ASIO攻破!!!(★firecat推荐★)        boost::asio::placeholders::error));
Boost ASIO攻破!!!(★firecat推荐★)
Boost ASIO攻破!!!(★firecat推荐★)    // 处理当前链接
Boost ASIO攻破!!!(★firecat推荐★)
    __addConnection(p);
Boost ASIO攻破!!!(★firecat推荐★)    p->start();
Boost ASIO攻破!!!(★firecat推荐★)}

最后,展示一下这个类的所有成员变量吧:
Boost ASIO攻破!!!(★firecat推荐★)    // 用于线程池异步处理的核心对象
Boost ASIO攻破!!!(★firecat推荐★)
    boost::asio::io_service mIoService;
Boost ASIO攻破!!!(★firecat推荐★)
Boost ASIO攻破!!!(★firecat推荐★)    // 网络链接的接收器,用于接收请求进入的链接
Boost ASIO攻破!!!(★firecat推荐★)
    boost::asio::ip::tcp::acceptor mAcceptor;
Boost ASIO攻破!!!(★firecat推荐★)
Boost ASIO攻破!!!(★firecat推荐★)    // 指向下一个将要被使用的链接对象
Boost ASIO攻破!!!(★firecat推荐★)
    Connection* mNextConnection;
Boost ASIO攻破!!!(★firecat推荐★)
Boost ASIO攻破!!!(★firecat推荐★)    // 存储服务器链接对象的容器
Boost ASIO攻破!!!(★firecat推荐★)
    ConnectionSet mConnections;
Boost ASIO攻破!!!(★firecat推荐★)
Boost ASIO攻破!!!(★firecat推荐★)    //// 为链接对象容器准备的strand,防止并行调用mConnections
Boost ASIO攻破!!!(★firecat推荐★)    //boost::asio::io_service::strand mStrand_mConnections;
Boost ASIO攻破!!!(★firecat推荐★)
Boost ASIO攻破!!!(★firecat推荐★)    
// 为链接对象容器准备的同步锁,防止并行调用mConnections
Boost ASIO攻破!!!(★firecat推荐★)
    boost::mutex mMutex4ConnSet;
Boost ASIO攻破!!!(★firecat推荐★)
Boost ASIO攻破!!!(★firecat推荐★)    // 为控制台输出流准备的strand,防止并行调用std::cout
Boost ASIO攻破!!!(★firecat推荐★)
    AsioService::strand mStrand_ConsoleIostream;
Boost ASIO攻破!!!(★firecat推荐★)
Boost ASIO攻破!!!(★firecat推荐★)    // 工作线程的数量
Boost ASIO攻破!!!(★firecat推荐★)
    uint32 mWorkerCount;


但愿这篇随笔也能对正在研究asio的朋友们有所帮助吧。