我在Boost ASIO文档中阅读的所有内容以及StackOverflow中的这些内容都暗示我可以通过在acceptor套接字上调用close
来停止async_accept
操作。但是,当我尝试执行此操作时,async_accept
处理程序中出现间歇性not_socket
错误。我做错了什么或者Boost ASIO不支持这个吗?如何安全取消Boost ASIO异步接受操作?
(注:我在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
操作的完成处理程序的接受程序。
我希望有人能告诉我我错了,并且有一种方法可以安全地关闭忙碌的接受者。
我已更正我的程序以使用'promise'来同步'async_accept'处理程序发生在'for'循环结束之前。但是,我仍然看到相同的“不是套接字”错误发生。 –
我应该澄清一点,我明白,'async_accept'操作成功完成和我关闭接受者的尝试之间的内在竞争是可以的。我想要的是'async_accept'(1)成功完成,否则(2)被取消。顺便说一句,谢谢你回答我的问题。我在StackOverflow上使用了许多其他的答案。 –
@ CraigM.Brandenburg很高兴能有所帮助。关闭接受者并不能保证未完成的'async_accept'操作将被取消。操作必须处于可取消状态,取消操作必须由底层系统调用支持。 'async_accept'操作使用['AccceptEx()'](https://msdn.microsoft.com/en-us/library/windows/desktop/ms737524(v = vs.85).aspx),它不会填充具有所有连接细节的对等套接字。 Asio试图在完成async_accept操作时终止连接的连接细节。 –