asio c++ 库
MacOS 安装
直接解压源码,例如从官网https://sourceforge.net/projects/asio/files/asio/1.28.0%20%28Stable%29/ 下载。
之后,使用如下命令编译程序,其中path-to-asio为asio文件所在路径
clang++ example.cpp -std=c++20 -stdlib=libc++ -I path-to-asio/include/
Timer.1 同步timer
这个示例程序通过展示如何在计时器上执行阻塞等待,来引入asio库。我们从包含必要的头文件开始。所有asio类可以通过仅仅包含"asio.hpp"头文件来使用。
#include <iostream>
#include <asio.hpp>
所有使用asio的程序需要至少一个IO执行环境,例如一个io_context或者thread_pool对象。一个IO执行环境提供IO功能的访问。我们在主函数中声明一个类型为io_context的对象。
int main{
asio::io_context io;
接下里啊我们声明类型为asio::steady_timer的对象。这个提供IO功能的核心asio对象总是将一个执行器或者一个对于执行环境(例如io_context)的引用作为它们的构造函数的第一个参数。构造函数的第二个参数将计时器设置为计时5s。
asio::steady_timer t(io, asio::chrono::seconds(5));
在这个简单的例子中,我们在timer上执行阻塞等待。也就是说,steady_timer::wait()的调用直到计时器超时才会返回,在它创建后5s(也就是说,不是从wait调用开始的时候)。
一个timer总是两个状态之一:“expired” / “not expired”。如果steady_timer::wait()函数在一个expired timer上调用,它将会立刻返回。
t.wait()
最后,我们打印通常的“Hello World”消息来展示何时timer expire。
std::cout << "Hello World!" << std::endl;
return 0;
}
Timer.2 使用异步的timer
这个示例程序展示了如何使用asio的异步功能,通过更改示例timer.1在timer上执行异步等待。
#include <iostream>
#include <asio.hpp>
使用 asio的异步功能意味着需要提供一个completion的标记,这可以决定结果如何分发给completion handler当异步操作完成时。在这个程序中,我们定义了一个名为print的函数,它在异步等待完成时调用。
void print(const asio::error_code& /* e */){
std::cout << "Hello, world!" << std::endl;
}
int main(){
asio::io_context io;
asio::steady_timer t(io, asio::chrono::seconds(5));
接下来,不是像timer.1执行阻塞等待,我们调用steady_timer::async_wait()函数来执行异步等待。当调用这个函数时,我们传入在上面定义的print函数。
t.async_wait(&print);
最后,我们必须在io_context对象上调用io_context::run()成员函数。asio库提供了一种保证,completion handlers只会从当前调用io_context::run的线程上调用。因此如果io_context::run函数不被调用,那么这个completion handler也将不会调用。
当仍有工作需要做时,这个io_context::run函数也将会继续运行。在这个例子中,这个工作是在timer上的异步等待,所以这个调用直到这个timer过期并且这个completion handler返回后,才会返回。
需要记住一件重要的事,在调用io_context::run函数前给io_context一些工作来做。例如,如果我们忽略上方对于steady_timer::async_wait()的调用,这个io_context将没有工作需要做,因此io_context::run将会立刻返回。
io::run();
return 0;
Timer.3 给completion handler绑定参数
在这个示例中,我们将更改Timer.2来使得timer每秒可以发射一次。将展示如何传入额外的参数向你的handler函数中。
#include <iostream>
#include <asio.hpp>
#include <boost/bind/bind.hpp>
为了使用asio实现重复的timer,你需要更改timer的过期时间在你的completion handler中,并且开始一个新的异步等待。显然这意味着completion handler将需要能够访问timer对象。在这里,我们向print函数中添加两个新的参数:
- 一个指向timer对象的指针
- 一个计数器,使得当计数器发射六次时可以停止程序
void print(const asio::error_code& /*e*/,asio::steady_timer* t, int* count){
正如上面所提及的,这个示例程序使用一个计数器来停止运行,当计时器发射第六次时。然而你将观察到没有显式调用来要求io_context终止。回忆一下,在timer.2中我们了解到io_context::run函数会在没有工作要做时完成。通过当计数器到达5时。不开启一个在timer上新的异步等待,这个io_context将会停止运行。
if(*count < 5){
std::cout << *count << std::endl;
++(*count);
接下来我们从timer的expiry time移动1s。
t->expires_at(t->expiry() + asio::chrono::seconds(1));
之后,我们在timer上开始一个新的异步等待。正如你所见,bind函数被用来将额外的参数于你的completion handler关联。这个steady_timer::async_wait()函数需要一个使用签名void(const asio::error_code&)
的handler函数。绑定额外参数将你的print函数转化为一个正确的函数对象。
在这个例子中,这个asio::placeholders::error参数是一个对于错误对象的命名占位符。当执行异步操作时,如果使用bind,你必须确定只要匹配到handler参数列表的参数。在Timer.4中,你将会看到这个占位符可以被消除,如果这个参数对于completion handler不是必须的。
t->async_wait(bind(print,asio::placeholders::error,t,count));
}
}
int main(){
asio::io_context io;
一个新的计数器变量需要增加
int count = 0;
asio::steady_timer t(io, asio::chrono::seconds(1));
t.async_wait(bind(print,asio::placeholders::error, &t, &count));
io::run();
std::cout << "Final count is " << count << std::endl;
return 0;
}
// 使用标准库的bind
#include <asio.hpp>
#include <iostream>
#include <functional>
void print(asio::steady_timer* t, int* count){
if(*count < 5){
std::cout << *count << std::endl;
++(*count);
t->expires_at(t->expiry() + asio::chrono::seconds(1));
t->async_wait(std::bind(print,
t, count));
}
}
int main(){
asio::io_context io;
int count = 0;
asio::steady_timer t(io, asio::chrono::seconds(1));
t.async_wait(std::bind(print,
&t, &count));
io.run();
std::cout << "Final count is " << count << std::endl;
return 0;
}