在上一篇博文中我们提到异步请求是从上层开始,一层一层转发到最下面的服务层的对象win_iocp_socket_service,由它将请求转发到操作系统(调用windows api),操作系统处理完异步请求之后又是如何返回给应用程序的呢,这里是通过iocp(完成端口)来实现的。让我们先来简要的看看iocp的基本步骤:

  1. 创建IOCP对象;
  2. 创建io object对象;
  3. 将io object IOCP对象绑定;
    4.进行异步调用;
  4. 创建线程或者由线程池等待完成事件的到来;

  asio实际上也是按照这个步骤去做的,再回头看看上一节中的那个简单的例子:

asio::io_service io_service;
tcp::socket socket(io_service);
boost::asio::async_connect(socket, server_address, connect_handler);
io_service.run();

  第一行中的io_service对象是asio的核心,它其实封装了iocp,创建一个io_service实际上就是创建了一个iocp对象win_iocp_io_service,因此后面所有的io object的创建都要引用这个io_service,目的是共用这个iocp对象。第二行创建了socket对象,它引用了第一行创建的iocp对象;第三行实际上是将异步请求层层转发到最下面的服务层win_iocp_socket_service对象,最终交给操作系统。通过它的名字就知道它与iocp相关,因为发起异步操作之前,它先要将io object对象与完成端口绑定,以便后面的完成事件会发到指定的完成端口。

  绑定io object和iocp对象的具体过程是这样的:async_connect内部会先调用base_xxx模板层的base_socket<tcp>的open方法,base_socket<tcp>又会调用服务层的服务对象stream_socket_service<tcp>的open方法,stream_socket_service<tcp>又调用最下面的服务对象win_iocp_socket_service的open方法,win_iocp_socket_service对象又委托io object对象引用的io_service对象(实际上是win_iocp_io_service)的do_open方法,在do_open方法中会调用register_handler方法,在该方法中会调用CreateIoCompletionPort将io object和iocp对象绑定起来。

  io object和iocp对象绑定之后,win_iocp_socket_service会调用操作系统的api,发起异步操作。

  再看第四行:io_service.run();

  io_service::run()又是委托win_iocp_io_service::run()来实现的,让我们来看看run的内部实现:

size_t win_iocp_io_service::run(boost::system::error_code& ec)
{
if (::InterlockedExchangeAdd(&outstanding_work_, ) == )
{
stop();
ec = boost::system::error_code();
return ;
} win_iocp_thread_info this_thread;
thread_call_stack::context ctx(this, this_thread); size_t n = ;
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函数继续处理。do_one()内部会调用GetQueuedCompletionStatus()函数,该函数会阻塞等待异步事件的完成,当异步事件完成时,就回调到应用层的完成事件处理函数,因为发起异步操作时已经将io object和完成端口绑定了,所以iocp能将异步完成事件回调到对应的应用层的完成处理函数中。

  至此,asio中一个异步操作的过程就完成了。在了解了这些内部实现细节之后,我们再来看看boost官网上给出的一个asio中proactor模式的一张图。

  这张图和上一篇博文中Proactor模式的图几乎是一样的,我们根据这张图再结合前面的分析,就能从细节中还原出asio中的Proactor模式了。下面我们来看看上图中的这些对象分别是asio中的哪些对象:

  • Initiator:对应用户调用asio的代码;
  • Asynchronous Operation Processor:异步操作处理器,他负责执行异步操作,并在操作完成后,把完成事件投放到完成事件队列上。stream_socket_service类就是一个这样的处理器,因为从tcp::socket发送的异步操作都是由其完成处理的,它最终是由底层的服务对象win_iocp_socket_service完成的,win_iocp_socket_service负责绑定io object和io_service对象和调用操作系统api发起异步操作。从高层的角度看,asio的stream_socket_service成为了Proactor中的异步操作处理器。
  • Asynchronous Operation:定义的一系列异步操作,对应到Windows平台,诸如AcceptEx,WSASend,WSARecv等函数。在asio中,这些函数封装在win_iocp_socket_service,resolver_service类中。[1]
  • Completion Handler:用户层完成事件处理器,由用户创建,一般是通过bind或者lambda表达式定义。
  • Completion Event Queue:完成事件队列,存储由异步操作处理器发送过来的完成事件,当异步事件多路分离器将其中一个事件取走之后,该事件从队列中删除;在Windows上,asio的完成事件队列由操作系统负责管理;
  • Asynchronous Event Demultiplexer:异步事件多路分离器,他的作用就是在完成事件队列上等待,一旦有事件到来,他就把该事件返回给调用者。在Windows上,这一功能也是由操作系统完成的,具体来说,是由GetQueuedCompletionStatus完成的,而该函数是由do_one()调用的,因此,从高层的角度来看,这个分离器,也是由io_service负责的。[2]
  • Proactor,前摄器,负责调度异步事件多路分离器去干活,并在异步操作完成时,调度所对应的Completion Handler。在asio中,这部分由io_service来做,具体Windows就是win_iocp_io_service。[3]

  从上面的分析可以看到,asoi中的Proactor模式已经很清晰了,io_service在asio中处于核心地位,不仅仅是对应了一个完成端口对象,还参与了Proactor模式中的异步事件处理和启动事件循环,调度异步事件多路分离器将异步事件回调到应用层。

  再来做一个小结:io object负责发起异步操作,发起异步操作的过程中,会委托stream_socket_service将异步操作转发到下面的服务层,最终转发到操作系统。io object创建时需要引用io_service,以便在后面绑定完成端口,同时还要提供完成事件处理函数,以便在异步操作完成后处理完成事件。io_service负责启动事件循环,等待异步事件的完成并将异步操作的结果回发到用户定义的完成事件处理函数中。


[1] [2] [3] http://blog.csdn.net/henan_lujun/article/details/8965044

如果你觉得这篇文章对你有用,可以点一下推荐,谢谢。

c++11 boost技术交流群:296561497,欢迎大家来交流技术。
 

(原创)拨开迷雾见月明-剖析asio中的proactor模式(二)的更多相关文章

  1. (原创)拨开迷雾见月明-剖析asio中的proactor模式(一)

    使用asio之前要先对它的设计思想有所了解,了解设计思想将有助于我们理解和应用asio.asio是基于proactor模式的,asio的proactor模式隐藏于大量的细节当中,要找到它的踪迹,往往有 ...

  2. boost.asio源码剖析(四) ---- asio中的泛型概念(concepts)

    * Protocol(通信协议) Protocol,是asio在网络编程方面最重要的一个concept.在第一章中的levelX类图中可以看到,所有提供网络相关功能的服务和I/O对象都需要Protoc ...

  3. 深入剖析Java中的装箱和拆箱

    深入剖析Java中的装箱和拆箱 自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最基本的东西,再来看一下面试笔试中经常遇到的与装箱 ...

  4. 【翻译】Anatomy of a Program in Memory—剖析内存中的一个程序(进程的虚拟存储器映像布局详解)

    [翻译]Anatomy of a Program in Memory—剖析内存中的一个程序(进程的虚拟存储器映像布局详解) . . .

  5. 从别人那淘的知识 深入剖析Java中的装箱和拆箱

    (转载的海子的博文   海子:http://www.cnblogs.com/dolphin0520/) 深入剖析Java中的装箱和拆箱 自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来 ...

  6. 月半小夜曲下的畅想--DOCTYPE模式

    月半小夜曲下的畅想--DOCTYPE模式 @(css3 box-sizing)[doctype声明|quirks模式|妙瞳] DOCTYPE文档类型标签,该标签是将特定的标准通用标记语言或者XML文档 ...

  7. 深入剖析Java中的自动装箱和拆箱过程

    深入剖析Java中的装箱和拆箱 自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最基本的东西,再来看一下面试笔试中经常遇到的与装箱 ...

  8. [ 转载 ]学习笔记-深入剖析Java中的装箱和拆箱

    深入剖析Java中的装箱和拆箱 自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最基本的东西,再来看一下面试笔试中经常遇到的与装箱 ...

  9. 【转】深入剖析Java中的装箱和拆箱

    深入剖析Java中的装箱和拆箱 自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最基本的东西,再来看一下面试笔试中经常遇到的与装箱 ...

随机推荐

  1. 简化版的SpringMVC框架的实现思路

    在SpringMVC基本统一Java web开发技术栈的环境下, 这是一个有点过时的话题了. SpringMVC的特点主要在于注解型的RequestMapping和参数机制非常灵活, 另外得益于Spr ...

  2. 基于Echarts的股票K线图展示

    发布时间:2018-10-31   技术:javascript+html5+canvas   概述 基于echarts的股票K线图展示,只需引用单个插件,通过简单配置,导入数据,即可实现炫酷复杂的K线 ...

  3. 【DeepLearning】UFLDL tutorial错误记录

    (一)Autoencoders and Sparsity章节公式错误: s2 应为 s3. 意为从第2层(隐藏层)i节点到输出层j节点的误差加权和. (二)Support functions for ...

  4. Linux命令:用“dirs”、“pushd”、“popd”来操作目录栈

    你可以将目录压入目录栈,也可以稍后将该目录弹出.在随后的示例中,将会用到以下三个命令: * dirs:显示目录栈 * pushd:将目录压入目录栈 * popd:将目录弹出目录栈 dirs命令显示当前 ...

  5. java 实现二分法

    http://www.cnblogs.com/vanezkw/archive/2012/06/29/2569470.html JDK里面的二分法实现.二分法的实现有多种今天就给大家分享两种.一种是递归 ...

  6. CSS中详解height属性

    目录结构: contents structure [+] hight属性值类型一览表 height的%的使用 定义 实例 需要注意的 参考文章 hight属性值类型一览表 value describt ...

  7. 《JAVA与模式》之备忘录模式

    一.备忘录(Memento)模式结构 备忘录对象是一个用来存储另外一个对象内部状态的快照(snapshot)的对象.备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉住,并外部化,存储起来, ...

  8. Maven for Eclipse 第二章 ——安装 m2eclipse插件

    m2eclipse 是一个提供了 Maven 与 Eclipse 整合的插件.它的意图是桥接上 Maven 和 Eclipse 之间的缺口.通过 Maven 原型提供的简单直白的接口创建项目,它使 M ...

  9. alibaba fastjson TypeReference 通过字符串反射返回对象

    TypeReferenceEditNew Page温绍 edited this page Nov 3, 2017 · 8 revisions1. 基础使用在fastjson中提供了一个用于处理泛型反序 ...

  10. postman 定义并使用全局变量

    第一步:找到并打开右上角的设置图案  第二步,点击“Global” 按钮 第三步.如图所示,定义全局变量,然后点击“save”即可 第四步:如何使用全局变量,只需要 {{ key }} 即可,如图所示 ...