boost::io_service 的使用 & 如何检查网络通讯正常?心跳包机制 or Detection of Half-Open (Dropped) Connections

see link: http://blog.stephencleary.com/2009/05/detection-of-half-open-dropped.html

  1. 首先明确一点,通过ping这种方式来检查wamp服务器是否连接正常存在一些缺点。ping工作在 ip 层(ICMP),能够ping通不能保证 tcp/ip以及其他依赖tcp/ip的高级协议工作正常。
  2. 二次连接正常也不能保证通讯正常。

下面通过boost timer写了一个最简单直接的检查网络是否连接正常的模块(远端必须周期性地触发 reset 函数,否则定时器超时,表示网络连接有问题):

    class HeartBeatService
    {
    public:
        HeartBeatService(boost::asio::io_service* p_io_service, unsigned int interval):
            p_io_service_(p_io_service),
            interval_(interval),  // second
            timer_(new boost::asio::deadline_timer(*p_io_service)),
            flag_(false)
        {}
        HeartBeatService(const HeartBeatService& other):
            p_io_service_(other.p_io_service_),
            interval_(other.interval_),
            timer_(new boost::asio::deadline_timer(*other.p_io_service_)),
            flag_(other.flag_)
        {}
        ~HeartBeatService()
        {
            delete timer_;
        }
        // Schedule the timer for the first time
        // The timer will fire after interval_ from io_service start run
        bool start()
        {
            timer_->expires_from_now(interval_);
            timer_->async_wait(boost::bind(&HeartBeatService::check, this, boost::asio::placeholders::error));
        }

        // 远端调用, reset flag
        void reset()
        {
            flag_ = false;
        }
        bool getFlag()  // For test
        {
            return flag_;
        }

    private:
        void check(const boost::system::error_code& e)  // timer 定时检查, 并设置标志
        {
            if( e )
            {
                // TODO print error message
                return;
            }
            if(flag_)
            {
                //TODO notify the controller offline!
                std::cerr << "TimeOut warning! The connection is lost!"
            }
            else
            {
                timer_->expires_at(timer_->expires_at() + interval_);
                timer_->async_wait(boost::bind(&HeartBeatService::check, this, boost::asio::placeholders::error));
                flag_ = true;
            }
        }
        boost::asio::io_service* p_io_service_;
        boost::posix_time::seconds interval_; // 超时时间
        boost::asio::deadline_timer* timer_;  // not copyable, so use pointer
        bool flag_;
    };

用 google test 编写单元测试,测试HeartBeatService类

#include <iostream>
#include <thread>
#include <future>
#include <boost/asio.hpp>
#include <gtest/gtest.h>

TEST(MyProject, HeartBeatTest)
{
    boost::asio::io_service io;
    unsigned int seconds = 3;  // 设定超时时间,客户端必须在这个时间内周期性地调用 reset函数
    HeartBeatServicet(&io, seconds);
    EXPECT_TRUE(t.start());

    // 调用 io_service::run, 定时器开始计时
    // 或者:auto future = std::async(std::launch::async, [&]{io.run();});  // better
    std::thread io_thread(boost::bind(&boost::asio::io_service::run, &io));

    std::this_thread::sleep_for(std::chrono::seconds(2));  // 等待 2s
    EXPECT_FALSE(t.getFlag());  // default is false
    std::this_thread::sleep_for(std::chrono::seconds(2));  // 等待 2s
    EXPECT_TRUE(t.getFlag());  // 定时器设置为3 sec,现在已经超时,所以会将flag设置为 true

    t.reset();  // 清除 flag
    EXPECT_FALSE(t.getFlag());

    std::this_thread::sleep_for(std::chrono::seconds(4));   // 再次等待超时
    EXPECT_TRUE(t.getFlag());  // 已经超时,并且设置了标志位为true
    testing::internal::CaptureStderr();  // 超时了会有打印信息,捕获打印
    std::this_thread::sleep_for(std::chrono::seconds(3));   // 再次等待超时,如果flag为true,并且再次等待超时则会打印信息
    std::string output1 = testing::internal::GetCapturedStdout();  // expect output
    std::string output = testing::internal::GetCapturedStderr();  // expect output
    EXPECT_FALSE(output.empty());
    std::cerr << output << std::endl;

    io.stop();
    io_thread.join();

}

int main (int argc, char** argv)
{
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

io_service.run 的使用注意

如果调换上述代码的顺序:

    boost::asio::io_service io;
    std::thread io_thread(boost::bind(&boost::asio::io_service::run, &io)); // 先运行 io_service::run

    unsigned int seconds = 3;  // 设定超时时间,客户端必须在这个时间内周期性地调用 reset函数
    HeartBeatServicet(&io, seconds);
    EXPECT_TRUE(t.start()); // 再开始计时

如果按照先调用 io.run() 再初始化计时器的顺序,则 io_service 会检测没有处理的消息,会马上返回。为了避免出现这种情况,可以手动加一个 boost::asio::io_service::work 以避免 io.run() 返回, see link: http://stackoverflow.com/questions/35945490/keep-io-service-alive

析构 boost::asio::io_service::work 并调用 io.stop() 会使得 io.run() 立刻返回。

io_service有很多非阻塞的调用方式( io.poll, io.poll_one, io.dispatch etc.) ,但是推荐最好的处理方式还是调用 io.run()

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值