• 7.5. 开发 Boost.Asio 扩展

    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 或以上)。