簡體   English   中英

Boost asio :: deadline_timer在超時之前重置

[英]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循環仍然繼續。

注意:

  1. 第一個read_until_async建議只讀取狀態行。 這根本不是那么回事! 請參見boost read_until不會在定界符處停止

    它將讀取一次包含最少\\r\\n數據的第一個“ swoop”。 實際上,許多服務器在同一數據包中發送標頭。 (實際上,有效行為可能取決於代理,可能還取決於硬件)。 因此,假設您始終需要第二個read_until_async來讀取標頭是read_until_async的。 而且由於可能永遠不會發生另一個"\\r\\n\\r\\n" ,因此很容易進入故障模式(例如,到達流末尾時)。

  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 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM