(原创)拨开迷雾见月明-剖析asio中的proactor模式(一)
使用asio之前要先对它的设计思想有所了解,了解设计思想将有助于我们理解和应用asio。asio是基于proactor模式的,asio的proactor模式隐藏于大量的细节当中,要找到它的踪迹,往往有种只见树木不见森林之感,笔者将剖析asio中的proactor模式,一步一步揭开它的面纱,最终拨开云雾,将一个完整的proactor模式还原出来。在剖析asio的proactor模式之前,我们先来看看常见的io设计模式。proactor(主动器)模式是一种重要的I/O设计模式,用来解决高并发网络中遇到的问题,另外还有一种模式是reactor(反应器),libevent是基于reactor实现的,让我们先看看这两种模式的一些特点。
反应器和主动器模式介绍
反应器需要应用程序先注册事件处理器,然后启动反应器的事件循环,不断的检查是否有就绪I/O事件,当有就绪事件时,同步事件多路分解器将会返回到反应器,反应器会将事件分发给多个句柄的回调函数以处理这些事件。【1】
反应器的一个特点是,具体的处理程序并不调用反应器,而是由反应器来通知处理程序去处理事件。这种方式也被称为“控制反转”,又称为“好莱坞原则”。下面是反应器模式的类图:

反应器模式大概的流程是这样的:
- 应用程序在反应器上注册具体事件处理器,处理器提供内部句柄给反应器。
- 注册之后,应用程序开始启动反应器事件循环。反应器将通过select等待发生在句柄集上的就绪事件。
- 当一个或多个句柄变成就绪状态时(比如某个socket读就绪了),反应器将通知注册的事件处理器。
- 事件处理器处理就绪事件,完成用户请求。
反应器模式使用起来相对直观,但是它不能同时支持大量的客户请求或者耗时过长的请求,因为它串行化了所有的事件处理过程。而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》
如果你觉得这篇文章对你有用,可以点一下推荐,谢谢。
(原创)拨开迷雾见月明-剖析asio中的proactor模式(一)的更多相关文章
- (原创)拨开迷雾见月明-剖析asio中的proactor模式(二)
在上一篇博文中我们提到异步请求是从上层开始,一层一层转发到最下面的服务层的对象win_iocp_socket_service,由它将请求转发到操作系统(调用windows api),操作系统处理完异步 ...
- boost.asio源码剖析(四) ---- asio中的泛型概念(concepts)
* Protocol(通信协议) Protocol,是asio在网络编程方面最重要的一个concept.在第一章中的levelX类图中可以看到,所有提供网络相关功能的服务和I/O对象都需要Protoc ...
- 深入剖析Java中的装箱和拆箱
深入剖析Java中的装箱和拆箱 自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最基本的东西,再来看一下面试笔试中经常遇到的与装箱 ...
- 【翻译】Anatomy of a Program in Memory—剖析内存中的一个程序(进程的虚拟存储器映像布局详解)
[翻译]Anatomy of a Program in Memory—剖析内存中的一个程序(进程的虚拟存储器映像布局详解) . . .
- 从别人那淘的知识 深入剖析Java中的装箱和拆箱
(转载的海子的博文 海子:http://www.cnblogs.com/dolphin0520/) 深入剖析Java中的装箱和拆箱 自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来 ...
- 月半小夜曲下的畅想--DOCTYPE模式
月半小夜曲下的畅想--DOCTYPE模式 @(css3 box-sizing)[doctype声明|quirks模式|妙瞳] DOCTYPE文档类型标签,该标签是将特定的标准通用标记语言或者XML文档 ...
- 深入剖析Java中的自动装箱和拆箱过程
深入剖析Java中的装箱和拆箱 自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最基本的东西,再来看一下面试笔试中经常遇到的与装箱 ...
- [ 转载 ]学习笔记-深入剖析Java中的装箱和拆箱
深入剖析Java中的装箱和拆箱 自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最基本的东西,再来看一下面试笔试中经常遇到的与装箱 ...
- 【转】深入剖析Java中的装箱和拆箱
深入剖析Java中的装箱和拆箱 自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最基本的东西,再来看一下面试笔试中经常遇到的与装箱 ...
随机推荐
- Fiddler配置https
问题描述: fiddler加载认证不成功... 问题解决: 手工生成认证证书 00.配置HTTPS 01.勾选https 02.添加ssh认证 11. 找到fiddler的安装目录 手工生成认证秘钥 ...
- 如何在 CentOS 7 中安装或升级最新的内核
虽然有些人使用 Linux 来表示整个操作系统,但要注意的是,严格地来说,Linux 只是个内核.另一方面,发行版是一个完整功能的系统,它建立在内核之上,具有各种各样的应用程序工具和库. 在正常操作期 ...
- 使用 kubectl drain 从集群中移除节点
对节点执行维护操作之前(例如:内核升级,硬件维护等),您可以使用 kubectl drain 安全驱逐节点上面所有的 pod.安全驱逐的方式将会允许 pod 里面的容器遵循指定的 PodDisrupt ...
- 系统监控nagios–安装
安装:环境:CentOS6.0 32bit 1.先相关软件包 yum install httpd php gcc glibc glibc-common gd gd-devel make 2.创建用户信 ...
- iphone 恢复出厂设置方法
1.下载安装并打开itunes. 2.让手机进入恢复模式: 一.先长按住电源键,出现关机选项时,请滑动关机: 二.随后再按电源键开机,屏幕会出现苹果标志,不要松开电源键: 三.接着再按住主屏 Home ...
- 树莓派进阶之路 (008) - 树莓派安装ftp服务器(转)
vsftpd是开源的轻量级的常用ftp服务器. 1,安装vsftpd服务器 (约400KB) sudo apt-get install vsftpd 2,启动ftp服务 sudo serv ...
- Windbg找出memory leak的一种笨办法
以下内容是转自 http://www.cnblogs.com/fbird/p/5889596.html 以前做项目碰到过一个问题,在客户的站点上面发现有严重的内存泄漏.幸运的是我们找到了重现的步骤,一 ...
- C++中四种类型转换方式(ynamic_cast,const_cast,static_cast,reinterpret_cast)
Q:什么是C风格转换?什么是static_cast, dynamic_cast 以及 reinterpret_cast?区别是什么?为什么要注意? A:转换的含义是通过改变一个变量的类型为别的类型从而 ...
- ELK 5.X版本遇到的坑
一.Kafka ->logstash ->elasticsearch logstash 5.X以上版本不兼容5.x以下版本,因此在升级logstash时会遇到很多坑.首先是配置的变化 ...
- 专注做好一件事(转) focus---这个世界上最成功的人,他们在某一领域获得成功之后,可通过经营杠杆进入任何他们想要涉足的领域。而这都得依赖于他们曾极致的专注在做好一件事情上。