[英]Boost asio::deadline_timer is resetting before timeout
我正在使用boost :: asio :: deadline_timer添加套接字超時選項。 我已經實現了異步HTTP讀取,並且當我開始與服務器連接時我啟動了duration_timer,並且在每個回調中,我使用了截止日期time_timer :: expires_from_now函數重置了duration_timer。 在截止日期計時器的錯誤處理程序中,我清楚地檢查了超時是實際的還是操作中止的。 但是幾乎總是我收到實際的超時,甚至在預期的超時之前。 請看看我給定的代碼。 我不明白在每個回調中我都要重置計時器,然后為什么會收到此超時錯誤。
#define TCP_SOCKET_TIMEOUT 10
Http::AsyncDownload::AsyncDownload(boost::asio::io_service& io_service,
const std::string &protocol,
const std::string &serverip,
const std::string &port,
const std::string &path,
const std::string &filename,
const std::string &localFilePath,
const std::string &username,
const std::string &password) :
resolver_(io_service),
socket_(io_service),
timer_(io_service, boost::posix_time::seconds(TCP_SOCKET_TIMEOUT)),
localFilePath_(localFilePath),
downloadFile_(filename),
protocol_(protocol)
{
........
// Start TCP Socket Timer
start_socket_timer();
// Start an asynchronous resolve to translate the server and service names
// into a list of endpoints.
tcp::resolver::query query(serverip, port);
resolver_.async_resolve(query, boost::bind(&AsyncDownload::resolve, this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator)
);
}
void Http::AsyncDownload::resolve(const boost::system::error_code &err,
tcp::resolver::iterator endpoint_iterator)
{
// Ok, we have received one packet, so refresh the socket timer
refresh_socket_timer();
if ( !err ) {
.........
boost::asio::async_connect(ssocket_->lowest_layer(), endpoint_iterator, boost::bind(&AsyncDownload::connect, this, boost::asio::placeholders::error));
} else {
// Error handling here
}
}
void Http::AsyncDownload::connect(const boost::system::error_code& err)
{
// Ok, we have received one packet, so refresh the socket timer
refresh_socket_timer();
if ( !err ) {
.........
boost::asio::async_write(socket_, request_,
boost::bind(&AsyncDownload::write_request, this, boost::asio::placeholders::error));
}
else {
// Error handling here
}
}
void Http::AsyncDownload::hand_shake(const boost::system::error_code& err)
{
// Ok, we have received one packet, so refresh the socket timer
refresh_socket_timer();
if ( !err ) {
.........
boost::asio::async_write(*ssocket_, request_,
boost::bind(&AsyncDownload::write_request, this,
boost::asio::placeholders::error));
} else {
// Error handling here.
}
}
void Http::AsyncDownload::write_request(const boost::system::error_code& err)
{
// Ok, we have received one packet, so refresh the socket timer
refresh_socket_timer();
if ( !err ) {
.............
boost::asio::async_read_until(*ssocket_, response_, "\r\n",
boost::bind(&AsyncDownload::read_status_line, this,
boost::asio::placeholders::error));
} else {
// Error handling here
}
}
void Http::AsyncDownload::read_status_line(const boost::system::error_code& err)
{
// Ok, we have received one packet, so refresh the socket timer
refresh_socket_timer();
if ( !err ) {
..........
boost::asio::async_read_until(*ssocket_, response_, "\r\n\r\n",
boost::bind(&AsyncDownload::read_headers, this,
boost::asio::placeholders::error));
} else {
// Error handling here.
}
}
void Http::AsyncDownload::read_headers(const boost::system::error_code& err)
{
refresh_socket_timer();
if ( !err ) {
..............
boost::asio::async_read(*ssocket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&AsyncDownload::read_content, this,
boost::asio::placeholders::error)
);
} else {
// Error handling here
}
}
void Http::AsyncDownload::read_content(const boost::system::error_code& err)
{
// Ok, we have received one packet, so refresh the socket timer
refresh_socket_timer();
if ( !err ) {
boost::asio::async_read(*ssocket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&AsyncDownload::read_content, this,
boost::asio::placeholders::error)
);
} else if ( err != boost::asio::error::eof ) {
// Error handling here.
} else {
// We have an EOF
}
}
void Http::AsyncDownload::start_socket_timer()
{
timer_.async_wait(boost::bind(&Http::AsyncDownload::socket_timeout, this,
boost::asio::placeholders::error));
}
void Http::AsyncDownload::refresh_socket_timer()
{
timer_.expires_from_now(boost::posix_time::seconds(TCP_SOCKET_TIMEOUT));
timer_.async_wait(boost::bind(&Http::AsyncDownload::socket_timeout, this, boost::asio::placeholders::error));
}
void Http::AsyncDownload::socket_timeout(const boost::system::error_code &error_)
{
// operation_aborted error is thrown whenever we cancel the timer or
// we reset the timer using expires_from_now function,
if ( error_ != boost::asio::error::operation_aborted ) {
csputils::Utils::DEBUG_MSG(downloadFile_, __LINE__, " ------------> TCP Connection Timeout. Broken Connection Found. Abort Operation ");
// Ok, our TCP connection is broken, we will cancel all asynchronous
// operations of our sockets.
ssocket_->shutdown(); // For Secure Socket & socket.close(); for normal socket.
} else {
// Ok, we have reset the timer, please continue...
}
}
好。 在上面的代碼中,您會注意到我正在構造函數中啟動計時器,並且一旦收到一個數據包,便使用expries_from_now函數調用刷新計時器。 該調用將使用錯誤代碼operation_aborted調用錯誤處理程序(socket_timeout),但是對於每個實際的超時,將在沒有operation_aborted的情況下調用此函數,並且您可以看到我正在顯式檢查operation_aborted,但是仍然按照我的期望,我會盡早收到超時我正在刷新接收到的每個數據包上的計時器,但是我確定它會在10秒之前過期。 我也嘗試了超時值= 60,但效果相同。 有什么想法嗎。
UPDATE更新了我的代碼,並在我的實際代碼中使用了錯誤處理。 為了簡單起見,我已經精簡了我的實際代碼。 您可以注意到在計時器超時處理程序中,我正在檢查超時是否不明確(即operation_aborted),然后關閉套接字。 一旦套接字被關閉,我將在套接字數據處理程序中得到一個異常(主要是read_content函數)。 在該函數中,當我收到異常消息時,我的套接字將退出並調用AsyncDownload析構函數,在此我將進行更多清理。 如果我刪除了duration_timer,我的下載代碼將非常完美。 我在這里添加了它以檢測意外的TCP連接斷開。 我在ARM體系結構的嵌入式Linux上運行此代碼,我的套接字是安全的,但是正如我已經提到的,我的代碼在沒有計時器的情況下可以完美運行,因此我認為問題與套接字無關。
好的,因此,我已經親身實踐了您的示例。
我可以看到超時到期時發生無限循環,這是因為缺少錯誤處理,盡管已經達到了超時的事實,但read_content
循環仍然繼續。
注意:
第一個read_until_async
建議只讀取狀態行。 這根本不是那么回事! 請參見boost read_until不會在定界符處停止 。
它將讀取一次包含最少\\r\\n
數據的第一個“ swoop”。 實際上,許多服務器在同一數據包中發送標頭。 (實際上,有效行為可能取決於代理,可能還取決於硬件)。 因此,假設您始終需要第二個read_until_async
來讀取標頭是read_until_async
的。 而且由於可能永遠不會發生另一個"\\r\\n\\r\\n"
,因此很容易進入故障模式(例如,到達流末尾時)。
如果您不仔細觀察流程,則可能會得出“計時器啟動得太早”的結論(例如,因為您進入了refresh_socket_timer
)。 但是,在我的錯誤情況下發生的事情是read_until_async
簡單地立即返回,這被錯誤地視為已返回數據包。
所以我建議像
void read_content(const boost::system::error_code& err)
{
if (err && socket_.is_open())
{
DEBUG_TRACE();
boost::asio::async_read(*ssocket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&AsyncDownload::read_content, this,
boost::asio::placeholders::error)
);
refresh_socket_timer();
}
else
std::cerr << "Error '" << err.message() << "'\n";
}
因此,我們避免了連接丟失的無限循環。
void refresh_socket_timer()
{
if (socket_.is_open())
{
DEBUG_TRACE();
timer_.expires_from_now(boost::posix_time::seconds(TCP_SOCKET_TIMEOUT));
timer_.async_wait(boost::bind(&AsyncDownload::socket_timeout, this, boost::asio::placeholders::error));
}
}
因此,我們避免在關閉套接字后刷新計時器。
void socket_timeout(const boost::system::error_code &error_)
{
// operation_aborted error is thrown whenever we cancel the timer or
// we reset the timer using expires_from_now function,
if ( error_ != boost::asio::error::operation_aborted ) {
DEBUG_TRACE();
std::cout << " ------------> TCP Connection Timeout. Broken Connection Found. Abort Operation\n";
// Ok, our TCP connection is broken, we will cancel all asynchronous
// operations of our sockets.
socket_.close();
}
}
因此,我們會在超時時主動關閉套接字。
如果您在上面注釋了if(...)
條件,您將看到我所描述的失敗模式。
這是我用來測試的完整示例:
#include <boost/asio.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/bind.hpp>
using tcp = boost::asio::ip::tcp;
#define TCP_SOCKET_TIMEOUT 2
#define DEBUG_TRACE() do { std::cout << __FILE__ << ':' << __LINE__ << "\t" << __FUNCTION__ << "\n"; } while(false)
struct AsyncDownload
{
tcp::resolver resolver_;
tcp::socket socket_;
tcp::socket* ssocket_ = &socket_;
boost::asio::deadline_timer timer_;
std::string localFilePath_;
boost::asio::streambuf response_;
AsyncDownload(boost::asio::io_service& io_service,
const std::string &protocol,
const std::string &serverip,
const std::string &port) :
resolver_(io_service),
socket_(io_service),
timer_(io_service, boost::posix_time::seconds(TCP_SOCKET_TIMEOUT))
{
DEBUG_TRACE();
// Start TCP Socket Timer
start_socket_timer();
// Start an asynchronous resolve to translate the server and service names
// into a list of endpoints.
tcp::resolver::query query(serverip, port);
resolver_.async_resolve(query, boost::bind(&AsyncDownload::resolve, this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator)
);
}
void resolve(const boost::system::error_code &err, tcp::resolver::iterator endpoint_iterator)
{
DEBUG_TRACE();
boost::asio::async_connect(ssocket_->lowest_layer(), endpoint_iterator, boost::bind(&AsyncDownload::connect, this, boost::asio::placeholders::error));
refresh_socket_timer();
}
void connect(const boost::system::error_code& err)
{
DEBUG_TRACE();
std::string const request_ = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n";
boost::asio::async_write(socket_, boost::asio::buffer(request_),
boost::bind(&AsyncDownload::write_request, this, boost::asio::placeholders::error));
refresh_socket_timer();
}
void write_request(const boost::system::error_code& err)
{
DEBUG_TRACE();
boost::asio::async_read_until(*ssocket_, response_, "\r\n",
boost::bind(&AsyncDownload::read_status_line, this,
boost::asio::placeholders::error));
refresh_socket_timer();
}
void read_status_line(const boost::system::error_code& err)
{
DEBUG_TRACE();
std::cout << "read_status_line: " << &response_ << "\n";
boost::asio::async_read_until(*ssocket_, response_, "\r\n\r\n",
boost::bind(&AsyncDownload::read_headers, this,
boost::asio::placeholders::error));
refresh_socket_timer();
}
void read_headers(const boost::system::error_code& err)
{
DEBUG_TRACE();
// ..............
boost::asio::async_read(*ssocket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&AsyncDownload::read_content, this,
boost::asio::placeholders::error)
);
refresh_socket_timer();
}
void read_content(const boost::system::error_code& err)
{
if (err && socket_.is_open())
{
DEBUG_TRACE();
boost::asio::async_read(*ssocket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&AsyncDownload::read_content, this,
boost::asio::placeholders::error)
);
refresh_socket_timer();
}
else
std::cerr << "Error '" << err.message() << "'\n";
}
void start_socket_timer()
{
DEBUG_TRACE();
timer_.async_wait(boost::bind(&AsyncDownload::socket_timeout, this, boost::asio::placeholders::error));
}
void refresh_socket_timer()
{
if (socket_.is_open())
{
DEBUG_TRACE();
timer_.expires_from_now(boost::posix_time::seconds(TCP_SOCKET_TIMEOUT));
timer_.async_wait(boost::bind(&AsyncDownload::socket_timeout, this, boost::asio::placeholders::error));
}
}
void socket_timeout(const boost::system::error_code &error_)
{
// operation_aborted error is thrown whenever we cancel the timer or
// we reset the timer using expires_from_now function,
if ( error_ != boost::asio::error::operation_aborted ) {
DEBUG_TRACE();
std::cout << " ------------> TCP Connection Timeout. Broken Connection Found. Abort Operation\n";
// Ok, our TCP connection is broken, we will cancel all asynchronous
// operations of our sockets.
socket_.close();
}
}
};
int main()
{
DEBUG_TRACE();
boost::asio::io_service io_service;
AsyncDownload download(
io_service,
"http",
"www.google.com",
"80");
io_service.run();
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.