我在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操作時終止連接的連接細節。 –