信号(signal)是UNIX系统里一种常用的进程间异步通信手段,成熟的UNIX程序几乎都以某种方式支持它。asio库提供了类signal_set,利用异步I/o 的方式很好地处理了UNIX信号。通过它我们可以快速熟悉 asio的基本使用方法。
- 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
- #include
- #include
- #include
- 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<
message()< - return;
- }
- if(n!=SIGINT)
- {
- return;
- }
- std::cout<<"handler recv="<
- std::cout<<"do something"<
- };
- function
handler2=[](const error_code& ec,int n) - {
- if(n!=SIGUSR1)
- {
- return ;
- }
- std::cout<<"handlerw2 recv ="<
-
- };
- sig.async_wait(handler1);
- sig.async_wait(handler2);
- io.run();
- std::cout<<"io stoped"<
- }
这段代码里我们在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
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
我们用输出重定向把跟踪日志写入到文件,注意它输出的是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<
cout<
t.wait()
cout<<"hello asio"<
计时器同步等待500毫秒,当等待结束后输出一条消息,然后程序结束.
异步计时器
接下来我们来研究异步定时器,代码大致与同步定时器相等,但使用async_wait()方法增加了回调函数;
io_service io;
steady_timer t(10,500_ms);
t.async_wait([](const error_code&ec)
{
cout<<"hello wrold"<
})
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
m_handler=[&](const error_code&) - {
- };
-
相关阅读:
Elasticsearch实战(十八)--ES搜索Doc Values/Fielddata 正排索引 深入解析
C++-IO相关
Window Server2008配置https
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException异常解决方法
上海市青少年算法2022年11月月赛(丙组)
197、管理 RabbitMQ 的虚拟主机
datazoom图表配置项 ---x轴滚动
最长上升子序列模型
【JAVA基础】String类概述
《优化接口设计的思路》系列:第六篇—接口防抖(防重复提交)的一些方式
-
原文地址:https://blog.csdn.net/qq_62309585/article/details/126937577