boost asio 应用方法学(二)——深入框架

2023-12-06 20:32

本文主要是介绍boost asio 应用方法学(二)——深入框架,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

要用好它,就必须先了解它,而且不能停止于表面,必须深入到内部。而了解一件事物,先要了解它的框架,再了解它的细节。了解了框架,我们就有了提纲挈领的认识。

关于 boost asio 框架结构,在其文档中,用了这样一张图来描述:

 

proactor

简单解释一下:

这里由使用者(Initiator)启动一个异步操作(Asynchronous Operation),在启动异步的同时它要负责创建一个异步回调对象(Completion Handler),然后该异步操作被交给了异步操作执行者(Asynchronous Operation Processor),由它负责执行异步操作,并在完成后将一个完成事件插入完成事件队列(Completion Event Queue);另一方面,前摄器(Proactor,这个词很难准确翻译,也有翻译为主动器,可能借义于proactive)驱动异步事件分派器(Asynchronous Event Demultiplexer)从完成事件队列中获取事件,这是一个阻塞的过程,一旦获取到完成事件,前摄器从事件上找出与该事件关联的回调对象,并执行回调。

这是一个标准的前摄器模式,这个模式是在 ACE 网络库中使用的概念。关于该模式的研究很多,搜索一下 ACE Proactor 就可以找到很多资料。上面的描述也比较容易理解,唯一比较难搞懂的是异步事件分派器(Asynchronous Event Demultiplexer),好像它的存在并不起多大作用,其实它的作用大着呢,特别是在多线程中,它要保证异步完成事件的及时分派,提高多线程并发度,以及降低线程切换开销。在 windows 完成端口的文档中有这方面的机制介绍。

总得来说,这是一个概念性的模型,仅用这个模型来描述 boost asio,根本体现不了 boost asio 的优点。即使从使用者的角度,仅掌握这样的模型也是不够,boost asio 还有很多值得学习借鉴的地方。

我们需要结合这个模型来深入 boost asio 的实现框架。

 boost asio 是如何实现前摄器模式的呢?我们使用 boost asio 第一步都需要创建一个 boost::asio::io_service,我们就从 io_service 开始开启我们的探秘之旅。

 io_service 类的定义很简单,总共就三个成员变量:

[cpp] view plain copy
  1. #if defined(BOOST_WINDOWS) || defined(__CYGWIN__)  
  2.   detail::winsock_init<> init_;  
  3. #elif defined(__sun) || defined(__QNX__) || defined(__hpux) || defined(_AIX) \  
  4.   || defined(__osf__)  
  5.   detail::signal_init<> init_;  
  6. #endif  
  7.   
  8.   // The service registry.  
  9.   boost::asio::detail::service_registry* service_registry_;  
  10.   
  11.   // The implementation.  
  12.   impl_type& impl_;  


其实简单反而意味着强大,因为这表明 boost asio 已经把功能结构划分的很清晰了。

三个成员变量中的 init_ 与结构没有太大关系,windows 平台的 winsock_init 里面调用了 WSAStartup,linux 平台的 signal_init 里面屏蔽了 SIG_PIPE 信号。这两个东东几乎是在我们刚开始接触网络编程就遇到的知识点。

其次我们再来看看 impl_ 成员,从名字上看应该是 io_service 的实现类,确实 io_service 的很多接口是直接转发给了 impl_ 成员,比如 run、poll、stop、reset、post、dispatch

[cpp] view plain copy
  1. inline std::size_t io_service::run(boost::system::error_code& ec)  
  2. {  
  3.   return impl_.run(ec);  
  4. }  
  5. inline std::size_t io_service::poll(boost::system::error_code& ec)  
  6. {  
  7.   return impl_.poll(ec);  
  8. }  
  9. inline void io_service::stop()  
  10. {  
  11.   impl_.stop();  
  12. }  
  13.   
  14. inline void io_service::reset()  
  15. {  
  16.   impl_.reset();  
  17. }  
  18.   
  19. template <typename Handler>  
  20. inline void io_service::dispatch(Handler handler)  
  21. {  
  22.   impl_.dispatch(handler);  
  23. }  
  24.   
  25. template <typename Handler>  
  26. inline void io_service::post(Handler handler)  
  27. {  
  28.   impl_.post(handler);  
  29. }  

但是 impl_type 是什么呢?它的定义如下:

[cpp] view plain copy
  1. #if defined(BOOST_ASIO_HAS_IOCP)  
  2.   typedef detail::win_iocp_io_service impl_type;  
  3. #elif defined(BOOST_ASIO_HAS_EPOLL)  
  4.   typedef detail::task_io_service<detail::epoll_reactor<false> > impl_type;  
  5. #elif defined(BOOST_ASIO_HAS_KQUEUE)  
  6.   typedef detail::task_io_service<detail::kqueue_reactor<false> > impl_type;  
  7. #elif defined(BOOST_ASIO_HAS_DEV_POLL)  
  8.   typedef detail::task_io_service<detail::dev_poll_reactor<false> > impl_type;  
  9. #else  
  10.   typedef detail::task_io_service<detail::select_reactor<false> > impl_type;  
  11. #endif  


原来是根据不同的平台支持特性,选择了不同的实现,要把这么多种实现融合起来,没有一个很好的架构是很难做到的。

我们再来看看 service_registry_,对这个成员变量的使用体现在 use_service、add_service、has_service 三个函数中:

[cpp] view plain copy
  1. template <typename Service>  
  2. inline Service& use_service(io_service& ios)  
  3. {  
  4.   // Check that Service meets the necessary type requirements.  
  5.   (void)static_cast<io_service::service*>(static_cast<Service*>(0));  
  6.   (void)static_cast<const io_service::id*>(&Service::id);  
  7.   
  8.   return ios.service_registry_->template use_service<Service>();  
  9. }  
  10.   
  11. template <typename Service>  
  12. void add_service(io_service& ios, Service* svc)  
  13. {  
  14.   // Check that Service meets the necessary type requirements.  
  15.   (void)static_cast<io_service::service*>(static_cast<Service*>(0));  
  16.   (void)static_cast<const io_service::id*>(&Service::id);  
  17.   
  18.   if (&ios != &svc->io_service())  
  19.     boost::throw_exception(invalid_service_owner());  
  20.   if (!ios.service_registry_->template add_service<Service>(svc))  
  21.     boost::throw_exception(service_already_exists());  
  22. }  
  23.   
  24. template <typename Service>  
  25. bool has_service(io_service& ios)  
  26. {  
  27.   // Check that Service meets the necessary type requirements.  
  28.   (void)static_cast<io_service::service*>(static_cast<Service*>(0));  
  29.   (void)static_cast<const io_service::id*>(&Service::id);  
  30.   
  31.   return ios.service_registry_->template has_service<Service>();  
  32. }  


看起来是个集合容器,里面的元素是服务(Service),服务有编号(id)。从 service_registry 的实现可以进一步了解到下面细节:

  1. 服务都是用一个类来实现的,唯一的接口要求是必须有 shutdown_service 接口
  2. 服务是延迟创建的,只有第一次被使用的时候才创建
  3. 每种类型的服务在同一个 service_registry (也是同一个io_service)里面最多只有一个实例
  4. 当service_registry (也就是io_service)被销毁(析构)时,服务会被 shutdown ,然后被销毁

 继续分析每个服务,还可以看到服务有下列特性:

  1. 服务在构造后就开始运作
  2. 服务在 shutdown 后停止运作
  3. 服务通过句柄(implementation_type)对外暴露其功能
  4. 服务之间有依赖性

最后,总结出所有的服务大概分为三类:

第一类服务是底层系统服务,是对操作系统平台提供功能的封装,有:

  • detail::win_iocp_io_service
  • detail::win_iocp_socket_service
  • detail:: task_io_service
  • detail::reactive_socket_service
  • detail::epoll_reactor
  • detail::kqueue_reactor
  • detail::dev_poll_reactor
  • detail::select_reactor
  • detail::resolver_service

中间四个都是 reactor,不能想象,所有的 reactor 应该有相同的对外服务接口。这里的 task_io_service 和 reactive_socket_service 是对 reactor 的再封装,所以上层的服务不会直接依赖 reactor,而是依赖 task_io_service 和 reactive_socket_service。

第二类服务是上层接口服务,面向具体的功能对象(Object),他们会针对运行平台选择依赖对应的底层服务

  • socket_acceptor_service(ip::basic_socket_acceptor -> ip::tcp::acceptor)
  • stream_socket_service(ip::basic_stream_socket -> ip::tcp::socket)
  • datagram_socket_service(ip::basic_datagram_socket -> ip::udp::socket)
  • raw_socket_service(ip::basic_raw_socket -> ip::icmp::socket)
  • deadline_timer_service(basic_deadline_timer -> deadline_timer)
    • detail::deadline_timer_service
  • ip::resolver_service(ip::basic_resolver -> ip::tcp::resolver, ip::udp::resolver, ip::icmp::resolver)

 前四个 socket 相关的服务会在不同的平台,选择依赖 win_iocp_socket_service 和 reactive_socket_service<xxx_reactor>,deadline_timer_service (具体实现在

detail::deadline_timer_service中)会选择 win_iocp_io_service 和 task_io_service<xxx_reactor>,ip::resolver_service 没得选择,只有依赖 detail::resolver_service。

第三类服务是一些特殊功能的服务,比如 detail::strand_service 等,他们对整体框架没有影响。

虽然 boost asio 中提供了这么多服务,但是上层应用并不会直接使用这些服务,服务通过句柄对外暴露其功能,而句柄被功能对象(Object)封装,然后提供给上层应用使用。

这里的功能对象(Object),就是我们在第二类服务后面的“()”里面给出的类,每个对象都包含着一个对相应服务的C++引用,以及服务对外暴露的句柄。

至此,我们了解了 boost asio 中通过一系列的服务封装了操作系统的底层功能,并且通过动态组装的方式把这些服务管理起来。可以看出,boost asio 使用了一种相当简单的方式,就解决了平台的多样性,甚至于模式的多样性;同时服务的动态加载和集中管理,为功能扩展提供了方便途径;另外对象中只包含句柄,提高了安全性。

再回过头来看看,我们心中还有个疑问,那就是 boost asio 是怎么实现前摄器(Proactor)模式的呢?其实前摄器(Proactor)的各个角色都是通过服务来表达的。

先看 windows 平台

Asynchronous Operation Processor 的功能是由 win_iocp_socket_service、resolver_service 完成,Proactor 功能由 win_iocp_io_service 完成,win_iocp_io_service 也包含 Asynchronous Event Demultiplexer 和 Completion Event Queue 的功能,不过其实是对 windows IOCP 的系统功能的封装。

再看看 linux 平台

Asynchronous Operation Processor 的功能是由 reactive_socket_service、resolver_service 完成,Proactor、Asynchronous Event Demultiplexer 和 Completion Event Queue功能都是由 task_io_service 完成。

最后,io_service 其实代表了Proator 这一端,socket、timer、resolver 等等代表了 Initiator 这一端。这就是上层使用者角度看到的景象。

这篇关于boost asio 应用方法学(二)——深入框架的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/463217

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

zoj3820(树的直径的应用)

题意:在一颗树上找两个点,使得所有点到选择与其更近的一个点的距离的最大值最小。 思路:如果是选择一个点的话,那么点就是直径的中点。现在考虑两个点的情况,先求树的直径,再把直径最中间的边去掉,再求剩下的两个子树中直径的中点。 代码如下: #include <stdio.h>#include <string.h>#include <algorithm>#include <map>#

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

AI行业应用(不定期更新)

ChatPDF 可以让你上传一个 PDF 文件,然后针对这个 PDF 进行小结和提问。你可以把各种各样你要研究的分析报告交给它,快速获取到想要知道的信息。https://www.chatpdf.com/

ASIO网络调试助手之一:简介

多年前,写过几篇《Boost.Asio C++网络编程》的学习文章,一直没机会实践。最近项目中用到了Asio,于是抽空写了个网络调试助手。 开发环境: Win10 Qt5.12.6 + Asio(standalone) + spdlog 支持协议: UDP + TCP Client + TCP Server 独立的Asio(http://www.think-async.com)只包含了头文件,不依