提升asio :: deadline_timer在超时前重置

时间:2014-01-30 10:50:29

标签: c++ boost tcp timer

我正在使用boost :: asio :: deadline_timer来添加套接字超时选项。我已经实现了异步HTTP读取,当我开始连接服务器时启动deadline_timer,并且在每次回调时,我使用函数deadline_timer :: expires_from_now重置deadline_timer。在deadline_timer的错误处理程序中,我清楚地检查超时是实际的还是operation_aborted。但是,即使在预期超时之前,我几乎总是会收到实际超请看一下我给出的代码。我不理解每次回调我都在重置计时器然后我为什么会收到这个超时错误。

#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但效果相同。任何想法。

更新 更新了我的代码,我在实际代码中使用了错误处理。为简单起见,我已经删除了我的实际代码。您可以注意到在计时器超时处理程序中,我正在检查超时是否未显式(即operation_aborted),然后关闭套接字。一旦套接字关闭,我将在我的套接字数据处理程序(主要是read_content函数)中得到一个异常。在该函数中,当我收到异常时,我的套接字将退出调用AsyncDownload析构函数,我正在做更多的清理工作。如果我删除deadline_timer,我的下载代码将非常完美。我在这里添加它来检测无法预料的TCP连接丢弃。我在ARM体系结构的嵌入式Linux上运行此代码,我的套接字是安全的,但正如我所提到的,我的代码在没有定时器的情况下工作正常,所以我不认为这个问题与套接字有关。

1 个答案:

答案 0 :(得分:0)

好的,所以,我已经玩过你的示例。

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

注意:

  1. 第一个read_until_async表示它只会读取状态行。这根本不是那么回事! 见boost read_until does not stop at delimiter

    它将读取包含至少\r\n一次的数据的第一个“俯冲”。实际上,许多服务器在同一个数据包中发送标头。 (事实上​​,有效的行为可能取决于代理和可能的硬件)。因此,假设您应始终要求第二个read_until_async读取标题是不安全的。并且由于另一个"\r\n\r\n"可能永远不会发生,因此很容易进入失败模式(例如,当达到流末尾时)。

  2. 如果你不仔细观察流程,可以得出“计时器过早发射”的结论(例如,因为你登陆refresh_socket_timer)。但是,在我的错误情况下发生的事情是read_until_async只是立即返回,错误地处理,就像返回了数据包一样。

  3. 所以我建议像

    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();
    }