asio中的信号与定时器

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 asio中的信号与定时器,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

信号(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

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!