boost asio(初学示例)
Timer.1 - 使用同步定时器
这个示例程序通过展示在定时器中执行一个阻塞等待来介绍Asio。
让我们从必须包含的头文件开始。
所有的Asio类只要简单的包含"asio.hpp"
头文件便可使用。
#include <iostream> #include <boost/asio.hpp>
因为本程序中使用了定时器,我们需要包含相应的的Boost.Date_Time 头文件来处理时间操作。
#include <boost/date_time/posix_time/posix_time.hpp>
使用Asio的所有程序都至少需要一个提供访问I/O功能的io_service 对象。因此在主函数中我们做的第一件事就是声明一个这个类型的对象。
int main() { boost::asio::io_service io;
接下来我们声明一个boost::asio::deadline_timer类型的对象。作为 Asio的核心类,它提供的I/O功能(在此为定时器功能)通常用一个io_service 的引用作为其构造函数的第一个参数。第二个参数设置一个从现在开始5秒后终止的定时器。
boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));
在这个简单的程序中,我们用定时器演示一个阻塞等待。deadline_timer::wait() 函数调用直到定时器终止(从定时器被创建算起,五秒后终止)才会返回。
一个deadline timer 通常是下面两种状态中的一种:"expired(终止)" 或"not expired(不终止)"。如果deadline_timer::wait() 函数被一个已经终止的定时器调用, 它将立即返回。
t.wait();
最后我们打印出 "Hello, world!"
信息以显示定时器已经终止。
std::cout <<"Hello, world!\n"; return0; }
Timer.2 - 使用异步定时器
这个示例程序示范了如何通过修改Timer.1 中的程序,使用Asio的异步回调功能在定时器中演示一个异步等待。
#include <iostream> #include <boost/asio.hpp> #include <boost/date_time/posix_time/posix_time.hpp>
使用Asio的异步功能意味着当一个异步操作完成时一个回调函数将被调用。在本程序中我们定义一个名为print
的函数,在异步等待结束后这个函数将被调用。
void print(const boost::system::error_code& /*e*/) { std::cout <<"Hello, world!\n"; } int main() { boost::asio::io_service io; boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));
接下来,我们调用 deadline_timer::async_wait() 函数执行一个异步等待去取代Timer.1例中的阻塞等待。当调用这个函数时我们传入上面定义的print
回调句柄。
t.async_wait(print);
最后,我们必须在io_service对象上调用io_service::run() 成员函数。
Asio保证回调句柄仅仅能被io_service::run()启动的当前线程所调用。 因此,如果io_service::run() 函数不执行,用于异步等待完成时的回调函数(在本例中为print函数)将永远不会被调用。
当仍旧有“工作”可做时,io_service::run() 函数会继续运行。在本例中,“工作”是定时器的异步等待,因此,直到定时器终止和回调函数执行完成,程序才会返回。
在调用io_service::run()之前确保给 io_service 一些工作去做,这非常重要。 例如,如果我们省略了上面调用的deadline_timer::async_wait() 函数,io_service对象将没有任何事情去做,因此io_service::run() 将立即返回。
io.run(); return0; }
Timer.3 - 回调函数绑定参数
在本示例程序中我们将修改Timer.2中的例子,使定时器每秒被激活一次。例子将示范如何给你的函数指针传递附加参数。
#include <iostream> #include <boost/asio.hpp> #include <boost/bind.hpp> #include <boost/date_time/posix_time/posix_time.hpp>
使用Asio实现一个重复定时器,你必须在你的回调函数中去改变定时器的终止时间,然后开始一个新的异步等待。显然这意味着回调函数必须拥有改变定时器对象的权限。为此我们为 print
函数增加两个新参数:
-
一个指向定时器对象的指针。
-
一个用于当定时器第6次被激活时我们可以中止程序的计数器。
void print(const boost::system::error_code& /*e*/, boost::asio::deadline_timer* t,int* count) {
如上所示,示例程序使用了一个计数器,当定时器被第6次激活时,用来中止程序。然而,你将看到这里并没有显式地要求io_service对象中止。回忆示例2中,当没有更多“工作”去做时,io_service::run() 函数完成。在计数器达到
5时,定时器并没有启动一个新的异步等待。该io_service执行完工作后停止运行。
if(*count <5) { std::cout <<*count <<"\n"; ++(*count);
接着,我们推迟定时器的终止时间。通过在原先的终止时间上增加延时,我们可以确保定时器不会在处理回调函数所需时间内到期。
t->expires_at(t->expires_at()+ boost::posix_time::seconds(1));
接着我们在定时器中启动一个新的异步等待。我们必须使用boost::bind() 函数给你的回调函数绑定额外的参数,因为deadline_timer::async_wait() 函数只期望得到一个拥用 void
(
const
boost
::
system
::
error_code
&)
签名的函数指针(或函数对象)。为你的print
函数绑定附加的参数后,它就成为与签名精确匹配的函数对象。
查看Boost.Bind 文档文档以获得更多如何使用boost::bind()的信息。
在本例中,boost::bind()的boost::asio::placeholders::error参数是为了给回调函数传入一个error对象。当开始异步操作时,如果使用boost::bind(),你必须指定和回调函数的参数列表相匹配的一个参数。在示例4中,如果在回调函数中,这个参数不是必需的,这个占位符会被省略。
t->async_wait(boost::bind(print, boost::asio::placeholders::error, t, count)); } } int main() { boost::asio::io_service io;
为了在定时器第6次被激活时终止程序,我们添加一个新的count
变量。
int count =0; boost::asio::deadline_timer t(io, boost::posix_time::seconds(1));
在第四步中,当在主函数
中的调用deadline_timer::async_wait() 函数时,我们绑定print
函数所需要的附加参数。
t.async_wait(boost::bind(print, boost::asio::placeholders::error,&t,&count)); io.run();
最后,为了证明count
变量在print
函数句柄中被使用,我们打印出它的值。
std::cout <<"Final count is "<< count <<"\n"; return0; }
Timer.5 - 多线程同步回调
本示例程序示范了使用boost::asio::strand 类来创建多线程程序中的同步回调句柄。
前四个例程只是在单线程下使用io_service::run() 函数来避免处理函同步。 如你所见,Asio库保证回调句柄仅能被当前正在调用 io_service::run(). 函数的线程调用。 因此,在单线程中调用io_service::run() 能确保回调句柄不被并发运行。
单线程通常是使用Asio开发应用程序最好的方式。下面是Asio在程序中的局限性,尤其是服务器方面,包括:
-
操作需要较长时间处理才能完成时弱响应。
-
在大规模的多处理机系统中表现不佳。
如果你发现自己陷入这些局限时,一个可供选择的方法是创建一个每个线程都调用io_service::run() 的线程池。 不过,因为这允许并发操作,当访问一个共享、非线程安全的资源时,我们需要一个同步方式。
#include <iostream> #include <boost/asio.hpp> #include <boost/thread.hpp> #include <boost/bind.hpp> #include <boost/date_time/posix_time/posix_time.hpp>
让我们从定义一个名为printer
的类开始,这与前一个示例中的类很相似。这个类是上一个例子的扩展,这里我们使用两个并行的定时器。
class printer { public:
除了初始化一对boost::asio::deadline_timer 成员变量外,构造函数还初始化一个boost::asio::strand类型strand_
成员变量。
boost::asio::strand 对象保证:对于通过它来分派执行的众操作中,只有一个操作执行完成之后才允许进入下一个操作。 这种保证与多少个线程调用io_service::run() 无关。当然,如果不是通过一个boost::asio::strand对象分派, 或者通过其它不同的boost::asio::strand对象分派,这些操作仍旧可能是并发的。
printer(boost::asio::io_service& io) : strand_(io), timer1_(io, boost::posix_time::seconds(1)), timer2_(io, boost::posix_time::seconds(1)), count_(0) {
当开始同步操作时,每一个回调句柄都使用boost::asio::strand对象进行“包装”。strand::wrap() 函数返回一个新的通过boost::asio::strand对象自动分派的内部句柄。 通过同一boost::asio::strand对象对句柄进行“ 包装”,我们可以保证操作不会并发执行。
timer1_.async_wait(strand_.wrap(boost::bind(&printer::print1,this))); timer2_.async_wait(strand_.wrap(boost::bind(&printer::print2,this))); } ~printer() { std::cout <<"Final count is "<< count_ <<"\n"; }
在一个多线程程序中,当访问同一共享资源时,异步操作必须是同步的。在本例中,print1
和print2
)函数使用的共享资源std
::
cout
和count_
数据成员。
void print1() { if(count_ <10) { std::cout <<"Timer 1: "<< count_ <<"\n"; ++count_; timer1_.expires_at(timer1_.expires_at()+ boost::posix_time::seconds(1)); timer1_.async_wait(strand_.wrap(boost::bind(&printer::print1,this))); } } void print2() { if(count_ <10) { std::cout <<"Timer 2: "<< count_ <<"\n"; ++count_; timer2_.expires_at(timer2_.expires_at()+ boost::posix_time::seconds(1)); timer2_.async_wait(strand_.wrap(boost::bind(&printer::print2,this))); } } private: boost::asio::strand strand_; boost::asio::deadline_timer timer1_; boost::asio::deadline_timer timer2_; int count_; };
main
函数中, io_service::run() 现在被两个线程调用:主线程和一个附加线程。这一切依赖于boost::thread对象来完成。
正如它被一个单线程调用一样,io_service::run()的并发调用会一直持续到无任何“工作”可做。后台线程直到所有异步操作都完成后才会退出。
int main() { boost::asio::io_service io; printer p(io); boost::thread t(boost::bind(&boost::asio::io_service::run,&io)); io.run(); t.join(); return0; }
Daytime.1 - 同步TCP daytime客户端
本示例程序显示如何使用Asio来实现一个TCP客户端程序。
让我们从添加必需的头文件开始。
#include <iostream> #include <boost/array.hpp> #include <boost/asio.hpp>
这个应用程序的目的是访问一个daytime服务器,因此我们需要用户去指定服务器。(如time-nw.nist.gov,用IP亦可)。
using boost::asio::ip::tcp; int main(int argc, char* argv[]) { try { if(argc !=2) { std::cerr <<"Usage: client <host>"<< std::endl; return1; }
所有使用asio的程序都至少需要一个io_service 对象。
boost::asio::io_service io_service;
我们需要把服务器的名称转化为TCP的节点,而该名称是通过应用程序的参数指定的。我们使用ip::tcp::resolver 对象来完成。
tcp::resolver resolver(io_service);
一个resolver对象获得一个query对象,并将其转换为节点列表.我们通过argv
[
1
]
中的服务器名称和服务名,在这里是 "daytime"
,构造一个query。
tcp::resolver::query query(argv[1],"daytime"); //相当与用名字换取ip的操作
节点列表用ip::tcp::resolver::iterator类型的迭代器返回。 返回的iterator将采用ip::tcp::resolver::iterator 的默认构造函数来构造。
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); tcp::resolver::iterator end;
现在我们建立一个socket并连接之,由于获得的节点既有IPv4也有IPv6的。所以,我们需要依次尝试访问它们直到找到一个可以正常工作的。这样做可使得我们的程序独立于特定的IP版本。
tcp::socket socket(io_service); boost::system::error_code error = boost::asio::error::host_not_found; while(error && endpoint_iterator != end) { socket.close(); socket.connect(*endpoint_iterator++, error); } if(error) throw boost::system::system_error(error);
连接打开后,现在我们需要做的就是读取daytime服务器的响应。
我们使用boost
::
array
来存放接收到的数据。 The boost::asio::buffer() 函数会自动确定array的长度来防止缓冲区溢出。我们也可以使用 char [] 或 std::vector来代替boost::array。
for(;;) { boost::array<char,128> buf; boost::system::error_code error; size_t len = socket.read_some(boost::asio::buffer(buf), error);
当服务器关闭连接时,ip::tcp::socket::read_some() 函数会以boost::asio::error::eof错误标志返回, 通过该错误标志,我们知道应该退出循环了。
if(error == boost::asio::error::eof) break;// Connection closed cleanly by peer. elseif(error) throw boost::system::system_error(error);// Some other error. std::cout.write(buf.data(), len); }
最后,处理所有可能抛出的异常 。
} catch(std::exception& e) { std::cerr << e.what()<< std::endl; }
Daytime.2 - 同步TCP daytime服务器
本示例示范如何使用Asio来实现一个TCP服务器程序。
#include <ctime> #include <iostream> #include <string> #include <boost/asio.hpp> using boost::asio::ip::tcp;
我们先定义一个make_daytime_string
()
来产生需要发送给客户端的字符串.这个函数会在我们所有的daytime服务器上被使用。
std::string make_daytime_string() { usingnamespace std;// For time_t, time and ctime; time_t now = time(0); return ctime(&now); } int main() { try { boost::asio::io_service io_service;
新建一个ip::tcp::acceptor 对象来监听新的连接。该对象应遵守IPv4协议,监听TCP端口13。
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(),13));
这是一个iterative server,也就是说同一时间只能处理一个连接。建立一个表示与客户端的连接的socket, 然后等待客户端的连接。
for(;;) { tcp::socket socket(io_service); acceptor.accept(socket);
当客户端访问服务器时,获取当前时间,并传送给客户端。
std::string message = make_daytime_string(); boost::system::error_code ignored_error; boost::asio::write(socket, boost::asio::buffer(message), boost::asio::transfer_all(), ignored_error); } }
最后,处理异常。
catch(std::exception& e) { std::cerr << e.what()<< std::endl; } return0; }