使用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. List集合去重的一些方法(常规遍历、Set去重、java8 stream去重、重写equals和hashCode方法)

    1. 常规元素去重 碰到List去重的问题,除了遍历去重,我们常常想到利用Set集合不允许重复元素的特点,通过List和Set互转,来去掉重复元素. // 遍历后判断赋给另一个list集合,保持原来顺 ...

  2. CentOS vps下,安装xfce/KDE/GNOME桌面+vncserver进行远程管理

    CentOS vps下,安装xfce/KDE/GNOME桌面+vncserver进行远程管理 首先安装桌面环境,我选择的是xfce,轻量级桌面,小巧实用不占太多内存,(占用内存方面,xfce少于kde ...

  3. 时间选择器(js,css,html)

    忘了从哪下载的了, 整理电脑, 做个记录, 效果图: 下载链接: 链接: https://pan.baidu.com/s/1qfYkcfn8dtLFg0oniB2GHA 提取码: 2amm

  4. ReactiveCocoa 中 RACSignal 是怎样发送信号

    前言 ReactiveCocoa是一个(第一个?)将函数响应式编程范例带入Objective-C的开源库.ReactiveCocoa是由Josh Abernathy和Justin Spahr-Summ ...

  5. mariadb/mysql配置允许远程访问方式

    首先配置允许访问的用户,采用授权的方式给用户权限 GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'IDENTIFIED BY '123456' WITH GRANT ...

  6. 安卓ListView中CheckBox的使用(支持Item列表项的删除,全选,全不选)

    ListView 自身提供了 CheckBox 只需要添加一行代码 getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 但是这种实现 ...

  7. 【java】自定义异常类

    目录结构: contents structure [+] 为什么需要自定义异常类 自定义异常的方式 实例 日常日志 一,为什么需要自定义异常类 当java中的异常类型没有能够满足我们所需的异常的时候就 ...

  8. spring MVC中传递的参数对象中包含list的情况

    测试需要的jar包:spring 3.2.jar +  jackson-all-1.8.5.jar. 写代码时碰到个需要将对象里的子明细一起传递到controller里去,当时就想直接将参数一起传递过 ...

  9. 像网页开发一样调试ios程序

    PonyDebugger https://github.com/square/PonyDebugger

  10. DVWA默认用户名密码

    有些东西不好找啊,自己动手丰衣足食-- DVWA默认的用户有5个,用户名密码如下(一个足以): admin/password gordonb/abc123 1337/charley pablo/let ...