使用asio之前要先对它的设计思想有所了解,了解设计思想将有助于我们理解和应用asio。asio是基于proactor模式的,asio的proactor模式隐藏于大量的细节当中,要找到它的踪迹,往往有种只见树木不见森林之感,笔者将剖析asio中的proactor模式,一步一步揭开它的面纱,最终拨开云雾,将一个完整的proactor模式还原出来。在剖析asio的proactor模式之前,我们先来看看常见的io设计模式。proactor(主动器)模式是一种重要的I/O设计模式,用来解决高并发网络中遇到的问题,另外还有一种模式是reactor(反应器),libevent是基于reactor实现的,让我们先看看这两种模式的一些特点。

反应器和主动器模式介绍

  反应器需要应用程序先注册事件处理器,然后启动反应器的事件循环,不断的检查是否有就绪I/O事件,当有就绪事件时,同步事件多路分解器将会返回到反应器,反应器会将事件分发给多个句柄的回调函数以处理这些事件。【1】

反应器的一个特点是,具体的处理程序并不调用反应器,而是由反应器来通知处理程序去处理事件。这种方式也被称为“控制反转”,又称为“好莱坞原则”。下面是反应器模式的类图:

反应器模式大概的流程是这样的:

  1. 应用程序在反应器上注册具体事件处理器,处理器提供内部句柄给反应器。
  2. 注册之后,应用程序开始启动反应器事件循环。反应器将通过select等待发生在句柄集上的就绪事件。
  3. 当一个或多个句柄变成就绪状态时(比如某个socket读就绪了),反应器将通知注册的事件处理器。
  4. 事件处理器处理就绪事件,完成用户请求。

  反应器模式使用起来相对直观,但是它不能同时支持大量的客户请求或者耗时过长的请求,因为它串行化了所有的事件处理过程。而proactor模式在这方面做了改进。

主动器的类图如下:

1. 应用程序需要定义一个异步执行的操作,例如socket的异步读写。
2. 执行异步操作,异步事件处理器将异步请求交给操作系统就返回了,让操作系统去完成具体的操作,操作系统在完成操作之后,会将完成事件放入一个完成事件队列。
3. 异步事件分离器会检测完成事件,当检测到完成事件,则从完成事件队列中取出完成事件,并通知应用程序注册的完成事件处理函数去处理。
4. 完成事件处理函数处理异步操作的结果。

  Reactor和Proactor模式的主要区别就是真正的操作(如读、写)是由谁来完成的,Reactor中需要应用程序自己读取或者写入数据,而Proactor模式中,应用程序不需要进行实际的读写过程,操作系统会读取缓冲区或者写入缓存区到真正的IO设备,应用程序只需要从缓冲区读取或者写入即可。Proactor模式中,用户发起异步操作之后就返回了,让操作系统去处理请求,然后等着回调到完成事件函数中处理异步操作的结果。

asio的proactor模式

  介绍了这两种IO设计模式之后,我们再看看asio中的proactor模式是怎么样的。要了解asio中的proactor首先要了解asio的基本用法和异步操作的流程,然后再根据流程并结合proactor模式来分析行。
  分析asio的proactor之前我们先看一个发起异步连接的简单例子,通过这个简单的例子来看看asio的一些重要的对象。
asio::io_service io_service;
tcp::socket socket(io_service);
boost::asio::async_connect(socket, server_address, connect_handler);
io_service.run();
  别看只有短短四行代码,它的内部其实做了很多复杂的事情,屏蔽了很多细节,才使得我们用起来很简单。首先来看看第一行代码,第一行代码定义了一个asio的核心对象io_service,所有的io object对象的初始化都要将这个io_service传入,关于这点先不细说,在后面再谈。第二行创建了一个tcp::socket 类型的io object对象,通过这个io object对象我们就可以发起异步操作了。发起异步操作之后,再调用io_service的run启动事件循环,等待异步事件的完成。有了一个初步认识之后我们再来看看这些对象是干啥的,有什么作用。
  先来看看io object,io object是asio给用户提供的一个io相关的操作对象,io object会将用户发起的操作转发给io_service,比如可以通过它们来发起异步连接、读和写等等,asio的io object有如下这些:

  让我们再来看看windows平台上发起一个异步操作的具体流程:

  用户通过io object对象 tcp::socket发起async_connect操作,scoket会委托内部的模板类basic_socket的async_connect,basic_socket采用handle body手法,它也仅仅是做个转发,它又委托服务类stream_socket_service调用async_connect,其实stream_socket_service也不是具体干事的,它继续委托给平台相关的具体服务类对象win_iocp_socket_service去完成最终的async_connect,win_iocp_socket_service实际上将这个异步请求交给了操作系统,然后应用程序就返回了,让操作系统去完成异步请求。再回过头来看看第三行代码:

boost::asio::async_connect(socket, server_address, connect_handler);

  别看它只有简单一行代码,它实际上在内部将这个异步请求转手了三次才最终发送到操作系统,够复杂吧。这里可以参考一个具体的时序图,看看发起异步操作的过程是怎样的:

  看到这里也许有人会看得云里雾里的,为啥一个异步操作要转这么多层才交给操作系统。这是因为asio的设计就是分了几层的,从应用层转到中间层,再转到服务层,再到最底层的操作系统,理解了这种分层架构就能理解为啥一个请求要转几次了。asio实际上分了三层,第一层为io objcet层,作为应用程序直接使用的对象,提供basic_xxx模板的基本io操作接口;第二层为basic_xxx模板类层,这一层的作用是将具体的操作转发给服务层;第三层是服务层,它提供操作的底层实现。它又分为两层:接收操作层和平台适配层。中间层的basic_xxx模板实例会将用户发起的操作转发到服务层的接收操作层,接收操作层又将操作转发到具体的平台适配层,平台适配层会调用操作系统的api完成操作。让我们看看asio具体分了哪几层:

  通过这个分层的架构图,我们就理解了为什么一个操作要转发这么多次,因为每一层都有自己的职责,高层的请求需要一层一层转发到最底层。这里需要提到asio的另外一个重要的对象win_iocp_socket_service,由于它是处于最底层的服务对象,用户层是看不见,但是用户层发起的操作大都是由它调用windows api完成的。

关于异步操作的发起并转发给操作系统我们已经说完了,但转发给操作系统的请求完成之后的处理还没谈,操作系统如何将操作完成的结果回调到应用层的呢,proactor的踪影又在哪里呢,且听下回分解。后面我们会慢慢从这些繁琐的细节中走出来,逐步从asio中清晰的还原出proactor模式来,让读者看清其庐山真面目。在下一节中,我会带领读者从proactor模式中来又回到proactor模式中去,相信读者看完自然会有豁然开朗之感。未完待续......

文中的部分插图来自于洋子lujun-cc的博客,部分内容作了参考,在此表示感谢。

[1]《面向模式的软件架构卷2》

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

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

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

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

    在上一篇博文中我们提到异步请求是从上层开始,一层一层转发到最下面的服务层的对象win_iocp_socket_service,由它将请求转发到操作系统(调用windows api),操作系统处理完异步 ...

  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. JavaMail 接收邮件及删除

    解析读取收件箱中邮件: import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io. ...

  2. uitableview分组的数据2中方式

    // // ViewController.m // tableviegroup // // Created by ganchaobo on 13-7-2. // Copyright (c) 2013年 ...

  3. Linux-内核态与用户态

    内核态: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序 用户态: 只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺 ...

  4. 使用Servlet发布WebService

    首先感谢熔岩的文章: http://lavasoft.blog.51cto.com/62575/227988 1.Eclipse安装上tomcat插件,http://www.eclipsetotale ...

  5. MATLAB 的输入输出命令

    MATLAB 的输入输出命令: >> A='woshi'; >> disp(A) woshi fscanf和fprintf命令的行为像C scanf和printf函数.他们支持 ...

  6. java struts2入门学习实例--用户注册和用户登录整合

    需求: 1.用户注册(user_register.jsp)-->注册成功(UserRegister.action)-->显示注册信息(register_success.jsp)2.用户登录 ...

  7. Bean的加载过程

    参考地址: http://blog.csdn.net/jy0902/article/details/50519115 http://blog.csdn.net/architect0719/articl ...

  8. (转)Groupon前传:从10个月的失败作品修改,1个月找到成功 并不挶泥在这个点子上面,它反而往后站一步,看看他们已经做好的这个网站,可以再怎么包装成另一个完完全全不同的网站?所有的人所做的每件失败的事情中, 一定有碰到或含有成功的答案」在里面,只是他们不知道而已。 人不怕失败」,只怕宣布失败」

    (转)Groupon前传:从10个月的失败作品修改,1个月找到成功 今天读到 一个非常励志人心的故事 ,就像现在「叶问」有「前传」,最近很火红的团集购网站Groupon 也出现了「Groupon前传」 ...

  9. MVC 3.0学习笔记(自定义控件)

    现有控件: 例如ASP.NET MVC框架包括以下设置标准的HTML控件(部分控件): Html.ActionLink() Html.BeginForm() Html.CheckBox() Html. ...

  10. 【java】解析JToolBar类的使用

    1.简介 在大部分的Look and Feels下,用户都可以把该工具条拖离原Window组件(除非floatable属性设置为false).为了能够正常的实现拖动效果,该类的实例被建议添加到Bord ...