Я пытаюсь создать класс, который предоставляет функцию 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 (). Или любые другие орудия, которые могут достичь этого.