我在Boost ASIO文档中阅读的所有内容以及StackOverflow上的内容都表明我可以通过调用acceptor套接字上的async_accept
来停止close
操作。但是,当我尝试执行此操作时,not_socket
处理程序中出现间歇async_accept
错误。我做错了什么或者Boost ASIO不支持这个吗?
(注意:我在Windows 7上运行并使用Visual Studio 2015编译器。)
我面临的核心问题是接受传入连接的async_accept
操作与我对close
的调用之间的竞争条件。即使使用明确或隐含的链,也会发生这种情况。
请注意我的致电至async_accept
严格在致电close
之前发生。我得出结论,竞争条件是在我调用close
和Boost ASIO中接受传入连接的底层代码之间。
我已经包含了演示此问题的代码。程序重复创建一个接受器,连接它,并立即关闭接受器。它希望async_accept
操作成功完成或取消。任何其他错误都会导致程序中止,这就是我间歇性地看到的。
对于同步,程序使用显式链。然而,对close
的调用与async_accept
操作的效果不同步,因此有时接受器在接受传入连接之前关闭,有时它会在之后关闭,有时也不会关闭 - 那个问题。
以下是代码:
#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:根据Tanner Sansbury的回答,我添加了std::promise
来表示async_accept
处理程序的完成情况。这对我所看到的行为没有影响。
更新#2 not_socket
错误源自setsockopt
来自call_setsockopt
,来自文件{socket_ops::setsockopt
的来电boost\asio\detail\impl\socket_ops.ipp
1}}(Boost版本1.59)。这是完整的电话:
socket_ops::setsockopt(new_socket, state,
SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,
&update_ctx_param, sizeof(SOCKET), ec);
Microsoft的documentation for setsockopt
说SO_UPDATE_ACCEPT_CONTEXT
:
使用侦听套接字的上下文更新接受套接字。
我不确定这究竟是什么意思,但听起来如果关闭侦听套接字就会失败。这表明,在Windows上,无法安全地close
当前正在为async_accept
操作运行完成处理程序的接受者。
我希望有人可以告诉我,我错了,并且有办法安全地关闭一个忙碌的接受者。
答案 0 :(得分:4)
示例程序不会取消async_accept
操作。建立连接后,async_accept
操作将在内部发布以完成。此时,操作不再可取消,并且不受acceptor.close()
的影响。
观察到的问题是未定义行为的结果。该程序无法满足async_accept
的 peer 参数的生命周期要求:
将接受新连接的套接字。对象的所有权由调用者保留,调用者必须保证它在调用处理程序之前有效。
特别是,对等套接字server_conn
在for
循环中具有自动范围。当async_accept
操作未完成时,循环可以开始新的迭代,导致server_conn
被破坏并违反生命周期要求。考虑通过以下任一方式延长server_conn
的生命周期:
std::future
并等待相关的std::promise
,然后继续循环的下一次迭代server_conn
并将所有权传递给接受处理程序