I am trying to create an HTTP server using boost::beast but I noticed a delay of one second in response time. I used the advanced server example and synchronous client example to benchmark this behavior.
Also tried to disable Nagle's algorithm but had no effect and doesn't seem to be an optimisation issue.
Requests were made manually one every few seconds, so a high load on the sever side is not reasonable.
It seems to be related to boost::asio sockets because I also created an HTTP server some time ago (with boost::asio - Boost v1.64 I think, when Beast wasn't around) and I expericend the same high response time - this is the reason I switched to Beast.
My questions:
Is this a known behavior for boost::asio sockets?
Can this delay be addressed?
Is there a reason why setting no_delay on the socket might not work?
Benchmark result:
The average response time from example server was between 1005 ms and 1020 ms as seen in the screenshot bellow. In contrast, the same client gets a response from google.com under 120 ms.
Here is a comparison between requests to my local server versus requests to www.google.com
Hence the question again: from where this 900 ms delay can come from? This is not an acceptable response time for any HTTP server.
And also tried the fast example server and the synchronous server example with the same result: over 1000 ms response time.
Benchmark setup
Test System:
Tested in x86 and x64 with Debug and Release builds. The only difference was, as expected, a 20-30ms additional delay on Debug builds.
Server setup
Client setup
In order to measure the request time I added timers in client code and commented out the output of the response to cout:
auto start = std::chrono::steady_clock::now();
/* client code */
// printing the response to cout can slow the client
//std::cout << res << std::endl;
auto finish = std::chrono::steady_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(finish - start);
std::cout << "request time: " << ms.count() << "\n";
This is the client code that disables TCP delay:
boost::asio::connect(socket, results.begin(), results.end());
socket.set_option(tcp::no_delay(true));
This is the only modification of the server example code. And the server code to disable TCP delay:
void
on_accept(boost::system::error_code ec)
{
if(ec)
{
fail(ec, "accept");
}
else
{
// disable TCP delay
socket_.set_option(tcp::no_delay(true));
// Create the http_session and run it
std::make_shared<http_session>(
std::move(socket_),
doc_root_)->run();
}
// Accept another connection
do_accept();
}
Here is the modified client:
//
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP client, synchronous
//
//------------------------------------------------------------------------------
//[example_http_client
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <cstdlib>
#include <iostream>
#include <string>
#include <chrono>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
// Performs an HTTP GET and prints the response
int main(int argc, char** argv)
{
try
{
// Check command line arguments.
if(argc != 4 && argc != 5)
{
std::cerr <<
"Usage: http-client-sync <host> <port> <target> [<HTTP version: 1.0 or 1.1(default)>]\n" <<
"Example:\n" <<
" http-client-sync www.example.com 80 /\n" <<
" http-client-sync www.example.com 80 / 1.0\n";
return EXIT_FAILURE;
}
auto const host = argv[1];
auto const port = argv[2];
auto const target = argv[3];
int version = argc == 5 && !std::strcmp("1.0", argv[4]) ? 10 : 11;
auto start = std::chrono::steady_clock::now();
// The io_context is required for all I/O
boost::asio::io_context ioc;
// These objects perform our I/O
tcp::resolver resolver{ioc};
tcp::socket socket{ioc};
// Look up the domain name
auto const results = resolver.resolve(host, port);
// Make the connection on the IP address we get from a lookup
boost::asio::connect(socket, results.begin(), results.end());
//socket.set_option(tcp::no_delay(true));
// Set up an HTTP GET request message
http::request<http::string_body> req{http::verb::get, target, version};
req.set(http::field::host, host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// Send the HTTP request to the remote host
http::write(socket, req);
// This buffer is used for reading and must be persisted
boost::beast::flat_buffer buffer;
// Declare a container to hold the response
http::response<http::dynamic_body> res;
// Receive the HTTP response
http::read(socket, buffer, res);
// Write the message to standard out
//std::cout << res << std::endl;
// Gracefully close the socket
boost::system::error_code ec;
socket.shutdown(tcp::socket::shutdown_both, ec);
// not_connected happens sometimes
// so don't bother reporting it.
//
if(ec && ec != boost::system::errc::not_connected)
throw boost::system::system_error{ec};
// If we get here then the connection is closed gracefully
auto finish = std::chrono::steady_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(finish - start);
std::cout << "request time: " << ms.count() << "\n";
}
catch(std::exception const& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
//]
I tried running the debug version of your program against the debug version of the advanced-server example, on Visual Studio 2017 and Windows 10. This was the output:
request time: 5
I think 5 milliseconds is pretty reasonable :)
Try initializing the start time just before the call to http::read
:
// Receive the HTTP response
auto start = std::chrono::steady_clock::now();
http::read(socket, buffer, res);
This way we can find out if the DNS lookup is performing slowly (a possibility).
I don't think TCP_NODELAY is going to help at all, the latency it adds is minimal and on Windows I believe the loopback device doesn't implement it. The latency you are seeing is far greater than anything Nagle's algorithm might impose.
These servers should perform with minimal latency and pretty fast results on all correctly configured platforms, I'm not sure what is going on in your particular situation but I feel confident it is an issue related to the operating system or environment.