信号(signal)是UNIX系统里一种常用的进程间异步通信手段,成熟的UNIX程序几乎都以某种方式支持它。asio库提供了类signal_set,利用异步I/o 的方式很好地处理了UNIX信号。通过它我们可以快速熟悉 asio的基本使用方法。
signal_set
class signal_set
{
public:
explicit signal_set(io_service& io_service);
signal_set(io_service&io_service,int number,...);
//
void add(int signal_number);
void remover(int siganl_number);
void clear();
void cancel();//取消所有异步操作
void async_wait(SignalHandler handler); //添加handler 非阻塞
}
signal_set的构造函数要求必须传入io_service对象,用于提交异步操作。第二种形式的构造函数还可以传入最多三个整数信号值,在构造的同时加入信号集。
signal_set的add ( ) /remove ( )/clear()成员函数很容易理解,可以添加或者删除信号量,同时也向io_service注册了信号事件。cancel ( )函数可以“取消”所有handler的执行,实际上是向它们传入 boost ::asio ::error : : operation_aborted错误,要求handler执行自己的cancel逻辑。
成员函数async_wait()用于异步添加信号处理函数,也就是handler,它是非阻塞的,无需任何等待就会返回。handler的形式必须是:
void handler(const system::error_code& ec,
int signal_number);
用法
在使用signal_set前我们必须先声明io_service对象,只有这样才能把信号处理加入事件处理循环异步等待信号的发生。之后可以用构造函数或者add ()向signal_set添加要捕获的信号,例如 SIGINT、SIGUSR1,等等,再用async_wait ()添加与之对应的信号处理函数,注意函数必须符合async_wait()对 handler的要求,否则会导致编译错误。
示例代码如下
#include<boost/asio.hpp>
#include<boost/system/error_code.hpp>
#include<iostream>
#include<boost/function.hpp>
using namespace boost::asio;
using namespace boost::system;
using namespace boost;
int main()
{
io_service io;
signal_set sig(io,SIGINT,SIGUSR1);
//等价于
// sig.add(SIGINT);
// sig.add(SIGUSR1);
auto handler1=[](const error_code& ec,int n)
{
if(ec)
{
std::cout<<ec.message()<<std::endl;
return;
}
if(n!=SIGINT)
{
return;
}
std::cout<<"handler recv="<<n<<std::endl;
std::cout<<"do something"<<std::endl;
};
function<handler_type>handler2=[](const error_code& ec,int n)
{
if(n!=SIGUSR1)
{
return ;
}
std::cout<<"handlerw2 recv ="<<n<<endl;
};
sig.async_wait(handler1);
sig.async_wait(handler2);
io.run();
std::cout<<"io stoped"<<std::endl;
}
这段代码里我们在signal_set构造的同时捕获了两个信号:SIGINT和 SIGUSR1,并使用lambda表达式定义了对应的两个处理函数。
在处理函数里首先要做的事情就是检查error code,如果发生了错误那么函数不应该继续执行。之后需要检查发生的信号,只有感兴趣的信号我们才应该处理,这里从略只是简单地输出字符串。
重要的是异步等待async wait (),它通知io service异步地执行I/0操作,并且注册了handler回调函数,用于在 I/ O操作完成时由事件多路分离器分派返回值
当所有 handler都添加完毕后我们必须调用io_service: :run ( ) ,它启动前摄器的事件处理循环,否则程序会因为因为不等待事件发生而立即结束。这时程序会进入阻塞状态,等待信号事件并分派事件,直到所有的操作完成然后退出run ( ) 。
执行命令“kill -10 pid”将会发送信号SIGUSR1,运行的结果是:
handler2 recv = 10
io stoped
可以看到程序捕获了信号SIGUSR1,执行了对应的 handler。由于信号发生后注册的异步事件“已经完成”,所以io_service会结束事件循环,退出run ()函数。
如果想让程序持续捕获信号,只在收到SIGINT时才退出,那么我们可以在信号处理完毕后重新添加handler,保证io_service仍然有事件需要处理:
function<handler_type>handler2=
[&](const error_code& ec,int n)
{
...
sig.async_wait(handler1);
sig.async_wait(handler2);
}
因为lambda表达式是匿名函数,所以不能直接调用自身,但使用function存储后就变成了一个有名变量,可以被捕获并调用,这是一个很有用的技巧。
上面的代码中的 handler2在处理完信号后再次调用了 sig.async_wait (),所以io service会持续等待信号事件的发生,直到被ctrl+C中断。
跟踪日志
使用宏BOoST_ASIO_ENABLE_HANDLER_TRACKING可以启用asio的跟踪日志:
#define BOOST_ASIO_ENABLE_HANDLER_TRACKING //启用跟踪日志
#include <boost/asio.hpp>
我们用输出重定向把跟踪日志写入到文件,注意它输出的是cerr,所以要用数字2:
signal_set 2>out. log //注意重定向标准错误流
在发送了两次SIGUSR1后用ctrl+C中断,日志的输出是(省略了前两个字段):
l0*1lsignal_set@0x7fff36f45f18.async_wait
|0*2|signal_set@0x7fff36f45f18.async_wait
l>1l ec=system : 0 , signal_number=10
|<1|
|>2|ec=system : 0, signal_nunber=10
l2* 3|signal_set@0x7fff36f45f18.async_wait|
|2* 4|signal_set@0x7fff36f45f18.async_wait
|<2I
|>3 | ec=system : 0 ,signal_number=10
|<3|
l>4 l ec=system : 0 , signal_number=10
l4*5 | signal_set@0x7fff36f45f18.async_waitl
4*6| signal_set@0x7fff36f45f18.async_wait
|<4 |
1>5lec=system : 0 , signal_number=2
|<5|
l>6 | ec=system : 0 , signal_number=2
|<6|
|0| signal_set@0x7fff36f45f18.cancel
分析
0号是主进程,它调用async_wait创建了1和2两个handler;
信号发生时进入1号 handler,因为不是SIGINT,所以无操作退出;
进入2号handler,它创建了3和4两个handler,然后退出;
第二次信号发生时3号无操作,4号创建了5和6两个handler然后退出;
最后收到了SIGINT信号,5号和6号运行后退出;
主进程事件循环结束,调用signal_set.cancel。
定时器
在异步I/O里定时器是一个非常重要的功能,它可以在指定的某个时刻调用函数,实现异步操作。
asio库提供四个定时器,分别是deadline_timer(忽略),steady_timer、system_timer和high_resolution_timer。
定时器有三种形式的构造函数,同signal_set一样要求必须有一个io_service对象,用于提交I/o 请求;第二个参数是定时器的终止时间,可以是绝对时间点或者是相对于当前时间点开始的一个时间长度。
一旦定时器对象创建,它就会立即开始计时,可以使用成员函数wait ()来同步等待定时器终止,或者使用async_wait()异步等待,当定时器终止时会调用handler函数。
如果创建定时器时不指定终止时间,那么定时器不会工作,可以用成员函数expires_at ()或expires_from_now ()分别设置定时器终止的时间点和相对时间,然后再调用wait()或async_wait()等待。expires_at ()和 expires_from_now () 函数也可以用于获得定时器的终止时间,只需要使用它们的无参重载形式。
async_wait()异步等待的handler要求形式必须是:
void handler(const error_code& ec);
同步定时器
io_service io;
steady_timer t(io,500_ms)
cout<<t.expires_at()<<endl;
cout<<t.expires_from_now()<<endl;
t.wait()
cout<<“hello asio”<<endl;
计时器同步等待500毫秒,当等待结束后输出一条消息,然后程序结束.
异步计时器
接下来我们来研究异步定时器,代码大致与同步定时器相等,但使用async_wait()方法增加了回调函数;
io_service io;
steady_timer t(10,500_ms);
t.async_wait([](const error_code&ec)
{
cout<<“hello wrold”<<endl;
})
io.run();
代码的前两行与同步定时器相同,这是所有asio程序基本的部分,通知 io_service一个要处理的定时器事件。随后的异步等待 async wait ()注册了回调函数,这样当定时器到期时io_service就会回调处理函数,完成异步操作。
同样最后我们必须调用io_service的成员函数run () ,它启动前摄器的事件处理循环,阻塞等待所有的操作完成并分派事件。
当定时器时间到终止时io _service将调用被注册的lambda函数,输出一条消息,然后程序结束。
使用bind
定时器非常有用,我们可以增加回调函数的参数,使它能够做更多的事情。虽然async_wait()接受的回调函数类型是固定的,但可以使用 bind 配合占位符 placeholders : :error来绑定参数来适配它的接口。
下面我们实现一个可以定时执行任意函数的定时器 timer_with_func,它持有asio定时器对象和计数器,还有一个 function对象用来保存回调函数:
class timer_with_func
{
typedef timer_with_func this_type;
private:
int m_count=10;//计数器成员变量;
int m_count_max=0;//计数器成员上限
function<void()> m_f; //function对象
steady_timer m_t //asio定时器对象
public:
template<typename F>
timer_with_func(io_service& io,int x,F func):
m_count_max(x),m_f(func),m_t(io,200_ms){
init();
}
private:
void init()
{
m_t.async_wait(bind(&this_type::handler,this,boost::asio::placeholders::error));
}
//使用boost::asio::placeholders::error 传递占位符
}
请读者注意在async_wait ()中 bind的用法。handler()是一个成员函数,因此我们需要绑定 this指针,同时我们还使用了占位符error来传递错误码。因为标准库定义了子名字空间std: :placeholders,所以我们要使用boost : :asio名字空间限定。
接下来是主要功能函数 handler (),它符合async_wait ()对回调函数的要求,有一个error code参数,当定时器终止时它将被调用执行。
handler ()内部累加计数器,如果计数器未达到上限则调用function对象,然后重新设置定时器的终止时间,再次异步等待被调用,从而达到反复执行的目的:
void handler(const error_code &ec)
{
if(m_count++>=m_count_max)
{
return ++
}
m_f();
m_t.expires_form_now(200_ms);
m_t.async_wait(bind(&this_type::handler,this,boost::asio::palceholders::error));
};
使用lambda
bind表达式的解法是最通用的,搭配占位符placeholders : :error可以把任意的函数或成员函数适配为asio要求的handler,但 bind 的写法稍显麻烦,而且当参数比较多时不易阅读。
lambda表达式可以达到与bind相同的效果,由于它的捕获列表可以自动获得所有的外部变量,所以写起来更加方便:
function<handler_type>m_handler=[&](const error_code&)
{
};
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/129667.html