[关闭]
@khan-lau 2016-05-04T02:27:21.000000Z 字数 14659 阅读 3942

boost.asio 学习笔记

C++ Boost.ASIO


boost.asio为异步IO提供了一份标准的C++的跨平台实现,特别针对网络IO提供了良好的支持,使之成为C++网络编程利器。关于如何使用asio,boost文档中已经有了详尽说明,而且附带的例子也很直观,我们不必再造轮子;本文则结合asio的基本应用,侧重于源代码的分析,特别是针对windows平台上的实现进行分析。

纵观asio源码,在统一的接口层之下,asio提供了大量的类来支持不同的平台(Windows、Unix...)、不同的IO类型(同步、异步)及IO模型(IOCP、Select、Poll)及网络协议(TCP,UDP,ICMP)。归纳起来,这一大堆类可以分为三层,分别是:

IO Object层
basic_ 模版层
服务实现层

本系列文章从io_service类型入手,开启代码分析行程,采用自底向上的方法,逐层推导,最后得到asio的体系结构;如果想先了解asio的整体结构,可以先跳到asio体系结构部分。

(理论上来说,行文伊始还是该先举个简单的例子说一下asio如何使用,不过这样的例子少说也要六七十行代码,为节约篇幅就不贴了;真想先看一个完整例子的,烦请移步下面链接瞧下那只麻雀:

  1. //
  2. // server.cpp
  3. // ~~~~~~~~~~
  4. //
  5. // Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
  6. //
  7. // Distributed under the Boost Software License, Version 1.0. (See accompanying
  8. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  9. //
  10. #include <ctime>
  11. #include <iostream>
  12. #include <string>
  13. #include <boost/bind.hpp>
  14. #include <boost/shared_ptr.hpp>
  15. #include <boost/enable_shared_from_this.hpp>
  16. #include <boost/asio.hpp>
  17. using boost::asio::ip::tcp;
  18. std::string make_daytime_string() {
  19. using namespace std; // For time_t, time and ctime;
  20. time_t now = time(0);
  21. return ctime(&now);
  22. }
  23. class tcp_connection : public boost::enable_shared_from_this<tcp_connection> {
  24. public:
  25. typedef boost::shared_ptr<tcp_connection> pointer;
  26. static pointer create(boost::asio::io_service& io_service) {
  27. return pointer(new tcp_connection(io_service));
  28. }
  29. tcp::socket& socket() {
  30. return socket_;
  31. }
  32. void start() {
  33. message_ = make_daytime_string();
  34. boost::asio::async_write(socket_, boost::asio::buffer(message_),
  35. boost::bind(&tcp_connection::handle_write, shared_from_this(),
  36. boost::asio::placeholders::error,
  37. boost::asio::placeholders::bytes_transferred));
  38. }
  39. private:
  40. tcp_connection(boost::asio::io_service& io_service) : socket_(io_service) {
  41. }
  42. void handle_write(const boost::system::error_code& /*error*/, size_t /*bytes_transferred*/) {
  43. }
  44. tcp::socket socket_;
  45. std::string message_;
  46. };
  47. class tcp_server {
  48. public:
  49. tcp_server(boost::asio::io_service& io_service) : acceptor_(io_service, tcp::endpoint(tcp::v4(), 13)) {
  50. start_accept();
  51. }
  52. private:
  53. void start_accept() {
  54. tcp_connection::pointer new_connection =
  55. tcp_connection::create(acceptor_.get_io_service());
  56. acceptor_.async_accept(new_connection->socket(),
  57. boost::bind(&tcp_server::handle_accept, this, new_connection,
  58. boost::asio::placeholders::error));
  59. }
  60. void handle_accept(tcp_connection::pointer new_connection, const boost::system::error_code& error) {
  61. if (!error) {
  62. new_connection->start();
  63. }
  64. start_accept();
  65. }
  66. tcp::acceptor acceptor_;
  67. };
  68. int main() {
  69. try {
  70. boost::asio::io_service io_service;
  71. tcp_server server(io_service);
  72. io_service.run();
  73. } catch (std::exception& e) {
  74. std::cerr << e.what() << std::endl;
  75. }
  76. return 0;
  77. }

从第一个boost.asio的教程开始,boost文档就一直在告诉我们:使用boost.asio第一步就是要创建一个io_service对象。那么io_service是个什么东西呢?

boost.asio文档说,io_service为下面的这些异步IO对象提供最核心的IO功能:

接着,文档就会说,像下面这样,就可以简简单单声明一个io_service对象了:

  1. int main() {
  2. boost::asio::io_service io;

上面的一行代码,表明无限地风光与潇洒,看起来简简单单一行,就几乎有了异步IO、网络操作的框架,那么他到底代表了什么,在后面,又有什么事情发生呢?io_service又是如何支撑起这些功能的呢?带着这些问题,我们开始分析吧……

从C++的角度看,上面的这一行代码,无非就是定义了一个io_service的实例,而C++的底层机制所隐藏起来的东西,无非就是初始化该对象的所有数据成员。再来看io_service的声明,我们知道,该类除了一堆成员函数之外,事实上只有三个成员(暗想:这几个成员肯定很是神奇无匹了):

  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. // The service registry.
  8. boost::asio::detail::service_registry* service_registry_;
  9. // The implementation.
  10. impl_type& impl_;

暂时抛开那几个成员,再来看一下io_service比较重要的一个函数:run() 的实现,发现该函数也就是将真正的功能委托给成员impl_去做事儿了:

  1. std::size_t io_service::run() {
  2. boost::system::error_code ec;
  3. std::size_t s = impl_.run(ec);
  4. boost::asio::detail::throw_error(ec);
  5. return s;
  6. }

种种迹象表明,impl_是个巨牛的东西了。统揽io_service的实现,我们不难发现,该类的所有功能,几乎都是委托给了impl_成员去干了——典型的“有事秘书干”。不过想想也挺容易理解的,io_service提供了一个上层的接口,充当抛头露面的BOSS,真正的工作则委托给下一层的员工去实现——哪家公司不是这样呢?

所以,要想了解io_service的玄机,就需要弄清楚这三个数据成员到底是什么来历,特别是impl_的来历才行。
io_service的初始化

成员 init_ 在io_service的各个函数中,并没有显式用到。其存在的价值,就在于该类的构造函数里面,调用了初始化相关的代码,具体到Windows平台,就是WinSock的初始化,也就是调用 ::WSAStartup() 这一WinSock编程所必须调用的第一个函数;而其析构函数则进行清理工作,同样交由WinSock函数 :: WSACleanup() 完成。

也就是说,io_service通过init_数据成员的创建与销毁,自动完成了相关的初始化及清理工作。
io_service的实现委托

前面说过,impl_ 带着无限的神秘默默地完成了io_service::run()的功能。至于他到底是什么style,现在来揭开盖头吧。

从直接声明来看,impl_ 具有 impl_type& 类型。用SourceInsight不难发现该类型只不过是一个类型别名:

  1. typedef detail::io_service_impl impl_type;

一波未平一波又起,这儿又冒出个io_service_impl。继续刨根问底,找到:

  1. #if defined(BOOST_ASIO_HAS_IOCP)
  2. namespace detail { typedef win_iocp_io_service io_service_impl; }
  3. #else
  4. namespace detail { typedef task_io_service io_service_impl; }
  5. #endif

终于知道,在某些情况下,它是 win_iocp_ ,在某些情况下,是 task_ 。事实上,在Win NT 环境下,如果没有禁用IOCP,也就是没有声明 BOOST_ASIO_DISABLE_IOCP 这个预处理器指令,那么asio就采用 win_iocp_io_service 来做那些脏活累活;在剩下的其他平台,或者Win上禁用了IOCP,则使用 task_io_service 来做事儿了。

先稍微提一下,win_iocp_io_service 是对Windows环境下的IOCP(完成端口IO)模型的封装,该类作为boost.asio在Windows下的核心,我们在后面详细分析其实现。
io_service的服务管理

io_service的另外一个数据成员 service_registry_,又具备什么样的身份和功能呢?我们来看看 io_service 的构造函数:

  1. io_service::io_service() : service_registry_
  2. (
  3. new boost::asio::detail::service_registry(
  4. *this,
  5. static_cast<impl_type*>(0),
  6. (std::numeric_limits<std::size_t>::max)()
  7. )
  8. ),
  9. impl_(service_registry_->first_service<impl_type>())
  10. {
  11. }

构造函数为了初始化service_registry_,动态分配了一个boost::asio::detail:: service_registry类对象。为了构造该对象,提供了三个参数:

再来观察service_registry的实现,发现其实际就是一个链表,管理io_service所容纳的所有service对象(win_iocp_io_service就是一种service)。每种service都有一个id,链表以此id作为标志,在客户通过io_service来请求一种服务时,例如调用 use_service(io_service&) 时,service_registry会查找链表,如果有对应类型的服务,就返回该类型服务实例的指针;否则就创建一个新的对象,并加入到链表末端,再返回此新创建的实例;通过这种形式,io_service确保每种类型的服务都只有一个实例存在。

asio提供了这样几个函数,来进行service的管理和使用——这也为扩展asio提供了可能,例如可以自己定义一种服务,使用add_service加入io_service进行管理。

  1. template <typename Service>
  2. Service& use_service(io_service& ios);
  3. template <typename Service>
  4. void add_service(io_service& ios, Service* svc);
  5. template <typename Service>
  6. bool has_service(io_service& ios);

io_service::service类型及跨平台策略

boost::asio::io_service



+ size_t run()
+ size_t poll()
+ void dispatch()
+ void stop()

- detail::winsock_init init_
- detail::service_registry* service_registry_
- impl_type& impl_
class boost::asio::io_service::::id
class boost::asio::io_service::work
enum boost::asio::io_service::fork_event
class boost::asio::io_service::strand
class boost::asio::io_service::service
- struct key key_
- boost::asio::io_service& owner_;
- service* next_;

io_service内部定义了这样一些类型,来为其服务:

针对service类型,asio从其派生出了数十个类分别完成不同的功能,例如在Win上充当io_service的win_iocp_io_service类,以及为各种IO Object类型提供服务的类,如对应于TCP的stream_socket_service,对应于UDP的datagram_socket_ service。下图显示了asio常用到的服务类,及其针对不同平台的适配类之间的关系。

boost.asio 学习笔记02——io_service类

图中左边的类,会在我们的应用程序中直接用到(由于asio又对这些类提供了一层动态组装,所以代码中不会去直接声明这些类型的实例,但是剥掉动态组装的外衣,我们声明的仍然是这些类的实例,具体在下面一部分说明),作为 应用层 的类;而右边部分,则是针对不同平台所提供的不同实现,作为 平台适配层。

应用层类在编译时,根据所在平台(其实是喂给编译器的各种预处理宏,如BOOST_ASIO_ HAS_IOCP),选择对应的类进行编译。例如用于TCP的服务类stream_socket_service是这样进行选择的:

  1. template <typename Protocol>
  2. class stream_socket_service {
  3. private:
  4. // The type of the platform-specific implementation.
  5. #if defined(BOOST_ASIO_HAS_IOCP)
  6. typedef detail::win_iocp_socket_service<Protocol> service_impl_type;
  7. #else
  8. typedef detail::reactive_socket_service<Protocol> service_impl_type;
  9. #endif
  10. // The platform-specific implementation.
  11. service_impl_type service_impl_;
  12. };

其他需要进行平台决策的类型,都是采用这种技术,来选择不同的实现的。

从io_service::service派生的完整的类列表如下:

还有几个非常重要的类,他们作为劳苦大众在金字塔底层默默提供service功能,但却没有从io_service::service派生;他们是上述那些服务类在各个平台的具体实现,为金字塔中间层的服务类提供再服务的(不难想象,提供服务的方式,又是那种“有事儿秘书干”的方式):

3. IO Objects

asio的文档,告诉我们在声明一个io_service对象之后,就可以创建io对象去干活了,例如:

  1. int main(int argc, char* argv[]) {
  2. boost::asio::io_service io_service;
  3. tcp::resolver resolver(io_service);
  4. tcp::resolver::query query("www.boost.org", "80");
  5. tcp::resolver::iterator iterator = resolver.resolve(query);

上图中main()的第二行代码声明了一个tcp::resolver对象,后续进行地址解析的逻辑,都是围绕此resolver对象展开的。那么这个resolver是一个什么样的类型呢?阅读源代码我们在boost::asio::ip::tcp类内部看到了这样的类型别名定义:

  1. typedef basic_resolver<tcp> resolver;

是否似曾相识呢? 是的,这和我们STL中的string, iostream等一样,使用的都是某个模板类的一个实例。这里的resolver是basic_resolver在tcp模版参数下的实例,string是basic_string在char模版参数下的实例——boost库和STL库统一风格的一个体现。

asio大量采用这种技术,所有和resolver一样提供io功能的类,都是某个baisc_ 模版类的实例化。下面我们来研究一下asio的io object逻辑。

io object 类关系网

继续向上追溯basic_resolver,知道该类从basic_io_object派生,主要负责地址解析相关的操作,提供的主要接口有resolver(), async_resolve()等。

查看整个asio的源代码,我们发现从basic_io_object派生的类不少,他们分别负责一些具体的事务,例如basic_socket_acceptor可以作为一个服务器进行侦听,提供了诸如bind(), listen()等接口;再如basic_socket类是对socket IO 操作的封装,提供了receive(), async_receive(), read_some(), async_readsome(), write_some(), async_write_some()等接口。

整个asio中的io object关系网,我们用下图来显示:
boost::asio IO Object关系图

这些io object的功能,简述如下:

另外一点,所有这些io object的构造函数,都要求有一个io_service& 作为参数,使用这一参数,这些io_object对象知道了自己的归属,之后自己所要派发出去的同步、异步操作请求,都将通过自己所在的这个io_service对象来完成。这也就说明了,为什么创建io_service对象是整个asio程序的第一步。

io object服务委托

上述的这些io object类,提供了应用开发中常用的各种“实际”功能,例如地址解析,以及socket读写等,那么这些io object类和第一部分中的service类之间存在着什么样的关系呢? 会不会是这些io object只是一个应用的接口,而具体的功能则委托给service类来完成呢? 如果是这样的,那么又是如何实现的呢?

带着这些问题,我们继续研究resolver类的源代码,看看他所提供的功能,到底是如何实现的。
resolver是如何委托的

我们知道,resolver类是在boost::asio::ip::tcp类中的一个类型别名:

  1. typedef basic_resolver<tcp> resolver;

那么,resolver类所提供的async_resolve()接口,就来自于basic_resolver类。再来看看basic_resolver::async_resolve()的实现——该函数有两个重载,我们以其中一个为例:

  1. template <typename ResolveHandler>
  2. void async_resolve(const query& q, BOOST_ASIO_MOVE_ARG(ResolveHandler) handler) {
  3. // If you get an error on the following line it means that your handler does
  4. // not meet the documented type requirements for a ResolveHandler.
  5. BOOST_ASIO_RESOLVE_HANDLER_CHECK(
  6. ResolveHandler, handler, iterator) type_check;
  7. return this->service.async_resolve(this->implementation, q,
  8. BOOST_ASIO_MOVE_CAST(ResolveHandler)(handler));
  9. }

可见,async_resolve()果然是委托给了某个service类来做事的——再一次,典型的有事儿秘书干。那么,这儿的“this->service” 又是什么呢?

通过跟踪代码的执行,知道该service其实是第一部分曾经提到过的boost::asio::ip::resolver_service,而它又委托给了boost::asio::detail::resolver_service——这家伙再无其他可以委托的对象了,只有苦逼的自己做事儿了——其基本思路就是先创建一个用以地址解析的resolve_op,用这个op来代表本次异步操作,之后启动op去做事情。至于op又是什么东西,稍后在operation部分做介绍;先贴出这段苦主:

  1. // boost::asio::detail::resolver_service
  2. template <typename Handler>
  3. void async_resolve(implementation_type& impl,
  4. const query_type& query, Handler handler)
  5. {
  6. // Allocate and construct an operation to wrap the handler.
  7. typedef resolve_op<Protocol, Handler> op;
  8. typename op::ptr p = { boost::addressof(handler),
  9. boost_asio_handler_alloc_helpers::allocate( sizeof(op), handler ), 0 };
  10. p.p = new (p.v) op(impl, query, io_service_impl_, handler);
  11. BOOST_ASIO_HANDLER_CREATION((p.p, "resolver", &impl, "async_resolve"));
  12. start_resolve_op(p.p);
  13. p.v = p.p = 0;
  14. }

PS:

OK,至此,整个async_resolve()从上到下就拉通了。概括起来,就是io object将具体功能委托给服务类,服务类又委托给和平台实现相关的服务类来完成最后的功能。

io_object的服务创建

通过resolver的执行,我们知道了其层层委托关系,那么resolver所委托的this->service又是怎么来的呢?下面部分,我们来分析io_object所做的服务管理工作。

首先来看该service的具体类型。

要想知道service是如何创建的,我们就要追根朔源找到resolver的具体类型声明,看看到底是如何将service拉上贼船的。好吧,我们再次从头开始(别嫌啰嗦):

  1. typedef basic_resolver<tcp> resolver;

针对这个模版实例,将basic_resolver展开:

  1. template <typename InternetProtocol,
  2. typename ResolverService = resolver_service<InternetProtocol> >
  3. class basic_resolver: public basic_io_object<ResolverService>
  4. template <tcp,
  5. typename ResolverService = resolver_service<tcp> >
  6. class basic_resolver: public basic_io_object< resolver_service<tcp> >

再对basic_io_object进行展开:

  1. template <typename IoObjectService>
  2. class basic_io_object
  3. template < resolver_service<tcp> >
  4. class basic_io_object

在basic_io_object内部展开:

  1. typedef IoObjectService service_type;
  2. typedef resolver_service<tcp> service_type;

于是,可以确定basic_io_object::service的类型为 resolver_service&;从而将io object和下层的service关联起来。

再来看basic_io_object::service的初始化。我们来看其构造函数:

  1. explicit basic_io_object(boost::asio::io_service& io_service)
  2. : service(boost::asio::use_service<IoObjectService>(io_service))
  3. {
  4. service.construct(implementation);
  5. }

很明显,在构造过程中,使用use_service的返回值来初始化该service成员;我们知道,use_service会在io_service所维护的service链表中查找该类型,如果没有,就创建一个新的service实例;在此,就可以确保resolve_service的实例已经被创建出来了,该服务就可以工作了(当然,不要忘记,该service事实上又委托了一层,而这个最底层的service实例,在此resolve_service的构建过程中,内部再次调用use_service()创建出来了。过程如下:

boost.asio学习笔记03——io objects2

除此之外,在构造函数体中,调用了service的construct函数,做进一步的初始化。(PS:是否有Symbian中的二段构造的影子呢?)

asio 的io逻辑总结

前面部分以resolver为例,分析了resolver的功能和对应service之间的关系,纵观所有的io object,都是采用了这种模式,总结起来有如下几点:

4. ASIO的体系结构

三层类关系图

根据前面的分析,我们知道asio有着这样的逻辑:

鉴于此,我们将asio体系划分为三层:io object层,basic_ 模版类层,服务层。

基本的体系结构关系如下图所示。 注意:图中并非全部asio中的类。

boost.asio 学习笔记04——asio的体系结构 - 地线 - 别再让虚假消息充斥这本已混乱的世界

动态组装

前面已经提过,resolver和STL中的string一样,都是使用了basic_模板类的一个具体实例。这种在编译时动态地选择对应的组件进行编译,我们姑且称为动态组装。这种技术使得我们可以对asio的service进行扩展。例如自己实现一个resolver的service类,然后告诉basic_resolver,你要使用自己的service类,而非默认的那个……

不过话说回来,自己对boost或者STL进行扩展,需要点实力的

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注