Cra*_*urg 5 c++ boost asynchronous boost-asio
我在 Boost ASIO 文档和 StackOverflow 上读到的所有内容都表明我可以async_accept通过调用close接受器套接字来停止操作。但是,当我尝试执行此操作时,处理程序not_socket中出现间歇性错误。async_accept我做错了什么或者 Boost ASIO 不支持这个吗?
(注意:我在 Windows 7 上运行并使用 Visual Studio 2015 编译器。)
\n\n我面临的核心问题是async_accept接受传入连接的操作与我对close. 即使使用显式或隐式的链时也会发生这种情况。
请注意,我对的调用async_accept严格发生在对 的调用之前close。我得出的结论是,竞争条件存在于我的调用close与 Boost ASIO 中接受传入连接的底层代码之间。
我已经包含了演示该问题的代码。该程序重复创建一个接受器,连接到它,然后立即关闭该接受器。它期望async_accept操作成功完成或被取消。任何其他错误都会导致程序中止,这就是我间歇性看到的情况。
为了同步,程序使用显式链。然而,对的调用与操作的效果close不同步,因此有时接受器在接受传入连接之前关闭,有时在之后关闭,有时两者都不是\xe2\x80\x94,因此出现问题。async_accept
这是代码:
\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}\nRun Code Online (Sandbox Code Playgroud)\n\n这是示例运行的输出:
\n\nasync_accept failed (5): An operation was attempted on something that is not a socket\nRun Code Online (Sandbox Code Playgroud)\n\n括号中的数字表示程序成功运行的迭代次数。
\n\n更新#1:根据 Tanner Sansbury 的回答,我添加了一个std::promise用于表示处理程序完成的信号async_accept。这对我所看到的行为没有影响。
更新#2:该not_socket错误源自对文件中的setsockopt、 from call_setsockopt、 from 的调用(Boost 版本 1.59)。这是完整的调用:socket_ops::setsockoptboost\\asio\\detail\\impl\\socket_ops.ipp
socket_ops::setsockopt(new_socket, state,\n SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,\n &update_ctx_param, sizeof(SOCKET), ec);\nRun Code Online (Sandbox Code Playgroud)\n\n微软的文档setsockopt说SO_UPDATE_ACCEPT_CONTEXT:
\n\n\n使用侦听套接字的上下文更新接受套接字。
\n
我不确定这到底意味着什么,但听起来如果关闭监听套接字就会失败。close这表明,在 Windows 上,当前正在运行操作的完成处理程序的接受器无法安全async_accept。
我希望有人能告诉我我错了,有一种方法可以安全地关闭繁忙的接受器。
\n示例程序不会取消该async_accept操作。一旦建立连接,async_accept操作将在内部发布以完成。此时,该操作不再可取消,并且不会受到 的影响acceptor.close()。
观察到的问题是未定义行为的结果。该程序无法满足 的对等参数的生命async_accept周期要求:
将接受新连接的套接字。对等对象的所有权由调用者保留,调用者必须保证它在调用处理程序之前一直有效。
特别是,对等套接字 ,在循环server_conn内具有自动作用域for。async_accept当操作未完成时,循环可能会开始新的迭代,从而导致server_conn被破坏并违反生命周期要求。考虑server_conn通过以下任一方式延长 的生命周期:
std::future并等待相关事件,std::promise然后再继续循环的下一次迭代server_conn通过智能指针进行管理并将所有权传递给接受处理程序