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:
- Windows 10 Pro x64
- Processor Intel i7-7700, 16GB RAM
- Boost 1.66
- Visual Studio 2015
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
- I took the advanced server example from Beast documentation
- Created a new empty console project in Visual Studio 2015
- Added the example code to a new cpp source file
- In Project's properties > VC++ Directories > Include Directories set boost "...boost\include" include folder
- In Project's properties > VC++ Directories > Library Directories set boost library folder
- In C/C++ > Preprocessor added "_WIN32_WINNT=0x0601"
- In Debugging > Command Arguments I added "0.0.0.0 8080 . 3" which means "bind server to all local addresses with port 8080, using . (current directory) as root folder and run server on 3 threads".
Client setup
- I used synchronous client from Beast documentation
- Created a similar Visual Studio project
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;
}
//]