2015-10-16 151 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操作时终止连接的连接细节。 –