2014-10-07 63 views
1

考慮此短代碼段,其中一個升壓:: deadline_timer中斷另一個:匹配升壓:: deadline_timer回調對應wait_async

#include <iostream> 
#include <boost/bind.hpp> 
#include <boost/function.hpp> 
#include <boost/asio.hpp> 

static boost::asio::io_service io; 
boost::asio::deadline_timer timer1(io); 
boost::asio::deadline_timer timer2(io); 

static void timer1_handler1(const boost::system::error_code& error) 
{ 
    std::cout << __PRETTY_FUNCTION__ << " time:" << time(0) << " error:" << error.message() << " expect:Operation canceled." << std::endl;   
}   

static void timer1_handler2(const boost::system::error_code& error) 
{ 
    std::cout << __PRETTY_FUNCTION__ << " time:" << time(0) << " error:" << error.message() << " expect:success." << std::endl;   
}   

static void timer2_handler1(const boost::system::error_code& error) 
{ 
    std::cout << __PRETTY_FUNCTION__ << " time:" << time(0) << " error:" << error.message() << " expect:success." << std::endl;   
    std::cout << "cancel and restart timer1. Bind to timer1_handler2" << std::endl; 
    timer1.cancel(); 
    timer1.expires_from_now(boost::posix_time::milliseconds(10000)); 
    timer1.async_wait(boost::bind(timer1_handler2, boost::asio::placeholders::error));   
}   

int main() 
{ 
    std::cout << "Start timer1. Bind to timer1_handler1." << std::endl; 
    timer1.expires_from_now(boost::posix_time::milliseconds(2000)); 
    timer1.async_wait(boost::bind(timer1_handler1, boost::asio::placeholders::error));   

    std::cout << "Start timer2. Bind to timer2_handler1. Will interrupt timer1." << std::endl; 
    timer2.expires_from_now(boost::posix_time::milliseconds(2000)); 
    timer2.async_wait(boost::bind(timer2_handler1, boost::asio::placeholders::error));   

    std::cout << "Run the boost io service." << std::endl; 
    io.run(); 

    return 0; 
} 

如果定時器2的時間被圍繞2秒標記改變,有時timer1_handler1報告成功,有時手術取消了。這可能是在微不足道的示例中確定的,因爲我們知道timer2設置爲什麼時間。

./timer1 
Start timer1. Bind to timer1_handler1. 
Start timer2. Bind to timer2_handler1. Will interrupt timer1. 
Run the boost io service. 
void timer1_handler1(const boost::system::error_code&) time:1412680360 error:Success expect:Operation canceled. 
void timer2_handler1(const boost::system::error_code&) time:1412680360 error:Success expect:success. 
cancel and restart timer1. Bind to timer1_handler2 
void timer1_handler2(const boost::system::error_code&) time:1412680370 error:Success expect:success. 

這代表了一個更復雜的系統,其中timer1實現了一個超時,而timer2實際上是一個異步套接字。偶爾我觀察到一個場景,其中timer1被取消的時間太晚,第​​一個處理程序在第二個async_wait()被調用後返回,從而導致虛假的超時。

顯然,我需要將處理程序回調與相應的async_wait()調用進行匹配。有沒有一個方便的方法來做到這一點?

回答

3

您可以向boost::bindboost::bind附加參數添加到可用於識別源的完成處理程序。

+0

是的,我注意到了。實際上,async_wait()被調用很多次,所以我需要某種狀態變量每次都會增加嗎?額外綁定變量是通過引用綁定還是綁定值,還是取決於您如何定義綁定? – 2014-10-07 11:33:43

+0

@SimonElliott這取決於你。你甚至可以選擇通過'boost :: ref(timer1)'傳遞一個對服務對象的引用(比如'deadline_timer')。注意:「async_wait()」被多次調用?但是,您不能在同一對象(套接字,計時器)上執行多個異步操作。您需要取消掛起的操作('timer1.expires_from_now()')或對操作進行排序(使用'strand'或者可能是協程) – sehe 2014-10-07 11:36:51

4

通過使用官方Boost timeout示例中使用的方法,解決構成問題的一種便捷方式是管理由多個非鏈式異步操作組成的更高級別的異步操作。在其中,處理程序通過檢查當前狀態來做出決定,而不是將處理程序邏輯與預期狀態或提供狀態耦合。

在開始解決方案之前,確定處理程序執行的所有可能情況非常重要。當運行io_service時,事件循環的一次迭代將執行所有可以運行的操作,並且在完成操作後,用戶的完成處理程序將排隊,並指示操作的狀態爲error_code。然後io_service將調用排隊的完成處理程序。因此,在一次迭代中,所有準備運行的操作在完成處理程序之前以未指定的順序執行,並且未指定完成處理程序的調用順序。例如,組成從async_read()async_wait(),其中任操作只其它操作的完成處理程序中取消了async_read_with_timeout()操作時,下面的情況是可能的:

  • async_read()運行和async_wait()還沒有準備好運行,然後async_read()的完成處理程序被調用並取消async_wait(),導致async_wait()的完成處理程序運行時出現錯誤boost::asio::error::operation_aborted
  • async_read()還沒有準備好運行和async_wait()運行,然後async_wait()的完成處理程序被調用,並取消async_read(),造成async_read()的完成處理程序與的boost::asio::error::operation_aborted錯誤運行。
  • async_read()async_wait()運行,然後async_read()的完成處理程序首先被調用,但async_wait()操作已經完成,並且無法取消,所以async_wait()的完成處理程序將沒有錯誤運行。
  • async_read()async_wait()運行,然後async_wait()的完成處理程序首先被調用,但async_read()操作已經完成,並且無法取消,所以async_read()的完成處理程序將沒有錯誤運行。

完成處理程序的error_code指示操作的狀態,並不反映由其他完成處理程序導致的狀態更改;因此,當error_code成功時,可能需要檢查當前狀態以執行條件分支。但是,在引入附加狀態之前,可能需要努力檢查更高級別操作的目標以及已經可用的狀態。在這個例子中,我們定義async_read_with_timeout()的目標是在到達截止日期之前沒有收到數據的情況下關閉套接字。對於狀態,插座是打開或關閉的;定時器提供到期時間;系統時鐘提供當前時間。檢查的目標和可用的狀態信息之後,可以建議:如果計時器的當前到期時間是在過去

  • async_wait()的處理程序應該只關閉套接字。
  • async_read()的處理程序應該設置計時器的到期時間。

這種做法,如果async_read()的完成處理async_wait()之前運行,那麼無論async_wait()將被取消或async_wait()的完成處理程序不會關閉連接,作爲當前的到期時間是在未來。另一方面,如果async_wait()的完成處理程序在async_read()之前運行,則async_read()將被取消或async_read()的完成處理程序可以檢測到套接字已關閉。

這是一個完整的小例子demonstrating這種方法用於各種用例:

#include <cassert> 
#include <iostream> 
#include <boost/asio.hpp> 
#include <boost/bind.hpp> 
#include <boost/thread.hpp> 

class client 
{ 
public: 

    // This demo is only using status for asserting code paths. It is not 
    // necessary nor should it be used for conditional branching. 
    enum status_type 
    { 
    unknown, 
    timeout, 
    read_success, 
    read_failure 
    }; 

public: 

    client(boost::asio::ip::tcp::socket& socket) 
    : strand_(socket.get_io_service()), 
     timer_(socket.get_io_service()), 
     socket_(socket), 
     status_(unknown) 
    {} 

    status_type status() const { return status_; } 

    void async_read_with_timeout(boost::posix_time::seconds seconds) 
    { 
    strand_.post(boost::bind(
     &client::do_async_read_with_timeout, this, seconds)); 
    } 

private: 

    void do_async_read_with_timeout(boost::posix_time::seconds seconds) 
    { 
    // Start a timeout for the read. 
    timer_.expires_from_now(seconds); 
    timer_.async_wait(strand_.wrap(boost::bind(
     &client::handle_wait, this, 
     boost::asio::placeholders::error))); 

    // Start the read operation. 
    boost::asio::async_read(socket_, 
     boost::asio::buffer(buffer_), 
     strand_.wrap(boost::bind(
      &client::handle_read, this, 
      boost::asio::placeholders::error, 
      boost::asio::placeholders::bytes_transferred))); 
    } 

    void handle_wait(const boost::system::error_code& error) 
    { 
    // On error, such as cancellation, return early. 
    if (error) 
    { 
     std::cout << "timeout cancelled" << std::endl; 
     return; 
    } 

    // The timer may have expired, but it is possible that handle_read() 
    // ran succesfully and updated the timer's expiration: 
    // - a new timeout has been started. For example, handle_read() ran and 
    // invoked do_async_read_with_timeout(). 
    // - there are no pending timeout reads. For example, handle_read() ran 
    // but did not invoke do_async_read_with_timeout(); 
    if (timer_.expires_at() > boost::asio::deadline_timer::traits_type::now()) 
    { 
     std::cout << "timeout occured, but handle_read ran first" << std::endl; 
     return; 
    } 

    // Otherwise, a timeout has occured and handle_read() has not executed, so 
    // close the socket, cancelling the read operation. 
    std::cout << "timeout occured" << std::endl; 
    status_ = client::timeout; 
    boost::system::error_code ignored_ec; 
    socket_.close(ignored_ec); 
    } 

    void handle_read(
    const boost::system::error_code& error, 
    std::size_t bytes_transferred) 
    { 
    // Update timeout state to indicate handle_read() has ran. This 
    // cancels any pending timeouts. 
    timer_.expires_at(boost::posix_time::pos_infin); 

    // On error, return early. 
    if (error) 
    { 
     std::cout << "read failed: " << error.message() << std::endl; 
     // Only set status if it is unknown. 
     if (client::unknown == status_) status_ = client::read_failure; 
     return; 
    } 

    // The read was succesful, but if a timeout occured and handle_wait() 
    // ran first, then the socket is closed, so return early. 
    if (!socket_.is_open()) 
    { 
     std::cout << "read was succesful but timeout occured" << std::endl; 
     return; 
    } 

    std::cout << "read was succesful" << std::endl; 
    status_ = client::read_success; 
    } 

private: 

    boost::asio::io_service::strand strand_; 
    boost::asio::deadline_timer timer_; 
    boost::asio::ip::tcp::socket& socket_; 
    char buffer_[1]; 
    status_type status_; 
}; 

// This example is not interested in the connect handlers, so provide a noop 
// function that will be passed to bind to meet the handler concept 
// requirements. 
void noop() {} 

/// @brief Create a connection between the server and client socket. 
void connect_sockets(
    boost::asio::ip::tcp::acceptor& acceptor, 
    boost::asio::ip::tcp::socket& server_socket, 
    boost::asio::ip::tcp::socket& client_socket) 
{ 
    boost::asio::io_service& io_service = acceptor.get_io_service(); 
    acceptor.async_accept(server_socket, boost::bind(&noop)); 
    client_socket.async_connect(acceptor.local_endpoint(), boost::bind(&noop)); 
    io_service.reset(); 
    io_service.run(); 
    io_service.reset(); 
} 

int main() 
{ 
    using boost::asio::ip::tcp; 
    boost::asio::io_service io_service; 
    tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0)); 

    // Scenario 1: timeout 
    // The server writes no data, causing a client timeout to occur. 
    { 
    std::cout << "[Scenario 1: timeout]" << std::endl; 
    // Create and connect I/O objects. 
    tcp::socket server_socket(io_service); 
    tcp::socket client_socket(io_service); 
    connect_sockets(acceptor, server_socket, client_socket); 

    // Start read with timeout on client. 
    client client(client_socket); 
    client.async_read_with_timeout(boost::posix_time::seconds(0)); 

    // Allow do_read_with_timeout to intiate actual operations. 
    io_service.run_one();  

    // Run timeout and read operations. 
    io_service.run(); 
    assert(client.status() == client::timeout); 
    } 

    // Scenario 2: no timeout, succesful read 
    // The server writes data and the io_service is ran before the timer 
    // expires. In this case, the async_read operation will complete and 
    // cancel the async_wait. 
    { 
    std::cout << "[Scenario 2: no timeout, succesful read]" << std::endl; 
    // Create and connect I/O objects. 
    tcp::socket server_socket(io_service); 
    tcp::socket client_socket(io_service); 
    connect_sockets(acceptor, server_socket, client_socket); 

    // Start read with timeout on client. 
    client client(client_socket); 
    client.async_read_with_timeout(boost::posix_time::seconds(10)); 

    // Allow do_read_with_timeout to intiate actual operations. 
    io_service.run_one(); 

    // Write to client. 
    boost::asio::write(server_socket, boost::asio::buffer("test")); 

    // Run timeout and read operations. 
    io_service.run(); 
    assert(client.status() == client::read_success); 
    } 

    // Scenario 3: no timeout, failed read 
    // The server closes the connection before the timeout, causing the 
    // async_read operation to fail and cancel the async_wait operation. 
    { 
    std::cout << "[Scenario 3: no timeout, failed read]" << std::endl; 
    // Create and connect I/O objects. 
    tcp::socket server_socket(io_service); 
    tcp::socket client_socket(io_service); 
    connect_sockets(acceptor, server_socket, client_socket); 

    // Start read with timeout on client. 
    client client(client_socket); 
    client.async_read_with_timeout(boost::posix_time::seconds(10)); 

    // Allow do_read_with_timeout to intiate actual operations. 
    io_service.run_one(); 

    // Close the socket. 
    server_socket.close(); 

    // Run timeout and read operations. 
    io_service.run(); 
    assert(client.status() == client::read_failure); 
    } 

    // Scenario 4: timeout and read success 
    // The server writes data, but the io_service is not ran until the 
    // timer has had time to expire. In this case, both the await_wait and 
    // asnyc_read operations complete, but the order in which the 
    // handlers run is indeterminiate. 
    { 
    std::cout << "[Scenario 4: timeout and read success]" << std::endl; 
    // Create and connect I/O objects. 
    tcp::socket server_socket(io_service); 
    tcp::socket client_socket(io_service); 
    connect_sockets(acceptor, server_socket, client_socket); 

    // Start read with timeout on client. 
    client client(client_socket); 
    client.async_read_with_timeout(boost::posix_time::seconds(0)); 

    // Allow do_read_with_timeout to intiate actual operations. 
    io_service.run_one(); 

    // Allow the timeout to expire, the write to the client, causing both 
    // operations to complete with success. 
    boost::this_thread::sleep_for(boost::chrono::seconds(1)); 
    boost::asio::write(server_socket, boost::asio::buffer("test")); 

    // Run timeout and read operations. 
    io_service.run(); 
    assert( (client.status() == client::timeout) 
      || (client.status() == client::read_success)); 
    } 
} 

,其輸出:

[Scenario 1: timeout] 
timeout occured 
read failed: Operation canceled 
[Scenario 2: no timeout, succesful read] 
read was succesful 
timeout cancelled 
[Scenario 3: no timeout, failed read] 
read failed: End of file 
timeout cancelled 
[Scenario 4: timeout and read success] 
read was succesful 
timeout occured, but handle_read ran first 
+0

尼斯詳細的答案。我想知道這種策略是否可以用來實現一個通用的「超時操作」機制,這個機制很容易被ASIO提供的現有操作所包裹。我知道,我以前經常需要這樣做,並最終多次重塑它。 – 2014-10-08 15:16:23

+0

@JasonR在當前的實現中,通用超時機制是可能的。然而,根據我的經驗,他們通常對應用程序代碼的質量產生的負面影響遠遠超過我的喜好。 – 2014-10-09 03:38:23

+0

感謝您的非常詳細的回覆。我確實看到了你鏈接的增強示例,但誤解了截止日期計時器檢查的全部內容。我更喜歡這種方法來維護定時器狀態。 – 2014-10-13 08:13:38