функция co_await в ASIO - PullRequest
       65

функция co_await в ASIO

2 голосов
/ 17 апреля 2020

Я пытаюсь создать класс, который предоставляет функцию Connect () для подключения к конечной точке.

#ifndef ENDPOINT_HPP
#define ENDPOINT_HPP

#include <asio.hpp>

namespace Utils {

class Endpoint {
public:
  using ResolveResult = asio::ip::basic_resolver_results<asio::ip::tcp>;

  Endpoint() = default;
  Endpoint(const std::string &hostname, const std::string &port);
  asio::awaitable<ResolveResult> Resolve();
  asio::awaitable<void>          Connect();
  const std::string &            Hostname();
  const std::string &            Port();
  asio::ip::tcp::socket &        Socket();
  std::string                    ToString();

private:
  std::unique_ptr<asio::ip::tcp::socket> socket;
  std::string                            hostname;
  std::string                            port;
};

} // namespace Utils

#endif

И определение конечной точки

#include "Utils/Endpoint.hpp"

#include <memory>
#include <spdlog/spdlog.h>
#include <stdexcept>
#include <string>

namespace Utils {

using asio::awaitable;
using asio::use_awaitable;
using asio::ip::tcp;
using ResolveResult = Endpoint::ResolveResult;

Endpoint::Endpoint(const std::string &hostname, const std::string &port)
    : hostname(hostname), port(port) {}

awaitable<ResolveResult> Endpoint::Resolve() {
  try {
    spdlog::trace("Resolving {}:{}", hostname, port);
    const asio::executor &executor = co_await asio::this_coro::executor;
    tcp::resolver         resolver(executor);
    ResolveResult         endpoints =
        co_await resolver.async_resolve({hostname, port}, use_awaitable);
    spdlog::trace("Resolve {}:{} success", hostname, port);
    co_return endpoints;

  } catch (std::exception &e) {
    spdlog::error("Resolve {}:{} failed. Exception: {}", hostname, port,
                  e.what());
    throw std::runtime_error(e.what());
  }
}

awaitable<void> Endpoint::Connect() {
  try {
    spdlog::trace("Connecting to {}:{}", hostname, port);
    if (socket.get()) {
      spdlog::trace("{}:{} has been connected", hostname, port);
      co_return;
    }

    const asio::executor &executor = co_await asio::this_coro::executor;
    socket                         = std::make_unique<tcp::socket>(executor);
    ResolveResult endpoints        = co_await Resolve();
    auto          strand           = asio::make_strand(executor);
    co_await asio::async_connect(*socket, endpoints, use_awaitable);
    spdlog::trace("Connect to {}:{} success, socket status: {}", hostname, port,
                  socket->is_open());

  } catch (std::exception &e) {
    spdlog::error("Connect to {}:{} failed. Exception: {}", hostname, port,
                  e.what());
    throw std::runtime_error(e.what());
  }
}

const std::string &Endpoint::Hostname() { return hostname; }

const std::string &Endpoint::Port() { return port; }

tcp::socket &Endpoint::Socket() { return *socket; }

std::string Endpoint::ToString() {
  return fmt::format("{}:{}", hostname, port);
}

} // namespace Utils

Если я co_spawn две сопрограммы одного экземпляра Endpoint и обе функции Connect (). Это не будет работать правильно. Одна ситуация состоит в том, что одна сопрограммная пауза в

socket                         = std::make_unique<tcp::socket>(executor);

, а другая будет go в ответвлении соединения, была подключена до завершения разрешения.

Мой тестовый код

#include "Utils/Endpoint.hpp"

#include <gtest/gtest.h>
#include <spdlog/spdlog.h>
#include <string>

using asio::co_spawn;
using asio::detached;
using asio::ip::tcp;

asio::awaitable<void> EndpointTest(Utils::Endpoint &endpoint) {
  const auto& executor= co_await asio::this_coro::executor;
  co_await endpoint.Connect();
  tcp::socket &socket = endpoint.Socket();
  co_await asio::async_write(socket,
                             asio::buffer(std::string("Hello World", 10000)),
                             asio::use_awaitable);
}

TEST(Utils, Endpoint) {
  asio::io_context ctx;
  Utils::Endpoint  endpoint = Utils::Endpoint("baidu.com", "80");
  co_spawn(
      ctx, [&] { return EndpointTest(endpoint); }, detached);
  co_spawn(
      ctx, [&] { return EndpointTest(endpoint); }, detached);
  ctx.run();
}

И вывод:

[2020-04-17 10:14:20.952] [trace] Connecting to baidu.com:80
[2020-04-17 10:14:20.953] [trace] Resolving baidu.com:80
[2020-04-17 10:14:20.953] [trace] Connecting to baidu.com:80
[2020-04-17 10:14:20.953] [trace] baidu.com:80 has been connected
[2020-04-17 10:14:22.403] [trace] Resolve baidu.com:80 success
[2020-04-17 10:14:22.478] [trace] Connect to baidu.com:80 success, socket status: true

Поэтому я хочу связать функцию Connect (), которая не будет выполняться до завершения предыдущего Connect (). Или любые другие орудия, которые могут достичь этого.

...