Несколько проблем с тем, как вы адаптировали этот пример:
- рабочий поток блокирует io_service с экземпляром
work
, поэтому он никогда не завершится
- вы
usleep
за некоторое время до запуска асинхронных задач, но вы никогда не запускаете ни одну из задач до тех пор, пока цикл не завершится... Это означает, что все задержки выполняются до запуска любая работа.
Вот мое предложение:
- запустите службу перед запуском асинхронных задач
- иметь 1 экземпляр
work
для блокировки службы на случай, если служба будет простаивать перед отправкой следующего HTTP-запроса.
- не блокируйте
work
внутри рабочего потока
Прямой эфир на Coliru
#include "example/common/root_certificates.hpp"
#include <boost/beast.hpp>
#include <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n";
}
// Performs an HTTP GET and prints the response
class session : public std::enable_shared_from_this<session>
{
tcp::resolver resolver_;
ssl::stream<tcp::socket> stream_;
boost::beast::flat_buffer buffer_; // (Must persist between reads)
http::request<http::empty_body> req_;
http::response<http::string_body> res_;
public:
// Resolver and stream require an io_context
explicit
session(boost::asio::io_context& ioc, ssl::context& ctx)
: resolver_(ioc)
, stream_(ioc, ctx)
{
}
// Start the asynchronous operation
void
run(
char const* host,
char const* port,
char const* target,
int version)
{
// Set SNI Hostname (many hosts need this to handshake successfully)
if(! SSL_set_tlsext_host_name(stream_.native_handle(), host))
{
boost::system::error_code ec{static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category()};
std::cerr << ec.message() << "\n";
return;
}
// Set up an HTTP GET request message
req_.version(version);
req_.method(http::verb::get);
req_.target(target);
req_.set(http::field::host, host);
req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// Look up the domain name
resolver_.async_resolve(
host,
port,
std::bind(
&session::on_resolve,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void
on_resolve(
boost::system::error_code ec,
tcp::resolver::results_type results)
{
if(ec)
return fail(ec, "resolve");
// Make the connection on the IP address we get from a lookup
boost::asio::async_connect(
stream_.next_layer(),
results.begin(),
results.end(),
std::bind(
&session::on_connect,
shared_from_this(),
std::placeholders::_1));
}
void
on_connect(boost::system::error_code ec)
{
if(ec)
return fail(ec, "connect");
// Perform the SSL handshake
stream_.async_handshake(
ssl::stream_base::client,
std::bind(
&session::on_handshake,
shared_from_this(),
std::placeholders::_1));
}
void
on_handshake(boost::system::error_code ec)
{
if(ec)
return fail(ec, "handshake");
// Send the HTTP request to the remote host
http::async_write(stream_, req_,
std::bind(
&session::on_write,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void
on_write(
boost::system::error_code ec,
std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if(ec)
return fail(ec, "write");
// Receive the HTTP response
http::async_read(stream_, buffer_, res_,
std::bind(
&session::on_read,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void
on_read(
boost::system::error_code ec,
std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if(ec)
return fail(ec, "read");
// Write the message to standard out
//std::cout << res_ << std::endl;
// Gracefully close the stream
stream_.async_shutdown(
std::bind(
&session::on_shutdown,
shared_from_this(),
std::placeholders::_1));
}
void
on_shutdown(boost::system::error_code ec)
{
if(ec == boost::asio::error::eof)
{
// Rationale:
// http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
ec.assign(0, ec.category());
}
if(ec)
return fail(ec, "shutdown");
// If we get here then the connection is closed gracefully
}
};
//up boost-beast-client-async-ssl session code.
struct io_context_runner
{
boost::asio::io_context& ioc;
void operator()()const
{
try{
ioc.run();
}catch(std::exception& e){
fprintf(stderr, "e: %s\n", e.what());
}
}
};
namespace async_http_ssl {
using ::session;
}
#include <thread>
int main(int argc, char *argv[]) {
// The io_context is required for all I/O
boost::asio::io_context ioc;
std::thread t;
try {
// Run the I/O service. The call will return when all work is complete
boost::asio::io_context::work w(ioc);
t = std::thread { io_context_runner{ioc} };
int total_run = 1;
if (argc > 1)
total_run = atoi(argv[1]);
#if 0
auto host = "104.236.162.70"; // IP of isocpp.org
auto port = "443"; //
auto target = "/"; //
#else
auto host = "127.0.0.1";
auto port = "443";
auto target = "/BBB/http_client_async_ssl.cpp";
#endif
std::string const body = ""; //
int version = 11;
// The SSL context is required, and holds certificates
ssl::context ctx{ssl::context::sslv23_client};
// This holds the root certificate used for verification
load_root_certificates(ctx);
typedef std::shared_ptr<async_http_ssl::session> pointer;
for (int i = 0; i < total_run; ++i) {
pointer s = std::make_shared<async_http_ssl::session>(ioc, ctx);
usleep(1000000 / total_run);
s->run(host, port, target, version);
}
} catch (std::exception const &e) {
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
if (t.joinable())
t.join();
// If we get here then the connections have been closed gracefully
}
В моей системе профилирование памяти с 1 соединением:
При 100 соединениях:
С 1000 подключений:
Анализ
Что это значит? Все еще кажется, что Beast использует все больше памяти при отправке большего количества запросов, верно?
Ну нет. Проблема в том, что вы начинаете запросы быстрее, чем они могут быть выполнены. Таким образом, нагрузка на память увеличивается в основном из-за того, что в данный момент существует много экземпляров session
. После завершения они автоматически освобождают ресурсы (из-за использования shared_ptr<session>
).
Выполнение запросов последовательно
Чтобы донести мысль, вот модифицированная версия, которая принимает обработчик on_completion_
с сеансом:
std::function<void()> on_complete_;
// Resolver and stream require an io_context
template <typename Handler>
explicit
session(boost::asio::io_context& ioc, ssl::context& ctx, Handler&& handler)
: resolver_(ioc)
, stream_(ioc, ctx)
, on_complete_(std::forward<Handler>(handler))
{
}
~session() {
if (on_complete_) on_complete_();
}
Теперь вы можете переписать основную логику программы в виде цепочки асинхронных операций:
struct Tester {
boost::asio::io_context ioc;
boost::optional<boost::asio::io_context::work> work{ioc};
std::thread t { io_context_runner{ioc} };
ssl::context ctx{ssl::context::sslv23_client};
Tester() {
load_root_certificates(ctx);
}
void run(int remaining = 1) {
if (remaining <= 0)
return;
auto s = std::make_shared<session>(ioc, ctx, [=] { run(remaining - 1); });
s->run("127.0.0.1", "443", "/BBB/http_client_async_ssl.cpp", 11);
}
~Tester() {
work.reset();
if (t.joinable()) t.join();
}
};
int main(int argc, char *argv[]) {
Tester tester;
tester.run(argc>1? atoi(argv[1]):1);
}
С помощью этой программы (Полный код на Coliru) мы получаем гораздо более стабильные результаты:
1 запрос:
100 запросов:
1000 запросов:
Восстановление пропускной способности
Ну, это слишком консервативно, отправка многих запросов может стать очень медленной. Как насчет некоторого параллелизма? Легкий:
int main(int argc, char *argv[]) {
int const total = argc>1? atoi(argv[1]) : 1;
int const concurrent = argc>2? atoi(argv[2]) : 1;
{
std::vector<Tester> chains(concurrent);
for (auto& chain : chains)
chain.run(total / concurrent);
}
std::cout << "All done\n";
}
Это все! Теперь у нас может быть concurrent
отдельных цепочек выполнения, обслуживающих ~общее количество запросов. Посмотрите разницу во времени выполнения:
$ time ./sotest 1000
All done
real 0m53.295s
user 0m13.124s
sys 0m0.232s
$ time ./sotest 1000 10
All done
real 0m8.808s
user 0m8.884s
sys 0m1.096s
Использование памяти продолжает выглядеть здоровым:
person
sehe
schedule
16.03.2018
io_service::run
. Вероятно, вам следует изменитьio_context_runner
, чтобы отразить это. - person sehe   schedule 17.03.2018