使用以下boost::asio代碼,我對 Docker node.js 簡單的 http 服務運行 1M 次連續 http 呼叫回圈,該服務生成亂數,但在幾千次呼叫后,我開始收到 async_connect 錯誤。node.js 部分沒有產生任何錯誤,我相信它可以正常作業。
為了避免在每次呼叫中決議主機并嘗試加速,我正在快取端點,這沒有區別,我已經測驗了兩種方式。
誰能看到我下面的代碼有什么問題?我缺少使用 asio 的壓力測驗工具的最佳實踐嗎?
//------------------------------------------------------------------------------
// https://www.boost.org/doc/libs/1_70_0/libs/beast/doc/html/beast/using_io/timeouts.html
HttpResponse HttpClientAsyncBase::_http(HttpRequest&& req)
{
using namespace boost::beast;
namespace net = boost::asio;
using tcp = net::ip::tcp;
HttpResponse res;
req.prepare_payload();
boost::beast::error_code ec = {};
const HOST_INFO host = resolve(req.host(), req.port, req.resolve);
net::io_context m_io;
boost::asio::spawn(m_io, [&](boost::asio::yield_context yield)
{
size_t retries = 0;
tcp_stream stream(m_io);
if (req.timeout_seconds == 0) get_lowest_layer(stream).expires_never();
else get_lowest_layer(stream).expires_after(std::chrono::seconds(req.timeout_seconds));
get_lowest_layer(stream).async_connect(host, yield[ec]);
if (ec) return;
http::async_write(stream, req, yield[ec]);
if (ec)
{
stream.close();
return;
}
flat_buffer buffer;
http::async_read(stream, buffer, res, yield[ec]);
stream.close();
});
m_io.run();
if (ec)
throw boost::system::system_error(ec);
return std::move(res);
}
我已經嘗試了 boost http 客戶端的同步/異步實作,但我遇到了完全相同的問題。
我得到的錯誤是“您沒有連接,因為網路上存在重復的名稱。如果加入域,請轉到控制面板中的系統更改計算機名稱并重試。如果加入作業組,請選擇另一個作業組名稱 [系統:52]"
uj5u.com熱心網友回復:
所以,我決定……試試看。我把你的代碼變成了獨立的例子:
#include <boost/asio/spawn.hpp>
#include <boost/beast.hpp>
#include <fmt/ranges.h>
#include <iostream>
namespace http = boost::beast::http;
//------------------------------------------------------------------------------
// https://www.boost.org/doc/libs/1_70_0/libs/beast/doc/html/beast/using_io/timeouts.html
struct HttpRequest : http::request<http::string_body> { // SEHE: don't do this
using base_type = http::request<http::string_body>;
using base_type::base_type;
std::string host() const { return "127.0.0.1"; }
uint16_t port = 80;
bool resolve = true;
int timeout_seconds = 0;
};
using HttpResponse = http::response<http::vector_body<uint8_t> >; // Do this or aggregation instead
struct HttpClientAsyncBase {
HttpResponse _http(HttpRequest&& req);
using HOST_INFO = boost::asio::ip::tcp::endpoint;
static HOST_INFO resolve(std::string const& host, uint16_t port, bool resolve) {
namespace net = boost::asio;
using net::ip::tcp;
net::io_context ioc;
tcp::resolver r(ioc);
using flags = tcp::resolver::query::flags;
auto f = resolve ? flags::address_configured
: static_cast<flags>(flags::numeric_host | flags::numeric_host);
tcp::resolver::query q(tcp::v4(), host, std::to_string(port), f);
auto it = r.resolve(q);
assert(it.size());
return HOST_INFO{it->endpoint()};
}
};
HttpResponse HttpClientAsyncBase::_http(HttpRequest&& req) {
using namespace boost::beast;
namespace net = boost::asio;
using net::ip::tcp;
HttpResponse res;
req.prepare_payload();
boost::beast::error_code ec = {};
const HOST_INFO host = resolve(req.host(), req.port, req.resolve);
net::io_context m_io;
spawn(m_io, [&](net::yield_context yield) {
// size_t retries = 0;
tcp_stream stream(m_io);
if (req.timeout_seconds == 0)
get_lowest_layer(stream).expires_never();
else
get_lowest_layer(stream).expires_after(std::chrono::seconds(req.timeout_seconds));
get_lowest_layer(stream).async_connect(host, yield[ec]);
if (ec)
return;
http::async_write(stream, req, yield[ec]);
if (ec) {
stream.close();
return;
}
flat_buffer buffer;
http::async_read(stream, buffer, res, yield[ec]);
stream.close();
});
m_io.run();
if (ec)
throw boost::system::system_error(ec);
return res;
}
int main() {
for (int i = 0; i<100'000; i) {
HttpClientAsyncBase hcab;
HttpRequest r(http::verb::get, "/bytes/10", 11);
r.timeout_seconds = 0;
r.port = 80;
r.resolve = false;
auto res = hcab._http(std::move(r));
std::cout << res.base() << "\n";
fmt::print("Data: {::02x}\n", res.body());
}
}
(旁注,這是docker run -p 80:80 kennethreitz/httpbin用于運行服務器端)
雖然這比在 bash 回圈中執行等效請求快約 10 倍curl,但這些都不是特別緊張。它沒有任何異步,而且資源使用似乎是溫和而穩定的,例如記憶體分析:

(為了完整性,我用 驗證了相同的結果timeout_seconds = 1)
由于您所做的實際上與異步 IO 正好相反,因此我會寫得更簡單:
struct HttpClientAsyncBase {
net::io_context m_io;
HttpResponse _http(HttpRequest&& req);
static auto resolve(std::string const& host, uint16_t port, bool resolve);
};
HttpResponse HttpClientAsyncBase::_http(HttpRequest&& req) {
HttpResponse res;
req.requestObject.prepare_payload();
const auto host = resolve(req.host(), req.port, req.resolve);
beast::tcp_stream stream(m_io);
if (req.timeout_seconds == 0)
stream.expires_never();
else
stream.expires_after(std::chrono::seconds(req.timeout_seconds));
stream.connect(host);
write(stream, req.requestObject);
beast::flat_buffer buffer;
read(stream, buffer, res);
stream.close();
return res;
}
這只是更簡單,運行速度更快,并且做同樣的事情,直到例外。
但是,您可能正試圖造成壓力,也許您需要重用一些連接和多執行緒?
你可以在這里看到一個非常完整的例子: 如何使這個 HTTPS 連接在 Beast 中持久化?
它包括重新連接斷開的連接、與不同主機的連接、不同的請求等。
uj5u.com熱心網友回復:
Alan 的評論給了我正確的指導,我很快發現netstat -a這是一個埠泄漏問題,在運行代碼一段時間后,數千個埠處于 TIME_WAIT 狀態。
根本原因在客戶端和服務器上:
在 node.js 服務器中,我必須確保回應通過添加來關閉連接
response.setHeader("connection", "close");在 boost::asio C 代碼中,我替換
stream.close()為stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);這似乎使一切變得不同。我也確保使用
req.set(boost::beast::http::field::connection, "close");在我的要求中。
我驗證了該工具運行了 5 個多小時,完全沒有問題,所以我想問題已經解決了!
uj5u.com熱心網友回復:
使用 boost::asio 實作“Abortive TCP/IP Close”以處理 EADDRNOTAVAIL 和 TIME_WAIT 用于 HTTP 客戶端壓力測驗工具
我正在重新審視這個問題,以提供一個實際上效果更好的替代方案。提醒一下,目標是開發一個壓力測驗工具,用于以 100 萬個請求訪問服務器。即使我之前的解決方案在 Windows 上運行,當我在 Docker/Alpine 上加載可執行檔案時,它開始崩潰并出現我無法追蹤的 SEGFAULT 錯誤。根本原因似乎與boost::asio::spawn(m_io, [&](boost::asio::yield_context yield)但時間迫使我解決 HTTP 問題有關。
我決定使用同步 HTTP 并處理EADDRNOTAVAIL和TIME_WAIT錯誤,方法是遵循Disable TIME_WAIT with boost sockets和TIME_WAIT with boost asio和來自https://www.boost.org/doc/libs/1_80_0/libs/beast/的模板代碼示例/http/client/sync/http_client_sync.cpp。
對于任何擁有帶有 boost::asio 的 EADDRNOTAVAIL 和 TIME_WAIT 的人來說,對我有用的解決方案實際上比以前在 Windows、Linux 和 Dockers 上都要快得多,如下所示:
HttpResponse HttpClientSyncBase::_http(HttpRequest&& req)
{
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp = net::ip::tcp;
HttpResponse res;
req.prepare_payload();
const auto host = req.host();
const auto port = req.port;
const auto target = req.target();
const bool abortive_close = boost::iequals(req.header("Connection"), "close");
const bool download_large_file = boost::iequals(req.header("X-LARGE-FILE-HINT"), "YES");
beast::error_code ec;
net::io_context ioc;
// Resolve host:port for IPv4
tcp::resolver resolver(ioc);
const auto endpoints = resolver.resolve(boost::asio::ip::tcp::v4(), host, port);
// Create stream and set timeouts
beast::tcp_stream stream(ioc);
if (req.timeout_seconds == 0) boost::beast::get_lowest_layer(stream).expires_never();
else boost::beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(req.timeout_seconds));
// Caution: we can get address_not_available[EADDRNOTAVAIL] due to TIME_WAIT port exhaustion
stream.connect(endpoints, ec);
if (ec == boost::system::errc::address_not_available)
throw beast::system_error{ ec };
// Write HTTP request
http::write(stream, req);
// Read HTTP response (or download large file >8MB)
beast::flat_buffer buffer;
if (download_large_file)
{
_HttpResponse tmp;
boost::beast::http::response_parser<boost::beast::http::string_body> parser{ std::move(tmp) };
parser.body_limit(boost::none);
boost::beast::http::read(stream, buffer, parser);
res = HttpResponse(std::move(parser.release()));
}
else
{
http::read(stream, buffer, res);
}
// Try to shut down socket gracefully
stream.socket().shutdown(tcp::socket::shutdown_both, ec);
if (abortive_close)
{
// Read until no more data are in socket buffers
// https://stackoverflow.com/questions/58983527/disable-time-wait-with-boost-sockets
try
{
http::response<http::dynamic_body> res;
beast::flat_buffer buffer;
http::read(stream, buffer, res);
}
catch (...)
{
// should get end of stream here, ignore it
}
// Perform "Abortive TCP/IP Close" to minimize TIME_WAIT port exhaustion
// https://stackoverflow.com/questions/35006324/time-wait-with-boost-asio
try
{
// enable linger with timeout 0 to force abortive close
boost::asio::socket_base::linger option(true, 0);
stream.socket().set_option(option);
stream.close();
}
catch (...)
{
}
}
else
{
try { stream.close(); } catch (...) {}
}
// Ignore not_connected and end_of_stream errors, handle the rest
if (ec && ec != beast::errc::not_connected && ec != beast::http::error::end_of_stream)
throw beast::system_error{ ec };
return std::move(res);
}
在上面的示例中,我應該在 write 中添加錯誤處理,但我想任何人都可以做到。_HttpResponse 如下,是 HttpResponse 的基礎。
using _HttpRequest = boost::beast::http::message<true, boost::beast::http::string_body, boost::beast::http::fields>;
using _HttpResponse = boost::beast::http::message<false, boost::beast::http::string_body, boost::beast::http::fields>;
using HttpHeaders = boost::beast::http::header<1, boost::beast::http::basic_fields<std::allocator<char>>>;
值得一提的是,當我開始這項作業的估計是 5-7 天。在我之前的解決方案中使用 connetion=close 可以縮短到 7-8 小時。使用Abortive TCP/IP Close我減少了 1.5 小時。
有趣的是,服務器也 boost::asio 可以處理壓力,而原來的壓力工具不能。最后,服務器及其壓力測驗工具都可以正常作業!該代碼還演示了如何下載一個大檔案(超過 8MB),這是另一個問題,因為我需要從服務器下載測驗結果。
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/527721.html
上一篇:如何使用TypeScriptObsidian庫發送多部分/表單資料有效負載?
下一篇:nuget打包靜態資源的問題
