7.2. I/O 服务与 I/O 对象
使用 Boost.Asio 进行异步数据处理的应用程序基于两个概念:I/O 服务和 I/O 对象。 I/O 服务抽象了操作系统的接口,允许第一时间进行异步数据处理,而 I/O 对象则用于初始化特定的操作。 鉴于 Boost.Asio 只提供了一个名为 boost::asio::io_service
的类作为 I/O 服务,它针对所支持的每一个操作系统都分别实现了优化的类,另外库中还包含了针对不同 I/O 对象的几个类。 其中,类 boost::asio::ip::tcp::socket
用于通过网络发送和接收数据,而类 boost::asio::deadline_timer
则提供了一个计时器,用于测量某个固定时间点到来或是一段指定的时长过去了。 以下第一个例子中就使用了计时器,因为与 Asio 所提供的其它 I/O 对象相比较而言,它不需要任何有关于网络编程的知识。
- #include <boost/asio.hpp>
- #include <iostream>
- void handler(const boost::system::error_code &ec)
- {
- std::cout << "5 s." << std::endl;
- }
- int main()
- {
- boost::asio::io_service io_service;
- boost::asio::deadline_timer timer(io_service, boost::posix_time::seconds(5));
- timer.async_wait(handler);
- io_service.run();
- }
函数 main()
首先定义了一个 I/O 服务 io_service,用于初始化 I/O 对象 timer。 就象 boost::asio::deadlinetimer
那样,所有 I/O 对象通常都需要一个 I/O 服务作为它们的构造函数的第一个参数。 由于 _timer 的作用类似于一个闹钟,所以 boost::asio::deadlinetimer
的构造函数可以传入第二个参数,用于表示在某个时间点或是在某段时长之后闹钟停止。 以上例子指定了五秒的时长,该闹钟在 _timer 被定义之后立即开始计时。
虽然我们可以调用一个在五秒后返回的函数,但是通过调用方法 async_wait()
并传入 handler()
函数的名字作为唯一参数,可以让 Asio 启动一个异步操作。 请留意,我们只是传入了 handler()
函数的名字,而该函数本身并没有被调用。
async_wait()
的好处是,该函数调用会立即返回,而不是等待五秒钟。 一旦闹钟时间到,作为参数所提供的函数就会被相应调用。 因此,应用程序可以在调用了 async_wait()
之后执行其它操作,而不是阻塞在这里。
象 async_wait()
这样的方法被称为是非阻塞式的。 I/O 对象通常还提供了阻塞式的方法,可以让执行流在特定操作完成之前保持阻塞。 例如,可以调用阻塞式的 wait()
方法,取代 boost::asio::deadline_timer
的调用。 由于它会阻塞调用,所以它不需要传入一个函数名,而是在指定时间点或指定时长之后返回。
再看看上面的源代码,可以留意到在调用 async_wait()
之后,又在 I/O 服务之上调用了一个名为 run()
的方法。这是必须的,因为控制权必须被操作系统接管,才能在五秒之后调用 handler()
函数。
async_wait()
会启动一个异步操作并立即返回,而 run()
则是阻塞的。因此调用 run()
后程序执行会停止。 具有讽刺意味的是,许多操作系统只是通过阻塞函数来支持异步操作。 以下例子显示了为什么这个限制通常不会成为问题。
- #include <boost/asio.hpp>
- #include <iostream>
- void handler1(const boost::system::error_code &ec)
- {
- std::cout << "5 s." << std::endl;
- }
- void handler2(const boost::system::error_code &ec)
- {
- std::cout << "10 s." << std::endl;
- }
- int main()
- {
- boost::asio::io_service io_service;
- boost::asio::deadline_timer timer1(io_service, boost::posix_time::seconds(5));
- timer1.async_wait(handler1);
- boost::asio::deadline_timer timer2(io_service, boost::posix_time::seconds(10));
- timer2.async_wait(handler2);
- io_service.run();
- }
上面的程序用了两个 boost::asio::deadline_timer
类型的 I/O 对象。 第一个 I/O 对象表示一个五秒后触发的闹钟,而第二个则表示一个十秒后触发的闹钟。 每一段指定时长过去后,都会相应地调用函数 handler1()
和 handler2()
。
在 main()
的最后,再次在唯一的 I/O 服务之上调用了 run()
方法。 如前所述,这个函数将阻塞执行,把控制权交给操作系统以接管异步处理。 在操作系统的帮助下,handler1()
函数会在五秒后被调用,而 handler2()
函数则在十秒后被调用。
乍一看,你可能会觉得有些奇怪,为什么异步处理还要调用阻塞式的 run()
方法。 然而,由于应用程序必须防止被中止执行,所以这样做实际上不会有任何问题。 如果 run()
不是阻塞的,main()
就会结束从而中止该应用程序。 如果应用程序不应被阻塞,那么就应该在一个新的线程内部调用 run()
,它自然就会仅仅阻塞那个线程。
一旦特定的 I/O 服务的所有异步操作都完成了,控制权就会返回给 run()
方法,然后它就会返回。 以上两个例子中,应用程序都会在闹钟到时间后马上结束。