BOOST ASIO :: DENALLINE_TIMER在超时之前重置

Boost asio::deadline_timer is resetting before timeout

本文关键字:超时 TIMER ASIO DENALLINE BOOST      更新时间:2023-10-16

我正在使用boost :: asio :: deadline_timer添加套接字超时选项。我已经实现了异步HTTP读取的读取,并且当我开始与服务器连接时启动deadline_timer,并且在每个回调上,我都会使用功能deadline_timer :: expires_from_now重置deadline_timer。在deadline_timer的错误处理程序中,我清楚地检查了超时是实际的还是操作_abort。但是几乎总是我甚至在预期超时之前就会收到实际的超时。请看看我给定的代码。我在每个回调中都不理解我正在重置计时器,所以为什么会遇到此超时错误。

#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_, "rn",
                         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_, "rnrn",
                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函数调用来刷新计时器。此呼叫将带有错误代码操作的呼叫错误处理程序(socket_timeout),但对于每个实际超时,此函数都会在没有操作_abort的情况下调用,您可以看到我正在明确检查aperation_aborted,但是按照我的期望,我仍然会尽早收到超时,但是我正在收到的每个数据包上的计时器刷新计时器,但我敢肯定它在10秒之前已过期。我还尝试了超时值= 60,但效果相同。任何想法。

更新我在实际代码中使用的错误处理更新了我的代码。为了简单起见,我已经修剪了实际的代码。您可以在计时器超时处理程序中注意到,我正在检查超时是否没有明确(即操作_abort),然后关闭套接字。插座关闭后,我将在套接字数据处理程序(主要是read_content函数)中获得例外。在此功能中,当我收到异常时,我的插座将退出调用ASYNCDOWNLOAD DESTRUCTOR,在此进行更多清洁。如果我删除deadline_timer,我的下载代码非常完美。我在此处添加了它以检测不可预见的TCP连接下降。我在Arm Architture上的嵌入式Linux上运行此代码,并且我的插座很安全,但是当我提到的没有计时器的情况下,我的代码正常工作,因此我认为问题与插座无关。

好吧,所以,我已经玩过你的榜样。

我可以看到超时到期时发生的无限循环,因为缺乏错误处理,尽管已经到达了超时,但read_content循环仍继续进行。

注意:

  1. 第一个read_until_async建议它只会读取状态线。这根本不是那么回事!请参阅boost read_until在定界线上不会停止。

    它将读取第一个数据的" swoop",其中包括至少 rn。实际上,许多服务器将标题发送给同一数据包。(实际上,有效的行为可能取决于代理和可能的硬件)。因此,假设您应该始终要求第二个read_until_async阅读标题是不安全的。而且由于可能永远不会发生"rnrn",因此很容易进入故障模式(例如,到达流程末端时)。

  2. 如果您不仔细观察流程,则可以得出结论:"计时器发射得太早"(例如,您降落在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 Operationn";
        // 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.1rnHost: www.example.comrnrn";
        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_, "rn",
                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_, "rnrn",
                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 Operationn";
            // 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();
}