并行运行多个 URL 的 boost monster 解析器

问题描述 投票:0回答:1

我想知道,如果我们运行多次

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
一次只处理一个解析请求吗?

c++ boost-asio boost-beast
1个回答
3
投票

您专门为每次调用让出并恢复协程。

您自己的协同例程是显式序列化调用的。

这是一个直接的、独立的重现:

住在Coliru

#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
方法:

住在Coliru

#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):

住在Coliru

#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();
}

或者使用并行组,特别是通过范围并行组使其动态:

住在Coliru

#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();
}

这两个 c++20 示例都会产生相同的执行结果:

© www.soinside.com 2019 - 2024. All rights reserved.