使用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. FTP下载工具

    开源的FTP下载工具,FTP搬运工.... 01.FileZilla_3.21.0_win64 官方地址: https://filezilla-project.org/ 下载地址: http://pa ...

  2. AllInOneConveter——编码转换工具

    一.Url编码解码 二.Base64编码解码 三.\u形式Unicode和汉互转 四.Md5加密 五.源代码 https://github.com/FrankFan/AllInOneConverter ...

  3. Notes of O_DIRECT flag

    What is O_DIRECT Starting with kernel 2.4, Linux allows an application to bypass the buffer cache wh ...

  4. 使用Cygwin登录Raspberry PI

    偿试了很多ssh终端程序,像ScureCRT,Putty,SSHSecureShellClient,SSH Client Tunnelier,每个工具都有自己的特点,putty对中文的支持还算好的,其 ...

  5. MATLAB 的数据导入与导出

    1 数据导入: %% 高层次读取数据. importdata 函数是一个高层次的函数 filename = 'weeklydata.txt'; delimiterIn =' '; %delimiter ...

  6. Zabbix 常见问题处理整理

    zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案. 下载: http://www.zabbix.com/download.php 帮助:https://www ...

  7. Kickstart无人值守安装[转载]

    导言 作为中小公司的运维,经常会遇到一些机械式的重复工作,例如:有时公司同时上线几十甚至上百台服务器,而且需要我们在短时间内完成系统安装. 常规的办法有什么? 光盘安装系统===>一个服务器DV ...

  8. linux 文件系统工作原理

    转:<http://linuxperf.com/?p=153> 一.概述 文件系统要解决的一个关键问题是怎样防止掉电或系统崩溃造成数据损坏,在此类意外事件中,导致文件系统损坏的根本原因在于 ...

  9. 【JQuery】jQuery(document).ready(function($) { });的几种表示方法及load和ready的区别

    jQuery中处理加载时机的几种方式 第一种: jQuery(document).ready(function() { alert("你好"); }); //或 $(documen ...

  10. OpenCV 学习笔记03 凸包convexHull、道格拉斯-普克算法Douglas-Peucker algorithm、approxPloyDP 函数

    凸形状内部的任意两点的连线都应该在形状里面. 1 道格拉斯-普克算法 Douglas-Peucker algorithm 这个算法在其他文章中讲述的非常详细,此处就详细撰述. 下图是引用维基百科的.ε ...