• asio中的信号与定时器


    信号(signal)是UNIX系统里一种常用的进程间异步通信手段,成熟的UNIX程序几乎都以某种方式支持它。asio库提供了类signal_set,利用异步I/o 的方式很好地处理了UNIX信号。通过它我们可以快速熟悉 asio的基本使用方法。

    signal_set

    1. class signal_set
    2. {
    3. public:
    4. explicit signal_set(io_service& io_service);
    5. signal_set(io_service&io_service,int number,...);
    6. //
    7. void add(int signal_number);
    8. void remover(int siganl_number);
    9. void clear();
    10. void cancel();//取消所有异步操作
    11. void async_wait(SignalHandler handler); //添加handler 非阻塞
    12. }

    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的要求,否则会导致编译错误。

    示例代码如下

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace boost::asio;
    6. using namespace boost::system;
    7. using namespace boost;
    8. int main()
    9. {
    10. io_service io;
    11. signal_set sig(io,SIGINT,SIGUSR1);
    12. //等价于
    13. // sig.add(SIGINT);
    14. // sig.add(SIGUSR1);
    15. auto handler1=[](const error_code& ec,int n)
    16. {
    17. if(ec)
    18. {
    19. std::cout<message()<
    20. return;
    21. }
    22. if(n!=SIGINT)
    23. {
    24. return;
    25. }
    26. std::cout<<"handler recv="<
    27. std::cout<<"do something"<
    28. };
    29. functionhandler2=[](const error_code& ec,int n)
    30. {
    31. if(n!=SIGUSR1)
    32. {
    33. return ;
    34. }
    35. std::cout<<"handlerw2 recv ="<
    36. };
    37. sig.async_wait(handler1);
    38. sig.async_wait(handler2);
    39. io.run();
    40. std::cout<<"io stoped"<
    41. }

    这段代码里我们在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仍然有事件需要处理:

    1. functionhandler2=
    2. [&](const error_code& ec,int n)
    3. {
    4. ...
    5. sig.async_wait(handler1);
    6. sig.async_wait(handler2);
    7. }

    因为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对象用来保存回调函数:

    1. class timer_with_func
    2. {
    3. typedef timer_with_func this_type;
    4. private:
    5. int m_count=10;//计数器成员变量;
    6. int m_count_max=0;//计数器成员上限
    7. function<void()> m_f; //function对象
    8. steady_timer m_t //asio定时器对象
    9. public:
    10. template<typename F>
    11. timer_with_func(io_service& io,int x,F func):
    12. m_count_max(x),m_f(func),m_t(io,200_ms){
    13. init();
    14. }
    15. private:
    16. void init()
    17. {
    18. m_t.async_wait(bind(&this_type::handler,this,boost::asio::placeholders::error));
    19. }
    20. //使用boost::asio::placeholders::error 传递占位符
    21. }

    请读者注意在async_wait ()中 bind的用法。handler()是一个成员函数,因此我们需要绑定 this指针,同时我们还使用了占位符error来传递错误码。因为标准库定义了子名字空间std: :placeholders,所以我们要使用boost : :asio名字空间限定。

    接下来是主要功能函数 handler (),它符合async_wait ()对回调函数的要求,有一个error code参数,当定时器终止时它将被调用执行。

    handler ()内部累加计数器,如果计数器未达到上限则调用function对象,然后重新设置定时器的终止时间,再次异步等待被调用,从而达到反复执行的目的:

    1. void handler(const error_code &ec)
    2. {
    3. if(m_count++>=m_count_max)
    4. {
    5. return ++
    6. }
    7. m_f();
    8. m_t.expires_form_now(200_ms);
    9. m_t.async_wait(bind(&this_type::handler,this,boost::asio::palceholders::error));
    10. };

    使用lambda

    bind表达式的解法是最通用的,搭配占位符placeholders : :error可以把任意的函数或成员函数适配为asio要求的handler,但 bind 的写法稍显麻烦,而且当参数比较多时不易阅读。

    lambda表达式可以达到与bind相同的效果,由于它的捕获列表可以自动获得所有的外部变量,所以写起来更加方便:

    1. functionm_handler=[&](const error_code&)
    2. {
    3. };

  • 相关阅读:
    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