如何安全取消 Boost ASIO 异步接受操作?

Cra*_*urg 5 c++ boost asynchronous boost-asio

我在 Boost ASIO 文档和 StackOverflow 上读到的所有内容都表明我可以async_accept通过调用close接受器套接字来停止操作。但是,当我尝试执行此操作时,处理程序not_socket中出现间歇性错误。async_accept我做错了什么或者 Boost ASIO 不支持这个吗?

\n\n

(相关问题:这里这里。)

\n\n

(注意:我在 Windows 7 上运行并使用 Visual Studio 2015 编译器。)

\n\n

我面临的核心问题是async_accept接受传入连接的操作与我对close. 即使使用显式或隐式的链时也会发生这种情况。

\n\n

请注意,我对的调用async_accept严格发生在对 的调用之前close。我得出的结论是,竞争条件存在于我的调用close与 Boost ASIO 中接受传入连接的底层代码之间。

\n\n

我已经包含了演示该问题的代码。该程序重复创建一个接受器,连接到它,然后立即关闭该接受器。它期望async_accept操作成功完成或被取消。任何其他错误都会导致程序中止,这就是我间歇性看到的情况。

\n\n

为了同步,程序使用显式链。然而,对的调用与操作的效果close不同步,因此有时接受器在接受传入连接之前关闭,有时在之后关闭,有时两者都不是\xe2\x80\x94,因此出现问题。async_accept

\n\n

这是代码:

\n\n
#include <algorithm>\n#include <boost/asio.hpp>\n#include <cstdlib>\n#include <future>\n#include <iostream>\n#include <memory>\n#include <thread>\n\nint main()\n{\n  boost::asio::io_service ios;\n  auto work = std::make_unique<boost::asio::io_service::work>(ios);\n\n  const auto ios_runner = [&ios]()\n  {\n    boost::system::error_code ec;\n    ios.run(ec);\n    if (ec)\n    {\n      std::cerr << "io_service runner failed: " << ec.message() << \'\\n\';\n      abort();\n    }\n  };\n\n  auto thread = std::thread{ios_runner};\n\n  const auto make_acceptor = [&ios]()\n  {\n    boost::asio::ip::tcp::resolver resolver{ios};\n    boost::asio::ip::tcp::resolver::query query{\n      "localhost",\n      "",\n      boost::asio::ip::resolver_query_base::passive |\n      boost::asio::ip::resolver_query_base::address_configured};\n    const auto itr = std::find_if(\n      resolver.resolve(query),\n      boost::asio::ip::tcp::resolver::iterator{},\n      [](const boost::asio::ip::tcp::endpoint& ep) { return true; });\n    assert(itr != boost::asio::ip::tcp::resolver::iterator{});\n    return boost::asio::ip::tcp::acceptor{ios, *itr};\n  };\n\n  for (auto i = 0; i < 1000; ++i)\n  {\n    auto acceptor = make_acceptor();\n    const auto saddr = acceptor.local_endpoint();\n\n    boost::asio::io_service::strand strand{ios};\n    boost::asio::ip::tcp::socket server_conn{ios};\n\n    // Start accepting.\n    std::promise<void> accept_promise;\n    strand.post(\n      [&]()\n    {\n      acceptor.async_accept(\n        server_conn,\n        strand.wrap(\n          [&](const boost::system::error_code& ec)\n          {\n            accept_promise.set_value();\n            if (ec.category() == boost::asio::error::get_system_category()\n              && ec.value() == boost::asio::error::operation_aborted)\n              return;\n            if (ec)\n            {\n              std::cerr << "async_accept failed (" << i << "): " << ec.message() << \'\\n\';\n              abort();\n            }\n          }));\n    });\n\n    // Connect to the acceptor.\n    std::promise<void> connect_promise;\n    strand.post(\n      [&]()\n    {\n      boost::asio::ip::tcp::socket client_conn{ios};\n      {\n        boost::system::error_code ec;\n        client_conn.connect(saddr, ec);\n        if (ec)\n        {\n          std::cerr << "connect failed: " << ec.message() << \'\\n\';\n          abort();\n        }\n        connect_promise.set_value();\n      }\n    });\n    connect_promise.get_future().get();   // wait for connect to finish\n\n    // Close the acceptor.\n    std::promise<void> stop_promise;\n    strand.post([&acceptor, &stop_promise]()\n    {\n      acceptor.close();\n      stop_promise.set_value();\n    });\n    stop_promise.get_future().get();   // wait for close to finish\n    accept_promise.get_future().get(); // wait for async_accept to finish\n  }\n\n  work.reset();\n  thread.join();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这是示例运行的输出:

\n\n
async_accept failed (5): An operation was attempted on something that is not a socket\n
Run Code Online (Sandbox Code Playgroud)\n\n

括号中的数字表示程序成功运行的迭代次数。

\n\n

更新#1:根据 Tanner Sansbury 的回答,我添加了一个std::promise用于表示处理程序完成的信号async_accept。这对我所看到的行为没有影响。

\n\n

更新#2:not_socket错误源自对文件中的setsockopt、 from call_setsockopt、 from 的调用(Boost 版本 1.59)。这是完整的调用:socket_ops::setsockoptboost\\asio\\detail\\impl\\socket_ops.ipp

\n\n
socket_ops::setsockopt(new_socket, state,\n  SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,\n  &update_ctx_param, sizeof(SOCKET), ec);\n
Run Code Online (Sandbox Code Playgroud)\n\n

微软的文档setsockoptSO_UPDATE_ACCEPT_CONTEXT

\n\n
\n

使用侦听套接字的上下文更新接受套接字。

\n
\n\n

我不确定这到底意味着什么,但听起来如果关闭监听套接字就会失败。close这表明,在 Windows 上,当前正在运行操作的完成处理程序的接受器无法安全async_accept

\n\n

我希望有人能告诉我我错了,有一种方法可以安全地关闭繁忙的接受器。

\n

Tan*_*ury 4

示例程序不会取消该async_accept操作。一旦建立连接,async_accept操作将在内部发布以完成。此时,该操作不再可取消,并且不会受到 的影响acceptor.close()

观察到的问题是未定义行为的结果。该程序无法满足 的对等参数的生命async_accept周期要求

将接受新连接的套接字。对等对象的所有权由调用者保留,调用者必须保证它在调用处理程序之前一直有效。

特别是,对等套接字 ,在循环server_conn内具有自动作用域forasync_accept当操作未完成时,循环可能会开始新的迭代,从而导致server_conn被破坏并违反生命周期要求。考虑server_conn通过以下任一方式延长 的生命周期:

  • 在接受处理程序中设置 astd::future并等待相关事件,std::promise然后再继续循环的下一次迭代
  • server_conn通过智能指针进行管理并将所有权传递给接受处理程序