BOOST::ASIO

原文http://powman.org/archives/category/teo/cpp

BOOST::ASIO为我们提供了两种I/O机制,分别是同步和异步。它可在多平台上移植,在不同系统上采用了不同的实现。ASIO为我们封装了一系列的socket api,同时为我们提供了一套相当完善的服务器编程体系,部分概念(关键词)如:io队列、基于回调的异步通知、定时器、自定义的多线程机制。

假设大家已经安装好了boost,并有一些C++基础,针对TCP服务器编程,我们有如下需求:
1.一个可用于接受新连接的类
2.提供基本的send/recv io操作
3.解决多线程下的资源竞争问题
4.管理连接的生命周期
5.为不同的网络事件提供回调方法

针对以上需求,先来设计一个简单的框架。我们分为TCPServer, Acceptor, Connection三个主要的类。它们的功能分别如下:
TCPServer : 对用户可见,用于启动服务,终止服务。一个服务需要一个TCPServer对象。
Acceptor : 对用户不可见。用于监听并接受新的客户端连接。有新连接到来之后通知TCPServer,由TCPServer告知用户。
Connection : 对用户可见。封装了对客户端连接的一个实体,每个Connection对象表示与客户端的一个连接。由智能指针管理生命周期。

时序图如下:

如上图所示,客户端先发起一个连接,Acceptor会接受该连接,然后通过回调告知TCPServer,再由TCPServer通知用户(这里指TCPServer的上层使用者)有一个新的客户端接入了。那么,Acceptor应该通过什么方式去告知TCPServer,而用户又通过什么样的方式去获得这些通知呢?我们来画一个服务启动时的时序图:

首先,User角色代表的也就是我们说的应用层用户,它先向TCPServer注册一些事件,而这些注册的事件,就是我们需要回调的方法。然后在启动Acceptor的时候,同时向Acceptor注册它所关心的事件,当Acceptor接受到新的连接时,它也会同时向新创建的Connection对象注册它所关心的事件。

这里所说的“注册事件”实质上是指设置一个函数指针。前段时间曾经在muduo的相关文章中看到陈硕提出了使用bind/functional方式来实现服务器网络事件的回调工作,我们不再需要和过去我们熟知的那种方式去继承某个类然后实现其接口,这种做法无疑会增加代码编写的灵活性,但他的文中同时提出的“继承是个坑”,我感觉这是一种比较情绪化的说法,不应该去拘泥哪种做法,个人建议根据具体场景去设计即可。

服务器的设计如上所述,下面将从ASIO的io_service说起,一步步去完善一个小型的服务端程序,并会逐一去解决需求中尚未实现的几条。

ASIO服务器编程系列1:io_service的工作机制与跨平台策略

以当前最新版本的boost为例(1.53.0,之后的代码也是以1.53.0为准)。
ASIO Examples:http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/examples.html

io_service在ASIO中有着举足轻重的地位置如果对io_service的实现不感兴趣可以选择性无视这篇文章。在开始说io_service之前,先介绍一下ASIO给我们提供的可用于TCP服务器开发的组件。

boost::asio::ip::tcp::acceptor  用于监听与接受连接的类。
boost::asio::ip::tcp::socket  套接字,提供同步/异步IO方法,一个socket对象面向一个客户端连接。
boost::asio::deadline_timer  计时器,提供同步/异步计时功能并在计时结束之后回调到一个函数。

io_service的工作方式

把io_service想象为一个Worker Pool,也可以把它当作一个IO队列,他是一个联系用户和ASIO核心实现的类。如果你看过ASIO的例子或者用过ASIO,在构造上面表格中的对象时,往往需要给它们传递一个io_service对象,因为io_service担任的是一个Worker Pool的作用,这些组件的任何IO操作都会通过io_service.post()方法去投递一个任务。

为了更直观地看到io_service的作用,来一段异步定时器(deadline_timer)工作的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include   //for std::bind()
#include
 
int main()
{
     void on_expired();
 
     boost::asio::io_service io_service;
     boost::asio::deadline_timer timer(io_service);
     timer.expires_from_now(boost::posix_time::milliseconds(3000));
     timer.async_wait(boost::bind(on_expired, boost::asio::placeholders::error));
     io_service.run();
 
     return 0;
}
 
void on_expired( const boost:: system ::error_code& error)
{
     if (!error)
     {
         std::cout << "timer expired." << std::endl;
     }
}

上面代码演示了一个异步定时器的工作流程:
1.定义一个io_service对象。
2.定义一个deadline_timer对象,并把io_service对象作为唯一参数传递给它。
3.使用expires_from_now()方法设置一个以当前时间为起始点的超时时间。
4.调用deadline_timer的async_wait()方法使定时器进入等待。
5.调用io_service的run()方法。

第4步调用async_wait()方法之后,deadline_timer的内部就做了一些和io_service打交道的事,它实则是向io_service提交了一个异步任务,io_service会把它放到队列里。第5步我们调用run()方法时,io_service就开始了它的工作,并把队列中的任务进行处理。

io_service用例图:

这个用例图展示了io_service的工作方式:
1.用户的应用程序提交一个操作。
2.操作转发给io_service,告诉它我有一个异步操作可能要执行,此时io_service便会把该操作放入一个队列里。
3.调用run()方法启动io_service。
4.io_service启动后,会把队列中的操作提交给操作系统,相当于一个联系了用户和操作系统的媒介。
5.操作系统把执行结果反馈给io_service,io_service会根据结果构造一个boost::system::error_code对象并回调给用户,通过该对象可获知所投递操作的执行结果。

流程很简单,从我们的应用程序逐层向下,完成之后再逐层向上反馈。但是,我们看到的其实只是冰山一角。ASIO是一个网络库,除了给我们提供了定时器这类开发组件之外,更重要的是它的网络通信功能。ASIO提供的网络通信功能,不仅封装了操作系统提供的socket api,同时也用自己的思想把不同平台的网络通信模型封装了起来。下面我们来深入io_service,揭开其面纱。

io_service的跨平台策略

考虑到在多平台下的实现细节,io_service可谓是做了不少功夫。要根据不同平台提供的api去设计一个统一的框架提供给用户,而且用户不必关心其实现细节,除了工程庞大之外,也必须要有一个良好的框架。我们从io_service的源码入手,探讨它如何去适应不同平台的实现。(推荐用Source Insight, Sublime Text 2或VC编辑器阅读源码)。

由io_service.run()开始,先转到run()的定义:

1
std:: size_t s = impl_.run(ec);

该方法实则上是调用了impl_对象的run()方法,看起来是io_service在impl_的基础上封装了一层,impl_是io_service的成员,不难看出,impl_担任了一个实现功能细节的角色。在io_service类的声明中,找到impl_的声明:

1
2
// The implementation.
impl_type& impl_;

然后是impl_type类型的定义:

1
typedef detail::io_service_impl impl_type;

原来impl_type的原本类型是detail::io_service_impl,实质上它是由宏来决定其最终类型。转到定义可以发现:

1
2
3
4
5
#if defined(BOOST_ASIO_HAS_IOCP)
namespace detail { typedef win_iocp_io_service io_service_impl; }
#else
namespace detail { typedef task_io_service io_service_impl; }
#endif

上面代码很清晰可以看到,如果定义了BOOST_ASIO_HAS_IOCP这个宏,那么io_service_impl的类型就是win_iocp_io_service,否则就是task_io_service. 如此说来,io_service_impl这个类型完全是由BOOST_ASIO_HAS_IOCP宏来决定的。跟踪一下这个宏,看它如何定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Windows: IO Completion Ports.
#if defined(BOOST_WINDOWS) || defined(__CYGWIN__)
# if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0400)
#  if !defined(UNDER_CE)
#   if !defined(BOOST_ASIO_DISABLE_IOCP)
#    define BOOST_ASIO_HAS_IOCP 1
#   endif // !defined(BOOST_ASIO_DISABLE_IOCP)
#  endif // !defined(UNDER_CE)
# endif // defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0400)
#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
 
// Linux: epoll, eventfd and timerfd.
#if defined(__linux__)
# include
# if !defined(BOOST_ASIO_DISABLE_EPOLL)
#  if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,45)
#   define BOOST_ASIO_HAS_EPOLL 1
#  endif // LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,45)
# endif // !defined(BOOST_ASIO_DISABLE_EVENTFD)
# if !defined(BOOST_ASIO_DISABLE_EVENTFD)
#  if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
#   define BOOST_ASIO_HAS_EVENTFD 1
#  endif // LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
# endif // !defined(BOOST_ASIO_DISABLE_EVENTFD)
# if defined(BOOST_ASIO_HAS_EPOLL)
#  if (__GLIBC__ > 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 8)
#   define BOOST_ASIO_HAS_TIMERFD 1
#  endif // (__GLIBC__ > 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 8)
# endif // defined(BOOST_ASIO_HAS_EPOLL)
#endif // defined(__linux__)

如果在Windows或者CYGWIN环境并且IOCP未被禁用的情况下,那么ASIO的网络模型就会被定义为IOCP;而在linux下,则有epoll, eventfd, timerfd三种情况,但结合io_service_impl的定义,可以发现除了BOOST_ASIO_HAS_IOCP在打开时io_service_impl被定义为win_iocp_io_service之外,其它情况都是task_io_service. 因此可以把ASIO归纳为只有两种实现,分别是win_iocp_io_service和task_io_service. 而在io_service中impl_成员的最终类型,也只有这两种。无论采用了哪种模型,io_service暴露的方法都一样,他们的具体实现都必须转给impl_去做。

这一章简要说了一下io_service的组成和工作机制,接着将介绍io_service的Proactor框架实现和io_service的service管理。因为这一章的内容和接下来要说的内容关联不算太大,而且说到Proactor实现部分比较长。其实下一章内容写的差不多了,不过还要写几天再发,先发这篇短的。


你可能感兴趣的:(STL/BOOST学习)