我正在构建一个低延迟项目,尽快发送 http 请求是一个关键组件。这是我当前 http 请求的构造
#include <thread>
#include <iostream>
#include <coroutine>
#include <optional>
#include <variant>
#include <vector>
#include <utility>
#include <string>
#include <chrono>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/json.hpp>
#include <boost/beast.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/coroutine/all.hpp>
#include <boost/beast/ssl/ssl_stream.hpp>
#include <boost/beast/core/tcp_stream.hpp>
#include <boost/beast/core/flat_static_buffer.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/beast/http/verb.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/detached.hpp>
class http_client {
private:
using response = boost::beast::http::response<boost::beast::http::string_body>;
std::string http_hostname;
std::string ip_address;
boost::asio::ssl::context ssl_context;
boost::asio::ip::tcp::resolver hostname_resolver;
std::optional<boost::beast::ssl_stream<boost::beast::tcp_stream>> tcp_stream;
boost::beast::flat_static_buffer<4 * 1024 * 1024> receive_flat_buffer;
public:
http_client(const std::string& http_name, boost::asio::io_context& io_context) :
http_hostname(http_name),
ssl_context(boost::asio::ssl::context::tlsv12_client),
hostname_resolver(io_context),
tcp_stream(boost::beast::ssl_stream<boost::beast::tcp_stream>(io_context, ssl_context)),
receive_flat_buffer()
{
ssl_context.set_verify_mode(boost::asio::ssl::verify_peer);
ssl_context.set_options(
boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::single_dh_use);
if (!SSL_set_tlsext_host_name(tcp_stream->native_handle(), http_hostname.c_str())) {
boost::beast::error_code error_code{static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category()};
throw boost::beast::system_error{error_code};
}
auto const resolved_endpoint = hostname_resolver.resolve(http_hostname, "443");
ip_address = resolved_endpoint->endpoint().address().to_string();
boost::beast::get_lowest_layer(tcp_stream.value()).connect(resolved_endpoint);
boost::beast::get_lowest_layer(tcp_stream.value()).socket().set_option(boost::asio::socket_base::keep_alive(true));
boost::beast::get_lowest_layer(tcp_stream.value()).socket().set_option(boost::asio::ip::tcp::no_delay(true));
std::cout << "Connected to REST endpoint at IP address <" << ip_address << "> which was resolved from <" << resolved_endpoint->host_name() << std::endl;
tcp_stream->handshake(boost::asio::ssl::stream_base::client);
}
void send_request(boost::asio::io_context& io_context, const std::string& target, const std::function<void(response)>& callback) {
boost::asio::spawn(
io_context, [target = std::move(target), callback = std::move(callback), this](boost::asio::yield_context yield_context) mutable
{
boost::beast::http::request<boost::beast::http::string_body> http_request{
boost::beast::http::verb::get,
target,
11};
http_request.set(boost::beast::http::field::host, http_hostname);
http_request.set(boost::beast::http::field::content_type, "application/json");
http_request.set(boost::beast::http::field::connection, "Keep-Alive");
http_request.set(boost::beast::http::field::keep_alive, "timeout=86400");
http_request.keep_alive(true);
http_request.prepare_payload();
size_t bytes_transferred = boost::beast::http::async_write(tcp_stream.value(), http_request, yield_context);
response http_response;
boost::beast::http::async_read(tcp_stream.value(), receive_flat_buffer, http_response, yield_context);
callback(http_response);
}
);
}
};
int main() {
boost::asio::io_context io_context{};
std::string host_name{"fapi.binance.com"};
http_client client(host_name, io_context);
for (int i = 0; i < 100; i++) {
auto const stime = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
client.send_request(io_context, "/fapi/v1/time", [&](boost::beast::http::response<boost::beast::http::string_body> const& http_response) {});
auto const etime = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
std::cout << "time diff = " << etime - stime << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
io_context.run();
return 0;
}
这是我正在使用的编译器标志
g++ -std=c++20 -O2 -flto -g beast_http_client.cpp -I/home/dev/vcpkg/installed/x64-linux/include -L/home/dev/vcpkg/installed/x64-linux/lib -lboost_system -lboost_coroutine -lboost_thread -lboost_json -lssl -lcrypto -lboost_context
我对此进行了计时,延迟平均约为 10-20 我们。我想知道是否可以做任何改进来将其降低到低个位数微。我知道 boost::beast 是一个相当重的库,但是是的,我想我会向专家学习有关明显优化的知识
硬件:我在具有 Intel Xeon 处理器 3GHz 的 AWS 虚拟机上运行此程序
有很多问题。
就像我在评论中提到的那样,不清楚您要测量什么(并以某种方式称为“延迟”)。
但是继续阅读,就会发现代码在很多方面都被破坏了。
main
循环调度 100 个协程,每个协程之间等待 100 毫秒(完全没有原因),然后,仅在 main 末尾通过调用 io_context.run()
一次性执行所有协程。
这不仅会在服务器上造成类似于拒绝服务攻击的情况,而且还明显违反了一次只能执行一个写入操作的限制(这尤其适用于 SSL 流,也适用于 SSL 流)底层 POSIX 互联网域流套接字)。
事实上,如果您运行调试构建,毫无疑问会有像这样的断言为您中止程序: 最测试:
/home/sehe/custom/superboost/boost/beast/core/detail/stream_base.hpp:116: void boost::beast::detail::stream_base::pending_guard::assign(bool&): Assertion `! *b_' failed.
其他一些问题包括: 解决这些问题和一些可靠性问题:
move
。不是很有用io_context
引用而不是使用执行器optional<>
。如果必须的话,至少使用 stream_(std::in_place, ...)
来构建string_body
而不是 empty_body
#include <iomanip>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
namespace net = boost::asio;
namespace ssl = net::ssl;
namespace beast = boost::beast;
namespace http = beast::http;
using beast::error_code;
using net::ip::tcp;
using namespace std::chrono_literals;
static constexpr auto now = std::chrono::steady_clock::now;
using duration = std::chrono::steady_clock::duration;
class http_client {
private:
using Stream = beast::ssl_stream<beast::tcp_stream>;
std::string host_;
ssl::context ctx_{ssl::context::tlsv12_client};
Stream stream_;
std::string ip_address;
beast::flat_static_buffer<4 << 10> buf_;
public:
using response = http::response<http::string_body>;
http_client(std::string host, net::io_context& ioc) : host_(std::move(host)), stream_(ioc, ctx_) {
ctx_.set_verify_mode(ssl::verify_peer);
using C = ssl::context;
ctx_.set_options(C::default_workarounds | C::no_sslv2 | C::no_sslv3 | C::single_dh_use);
if (!SSL_set_tlsext_host_name(stream_.native_handle(), host_.c_str()))
throw beast::system_error(::ERR_get_error(), net::error::get_ssl_category());
auto eps = tcp::resolver(ioc).resolve(host_, "443");
ip_address = eps->endpoint().address().to_string();
{
auto& ll = beast::get_lowest_layer(stream_);
auto& s = ll.socket();
s.open(tcp::v4());
s.set_option(tcp::no_delay(true));
s.set_option(tcp::socket::keep_alive(true));
ll.connect(eps);
}
std::cout << "Connected to REST endpoint at IP address " << quoted(ip_address)
<< " which was resolved from " << quoted(eps->host_name()) << std::endl;
stream_.handshake(Stream::client);
}
// template <typename F>
// requires std::invocable<F, error_code, response, duration>
void send_request(std::string target, std::function<void(error_code, response, duration)> callback) {
spawn(stream_.get_executor(),
[start = now(), target = std::move(target), cb = std::move(callback),
this](net::yield_context yield) mutable {
http::request<http::empty_body> http_request{http::verb::get, target, 11};
http_request.set(http::field::host, host_);
http_request.set(http::field::content_type, "application/json");
http_request.set(http::field::connection, "Keep-Alive");
http_request.set(http::field::keep_alive, "timeout=86400");
http_request.keep_alive(true);
http_request.prepare_payload();
/*size_t bytes_transferred =*/async_write(stream_, http_request, yield);
response http_response;
error_code ec;
async_read(stream_, buf_, http_response, yield[ec]);
std::move(cb)(ec, std::move(http_response), now() - start);
});
}
};
void send_loop(http_client& client, std::string const& target, unsigned n) {
if (n == 0)
return;
client.send_request(target, [=, &client](error_code ec, http_client::response res, duration dur) {
send_loop(client, target, n - 1); // only now it is safe to schedule a new write
std::cout << "#" << n << " " << ec.message() << " in " << (dur / 1ms) << " ms";
if (!ec)
std::cout << " HTTP " << res.reason();
std::cout << std::endl;
});
}
int main() {
net::io_context io_context;
http_client client("fapi.binance.com", io_context);
send_loop(client, "/fapi/v1/time", 3);
io_context.run();
}
本地演示:
any_io_executor
)您可能应该考虑一个单独的 IO 线程和可能的多个连接。参见例如如何使此 HTTPS 连接在 Beast 中持久存在? 从这里获取想法。