2015-10-16 190 views
4

我在Boost ASIO文檔中閱讀的所有內容以及StackOverflow中的這些內容都暗示我可以通過在acceptor套接字上調用close來停止async_accept操作。但是,當我嘗試執行此操作時,async_accept處理程序中出現間歇性not_socket錯誤。我做錯了什麼或者Boost ASIO不支持這個嗎?如何安全取消Boost ASIO異步接受操作?

(相關的問題:herehere

(注:我在Windows 7上運行,並使用Visual Studio 2015年編譯器。)

的核心問題,我面對的是之間的競爭條件接受傳入連接的async_accept操作和我致電close的操作。即使使用明確的或隱含的鏈,也會發生這種情況。

注意我呼叫async_accept嚴格我呼籲close發生。我得出結論,競爭條件是在我致電close與Boost ASIO中接受傳入連接的底層代碼之間。

我已經包含演示問題的代碼。程序重複創建一個接受者,連接到它,並立即關閉接受者。它預計async_accept操作要麼成功完成,要麼被取消。任何其他錯誤都會導致程序異常中止,這是我所看到的。

爲了同步,程序使用顯式鏈。儘管如此,close的調用與async_accept操作的的10不同步,因此有時候接受者在接受傳入連接之前會關閉,有時會在之後關閉,有時也會關閉,因此也不是問題。

下面的代碼:

#include <algorithm> 
#include <boost/asio.hpp> 
#include <cstdlib> 
#include <future> 
#include <iostream> 
#include <memory> 
#include <thread> 

int main() 
{ 
    boost::asio::io_service ios; 
    auto work = std::make_unique<boost::asio::io_service::work>(ios); 

    const auto ios_runner = [&ios]() 
    { 
    boost::system::error_code ec; 
    ios.run(ec); 
    if (ec) 
    { 
     std::cerr << "io_service runner failed: " << ec.message() << '\n'; 
     abort(); 
    } 
    }; 

    auto thread = std::thread{ios_runner}; 

    const auto make_acceptor = [&ios]() 
    { 
    boost::asio::ip::tcp::resolver resolver{ios}; 
    boost::asio::ip::tcp::resolver::query query{ 
     "localhost", 
     "", 
     boost::asio::ip::resolver_query_base::passive | 
     boost::asio::ip::resolver_query_base::address_configured}; 
    const auto itr = std::find_if(
     resolver.resolve(query), 
     boost::asio::ip::tcp::resolver::iterator{}, 
     [](const boost::asio::ip::tcp::endpoint& ep) { return true; }); 
    assert(itr != boost::asio::ip::tcp::resolver::iterator{}); 
    return boost::asio::ip::tcp::acceptor{ios, *itr}; 
    }; 

    for (auto i = 0; i < 1000; ++i) 
    { 
    auto acceptor = make_acceptor(); 
    const auto saddr = acceptor.local_endpoint(); 

    boost::asio::io_service::strand strand{ios}; 
    boost::asio::ip::tcp::socket server_conn{ios}; 

    // Start accepting. 
    std::promise<void> accept_promise; 
    strand.post(
     [&]() 
    { 
     acceptor.async_accept(
     server_conn, 
     strand.wrap(
      [&](const boost::system::error_code& ec) 
      { 
      accept_promise.set_value(); 
      if (ec.category() == boost::asio::error::get_system_category() 
       && ec.value() == boost::asio::error::operation_aborted) 
       return; 
      if (ec) 
      { 
       std::cerr << "async_accept failed (" << i << "): " << ec.message() << '\n'; 
       abort(); 
      } 
      })); 
    }); 

    // Connect to the acceptor. 
    std::promise<void> connect_promise; 
    strand.post(
     [&]() 
    { 
     boost::asio::ip::tcp::socket client_conn{ios}; 
     { 
     boost::system::error_code ec; 
     client_conn.connect(saddr, ec); 
     if (ec) 
     { 
      std::cerr << "connect failed: " << ec.message() << '\n'; 
      abort(); 
     } 
     connect_promise.set_value(); 
     } 
    }); 
    connect_promise.get_future().get(); // wait for connect to finish 

    // Close the acceptor. 
    std::promise<void> stop_promise; 
    strand.post([&acceptor, &stop_promise]() 
    { 
     acceptor.close(); 
     stop_promise.set_value(); 
    }); 
    stop_promise.get_future().get(); // wait for close to finish 
    accept_promise.get_future().get(); // wait for async_accept to finish 
    } 

    work.reset(); 
    thread.join(); 
} 

下面是一個示例運行的輸出:

async_accept failed (5): An operation was attempted on something that is not a socket 

括號中的數字表示有多少成功的迭代程序運行。

更新#1:基於坦納桑斯伯裏的回答,我已經爲信令async_accept處理程序的完成增加了一個std::promise。這對我看到的行爲沒有影響。

更新#2:not_socket誤差從到setsockopt呼叫發起,從call_setsockopt,從socket_ops::setsockopt在文件boost\asio\detail\impl\socket_ops.ipp(升壓版本1.59)。下面是完整的呼叫:

socket_ops::setsockopt(new_socket, state, 
    SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, 
    &update_ctx_param, sizeof(SOCKET), ec); 

微軟documentation for setsockopt說,大約SO_UPDATE_ACCEPT_CONTEXT

更新與監聽套接字的背景下接受插座。

我不確定這意味着什麼,但聽起來像是一些失敗,如果偵聽套接字關閉。這表明,在Windows上,不能安全地接受當前運行async_accept操作的完成處理程序的接受程序。

我希望有人能告訴我我錯了,並且有一種方法可以安全地關閉忙碌的接受者。

回答

4

示例程序不會取消async_accept操作。連接建立後,async_accept操作將在內部發布完成。此時,該操作不再可取消,並且不會受到acceptor.close()的影響。

觀察到的問題是未定義行爲的結果。該方案不符合async_accept同行參數壽命要求:

插座到其中的新的連接將被接受。對等體對象的所有權由調用者保留,調用者必須保證它在調用處理程序之前是有效的。

特別是,對等套接字server_connfor循環內具有自動範圍。循環可能會開始新的迭代,而async_accept操作未完成,導致server_conn被破壞並違反壽命要求。考慮要麼延長server_conn的一生:

  • 設置內接受處理,並通過智能指針繼續循環的下一次迭代
  • 管理server_conn並通過所有權之前,對相關std::promise等待std::future接受處理程序
+0

我已更正我的程序以使用'promise'來同步'async_accept'處理程序發生在'for'循環結束之前。但是,我仍然看到相同的「不是套接字」錯誤發生。 –

+0

我應該澄清一點,我明白,'async_accept'操作成功完成和我關閉接受者的嘗試之間的內在競爭是可以的。我想要的是'async_accept'(1)成功完成,否則(2)被取消。順便說一句,謝謝你回答我的問題。我在StackOverflow上使用了許多其他的答案。 –

+0

@ CraigM.Brandenburg很高興能有所幫助。關閉接受者並不能保證未完成的'async_accept'操作將被取消。操作必須處於可取消狀態,取消操作必須由底層系統調用支持。 'async_accept'操作使用['AccceptEx()'](https://msdn.microsoft.com/en-us/library/windows/desktop/ms737524(v = vs.85).aspx),它不會填充具有所有連接細節的對等套接字。 Asio試圖在完成async_accept操作時終止連接的連接細節。 –