boost.asio

boost.asio为异步IO提供了一份标准的C++的跨平台实现,特别针对网络IO提供了良好的支持,使之成为C++网络编程利器。关于如何使用asio,boost文档中已经有了详尽说明,而且附带的例子也很直观,我们不必再造轮子;本文则结合asio的基本应用,侧重于源代码的分析,特别是针对windows平台上的实现进行分析。 

纵观asio源码,在统一的接口层之下,asio提供了大量的类来支持不同的平台(Windows、Unix...)、不同的IO类型(同步、异步)及IO模型(IOCP、Select、Poll)及网络协议(TCP,UDP,ICMP)。归纳起来,这一大堆类可以分为三层,分别是:

  • IO Object层
  • basic_ 模版层
  • 服务实现层

 本系列文章从io_service类型入手,开启代码分析行程,采用自底向上的方法,逐层推导,最后得到asio的体系结构;如果想先了解asio的整体结构,可以先跳到asio体系结构部分。

(理论上来说,行文伊始还是该先举个简单的例子说一下asio如何使用,不过这样的例子少说也要六七十行代码,为节约篇幅就不贴了;真想先看一个完整例子的,烦请移步下面链接瞧下那只麻雀:http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/tutorial/tutdaytime3/src.html

·io_service类  

从第一个boost.asio的教程开始,boost文档就一直在告诉我们:使用boost.asio第一步就是要创建一个io_service对象。那么io_service是个什么东西呢?

 

boost.asio文档说,io_service为下面的这些异步IO对象提供最核心的IO功能:

  • boost::asio::ip::tcp::socket
  • boost::asio::ip::tcp::acceptor
  • boost::asio::ip::udp::socket
  • deadline_timer.

 

接着,文档就会说,像下面这样,就可以简简单单声明一个io_service对象了:

int main()

{

  boost::asio::io_service io;

 

上面的一行代码,表明无限地风光与潇洒,看起来简简单单一行,就几乎有了异步IO、网络操作的框架,那么他到底代表了什么,在后面,又有什么事情发生呢?io_service又是如何支撑起这些功能的呢?带着这些问题,我们开始分析吧……

 

C++的角度看,上面的这一行代码,无非就是定义了一个io_service的实例,而C++的底层机制所隐藏起来的东西,无非就是初始化该对象的所有数据成员。再来看io_service的声明,我们知道,该类除了一堆成员函数之外,事实上只有三个成员(暗想:这几个成员肯定很是神奇无匹了):

 

#if defined(BOOST_WINDOWS) || defined(__CYGWIN__)

  detail::winsock_init<> init_;

#elif defined(__sun) || defined(__QNX__) || defined(__hpux) || defined(_AIX) \

  || defined(__osf__)

  detail::signal_init<> init_;

#endif 

 

  // The service registry.

  boost::asio::detail::service_registry* service_registry_;

 

  // The implementation.

  impl_type& impl_;

 

暂时抛开那几个成员,再来看一下io_service比较重要的一个函数:run() 的实现,发现该函数也就是将真正的功能委托给成员impl_去做事儿了:

std::size_t io_service::run()

{

  boost::system::error_code ec;

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

  boost::asio::detail::throw_error(ec);

  return s;

}

 

种种迹象表明,impl_是个巨牛的东西了。统揽io_service的实现,我们不难发现,该类的所有功能,几乎都是委托给了impl_成员去干了——典型的“有事秘书干”。不过想想也挺容易理解的,io_service提供了一个上层的接口,充当抛头露面的BOSS,真正的工作则委托给下一层的员工去实现——哪家公司不是这样呢?

 

所以,要想了解io_service的玄机,就需要弄清楚这三个数据成员到底是什么来历,特别是impl_的来历才行。

io_service的初始化

成员 init_ io_service的各个函数中,并没有显式用到。其存在的价值,就在于该类的构造函数里面,调用了初始化相关的代码,具体到Windows平台,就是WinSock的初始化,也就是调用 ::WSAStartup() 这一WinSock编程所必须调用的第一个函数;而其析构函数则进行清理工作,同样交由WinSock函数 :: WSACleanup() 完成。

 

也就是说,io_service通过init_数据成员的创建与销毁,自动完成了相关的初始化及清理工作。

io_service的实现委托

前面说过,impl_ 带着无限的神秘默默地完成了io_service::run()的功能。至于他到底是什么style,现在来揭开盖头吧。

 

从直接声明来看,impl_ 具有 impl_type& 类型。用SourceInsight不难发现该类型只不过是一个类型别名:

 

typedef detail::io_service_impl impl_type;

 

一波未平一波又起,这儿又冒出个io_service_impl。继续刨根问底,找到:

 

       #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

 

终于知道,在某些情况下,它是 win_iocp_ ,在某些情况下,是 task_ 。事实上,在Win NT 环境下,如果没有禁用IOCP,也就是没有声明 BOOST_ASIO_DISABLE_IOCP 这个预处理器指令,那么asio就采用 win_iocp_io_service 来做那些脏活累活;在剩下的其他平台,或者Win上禁用了IOCP,则使用 task_io_service 来做事儿了。

 

先稍微提一下,win_iocp_io_service 是对Windows环境下的IOCP(完成端口IO)模型的封装,该类作为boost.asioWindows下的核心,我们在后面详细分析其实现。

io_service的服务管理

io_service的另外一个数据成员 service_registry_,又具备什么样的身份和功能呢?我们来看看 io_service 的构造函数:

io_service::io_service()

  : service_registry_

    (

       new boost::asio::detail::service_registry(

              *this,

              static_cast<impl_type*>(0),

              (std::numeric_limits<std::size_t>::max)()

       )

    ),

    impl_(service_registry_->first_service<impl_type>())

{

}

 

构造函数为了初始化service_registry_,动态分配了一个boost::asio::detail:: service_registry类对象。为了构造该对象,提供了三个参数:

  • *this 这个是io_service对象本身,作为所有服务的owner存在。
  • static_cast<impl_type*>(0),一个空指针对象,主要是为了模版参数类型推导。在service_registry的构造过程中,也会自动创建一个该类型的对象,作为第一个service对象。具体到Win平台,即为构造一个 win_iocp_io_service 对象。
  • (std::numeric_limits<std::size_t>::max)():提供一个最大的整数作为service构造时的参数——目前还没看到这个参数的用途。

 

再来观察service_registry的实现,发现其实际就是一个链表,管理io_service所容纳的所有service对象(win_iocp_io_service就是一种service)。每种service都有一个id,链表以此id作为标志,在客户通过io_service来请求一种服务时,例如调用 use_service<ServiceType>(io_service&) 时,service_registry会查找链表,如果有对应类型的服务,就返回该类型服务实例的指针;否则就创建一个新的对象,并加入到链表末端,再返回此新创建的实例;通过这种形式,io_service确保每种类型的服务都只有一个实例存在。

 

asio提供了这样几个函数,来进行service的管理和使用——这也为扩展asio提供了可能,例如可以自己定义一种服务,使用add_service加入io_service进行管理。

  • template <typename Service>

Service& use_service(io_service& ios);

 

  • template <typename Service>

void add_service(io_service& ios, Service* svc);

 

  • template <typename Service>

bool has_service(io_service& ios);

io_service::service类型及跨平台策略

 

 

 

 

 

boost::asio::io_service

 

 


+ size_t run()
+ size_t poll()
+ void    dispatch()
+ void    stop()

- detail::winsock_init<> init_
- detail::service_registry* service_registry_
- impl_type& impl_

class boost::asio::io_service::::id

 

 

 

 

 

class boost::asio::io_service::work

 

 

 

 

 

enum boost::asio::io_service::fork_event

 

 

 

 

 

class boost::asio::io_service::strand

 

 

 

 

 

class boost::asio::io_service::service
- struct key key_
- boost::asio::io_service& owner_;
- service* next_;

 

 

 

 

 

 

io_service内部定义了这样一些类型,来为其服务:

  • io_service::id,所有service具体类的标识
  • io_service::service,所有service具体类的基类
  • io_service::strand,对所有并发提交的hanlder串行化执行
  • io_service::work,通知io_service有事儿要干的类,有点令人讨厌的主儿
  • io_service::fork_eventfork事件通知

 

针对service类型,asio从其派生出了数十个类分别完成不同的功能,例如在Win上充当io_servicewin_iocp_io_service类,以及为各种IO Object类型提供服务的类,如对应于TCPstream_socket_service,对应于UDPdatagram_socket_ service。下图显示了asio常用到的服务类,及其针对不同平台的适配类之间的关系。

 

boost.asio 学习笔记02——io_service类 - 地线 - 别再让虚假消息充斥这本已混乱的世界
 

 图中左边的类,会在我们的应用程序中直接用到(由于asio又对这些类提供了一层动态组装,所以代码中不会去直接声明这些类型的实例,但是剥掉动态组装的外衣,我们声明的仍然是这些类的实例,具体在下面一部分说明),作为 应用层 的类;而右边部分,则是针对不同平台所提供的不同实现,作为 平台适配层

 

应用层类在编译时,根据所在平台(其实是喂给编译器的各种预处理宏,如BOOST_ASIO_ HAS_IOCP),选择对应的类进行编译。例如用于TCP的服务类stream_socket_service是这样进行选择的:

     template <typename Protocol>

class stream_socket_service

{

private:

  // The type of the platform-specific implementation.

#if defined(BOOST_ASIO_HAS_IOCP)

  typedef detail::win_iocp_socket_service<Protocol> service_impl_type;

#else

  typedef detail::reactive_socket_service<Protocol> service_impl_type;

#endif

 

// The platform-specific implementation.

  service_impl_type service_impl_;

};

 

其他需要进行平台决策的类型,都是采用这种技术,来选择不同的实现的。

 

io_service::service派生的完整的类列表如下:

  • boost::asio::detail::win_iocp_io_service
  • boost::asio::detail::task_io_service
  • boost::asio::detail::select_reactor
  • boost::asio::detail::epoll_reactor
  • boost::asio::detail::dev_poll_reactor
  • boost::asio::detail::kqueue_reactor

 

  • boost::asio::ip::resolver_service
  • boost::asio::socket_acceptor_service

 

  • boost::asio::stream_socket_service
  • boost::asio::datagram_socket_service
  • boost::asio::raw_socket_service
  • boost::asio::seq_packet_socket_service

 

  • boost::asio::serial_port_service

 

  • boost::asio::signal_set_service

 

  • boost::asio::deadline_timer_service
  • boost::asio::waitable_timer_service

 

  • boost::asio::detail::strand_service

 

  • boost::asio::posix::stream_descriptor_service

 

  • boost::asio::ssl::old::context_service
  • boost::asio::ssl::old::stream_service
  • boost::asio::ssl::old::detail::openssl_context_service
  • boost::asio::ssl::old::detail::openssl_stream_service

 

  • boost::asio::windows::object_handle_service
  • boost::asio::windows::random_access_handle_service
  • boost::asio::windows::stream_handle_service

 

还有几个非常重要的类,他们作为劳苦大众在金字塔底层默默提供service功能,但却没有从io_service::service派生;他们是上述那些服务类在各个平台的具体实现,为金字塔中间层的服务类提供再服务的(不难想象,提供服务的方式,又是那种“有事儿秘书干”的方式):

  • boost::asio::detail::win_iocp_socket_service
  • boost::asio::detail::win_iocp_handle_service
  • boost::asio::detail::reactive_descriptor_service
  • boost::asio::detail::reactive_serial_port_service
  • boost::asio::detail::reactive_socket_service
  • boost::asio::detail::win_iocp_serial_port_service
  • boost::asio::detail::win_object_handle_service

·io objects  

asio的文档,告诉我们在声明一个io_service对象之后,就可以创建io对象去干活了,例如:

int main(int argc, char* argv[])

{

    boost::asio::io_service io_service;

 

    tcp::resolver resolver(io_service);

    tcp::resolver::query query("www.boost.org", "80");

    tcp::resolver::iterator iterator = resolver.resolve(query);

 

 

上图中main()的第二行代码声明了一个tcp::resolver对象,后续进行地址解析的逻辑,都是围绕此resolver对象展开的。那么这个resolver是一个什么样的类型呢?阅读源代码我们在boost::asio::ip::tcp类内部看到了这样的类型别名定义:

    

typedef basic_resolver<tcp> resolver;

 

是否似曾相识呢? 是的,这和我们STL中的string, iostream等一样,使用的都是某个模板类的一个实例。这里的resolverbasic_resolvertcp模版参数下的实例,stringbasic_stringchar模版参数下的实例——boost库和STL库统一风格的一个体现。

 

asio大量采用这种技术,所有和resolver一样提供io功能的类,都是某个baisc_ 模版类的实例化。下面我们来研究一下asioio object逻辑。

 

io object 类关系网

继续向上追溯basic_resolver,知道该类从basic_io_object派生,主要负责地址解析相关的操作,提供的主要接口有resolver(), async_resolve()等。

 

查看整个asio的源代码,我们发现从basic_io_object派生的类不少,他们分别负责一些具体的事务,例如basic_socket_acceptor可以作为一个服务器进行侦听,提供了诸如bind(), listen()等接口;再如basic_socket类是对socket IO 操作的封装,提供了receive(), async_receive(), read_some(), async_readsome(), write_some(), async_write_some()等接口。

 

整个asio中的io object关系网,我们用下图来显示:

 

boost.asio学习笔记03——io objects - 地线 - 别再让虚假消息充斥这本已混乱的世界

 

这些io object的功能,简述如下:

  • basic_deadline_timer 提供定时器功能,可以同步等待,也可以异步等待。
  • basic_waitable_timer basic_deadline_timer具有同样的功能,主要区别为该定时器可以和C++ 11中引入的chrono库协作。
  • basic_signal_set 支持信号相关的操作,异步方式等待单个或者多个信号的发生。
  • basic_socket_acceptor 作为服务器进行侦听,接收连接请求。
  • basic_serial_port 对串口操作进行支持。
  • basic_resolver 地址解析类。
  • basic_stream_socket 提供同步、异步方式的基于流的socket操作。
  • basic_datagram_socket 提供同步、异步方式的基于数据报文的socket操作。
  • basic_raw_socket提供同步、异步方式的基于raw数据的socket操作
  • basic_seq_packet_socket提供同步、异步方式的基于有序包的socket操作
  • basic_socket_streambuf ?
  • basic_object_handle windows handle的封装,可以以异步或者同步方式等待
  • basic_random_access_handlewindows 可随机访问的handle的封装,可以以异步或者同步方式等待
  • basic_stream_handlewindows面向流handle的封装,可以以异步或者同步方式等待
  • basic_descriptor POSIX描述符进行封装。

 

另外一点,所有这些io object的构造函数,都要求有一个io_service& 作为参数,使用这一参数,这些io_object对象知道了自己的归属,之后自己所要派发出去的同步、异步操作请求,都将通过自己所在的这个io_service对象来完成。这也就说明了,为什么创建io_service对象是整个asio程序的第一步。

 

io object服务委托

上述的这些io object类,提供了应用开发中常用的各种“实际”功能,例如地址解析,以及socket读写等,那么这些io object类和第一部分中的service类之间存在着什么样的关系呢? 会不会是这些io object只是一个应用的接口,而具体的功能则委托给service类来完成呢? 如果是这样的,那么又是如何实现的呢?

 

带着这些问题,我们继续研究resolver类的源代码,看看他所提供的功能,到底是如何实现的。

resolver是如何委托的

我们知道,resolver类是在boost::asio::ip::tcp类中的一个类型别名:

 

     typedef basic_resolver<tcp> resolver;

 

那么,resolver类所提供的async_resolve()接口,就来自于basic_resolver类。再来看看basic_resolver::async_resolve()的实现——该函数有两个重载,我们以其中一个为例:

  template <typename ResolveHandler>

  void async_resolve(const query& q, BOOST_ASIO_MOVE_ARG(ResolveHandler) handler)

  {

    // If you get an error on the following line it means that your handler does

    // not meet the documented type requirements for a ResolveHandler.

    BOOST_ASIO_RESOLVE_HANDLER_CHECK(

        ResolveHandler, handler, iterator) type_check;

 

    return this->service.async_resolve(this->implementation, q,

        BOOST_ASIO_MOVE_CAST(ResolveHandler)(handler));

  }

 

可见,async_resolve()果然是委托给了某个service类来做事的——再一次,典型的有事儿秘书干。那么,这儿的“this->service 又是什么呢?

 

通过跟踪代码的执行,知道该service其实是第一部分曾经提到过的boost::asio::ip::resolver_service,而它又委托给了boost::asio::detail::resolver_service——这家伙再无其他可以委托的对象了,只有苦逼的自己做事儿了——其基本思路就是先创建一个用以地址解析的resolve_op,用这个op来代表本次异步操作,之后启动op去做事情。至于op又是什么东西,稍后在operation部分做介绍;先贴出这段苦主:

  // boost::asio::detail::resolver_service

  template <typename Handler>

  void  async_resolve(implementation_type& impl,

                      const query_type& query, Handler handler)

  {

    // Allocate and construct an operation to wrap the handler.

    typedef resolve_op<Protocol, Handler> op;

    typename op::ptr p = { boost::addressof(handler),

      boost_asio_handler_alloc_helpers::allocate(

        sizeof(op), handler), 0 };

    p.p = new (p.v) op(impl, query, io_service_impl_, handler);

 

    BOOST_ASIO_HANDLER_CREATION((p.p, "resolver", &impl, "async_resolve"));

 

    start_resolve_op(p.p);

    p.v = p.p = 0;

  }

PS

-   做过Symbian开发的对这种形式似曾相识,这多少和Active Object异曲同工

-   那几行创建op的代码还不是很明白

 

OK,至此,整个async_resolve()从上到下就拉通了。概括起来,就是io object将具体功能委托给服务类,服务类又委托给和平台实现相关的服务类来完成最后的功能。

io_object的服务创建

通过resolver的执行,我们知道了其层层委托关系,那么resolver所委托的this->service又是怎么来的呢?下面部分,我们来分析io_object所做的服务管理工作。

 

首先来看该service的具体类型。

 

要想知道service是如何创建的,我们就要追根朔源找到resolver的具体类型声明,看看到底是如何将service拉上贼船的。好吧,我们再次从头开始(别嫌啰嗦):

 

typedef basic_resolver<tcp> resolver;

 

针对这个模版实例,将basic_resolver展开:

 

template <typename InternetProtocol,

         typename ResolverService = resolver_service<InternetProtocol> >

class basic_resolver: public basic_io_object<ResolverService>

à

template <tcp,

         typename ResolverService = resolver_service<tcp> >

class basic_resolver: public basic_io_object< resolver_service<tcp> >

 

再对basic_io_object进行展开:

template <typename IoObjectService>

class basic_io_object

à

template < resolver_service<tcp> >

class basic_io_object

 

basic_io_object内部展开:

typedef IoObjectService service_type;

à

typedef resolver_service<tcp> service_type;

 

于是,可以确定basic_io_object::service的类型为 resolver_service<tcp>&从而将io object和下层的service关联起来。

 

再来看basic_io_object::service的初始化。我们来看其构造函数:

  explicit basic_io_object(boost::asio::io_service& io_service)

    : service(boost::asio::use_service<IoObjectService>(io_service))

  {

    service.construct(implementation);

  }

 

很明显,在构造过程中,使用use_service的返回值来初始化该service成员;我们知道,use_service会在io_service所维护的service链表中查找该类型,如果没有,就创建一个新的service实例;在此,就可以确保resolve_service<tcp>的实例已经被创建出来了,该服务就可以工作了(当然,不要忘记,该service事实上又委托了一层,而这个最底层的service实例,在此resolve_service<tcp>的构建过程中,内部再次调用use_service()创建出来了。过程如下:

boost.asio学习笔记03——io objects - 地线 - 别再让虚假消息充斥这本已混乱的世界

  

除此之外,在构造函数体中,调用了serviceconstruct函数,做进一步的初始化。(PS:是否有Symbian中的二段构造的影子呢?)

asio io逻辑总结

前面部分以resolver为例,分析了resolver的功能和对应service之间的关系,纵观所有的io object,都是采用了这种模式,总结起来有如下几点:

·           asio提供了多个basic_ 模版类。

·           应用层使用对应的basic_ 模版类的typedef的具体类,对外提供服务接口。

·           io object内部,将操作委托给底层服务类。

·           底层服务类再次将操作委托给平台实现层,完成实际的工作。

 

·asio的体系结构

三层类关系图

根据前面的分析,我们知道asio有着这样的逻辑:

  • 参考STL,提供basic模版,对外使用basic模版的实例提供接口。
  • basic模版将具体操作委托给下层服务类完成。
  • 下层服务类再把操作委托给平台相关的服务类。

 

鉴于此,我们将asio体系划分为三层:io object层,basic_ 模版类层,服务层。

  • 第一层:io object层,作为应用程序直接使用的对象,是各种basic_ 模版类的typedef实例类。
  • 第二层:basic_ 模版类层,提供对外操作的接口,并把具体操作转发给服务类。
  • 第三层:服务层。提供具体操作的底层实现,这一层又分为两层:
    • 操作接收层
    • 平台适配层

 

基本的体系结构关系如下图所示。 注意:图中并非全部asio中的类。

 

boost.asio 学习笔记04——asio的体系结构 - 地线 - 别再让虚假消息充斥这本已混乱的世界

 

动态组装                 

前面已经提过,resolverSTL中的string一样,都是使用了basic_模板类的一个具体实例。这种在编译时动态地选择对应的组件进行编译,我们姑且称为动态组装。这种技术使得我们可以对asioservice进行扩展。例如自己实现一个resolverservice类,然后告诉basic_resolver,你要使用自己的service类,而非默认的那个……

 

不过话说回来,自己对boost或者STL进行扩展,需要点实力的

·asio的windows实现  

Operation

还记得前面我们在分析resolver的实现的时候,挖了一个关于operation的坑?为了不让自己陷进去,现在来填吧;接下来我们就来看看asio中的各种operation

 

和前面提到过的service的类似,这里的operation也分为两大系:IOCP EnableDisable系列。这里我们重点关注下图中红色部分表示的IOCP Enable系列operation

boost.asio 学习笔记05——asio的windows实现 - 地线 - 别再让虚假消息充斥这本已混乱的世界

  

OVERLAPPED基类

从上图可以看到,所有IOCP Enableoperation,其基类都是struct OVERLAPPED结构,该结构是Win32进行交叠IO一个非常重要的结构,用以异步执行过程中的参数传递。所有的operation直接从该结构继承的结果,就是所有operation对象,可以直接作为OVERLAPPED结构在异步函数中进行传递。

 

例如在win_iocp_socket_service_base中,为了启动一个receive的异步操作, start_receive_op函数就直接把传递进来的operation指针作为OVERLAPPED结构传递给::WSARecv函数,从而发起一个异步服务请求。

void win_iocp_socket_service_base::start_receive_op(

    win_iocp_socket_service_base::base_implementation_type& impl,

    WSABUF* buffers, std::size_t buffer_count,

    socket_base::message_flags flags, bool noop, operation* op)

{

  update_cancellation_thread_id(impl);

  iocp_service_.work_started();

 

  if (noop)

    iocp_service_.on_completion(op);

  else if (!is_open(impl))

    iocp_service_.on_completion(op, boost::asio::error::bad_descriptor);

  else

  {

    DWORD bytes_transferred = 0;

    DWORD recv_flags = flags;

    int result = ::WSARecv(impl.socket_, buffers, static_cast<DWORD>(buffer_count),

        &bytes_transferred, &recv_flags, op, 0);

    DWORD last_error = ::WSAGetLastError();

    if (last_error == ERROR_NETNAME_DELETED)

      last_error = WSAECONNRESET;

    else if (last_error == ERROR_PORT_UNREACHABLE)

      last_error = WSAECONNREFUSED;

    if (result != 0 && last_error != WSA_IO_PENDING)

      iocp_service_.on_completion(op, last_error, bytes_transferred);

    else

      iocp_service_.on_pending(op);

  }

}

 

执行流程

关于operation对象的创建、传递,以及完成handler的执行序列等,使用下图可以清晰的描述。

boost.asio 学习笔记05——asio的windows实现 - 地线 - 别再让虚假消息充斥这本已混乱的世界

  

下表反映了Windows环境下,部分的异步请求所对应的服务、win函数、operation等信息:

 

异步请求

服务

start op

Win32函数

对应operation

ip::tcp::socket:: async_connect

win_iocp_socket_service

start_connect_op()

::connect

reactive_socket_connect_op

ip::tcp::socket:: async_read_some

start_receive_op()

::WSARecv

win_iocp_socket_recv_op

ip::tcp::socket:: async_receive

start_receive_op()

::WSARecv

win_iocp_socket_recv_op

ip::tcp::socket:: async_write_some

start_send_op()

::WSASend

win_iocp_socket_send_op

ip::tcp::socket:: async_send

start_send_op()

::WSASend

win_iocp_socket_send_op

ip::tcp::acceptor:: async_accept

start_accept_op()

:: AcceptEx

win_iocp_socket_accept_op

 

 

 

 

 

ip::tcp::resolver:: async_resolve

resolver_service

start_resolve_op()

:: getaddrinfo

resolve_op

 

 

静态的do_complete

不知你是否注意到,在operation的类图中,所有从operation继承的子类,都定义了一个do_complete()函数,然而该函数声明为static,这又是为何呢?

 

我们以win_iocp_socket_recv_op为例来进行说明。该类中的do_complete是这样声明的:

      static void do_complete(io_service_impl* owner,

           operation* base,

           const boost::system::error_code& result_ec,

           std::size_t bytes_transferred)

 

该类的构造函数,又把此函数地址传递给父类win_iocp_operation去初始化父类成员,这两个类的构造函数分别如下,请注意加粗代码:

win_iocp_socket_recv_op ::

win_iocp_socket_recv_op(socket_ops::state_type state,

      socket_ops::weak_cancel_token_type cancel_token,

      const MutableBufferSequence& buffers, Handler& handler)

    : operation(&win_iocp_socket_recv_op::do_complete),

           state_(state),

           cancel_token_(cancel_token),

           buffers_(buffers),

           handler_(BOOST_ASIO_MOVE_CAST(Handler)(handler))

     {

     }

 

win_iocp_operation ::win_iocp_operation(func_type func)

           : next_(0),

           func_(func)

     {

           reset();

     }

 

至此,我们明白,将do_complete声明为static,可以方便获取函数指针,并在父类中进行回调。那么,不仅要问,既然两个类存在继承关系,那么为何不将do_complete声明为虚函数呢?

 

再回头看看这些类的最顶层基类,就会明白的。最顶层的OVERLAPPED基类,使得将operation对象作为OVERLAPPED对象在异步函数中进行传递成为可能;如果将do_complete声明为虚函数,则多数编译器会在对象起始位置放置vptr,这样就改变了内存布局,从而不能再把operation对象直接作为OVERLAPPED对象进行传递了。

 

当然,一定要用虚函数的话,也不是不可能,只是在传递对象的时候,就需要考虑到vptr的存在,这会有两个方面的问题:一是进行多态类型转换时,效率上的损失;二是各家编译器对vtpr的实现各不相同,跨平台的asio就需要进行多种适配,这无疑又过于烦躁了。于是作者就采取了最为简单有效的方式——用static函数来进行回调——简单,就美。

 

win_iocp_io_service的实现

Windows NT环境下(IOCP Enabled),win_iocp_io_service代表着io_service,是整个asio的运转核心。本节开始来分析该类的实现。

 

从类的命名也可以看出,IOCP是该实现的核心。IOCPIO Completion Port IOCP)在windows上,可以说是效率最高的异步IO模型了,他使用有限的线程,处理尽可能多的并发IO请求。该模型虽说可以应用于各种IO处理,但目前应用较多的还是网络IO方面。

 

我们都知道,在Window是环境下使用IOCP,基本上需要这样几个步骤:

  1. 使用Win函数CreateIoCompletionPort()创建一个完成端口对象;
  2. 创建一个IO对象,如用于listensocket对象;
  3. 再次调用CreateIoCompletionPort()函数,分别在参数中指定第二步创建的IO对象和第一步创建的完成端口对象。由于指定了这两个参数,这一次的调用,只是告诉系统,后续该IO对象上所有的完成事件,都投递到绑定的完成端口上。
  4. 创建一个线程或者线程池,用以服务完成端口事件;

所有这些线程调用GetQueuedCompletionStatus()函数等待一个完成端口事件的到来;

  1. 进行异步调用,例如WSASend()等操作。
  2. 在系统执行完异步操作并把事件投递到端口上,或者客户自己调用了PostQueuedCompletionStatus()函数,使得在完成端口上等待的一个线程苏醒,执行后续的服务操作。

 

那么,这些步骤,是如何分散到asio中的呢? 来吧,先从完成端口创建开始。

完成端口的创建

如上所述,完成端口的创建,需要调用CreateIoCompletionPort()函数,在win_iocp_io_service的构造函数中,就有这样的操作:

win_iocp_io_service::win_iocp_io_service(

    boost::asio::io_service& io_service, size_t concurrency_hint)

  : boost::asio::detail::service_base<win_iocp_io_service>(io_service),

    iocp_(),

    outstanding_work_(0),

    stopped_(0),

    stop_event_posted_(0),

    shutdown_(0),

    dispatch_required_(0)

{

  BOOST_ASIO_HANDLER_TRACKING_INIT;

 

  iocp_.handle = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0,

      static_cast<DWORD>((std::min<size_t>)(concurrency_hint, DWORD(~0))));

  if (!iocp_.handle)

  {

    DWORD last_error = ::GetLastError();

    boost::system::error_code ec(last_error,

        boost::asio::error::get_system_category());

    boost::asio::detail::throw_error(ec, "iocp");

  }

}

 

win_iocp_io_service的构造函数,负责创建一个完成端口,并把此完成端口对象的句柄交给一个auto_handle进行管理——auto_handle的唯一用途,就是在对象析构时,调用::CloseHandle()windows句柄资源关闭,从而保证不会资源泄露。

 

我们在windows环境下,声明一个boost::asio::io_service对象,其内部就创建了一个win_iocp_io_service的实例;因此,一个io_service对象就对应着一个完成端口对象——这也就可以解释,为什么所有的IO Object都需要一个io_service参数了——这样,大家就好公用外面定义好的完成端口对象。

 

除了io_service对象会创建一个完成端口对象,事实上,在asio中,另外一个service也会创建一个,这就是boost::asio::ip::resolver_service。该类对应的detail实现boost::asio::detail::resolver_service中,有一个数据成员是: io_service,这样就同样创建了一个完成端口对象:

     namespace boost {

namespace asio {

namespace detail {

 

class resolver_service_base

{

...

protected:

// Private io_service used for performing asynchronous host resolution.

              scoped_ptr<boost::asio::io_service> work_io_service_;

...

 

至于该完成端口的用途如何,我们在后续部分再来说明——搽,又开始挖坑了。

完成端口的绑定

在创建了io对象后,例如socket,就需要将此对象和完成端口对象绑定起来,以指示操作系统将该io对象上后续所有的完成事件发送到某个完成端口上,该操作同样是由CreateIoCompletionPort()函数完成,只是所使用的参数不同。

 

win_iocp_io_service中,这个操作由下面的代码完成——请注意参数的差别:

boost::system::error_code win_iocp_io_service::register_handle(

    HANDLE handle, boost::system::error_code& ec)

{

  if (::CreateIoCompletionPort(handle, iocp_.handle, 0, 0) == 0)

  {

    DWORD last_error = ::GetLastError();

    ec = boost::system::error_code(last_error,

        boost::asio::error::get_system_category());

  }

  else

  {

    ec = boost::system::error_code();

  }

  return ec;

}

 

通过代码搜索,我们发现函数win_iocp_socket_service_base::do_open()内部调用了register_handle();该函数的作用是打开一个socket(其中调用了socket函数socket()去创建一个socket),也就是说,在打开一个socket后,就把该socket绑定到指定的完成端口上,这样,后续的事件就会发送到完成端口了。

 

此外还有另外的和assign相关的两个函数也调用了register_handle(),不再贴出其代码了。

 

线程函数

IOCP要求至少有一个线程进行服务,也可以有一堆线程;io_service早就为这些线程准备好了服务例程,即io_service::run()函数。

  • 如果应用只打算使用一个线程进行服务,那么在主线程中准备好了异步请求后,调用io_service::run()即可。注意,必须先发起一个异步请求,然后才能调用run()。参考一下run()的实现就会明白。
  • 如果打算用多个线程进行服务,可以创建多个线程,指定io_service::run() 作为线程函数即可。一个最简单的示例是:

void server::run()

{

  // Create a pool of threads to run all of the io_services.

  std::vector<boost::shared_ptr<boost::thread> > threads;

  for (std::size_t i = 0; i < thread_pool_size_; ++i)

  {

boost::shared_ptr<boost::thread>

    thread(

        new boost::thread(

                boost::bind(&boost::asio::io_service::run, &io_service_)

            )

        );

    threads.push_back(thread);

  }

 

  // Wait for all threads in the pool to exit.

  for (std::size_t i = 0; i < threads.size(); ++i)

    threads[i]->join();

}

 

由于io_service::run()又是委托win_iocp_io_service::run()来实现的,我们来看看后者的实现:

size_t win_iocp_io_service::run(boost::system::error_code& ec)

{

  if (::InterlockedExchangeAdd(&outstanding_work_, 0) == 0)

  {

    stop();

    ec = boost::system::error_code();

    return 0;

  }

 

  win_iocp_thread_info this_thread;

  thread_call_stack::context ctx(this, this_thread);

 

  size_t n = 0;

   while (do_one(true, ec))

    if (n != (std::numeric_limits<size_t>::max)())

      ++n;

  return n;

}

 

run()首先检查是否有需要处理的操作,如果没有,函数退出;win_iocp_io_service使用 outstanding_work_ 来记录当前需要处理的任务数。如果该数值不为0,则委托do_one函数继续处理——asio中,所有的脏活累活都在这里处理了。

 

win_iocp_io_service::do_one函数较长,我们只贴出核心代码

size_t win_iocp_io_service::do_one(bool block, boost::system::error_code& ec)

{

  for (;;)

  {

    // Try to acquire responsibility for dispatching timers and completed ops.

    if (::InterlockedCompareExchange(&dispatch_required_, 0, 1) == 1) ? #1

    {

      mutex::scoped_lock lock(dispatch_mutex_);

 

      // Dispatch pending timers and operations.

      op_queue<win_iocp_operation> ops;

      ops.push(completed_ops_);

      timer_queues_.get_ready_timers(ops);

      post_deferred_completions(ops); ? #2

      update_timeout();

    }

 

    // Get the next operation from the queue.

    DWORD bytes_transferred = 0;

    dword_ptr_t completion_key = 0;

    LPOVERLAPPED overlapped = 0;

::SetLastError(0);

    BOOL ok = ::GetQueuedCompletionStatus(iocp_.handle, &bytes_transferred,

        &completion_key, &overlapped, block ? gqcs_timeout : 0); ? #3

    DWORD last_error = ::GetLastError();

 

    if (overlapped)

    {

      win_iocp_operation* op = static_cast<win_iocp_operation*>(overlapped); ? #4

      boost::system::error_code result_ec(last_error,

          boost::asio::error::get_system_category());

 

      // We may have been passed the last_error and bytes_transferred in the

      // OVERLAPPED structure itself.

      if (completion_key == overlapped_contains_result)

      {

        result_ec = boost::system::error_code(static_cast<int>(op->Offset),

            *reinterpret_cast<boost::system::error_category*>(op->Internal));

        bytes_transferred = op->OffsetHigh;

      }

 

      // Otherwise ensure any result has been saved into the OVERLAPPED

      // structure.

      else

      {

        op->Internal = reinterpret_cast<ulong_ptr_t>(&result_ec.category());

        op->Offset = result_ec.value();

        op->OffsetHigh = bytes_transferred;

      }

 

      // Dispatch the operation only if ready. The operation may not be ready

      // if the initiating function (e.g. a call to WSARecv) has not yet

      // returned. This is because the initiating function still wants access

      // to the operation's OVERLAPPED structure.

      if (::InterlockedCompareExchange(&op->ready_, 1, 0) == 1)

      {

        // Ensure the count of outstanding work is decremented on block exit.

        work_finished_on_block_exit on_exit = { this };? #5

        (void)on_exit; ? #6

 

        op->complete(*this, result_ec, bytes_transferred); ? #7

        ec = boost::system::error_code();

        return 1;

      }

    }

    else if (!ok)

{

...

 

做一下简要说明:

-   #1 变量 dispatch_required_ 记录了由于资源忙,而没有成功投递的操作数;所有这些操作都记录在队列 completed_ops_ 中;

-   #2 将所有需要投递的操作,投递出去;至于什么样的操作需要投递,何时投递,以及为先前会投递失败,失败后如何处理等,我们后续说明——再次挖坑了。

-   #3 IOCP的核心操作函数 GetQueuedCompletionStatus() 出现了。该函数导致线程在完成端口上进行等待,直到超时或者某个完成端口数据包到来。

-   #4 注意这里将 OVERLAPPED 结构直接转换为 operation 对象。相关内容在前面的operation:OVERLAPPED基类部分已经有说明。

-   #5 该变量保证在操作完成,return之后,win_iocp_io_service对象所记录的任务数 outstanding_work_ 会自动减1——是啊,辛辛苦苦做的事儿,能不记录下来嘛!

-   #6 这一行从功能上讲没有什么特别的用途;不过有了这一行,可以抑制有些编译器针对 #5 所声明的变量没有被使用的编译器警告;

-   #7 调用operation对象的complete()函数,从而调用到异步操作所设定的回调函数。具体流程参考operation:执行流程

 

任务投递

上述的线程函数,会在 GetQueuedCompletionStatus() 函数上进行等待,直到超时或者有完成端口数据包到来;

 

完成端口数据包,有两个来源:一个是用户所请求的异步操作完成,异步服务执行者(这里是操作系统)向该完成端口投递完成端口数据包;另外一种情况是,用户自己使用IOCP的另外一个核心函数 PostQueuedCompletionStatus() 向完成端口投递数据包;

 

一般的异步操作请求,是不需要用户自己主动向完成端口投递数据的,例如async_read, asyn_write等操作;

 

有另外一些操作,由于没有对应或者作者并没有采用支持OVERLAPPED IO操作的Win32函数,就需要实现者自己管理完成事件,并进行完成端口数据包的投递,比如:

  • async_resolve:由于系统没有提供对应的OVERLAPPED IO操作,需要实现者自己管理,所以其自己进行投递
  • async_connect:由于作者并没有采用支持OVERLAPPED IOConnectEx()版本的连接函数,而是采用了标准的socket函数connect()进行连接,所以也需要自己进行投递

 

另外还有一些io_service提供的操作,例如请求io_service执行代为执行指定handler的操作:

  • dispatch(handler)
  • post(handler)

 

所有这些需要自己投递完成端口数据包的操作,基本上都是这样一个投递流程:

  • 调用win_iocp_io_service::post_immediate_completion(op)
    • 调用work_started() outstanding_work_ 1
    • 调用post_deferred_completion(op)
      • 由于自行管理,主动将op->ready_ 置为 1,表明op就绪
      • 调用 PostQueuedCompletionStatus(op) 进行投递
      • 如果投递失败,则把该op放置到等待dispatch的队列 completed_ops_  中,待下一次do_one()执行时,再次投递

 

OK,至此,基本分析完了operation的投递,总数填了一个前面挖下的坑。

Resolver自己的IOCP

前面说过,Resolver自己会创建一个IOCP,为什么会这样呢? 由于Win32下面没有提供对应于地址解析的overlapped版本的函数,为了实现async_resolve操作,作者自己实现了这样一个异步服务。在resolver_service内部,有一个io_service数据成员,该数据成员创建了一个IOCP;除此之外,该service内部还启动一个工作线程来执行io_service::run(),使用此线程来模拟异步服务。

 

使用resolver进行async_resolve的详细过程如下:

Main Thread (IOCP#1)

 

Resolver Thread (IOCP #2)

 

 

 

1. 构建 io_service 对象, IOCP#1 被创建

 

 

 

 

 

2. 构建 resolver对象, IOCP#2 被创建, 同时该resolver持有 io_service的引用

 

 

 

 

 

3. 发起异步调用:resolver.async_resolve()

 

 

 

 

 

4. resolve_op 被创建

 

 

 

 

 

5. Resolver 线程启动, 主线程开始等待

 

 

 

 

 

 

 

6. 开始运行,激活等待事件,并在 IOCP#2上开始等待

 

 

 

7. 线程恢复执行;将op投递到 IOCP#2

 

 

 

 

 

 

 

8. 执行op->do_complete() 操作, 地址解析完成后,将op再回投给IOCP#1

 

 

 

9. do_one() 得到 Resolver线程投递回 来的op 开始执行op->do_complete() 操作,此时回调async_resolve所设置的handler

 

 

 

 

 

10. 结束

 

 

 

请注意step8 step9 执行同样一个op->do_complete()函数,为什么操作不一样呢? 看其实现就知道,该函数内部,会判断执行此函数时的owner,如果owner是主io_service对象,则说明是在主线程中执行,此时进行handler的回调;否则就说明在工作线程中,就去执行真正的地址解析工作;

任务的取消

针对socket上提交的异步请求,可以使用cancel()函数来取消掉该socket上所有没执行的异步请求。

 

使用该函数,在Windows Vista(不含)以前的版本,有几点需要注意:

  • 需要定义 BOOST_ASIO_ENABLE_CANCELIO 来启用该功能
  • cancel()函数在内部调用Win32函数 CancelIo()
  • 该函数只能取消来自当前线程的异步请求
  • 对于正在执行的异步操作,则要看异步服务提供者是如何实现的了,可能会被取消,也可能不会;

针对这些问题,另外的替代方案是:

  • Window是上定义 BOOST_ASIO_DISABLE_IOCP 来禁用IOCP,使用老式的reactor模式(及select IO)。
  • 或者使用close()来关闭socket,如此一来所有未被执行的请求则都会被取消掉。

 

windows vista及后续版本中,cancel()函数在内部调用Win32函数 CancelIoEx(),该函数可以取消来自任何线程的异步操作请求,不存在上述问题。

 

需要注意的是,即使异步请求被取消了,所指定的handler也会被执行,只是传递的error code 为: boost::asio::error::operation_aborted

win_iocp_socket_service实现

service提供了windows下所有socket相关的功能,是asiowindows环境中一个非常重要的角色,他所提供的函数主要分下面两类:

  • XXXXX(), async_XXXXX() 对某个操作的同步、异步函数接口;主要被上层服务调用;例如connect(), async_connect()等;
  • start_XXXXX_op() : windows发出对应的异步操作请求,例如WSARecv

 

不过关于该类的实现前面已经做了较多的涉及,不再单独详述了。

前摄器模式

现在我们已经把Windows环境下所涉及到的关键部件都涉及到了,此刻我们再回过头来,从高层俯瞰一下asio的架构,看看是否会有不一样的感受呢? 事实上,asio的文档用下面的图来说明asio的高层架构——前摄器模式,我们也从这个图开始:

boost.asio 学习笔记05——asio的windows实现 - 地线 - 别再让虚假消息充斥这本已混乱的世界

 

 

呵呵,其实这张图,从一开始就是为了表达Proactor(前摄器)模式的,基本上它和asio没半毛钱关系,只不过asio既支持同步IO,又支持异步IO,在异步IO部分,是参照Proactor模式来实现的。下面我们来分别说说asio的前摄器模式中的各个组件:

  • Initiator,(初始化器?)中文名还真不清楚,不过其实就是客户代码,甚至可以简单理解到main函数,所有的是是非非,都是从这儿开始的。
  • Asynchronous Operation, 定义的一系列异步操作,对应到Windows平台,诸如AcceptExWSASendWSARecv等函数。在asio中,这些函数封装在win_iocp_socket_service resolver_service类中。
  • Asynchronous Operation Processor, 异步操作处理器,他负责执行异步操作,并在操作完成后,把完成事件投放到完成事件队列上。

仅仅从asio使用者的角度看,高层的stream_socket_service类就是一个这样的处理器,因为从tcp::socket发送的异步操作都是由其完成处理的。但是从真正实现的角度看,这样的异步操作在Windwos上,大部分由操作系统负责完成的,另外一部分由asio自己负责处理,如resolver_service,因此Windows操作系统和asio一起组成了异步操作处理器。

  • Completion Handler,完成事件处理器;这是由用户自己定义的一个函数(函数对象),在异步操作完成后,由前摄器负责把该函数调用起来。

Windows平台上,io_service类通过win_iocp_io_service类的do_one()函数把每个异步操作所设定的completion handler调用起来。

  • Completion Event Queue 完成事件队列,存储由异步操作处理器发送过来的完成事件,当异步事件多路分离器将其中一个事件取走之后,该事件从队列中删除;

Windows上,asio的完成事件队列由操作系统负责管理;只不过该队列中的数据有两个来源,一个是Windows内部,另外一个是asio中自己PostQueuedCompletionStatus()所提交的事件。

  • Asynchronous Event Demultiplexer,异步事件多路分离器,他的作用就是在完成事件队列上等待,一旦有事件到来,他就把该事件返回给调用者。

Windows上,这一功能也是由操作系统完成的,具体来说,我认为是由GetQueuedCompletionStatus完成的,而该函数时由do_one()调用的,因此,从高层的角度来看,这个分离器,也是由io_service负责的。

  • Proactor,前摄器,负责调度异步事件多路分离器去干活,并在异步操作完成时,调度所对应的Completion Handler。在asio中,这部分由io_service来做,具体Windows就是win_iocp_io_service

 

基于上述信息,我们重绘practor模式架构图如下:

boost.asio 学习笔记05——asio的windows实现 - 地线 - 别再让虚假消息充斥这本已混乱的世界

  

·其他

asnyc_read VS. async_read_some VS. async_receive

async_read是一个全局函数;后面两个则于ip::tcp::socket的成员个函数;都可以用来异步读取操作,他们有什么样的差别呢。先来看async_read_someasync_receive,他们的文档说明如下:

  • async_read_some: Start an asynchronous read. This function is used to asynchronously read data from the stream socket. The function call always returns immediately.
  • async_receive: Start an asynchronous receive. This function is used to asynchronously receive data from the stream socket. The function call always returns immediately.

 

从文档来看,只有一个单词的差别,一个是read,一个是receive;反正都是从socket中获取数据,这两个词有什么差别呢?我是看不出他们的差别,那就看代码吧:

template <typename MutableBufferSequence, typename ReadHandler>

  void async_read_some(const MutableBufferSequence& buffers,

      BOOST_ASIO_MOVE_ARG(ReadHandler) handler)

  {

    // If you get an error on the following line it means that your handler does

    // not meet the documented type requirements for a ReadHandler.

    BOOST_ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check;

 

    this->get_service().async_receive(this->get_implementation(),

        buffers, 0, BOOST_ASIO_MOVE_CAST(ReadHandler)(handler));

  }

 

template <typename MutableBufferSequence, typename ReadHandler>

  void async_receive(const MutableBufferSequence& buffers,

      BOOST_ASIO_MOVE_ARG(ReadHandler) handler)

  {

    // If you get an error on the following line it means that your handler does

    // not meet the documented type requirements for a ReadHandler.

    BOOST_ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check;

 

    this->get_service().async_receive(this->get_implementation(),

        buffers, 0, BOOST_ASIO_MOVE_CAST(ReadHandler)(handler));

  }

 

好了,有了源代码,就无从狡辩了吧。他们都是使用底层服务的async_recevie来读取数据,因此他们的功能是一样的,都是从socket获取一定的数据,但是该函数并不保证能够从获得指定长度的数据——也即不保证填满缓冲区;

 

如果想要保证异步操作完成时获取到指定数量(缓冲区的长度)的数据,那么使用全局函数async_read。该函数内部启动一个 read_op ,该op内部,会多次调用stream.async_read_some,直到缓冲区填满,或者读操作结束;

 

这既是这三个函数的异同之所在。

 

妖怪async_read

前面一节提到过,全局函数 async_read 会启动read_op并在该op内部反腐调用async _read_some()来读取数据直到缓冲区慢,或者EOF出现;如果要来看一下read_opoperator(),你多半会感觉看到妖怪了。乖,别怕,来看看吧:

void operator()(const boost::system::error_code& ec,

        std::size_t bytes_transferred, int start = 0)

    {

      std::size_t n = 0;

      switch (start)

      {

        case 1:

        n = this->check_for_completion(ec, total_transferred_);

        for (;;)

        {

          stream_.async_read_some(

              boost::asio::buffer(buffer_ + total_transferred_, n),

              BOOST_ASIO_MOVE_CAST(read_op)(*this));

          return; default:

          total_transferred_ += bytes_transferred;

          if ((!ec && bytes_transferred == 0)

              || (n = this->check_for_completion(ec, total_transferred_)) == 0

              || total_transferred_ == boost::asio::buffer_size(buffer_))

            break;

        }

 

        handler_(ec, static_cast<const std::size_t&>(total_transferred_));

      }

    }

 

不知道你是否会和我一样感觉毛骨悚然,惊叫switch-case的邪乎。是的,说他妖怪,就妖怪在switch的那两个case标签,第一个 “case 1” 还算人性,第二个标签default就完全是妖怪了,它放置在for循环的内部——是的,编译器是允许的,假设程序流程首先跳转到default标签,在执行完default分支后,他会像正常的for循环流程一样,继续for loop的,这就是这个妖怪。

 

自己写的一个简单的测试及输出:

void jumpinto( int state )

{

    int i = 0;

    switch( state )

       {

           case 1:

                  cout << "case 1: begin for()" << endl;

                     for( ; ; )

                     {

                         cout << "    in for loop, i = " << i++ << endl;

                         //return;

                         default:

                         cout << "case default " << endl;

                           if( i > 2 )

                           {

                               cout << "     i > 2, break" << endl;

                                  break;

                           }

                     }// end for

       };//end switch

}

 

int main()

{

    jumpinto(0);

    return 0;

}

 

输出如下:

D:\studyprograms\test>caseblock.exe

case default

    in for loop, i = 0

case default

    in for loop, i = 1

case default

    in for loop, i = 2

case default

     i > 2, break

 

 

关于为何作者要这样写,暂时还没理解到。更多信息移驾翊坤宫再议:http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html

 

优雅退出

使用asio开发的应用要中途退出,或者开发的server要停止,咱如何优优雅雅地清理asio组件呢? 呵呵,肯定可以,asio已经提供了一些手段:signal。下面是从asio示例代码中提取出的一些代码,以示说明。

class server : private boost::noncopyable

{

public:

    explicit server(const std::string& address, const std::string& port,

      const std::string& doc_root, std::size_t thread_pool_size)

        :

    signals_(io_service_pool_.get_io_service()),

    acceptor_(...)

       {

              signals_.add(SIGINT);

              signals_.add(SIGTERM);

             

              #if defined(SIGQUIT)

                     signals_.add(SIGQUIT);

              #endif // defined(SIGQUIT)

             

              signals_.async_wait(boost::bind(&server::handle_stop, this));

       }

 

private:

  void handle_stop()

  {

      io_service_.stop();

  }

 

  boost::asio::io_service io_service_;

  boost::asio::signal_set signals_;

  boost::asio::ip::tcp::acceptor acceptor_;

};

 

·总结:

至此,我们对boost.asio的基本体系结构以及在windows上的实现,做了一个梳理;个人认为常用到的操作及其实现都涉及到了。当然,本文并没有对非Win环境下的实现、SSL,串口、buffer管理、以及如何使用asio等进行讨论。

 

针对asio,个人也是刚刚接触正在学习,觉得要想使用好他,还是首先要了解socket的基本操作,在windows环境下,还要了解IOCP模型,然后才能用好它。欢迎讨论。

 

最后,向Christopher M. Kohlhoff (chris@kohlhoff.com)跪一下。

 

转载地址:http://blog.163.com/henan_lujun/blog/static/19538333201341451545880/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值