(原创)拨开迷雾见月明-剖析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中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最基本的东西,再来看一下面试笔试中经常遇到的与装箱 ...
随机推荐
- Log4j 把不同包的日志打印到不同位置
如果需要将不同的日志打印到不同的地方,则需要定义不同的Appender,然后定义每一个 Appender的日志级别.打印形式.输出位置! 配置log4j.properties文件如下: ####### ...
- 微信小程序支付源码,后台服务端代码
作者:尹华南,来自原文地址 微信小程序支付绕坑指南 步骤 A:小程序向服务端发送商品详情.金额.openid B:服务端向微信统一下单 C:服务器收到返回信息二次签名发回给小程序 D:小程序发起支付 ...
- Echart示例
echart.html: 需要注意js文件加载的顺序 <!DOCTYPE html> <html lang="en"> <head> < ...
- Librec的AoBPR算法实现
Librec的AoBPR算法实现:(基于1.3版本) 要用AoBPR,但是没有找到相应的配置文件,应该怎么办呢? ——因为用的是1.3版本,所以没有,2.0版本有的.[跟BPR参数一样,就 ...
- Spring异常解决 java.lang.NullPointerException,配置spring管理hibernate时出错
@Repository public class SysUerCDAO { @Autowired private Hibernate_Credit hibernate_credit; /** * 根据 ...
- 老男孩linux实训学生入学资格考试题(技术部分)
################################################################ 本文内容摘录于老男孩linux实战运维培训中心入学考试题(答案部分) ...
- 树莓派进阶之路 (029) - 语音识别模块 LD3320(原创)
近几天听朋友有说到LD3320 语音模块,刚好身边有块树莓派3,就在某宝上买了块自带mcu的LD3320 . 准备: 树莓派一个(配置了wiringPi开发环境的详情见本人博客:树莓派进阶之路 (00 ...
- errno.h - C Error Codes in Linux
All the Linux/C error codes are listed below. I occasionally google C error codes, but always end up ...
- React(0.13) 服务端渲染的两个函数
1.React.renderToString 函数, 参数是组件,返回一个字符串 <!DOCTYPE html> <html> <head> <title& ...
- 【HTML】网页中如何让DIV在网页滚动到特定位置时出现
用js或者jquery比较好实现.但你要知道,滚动到哪个特定位置,例如滚动到一个标题h3那显示这个div,那么可以用jquery算这个h3距离网页顶部的距离:$("h3").off ...