我想知道,如果我们运行多次
async_resolve
调用,那么由于已知的限制,io_context
不会同时运行多个请求。
这是一个示例示例,我希望每个解析请求在等待 dns 响应返回时都会产生结果,并移至下一个请求,依此类推。
但从 Wireshark 来看,下一个解析似乎只有在前一个解析完成后才会执行。
boost::asio::ip::tcp::resolver resolver(io_ctx_);
const auto results1 = resolver.async_resolve(host1_, std::to_string(port1_), yield);
const auto results2 = resolver.async_resolve(host2_, std::to_string(port2_), yield);
const auto results3 = resolver.async_resolve(host3_, std::to_string(port3_), yield);
io_context
一次只处理一个解析请求吗?
您专门为每次调用让出并恢复协程。
您自己的协同例程是显式序列化调用的。
这是一个直接的、独立的重现:
#include <boost/asio.hpp>
#include <boost/asio/experimental/promise.hpp>
#include <boost/asio/experimental/use_promise.hpp>
#include <boost/asio/spawn.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
void foo(asio::yield_context yield) {
struct {
std::string host;
uint16_t port;
tcp::resolver::results_type results;
} queries[]{
{"www.example.com", 80, {}},
{"localhost", 53, {}},
{"httpbin.org", 443, {}},
};
tcp::resolver resolver(yield.get_executor());
for (auto& [h, p, r] : queries)
r = resolver.async_resolve(h, std::to_string(p), yield);
for (auto& q : queries)
for (auto& ep : q.results)
std::cout << ep.host_name() << ":" << ep.service_name() << " -> " << ep.endpoint() << std::endl;
}
int main() {
asio::io_context ioc;
asio::spawn(ioc, foo);
ioc.run();
}
打印例如
www.example.com:80 -> 93.184.216.34:80
www.example.com:80 -> [2606:2800:220:1:248:1893:25c8:1946]:80
localhost:53 -> 127.0.0.1:53
httpbin.org:443 -> 3.221.184.26:443
httpbin.org:443 -> 35.173.166.175:443
httpbin.org:443 -> 54.165.134.201:443
httpbin.org:443 -> 174.129.27.151:443
使用处理程序跟踪:
有很多方法可以修复它,但让我在这里尝试“新”
use_promise
方法:
#include <boost/asio.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/experimental/promise.hpp>
#include <boost/asio/experimental/use_promise.hpp>
#include <boost/asio/spawn.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::experimental::promise;
using asio::experimental::use_promise;
using asio::ip::tcp;
using boost::system::error_code;
void foo(asio::yield_context yield) {
struct {
std::string host;
uint16_t port;
} queries[]{
{"www.example.com", 80},
{"localhost", 53},
{"httpbin.org", 443},
};
tcp::socket s(yield.get_executor());
tcp::resolver resolver(yield.get_executor());
std::vector<promise<void(error_code, tcp::resolver::results_type)>> pp;
for (auto& [h, p] : queries)
pp.emplace_back(resolver.async_resolve(h, std::to_string(p), use_promise));
for (auto& p : pp)
for (auto& ep : p(yield))
std::cout << ep.host_name() << ":" << ep.service_name() << " -> " << ep.endpoint() << std::endl;
}
int main() {
asio::io_context ioc;
spawn(ioc, foo);
ioc.run();
}
相同的输出,但现在使用处理程序跟踪:
在 c++20 协程中使用可等待运算符通常更友好,并且不需要 Boost Context(或 Boost Coroutine):
#include <boost/asio.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <iostream>
#include <ranges>
namespace asio = boost::asio;
using namespace asio::experimental::awaitable_operators;
using asio::ip::tcp;
asio::awaitable<void> foo() {
tcp::resolver resolver(co_await asio::this_coro::executor);
auto op1 = resolver.async_resolve("www.example.com", std::to_string(80), asio::use_awaitable);
auto op2 = resolver.async_resolve("localhost", std::to_string(53), asio::use_awaitable);
auto op3 = resolver.async_resolve("httpbin.org", std::to_string(443), asio::use_awaitable);
auto [r1, r2, r3] = co_await (std::move(op1) && std::move(op2) && std::move(op3));
for (auto&& eps : {r1, r2, r3})
for (auto&& ep : eps)
std::cout << ep.host_name() << ":" << ep.service_name() << " -> " << ep.endpoint() << std::endl;
}
int main() {
asio::io_context ioc;
co_spawn(ioc, foo, asio::detached);
ioc.run();
}
或者使用并行组,特别是通过范围并行组使其动态:
#include <boost/asio.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/spawn.hpp>
#include <iostream>
#include <ranges>
namespace r = std::ranges;
namespace v = r::views;
namespace asio = boost::asio;
using asio::ip::tcp;
asio::awaitable<void> foo() {
struct {
std::string host;
uint16_t port;
} queries[]{
{"www.example.com", 80},
{"localhost", 53},
{"httpbin.org", 443},
};
tcp::resolver resolver(co_await asio::this_coro::executor);
auto start_resolve = [&resolver](auto& q) {
return resolver.async_resolve(q.host, std::to_string(q.port), asio::deferred);
};
auto r = queries | v::transform(start_resolve);
auto pg = asio::experimental::make_parallel_group(std::vector(r.begin(), r.end()));
auto results = co_await pg.async_wait(asio::experimental::wait_for_all{}, asio::deferred);
for (auto&& eps : get<2>(results))
for (auto&& ep : eps)
std::cout << ep.host_name() << ":" << ep.service_name() << " -> " << ep.endpoint() << std::endl;
}
int main() {
asio::io_context ioc;
co_spawn(ioc, foo, asio::detached);
ioc.run();
}