see link: http://blog.stephencleary.com/2009/05/detection-of-half-open-dropped.html
- 首先明确一点,通过ping这种方式来检查wamp服务器是否连接正常存在一些缺点。ping工作在 ip 层(ICMP),能够ping通不能保证 tcp/ip以及其他依赖tcp/ip的高级协议工作正常。
- 二次连接正常也不能保证通讯正常。
下面通过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()