7.5. 开发 Boost.Asio 扩展

虽然 Boost.Asio 主要是支持网络功能的,但是加入其它 I/O 对象以执行其它的异步操作也非常容易。 本节将介绍 Boost.Asio 扩展的一个总体布局。 虽然这不是必须的,但它为其它扩展提供了一个可行的框架作为起点。

要向 Boost.Asio 中增加新的异步操作,需要实现以下三个类:

  • 一个派生自 boost::asio::basic_io_object 的类,以表示新的 I/O 对象。使用这个新的 Boost.Asio 扩展的开发者将只会看到这个 I/O 对象。

  • 一个派生自 boost::asio::io_service::service 的类,表示一个服务,它被注册为 I/O 服务,可以从 I/O 对象访问它。 服务与 I/O 对象之间的区别是很重要的,因为在任意给定的时间点,每个 I/O 服务只能有一个服务实例,而一个服务可以被多个 I/O 对象访问。

  • 一个不派生自任何其它类的类,表示该服务的具体实现。 由于在任意给定的时间点每个 I/O 服务只能有一个服务实例,所以服务会为每个 I/O 对象创建一个其具体实现的实例。 该实例管理与相应 I/O 对象有关的内部数据。

本节中开发的 Boost.Asio 扩展并不仅仅提供一个框架,而是模拟一个可用的 boost::asio::deadline_timer 对象。 它与原来的 boost::asio::deadline_timer 的区别在于,计时器的时长是作为参数传递给 wait()async_wait() 方法的,而不是传给构造函数。

  1. #include <boost/asio.hpp>
  2. #include <cstddef>
  3.  
  4. template <typename Service>
  5. class basic_timer
  6. : public boost::asio::basic_io_object<Service>
  7. {
  8. public:
  9. explicit basic_timer(boost::asio::io_service &io_service)
  10. : boost::asio::basic_io_object<Service>(io_service)
  11. {
  12. }
  13.  
  14. void wait(std::size_t seconds)
  15. {
  16. return this->service.wait(this->implementation, seconds);
  17. }
  18.  
  19. template <typename Handler>
  20. void async_wait(std::size_t seconds, Handler handler)
  21. {
  22. this->service.async_wait(this->implementation, seconds, handler);
  23. }
  24. };

每个 I/O 对象通常被实现为一个模板类,要求以一个服务来实例化 - 通常就是那个特定为此 I/O 对象开发的服务。 当一个 I/O 对象被实例化时,该服务会通过父类 boost::asio::basic_io_object 自动注册为 I/O 服务,除非它之前已经注册。 这样可确保任何 I/O 对象所使用的服务只会每个 I/O 服务只注册一次。

在 I/O 对象的内部,可以通过 service 引用来访问相应的服务,通常的访问就是将方法调用前转至该服务。 由于服务需要为每一个 I/O 对象保存数据,所以要为每一个使用该服务的 I/O 对象自动创建一个实例。 这还是在父类 boost::asio::basicio_object 的帮助下实现的。 实际的服务实现被作为一个参数传递给任一方法调用,使得服务可以知道是哪个 I/O 对象启动了这次调用。 服务的具体实现是通过 _implementation 属性来访问的。

一般一上谕,I/O 对象是相对简单的:服务的安装以及服务实现的创建都是由父类 boost::asio::basic_io_object 来完成的,方法调用则只是前转至相应的服务;以 I/O 对象的实际服务实现作为参数即可。

  1. #include <boost/asio.hpp>
  2. #include <boost/thread.hpp>
  3. #include <boost/bind.hpp>
  4. #include <boost/scoped_ptr.hpp>
  5. #include <boost/shared_ptr.hpp>
  6. #include <boost/weak_ptr.hpp>
  7. #include <boost/system/error_code.hpp>
  8.  
  9. template <typename TimerImplementation = timer_impl>
  10. class basic_timer_service
  11. : public boost::asio::io_service::service
  12. {
  13. public:
  14. static boost::asio::io_service::id id;
  15.  
  16. explicit basic_timer_service(boost::asio::io_service &io_service)
  17. : boost::asio::io_service::service(io_service),
  18. async_work_(new boost::asio::io_service::work(async_io_service_)),
  19. async_thread_(boost::bind(&boost::asio::io_service::run, &async_io_service_))
  20. {
  21. }
  22.  
  23. ~basic_timer_service()
  24. {
  25. async_work_.reset();
  26. async_io_service_.stop();
  27. async_thread_.join();
  28. }
  29.  
  30. typedef boost::shared_ptr<TimerImplementation> implementation_type;
  31.  
  32. void construct(implementation_type &impl)
  33. {
  34. impl.reset(new TimerImplementation());
  35. }
  36.  
  37. void destroy(implementation_type &impl)
  38. {
  39. impl->destroy();
  40. impl.reset();
  41. }
  42.  
  43. void wait(implementation_type &impl, std::size_t seconds)
  44. {
  45. boost::system::error_code ec;
  46. impl->wait(seconds, ec);
  47. boost::asio::detail::throw_error(ec);
  48. }
  49.  
  50. template <typename Handler>
  51. class wait_operation
  52. {
  53. public:
  54. wait_operation(implementation_type &impl, boost::asio::io_service &io_service, std::size_t seconds, Handler handler)
  55. : impl_(impl),
  56. io_service_(io_service),
  57. work_(io_service),
  58. seconds_(seconds),
  59. handler_(handler)
  60. {
  61. }
  62.  
  63. void operator()() const
  64. {
  65. implementation_type impl = impl_.lock();
  66. if (impl)
  67. {
  68. boost::system::error_code ec;
  69. impl->wait(seconds_, ec);
  70. this->io_service_.post(boost::asio::detail::bind_handler(handler_, ec));
  71. }
  72. else
  73. {
  74. this->io_service_.post(boost::asio::detail::bind_handler(handler_, boost::asio::error::operation_aborted));
  75. }
  76. }
  77.  
  78. private:
  79. boost::weak_ptr<TimerImplementation> impl_;
  80. boost::asio::io_service &io_service_;
  81. boost::asio::io_service::work work_;
  82. std::size_t seconds_;
  83. Handler handler_;
  84. };
  85.  
  86. template <typename Handler>
  87. void async_wait(implementation_type &impl, std::size_t seconds, Handler handler)
  88. {
  89. this->async_io_service_.post(wait_operation<Handler>(impl, this->get_io_service(), seconds, handler));
  90. }
  91.  
  92. private:
  93. void shutdown_service()
  94. {
  95. }
  96.  
  97. boost::asio::io_service async_io_service_;
  98. boost::scoped_ptr<boost::asio::io_service::work> async_work_;
  99. boost::thread async_thread_;
  100. };
  101.  
  102. template <typename TimerImplementation>
  103. boost::asio::io_service::id basic_timer_service<TimerImplementation>::id;

为了与 Boost.Asio 集成,一个服务必须符合几个要求:

  • 它必须派生自 boost::asio::io_service::service。 构造函数必须接受一个指向 I/O 服务的引用,该 I/O 服务会被相应地传给 boost::asio::io_service::service 的构造函数。

  • 任何服务都必须包含一个类型为 boost::asio::ioservice::id 的静态公有属性 _id。在 I/O 服务的内部是用该属性来识别服务的。

  • 必须定义两个名为 construct()destruct() 的公有方法,均要求一个类型为 implementation_type 的参数。 implementation_type 通常是该服务的具体实现的类型定义。 正如上面例子所示,在 construct() 中可以很容易地使用一个 boost::shared_ptr 对象来初始化一个服务实现,以及在 destruct() 中相应地析构它。 由于这两个方法都会在一个 I/O 对象被创建或销毁时自动被调用,所以一个服务可以分别使用 construct()destruct() 为每个 I/O 对象创建和销毁服务实现。

  • 必须定义一个名为 shutdown_service() 的方法;不过它可以是私有的。 对于一般的 Boost.Asio 扩展来说,它通常是一个空方法。 只有与 Boost.Asio 集成得非常紧密的服务才会使用它。 但是这个方法必须要有,这样扩展才能编译成功。

为了将方法调用前转至相应的服务,必须为相应的 I/O 对象定义要前转的方法。 这些方法通常具有与 I/O 对象中的方法相似的名字,如上例中的 wait()async_wait()。 同步方法,如 wait(),只是访问该服务的具体实现去调用一个阻塞式的方法,而异步方法,如 async_wait(),则是在一个线程中调用这个阻塞式方法。

在线程的协助下使用异步操作,通常是通过访问一个新的 I/O 服务来完成的。 上述例子中包含了一个名为 asyncioservice_ 的属性,其类型为 boost::asio::io_service。 这个 I/O 服务的 run() 方法是在它自己的线程中启动的,而它的线程是在该服务的构造函数内部由类型为 boost::thread 的 _async_thread 创建的。 第三个属性 _async_work 的类型为 boost::scoped_ptr<boost::asio::io_service::work>,用于避免 run() 方法立即返回。 否则,这可能会发生,因为已没有其它的异步操作在创建。 创建一个类型为 boost::asio::io_service::work 的对象并将它绑定至该 I/O 服务,这个动作也是发生在该服务的构造函数中,可以防止 run() 方法立即返回。

一个服务也可以无需访问它自身的 I/O 服务来实现 - 单线程就足够的。 为新增的线程使用一个新的 I/O 服务的原因是,这样更简单: 线程间可以用 I/O 服务来非常容易地相互通信。 在这个例子中,async_wait() 创建了一个类型为 wait_operation 的函数对象,并通过 post() 方法将它传递给内部的 I/O 服务。 然后,在用于执行这个内部 I/O 服务的 run() 方法的线程内,调用该函数对象的重载 operator()()post() 提供了一个简单的方法,在另一个线程中执行一个函数对象。

waitoperation 的重载 operator()() 操作符基本上就是执行了和 wait() 方法相同的工作:调用服务实现中的阻塞式 wait() 方法。 但是,有可能这个 I/O 对象以及它的服务实现在这个线程执行 operator()() 操作符期间被销毁。 如果服务实现是在 destruct() 中销毁的,则 operator()() 操作符将不能再访问它。 这种情形是通过使用一个弱指针来防止的,从第一章中我们知道:如果在调用 lock() 时服务实现仍然存在,则弱指针 impl 返回它的一个共享指针,否则它将返回0。 在这种情况下,operator()() 不会访问这个服务实现,而是以一个 boost::asio::error::operation_aborted 错误来调用句柄。

  1. #include <boost/system/error_code.hpp>
  2. #include <cstddef>
  3. #include <windows.h>
  4.  
  5. class timer_impl
  6. {
  7. public:
  8. timer_impl()
  9. : handle_(CreateEvent(NULL, FALSE, FALSE, NULL))
  10. {
  11. }
  12.  
  13. ~timer_impl()
  14. {
  15. CloseHandle(handle_);
  16. }
  17.  
  18. void destroy()
  19. {
  20. SetEvent(handle_);
  21. }
  22.  
  23. void wait(std::size_t seconds, boost::system::error_code &ec)
  24. {
  25. DWORD res = WaitForSingleObject(handle_, seconds * 1000);
  26. if (res == WAIT_OBJECT_0)
  27. ec = boost::asio::error::operation_aborted;
  28. else
  29. ec = boost::system::error_code();
  30. }
  31.  
  32. private:
  33. HANDLE handle_;
  34. };

服务实现 timer_impl 使用了 Windows API 函数,只能在 Windows 中编译和使用。 这个例子的目的只是为了说明一种潜在的实现。

timer_impl 提供两个基本方法:wait() 用于等待数秒。 destroy() 则用于取消一个等待操作,这是必须要有的,因为对于异步操作来说,wait() 方法是在其自身的线程中调用的。 如果 I/O 对象及其服务实现被销毁,那么阻塞式的 wait() 方法就要尽使用 destroy() 来取消。

这个 Boost.Asio 扩展可以如下使用。

  1. #include <boost/asio.hpp>
  2. #include <iostream>
  3. #include "basic_timer.hpp"
  4. #include "timer_impl.hpp"
  5. #include "basic_timer_service.hpp"
  6.  
  7. void wait_handler(const boost::system::error_code &ec)
  8. {
  9. std::cout << "5 s." << std::endl;
  10. }
  11.  
  12. typedef basic_timer<basic_timer_service<> > timer;
  13.  
  14. int main()
  15. {
  16. boost::asio::io_service io_service;
  17. timer t(io_service);
  18. t.async_wait(5, wait_handler);
  19. io_service.run();
  20. }

与本章开始的例子相比,这个 Boost.Asio 扩展的用法类似于 boost::asio::deadline_timer。 在实践上,应该优先使用 boost::asio::deadline_timer,因为它已经集成在 Boost.Asio 中了。 这个扩展的唯一目的就是示范一下 Boost.Asio 是如何扩展新的异步操作的。

目录监视器(Directory Monitor) 是现实中的一个 Boost.Asio 扩展,它提供了一个可以监视目录的 I/O 对象。 如果被监视目录中的某个文件被创建、修改或是删除,就会相应地调用一个句柄。 当前的版本支持 Windows 和 Linux (内核版本 2.6.13 或以上)。