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()
方法的,而不是传给构造函数。
- #include <boost/asio.hpp>
- #include <cstddef>
- template <typename Service>
- class basic_timer
- : public boost::asio::basic_io_object<Service>
- {
- public:
- explicit basic_timer(boost::asio::io_service &io_service)
- : boost::asio::basic_io_object<Service>(io_service)
- {
- }
- void wait(std::size_t seconds)
- {
- return this->service.wait(this->implementation, seconds);
- }
- template <typename Handler>
- void async_wait(std::size_t seconds, Handler handler)
- {
- this->service.async_wait(this->implementation, seconds, handler);
- }
- };
每个 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 对象的实际服务实现作为参数即可。
- #include <boost/asio.hpp>
- #include <boost/thread.hpp>
- #include <boost/bind.hpp>
- #include <boost/scoped_ptr.hpp>
- #include <boost/shared_ptr.hpp>
- #include <boost/weak_ptr.hpp>
- #include <boost/system/error_code.hpp>
- template <typename TimerImplementation = timer_impl>
- class basic_timer_service
- : public boost::asio::io_service::service
- {
- public:
- static boost::asio::io_service::id id;
- explicit basic_timer_service(boost::asio::io_service &io_service)
- : boost::asio::io_service::service(io_service),
- async_work_(new boost::asio::io_service::work(async_io_service_)),
- async_thread_(boost::bind(&boost::asio::io_service::run, &async_io_service_))
- {
- }
- ~basic_timer_service()
- {
- async_work_.reset();
- async_io_service_.stop();
- async_thread_.join();
- }
- typedef boost::shared_ptr<TimerImplementation> implementation_type;
- void construct(implementation_type &impl)
- {
- impl.reset(new TimerImplementation());
- }
- void destroy(implementation_type &impl)
- {
- impl->destroy();
- impl.reset();
- }
- void wait(implementation_type &impl, std::size_t seconds)
- {
- boost::system::error_code ec;
- impl->wait(seconds, ec);
- boost::asio::detail::throw_error(ec);
- }
- template <typename Handler>
- class wait_operation
- {
- public:
- wait_operation(implementation_type &impl, boost::asio::io_service &io_service, std::size_t seconds, Handler handler)
- : impl_(impl),
- io_service_(io_service),
- work_(io_service),
- seconds_(seconds),
- handler_(handler)
- {
- }
- void operator()() const
- {
- implementation_type impl = impl_.lock();
- if (impl)
- {
- boost::system::error_code ec;
- impl->wait(seconds_, ec);
- this->io_service_.post(boost::asio::detail::bind_handler(handler_, ec));
- }
- else
- {
- this->io_service_.post(boost::asio::detail::bind_handler(handler_, boost::asio::error::operation_aborted));
- }
- }
- private:
- boost::weak_ptr<TimerImplementation> impl_;
- boost::asio::io_service &io_service_;
- boost::asio::io_service::work work_;
- std::size_t seconds_;
- Handler handler_;
- };
- template <typename Handler>
- void async_wait(implementation_type &impl, std::size_t seconds, Handler handler)
- {
- this->async_io_service_.post(wait_operation<Handler>(impl, this->get_io_service(), seconds, handler));
- }
- private:
- void shutdown_service()
- {
- }
- boost::asio::io_service async_io_service_;
- boost::scoped_ptr<boost::asio::io_service::work> async_work_;
- boost::thread async_thread_;
- };
- template <typename TimerImplementation>
- 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
错误来调用句柄。
- #include <boost/system/error_code.hpp>
- #include <cstddef>
- #include <windows.h>
- class timer_impl
- {
- public:
- timer_impl()
- : handle_(CreateEvent(NULL, FALSE, FALSE, NULL))
- {
- }
- ~timer_impl()
- {
- CloseHandle(handle_);
- }
- void destroy()
- {
- SetEvent(handle_);
- }
- void wait(std::size_t seconds, boost::system::error_code &ec)
- {
- DWORD res = WaitForSingleObject(handle_, seconds * 1000);
- if (res == WAIT_OBJECT_0)
- ec = boost::asio::error::operation_aborted;
- else
- ec = boost::system::error_code();
- }
- private:
- HANDLE handle_;
- };
服务实现 timer_impl
使用了 Windows API 函数,只能在 Windows 中编译和使用。 这个例子的目的只是为了说明一种潜在的实现。
timer_impl
提供两个基本方法:wait()
用于等待数秒。 destroy()
则用于取消一个等待操作,这是必须要有的,因为对于异步操作来说,wait()
方法是在其自身的线程中调用的。 如果 I/O 对象及其服务实现被销毁,那么阻塞式的 wait()
方法就要尽使用 destroy()
来取消。
这个 Boost.Asio 扩展可以如下使用。
- #include <boost/asio.hpp>
- #include <iostream>
- #include "basic_timer.hpp"
- #include "timer_impl.hpp"
- #include "basic_timer_service.hpp"
- void wait_handler(const boost::system::error_code &ec)
- {
- std::cout << "5 s." << std::endl;
- }
- typedef basic_timer<basic_timer_service<> > timer;
- int main()
- {
- boost::asio::io_service io_service;
- timer t(io_service);
- t.async_wait(5, wait_handler);
- io_service.run();
- }
与本章开始的例子相比,这个 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 或以上)。