[英]How to safely cancel a Boost ASIO asynchronous accept operation?
我在Boost ASIO文档和StackOverflow中读到的所有内容都表明我可以通过在acceptor套接字上调用close
来停止async_accept
操作。 但是,当我尝试执行此操作时,我在async_accept
处理程序中出现间歇性not_socket
错误。 我做错了什么或者Boost ASIO不支持这个吗?
(注意:我在Windows 7上运行并使用Visual Studio 2015编译器。)
我面临的核心问题是接受传入连接的async_accept
操作和我close
调用之间的竞争条件。 即使使用明确或隐含的链,也会发生这种情况。
请注意, 在调用close
之前,我对async_accept
调用严格发生 。 我得出结论,竞争条件是在我的呼叫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.59)。 这是完整的电话:
socket_ops::setsockopt(new_socket, state,
SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,
&update_ctx_param, sizeof(SOCKET), ec);
关于setsockopt
Microsoft 文档说SO_UPDATE_ACCEPT_CONTEXT
:
使用侦听套接字的上下文更新接受套接字。
我不确定这究竟是什么意思,但听起来如果关闭侦听套接字会失败。 这表明,在Windows上,无法安全地close
当前正在为async_accept
操作运行完成处理程序的async_accept
。
我希望有人可以告诉我,我错了,有一种方法可以安全地关闭一个忙碌的接受者。
示例程序不会取消async_accept
操作。 建立连接后,将在内部发布async_accept
操作以完成。 此时,该操作不再可取消,并且不受acceptor.close()
影响。
观察到的问题是未定义行为的结果。 该程序无法满足async_accept
的peer参数的生命周期要求:
将接受新连接的套接字。 对象的所有权由调用者保留,调用者必须保证它在调用处理程序之前有效。
特别是,对等套接字server_conn
在for
循环中具有自动范围。 当async_accept
操作未完成时,循环可以开始新的迭代,导致server_conn
被破坏并违反生命周期要求。 考虑通过以下任一方式扩展server_conn
的生命周期:
std::future
并等待相关的std::promise
然后继续循环的下一次迭代 server_conn
并将所有权传递给accept处理程序
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.