Boost::Asio HTTP Server extremely slow - c++

I'm currently trying to create a http server using Boost.Asio, I made it like this HTTP Server 3.
Currently I just read the Request and always return an OK Message. So nothing special or time consuming.
The Problem I come across is, running the Server with 12 Threads (16 cores # 2.53GHz), the server handles arround 200-300 requests per second.
I did the same in C# using HttpListener, running with 12 Threads, it handles arround 5000-7000 requests.
What the heck is Boost.Asio doing?
Using Instrumentation Profiling with Visual Studio get following "Functions With Most Individual Work":
Name Exclusive Time %
GetQueuedCompletionStatus 44,46
std::_Lockit::_Lockit 14,54
std::_Container_base12::_Orphan_all 3,46
std::_Iterator_base12::~_Iterator_base12 2,06
Edit 1:
if (!err) {
//Add data to client request
if(client_request_.empty())
client_request_ = std::string(client_buffer_.data(), bytes_transferred);
else
client_request_ += std::string(client_buffer_.data(), bytes_transferred);
//Check if headers complete
client_headerEnd_ = client_request_.find("\r\n\r\n");
if(client_headerEnd_ == std::string::npos) {
//Headers not yet complete, read again
client_socket_.async_read_some(boost::asio::buffer(client_buffer_),
boost::bind(&session::handle_client_read_headers, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
} else {
//Search Cookie
std::string::size_type loc=client_request_.find("Cookie");
if(loc != std::string::npos) {
//Found Cookie
std::string::size_type locend=client_request_.find_first_of("\r\n", loc);
if(locend != std::string::npos) {
std::string lCookie = client_request_.substr(loc, (locend-loc)); loc = lCookie.find(": "); if(loc != std::string::npos) {
std::string sCookies = lCookie.substr(loc+2);
std::vector<std::string> vCookies;
boost::split(vCookies, sCookies, boost::is_any_of(";"));
for (std::size_t i = 0; i < vCookies.size(); ++i) {
std::vector<std::string> vCookie;
boost::split(vCookie, vCookies[i], boost::is_any_of("="));
if(vCookie[0].compare("sessionid") == 0) {
if(vCookie.size() > 1) {
client_sessionid_ = vCookie[1];
break;
}
}
} }
} }
//Search Content-Length
loc=client_request_.find("Content-Length");
if(loc == std::string::npos) {
//No Content-Length, no Content? -> stop further reading
send_bad_request();
return;
}
else {
//Parse Content-Length, for further body reading
std::string::size_type locend=client_request_.find_first_of("\r\n", loc);
if(locend == std::string::npos) {
//Couldn't find header end, can't parse Content-Length -> stop further reading
send_bad_request();
return;
}
std::string lHeader = client_request_.substr(loc, (locend-loc));
loc = lHeader.find(": ");
if(loc == std::string::npos) {
//Couldn't find colon, can't parse Content-Length -> stop further reading
send_bad_request();
return;
}
//Save Content-Length
client_request_content_length_ = boost::lexical_cast<std::string::size_type>(lHeader.substr(loc+2));
//Check if already read complete body
if((client_request_.size()-(client_headerEnd_)) < client_request_content_length_) {
//Content-Length greater than current body, start reading.
client_socket_.async_read_some(boost::asio::buffer(client_buffer_),
boost::bind(&session::handle_client_read_body, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else {
//Body is complete, start handling
handle_request();
}
}
}
}
Edit 2:
Client used for testing is a simple C#-Application which starts 128-Threads each iterate 1000 times without any Sleep.
System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(BaseUrl);
req.Method = "POST";
byte[] buffer = Encoding.ASCII.GetBytes("{\"method\":\"User.Login\",\"params\":[]}");
req.GetRequestStream().Write(buffer, 0, buffer.Length);
req.GetRequestStream().Close();

The reason for the slowness probably is that Boost::Asio HTTP Server 3 example always closes the connection after each response, forcing the client to create a new connection for each request. Opening and closing connection on every request takes lots of time. Obviously, this could not outperform any server that supports HTTP/1.1 and Keep-alive (basically, doesn't close client connection and allows client to reuse it for subsequent requests).
Your C# server, System.Net.HttpListener, does support Keep-alive. The client, System.Net.HttpWebRequest, also has Keep-alive enabled by default. So, the connections are reused in this configuration.
Adding keep-alive to HTTP Server 3 example is straightforward:
inside connection::handle_read() check the request if client requested Keep-alive and store this flag within the connection
change connection::handle_write() so that it initiates graceful connection closure only when client doesn't support Keep-alive, otherwise just initiate async_read_some() like you already do in connection::start():
socket_.async_read_some(boost::asio::buffer(buffer_),
strand_.wrap(
boost::bind(&connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
And don't forget to clear your request/reply and reset the request_parser before calling async_read_some().

it seems that client_request_.find("\r\n\r\n"); is called repeatedly -- hunting for the end tokens from the beginning of the string each loop. use a starting position position. such as client_request_.find("\r\n\r\n", lastposition); (using bytes_transferred)
its possible to use asycn_read_until( ,"\r\n\r\n"); found here
or async_read which should read all (instead of some).

About HTTP server 3 example. Look at the request_parser source code. The methods parse/consume. It is really not optimial cus it getting data from buffer byte-by-byte and working with each byte; pushing into std::string using push_back and so on. Its just an example.
Also, if you are using asio::strand notice that it uses a mutex t lock "strand implementation". For HTTP server its easily possible to remove asio::strand at all, so i recomment to do this. If you want to stay with strands - to avoid delays on locking you can set those defines at compile time:
-DBOOST_ASIO_STRAND_IMPLEMENTATIONS=30000 -DBOOST_ASIO_ENABLE_SEQUENTIAL_STRAND_ALLOCATION

Related

BOOST::ASIO - UDP - endpoint gets overwritten

I am trying to implement some keep-alive service in UDP using BOOST::ASIO, these are the general steps:
Sending keep-alives to 2 processes on the same machine, they are listening on the same ip with a different port.
Loop to send async_send_to to both, and the callback is a function that calls async_receive_from with a callback F().
Both refer to the same endpoint and data buffers.
while loop with io_service.run_one() inside.
The processes reply immediately.
The issue is that sporadically I either get the 2 differing ports when I check the endpoints' ports (the wanted case) F() runs, or, I get twice the same port.
It seems as the endpoint buffer (and probably the data) is getting overwritten by the later packet.
I was thinking the since I'm using run_one() the packets should be processed one by one and there will be no overwriting.
Initial send -
void GetInstancesHeartbeat(udp::endpoint &sender_endpoint)
{
int instanceIndex = 0;
for (; instanceIndex <= amountOfInstances ; instanceIndex++)
{
udp::endpoint endpoint = udp::endpoint(IP, Port+ instanceIndex);
m_instancesSocket->async_send_to(
boost::asio::buffer((char*)&(message),
sizeof(message)),endpoint,
boost::bind(&ClusterManager::handle_send_to_instance,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred,
sender_endpoint));
}
}
Then the handler -
void handle_send_to_instance(const boost::system::error_code& error, size_t
bytes_recvd, udp::endpoint &sender_endpoint)
{
m_instancesSocket->async_receive_from(
boost::asio::buffer(m_dataBuffer, m_maxLength), m_endpoint,
boost::bind(&ClusterManager::handle_receive_from_instance, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred,
sender_endpoint));
}
While loop -
while(true){
io_service.run_one();
}
And the handle receive where the port results twice the same -
void handle_receive_from_instance(const boost::system::error_code& error, size_t
bytes_recvd, udp::endpoint&sender_endpoint)
{
if (!error && bytes_recvd > 0)
{
int instancePort = m_endpoint.port();
} else {
//PRINT ERROR
}
}
The actual operations are asynchronous, so there's no telling when the endpoint reference gets written to. That's the nature of asynchronous calls.
So, what you need to have is an endpoint receiving variable per asynchronous call (you might store it per instance index).
There are a number of other really suspicious bits:
what's the type of message? For most types you'd write just boost::asio::buffer(message) (which deals with T [], std::vector<T>, array<T> etc). This works when T is char or any POD type.
If message is actually a struct of some type, consider using a single-element array to avoid having to dangerous casting:
Live On Coliru
POD message[1] = {pod};
s.async_send_to(boost::asio::buffer(message), udp::endpoint{{}, 6767}, [](boost::system::error_code ec, size_t transferred) {
std::cout << "Transferred: " << transferred << " (" << ec.message() << ")\n";
});
(Sends 12 bytes on a typical system).
Whatever you do, don't write the unsafe C-style cast (Why use static_cast<int>(x) instead of (int)x?).
You have while(true) { io.run_one(); } which is an infinite loop. A better way to write it would be: while(io.run_one()) {}
However, that would basically be the same as io.run();, but less correctly and less efficiently (see https://www.boost.org/doc/libs/1_68_0/boost/asio/detail/impl/scheduler.ipp line 138), so why not use it?

How to check boost socket write_some method ends or not

I am trying to send some data by using boost socket.
TCPClient class's role is to make a connection cna can send data throw sendMessage method.
When I executed under code it does not work. However, it works when I debug it.
I think the problem is timing.
delete[] msg; works before sending msg.(just my thought)
so, I want to check whether msg is sent or not.
or any other good way.
client main() code
TCPClient *client = new TCPClient(ip, port);
client->sendMessage((char *)msg, 64 + headerLength + bodyLength);
delete[] msg;
under code is snedMessage method.
void TCPClient::sendMessage(const char *message, int totalLength) throw(boost::system::system_error) {
if(false == isConnected())
setConnection();
boost::system::error_code error;
this->socket.get()->write_some(boost::asio::buffer(message, totalLength), error);
if(error){
//do something
}
}
Your sendMessage() function is written incorrectly. You cannot expect that socket will send all of your data at once, you need a loop where you try to send, check how many bytes were sent, offset buffer (and update totalLength accordingly of course) if necessary and repeat until all data is sent. Or interrupt if there is error condition. You try to send only once, ignore result and assume that if there is no error then all data was sent. This is not a case. Stream socket may send one or two or whatever amount of bytes at a time, and your code needs to handle that.
Your code should be something like this:
while( totalLength ) {
boost::system::error_code error;
auto sz = this->socket.get()->write_some(boost::asio::buffer(message, totalLength), error);
if(error){
//do something and interrupt the loop
}
totalLength -= sz;
message += sz;
}

Consume only part of data in boost::asio basic_stream_socket::async_read_some handler

I am new into boost::asio so my question maight be dumb - sorry if it is such.
I am writing asynchronous server application with keepalive (multiple requests may be sent on single connection).
Connection handling routine is simple:
In a loop:
schedule read request with socket->async_read_some(buffer, handler)
from handler schedule write response with async_write.
The problem I am facing is that when
handler passed to async_read_some is called by on of io_service threads, buffers may actually contain more data than single request (e.g. part of next request sent by client).
I do not want to (and cannot if it is only part of request) handle this remaining bytes at the moment.
I would like to do it after handling previous request is finished.
It would be easy to address this if I had the possiblity to reinject unnecessary remainging data back to the socket. So it is handled on next async_read_some call.
Is there such possiblity in boost::asio or do I have to store the remaining data somewhere aside, and handle it myself with extra code.
I think what you are looking for is asio::streambuf.
Basically, you can inspect your seeded streambuf as a char*, read as much as you see fit, and then inform how much was actually processed by consume(amount).
Working code-example to parse HTTP-header as a client:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <string>
namespace asio = boost::asio;
std::string LINE_TERMINATION = "\r\n";
class Connection {
asio::streambuf _buf;
asio::ip::tcp::socket _socket;
public:
Connection(asio::io_service& ioSvc, asio::ip::tcp::endpoint server)
: _socket(ioSvc)
{
_socket.connect(server);
_socket.send(boost::asio::buffer("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"));
readMore();
}
void readMore() {
// Allocate 13 bytes space on the end of the buffer. Evil prime number to prove algorithm works.
asio::streambuf::mutable_buffers_type buf = _buf.prepare(13);
// Perform read
_socket.async_read_some(buf, boost::bind(
&Connection::onRead, this,
asio::placeholders::bytes_transferred, asio::placeholders::error
));
}
void onRead(size_t read, const boost::system::error_code& ec) {
if ((!ec) && (read > 0)) {
// Mark to buffer how much was actually read
_buf.commit(read);
// Use some ugly parsing to extract whole lines.
const char* data_ = boost::asio::buffer_cast<const char*>(_buf.data());
std::string data(data_, _buf.size());
size_t start = 0;
size_t end = data.find(LINE_TERMINATION, start);
while (end < data.size()) {
std::cout << "LINE:" << data.substr(start, end-start) << std::endl;
start = end + LINE_TERMINATION.size();
end = data.find(LINE_TERMINATION, start);
}
_buf.consume(start);
// Wait for next data
readMore();
}
}
};
int main(int, char**) {
asio::io_service ioSvc;
// Setup a connection and run
asio::ip::address localhost = asio::ip::address::from_string("127.0.0.1");
Connection c(ioSvc, asio::ip::tcp::endpoint(localhost, 80));
ioSvc.run();
}
One way of tackling this when using a reliable and ordered transport like TCP is to:
Write a header of known size, containing the size of the rest of the message
Write the rest of the message
And on the receiving end:
Read just enough bytes to get the header
Read the rest of the message and no more
If you know the messages are going to be of a fixed length, you can do something like the following:
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
Connection::readMore()
{
if (m_connected)
{
// Asynchronously read some data from the connection into the buffer.
// Using shared_from_this() will prevent this Connection object from
// being destroyed while data is being read.
boost::asio::async_read(
m_socket,
boost::asio::buffer(
m_readMessage.getData(),
MessageBuffer::MESSAGE_LENGTH
),
boost::bind(
&Connection::messageBytesRead,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
),
boost::bind(
&Connection::handleRead,
shared_from_this(),
boost::asio::placeholders::error
)
);
}
}
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
std::size_t
Connection::messageBytesRead(const boost::system::error_code& _errorCode,
std::size_t _bytesRead)
{
return MessageBuffer::MESSAGE_LENGTH - _bytesRead;
}
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
Connection::handleRead(const boost::system::error_code& _errorCode)
{
if (!_errorCode)
{
/// Do something with the populated m_readMessage here.
readMore();
}
else
{
disconnect();
}
}
The messageBytesRead callback will indicate to boost::asio::async_read when a complete message has been read. This snippet was pulled from an existing Connection object from running code, so I know it works...

Boost async_read doesn't give me an "end of frame" flag

I'm still working on some kind of client for communication with an IP Camera. Now I have the following issue:
I send a request to the camera ( a RTSP DESCRIBEin particular ). Now I get it's answer which looks like this:
RTSP/1.0 200 OK
CSeq: 2
Date: Thu, Jan 01 1970 00:31:41 GMT
Content-Base: rtsp://192.168.0.42/mpeg4?mode=Live&stream=-1&buffer=0&seek=0&fps=100& metainfo=/
Content-Type: application/sdp
Content-Length: 517
This is the header of the answer, followed by a so called Session Description which has the size shown in the field Content-Length. Actually I don't care much for the Session Description , I'm just interested in the Content-Base field. But still, since there is some communication following on the same socket, I need to get rid of all the data.
For receiving'm using the async_read calls from boost::asio.
My code looks ( simplified ) like this:
CommandReadBuffer::CallbackFromAsyncWrite()
{
boost::asio::async_read_until(*m_Socket, m_ReceiveBuffer,"\r\n\r\n",
boost::bind(&CommandReadBuffer::handle_rtsp_describe, this->shared_from_this(),
boost::asio::placeholders::error,boost::asio::placeholders::bytes_transferred));
}
This one reads at least the header ( shown above ) since its terminated by a blank line. As usual for async_write it just reads some more of the data, but nevermind. Now to the next callback function:
void CommandReadBuffer::handle_rtsp_describe(const boost::system::error_code& err,size_t bytesTransferred)
{
std::istream response_stream(&m_ReceiveBuffer);
std::string header;
// Just dump the data on the console
while (std::getline(response_stream, header))
{
// Normally I would search here for the desired content-base field
std::cout << header << "\n";
}
boost::asio::async_read(*m_Socket, m_ReceiveBuffer, boost::asio::transfer_at_least(1),
boost::bind(&CommandReadBuffer::handle_rtsp_setup, this->shared_from_this(),
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
Now this works fine as well, if I print out the number of received bytes it's always 215.
Now we go on to the critical callback:
void CommandReadBuffer::handle_rtsp_setup(const boost::system::error_code& err, size_t bytesTransferred)
{
std::cout << "Error: " << err.message() << "\n";
if (!err)
{
// Write all of the data that has been read so far.
std::cout << &m_ReceiveBuffer;
// Continue reading remaining data until EOF.
m_DeadlineTimer->async_wait(boost::bind(&CommandReadBuffer::handleTimeout, this->shared_from_this(),boost::asio::placeholders::error));
boost::asio::async_read(*m_Socket, m_ReceiveBuffer, boost::asio::transfer_at_least(1),
boost::bind(&CommandReadBuffer::handle_rtsp_setup, this->shared_from_this(),
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
else if (err != boost::asio::error::eof)
{
std::cout << "Error: " << err.message() << "\n";
}
else
{
std::cout << "End of Frame " << err.message() << "\n";
}
}
This part reads 220 Bytes. If I look at console Output from this call and compare it with the actualy payload of the frame ( as seen in Wireshark ) I can see that all data has been received. Now I would actually assume that async_read would set me the eof error. But instead the return code of error is success and so it calls async_read again. This time there is no data to be received and it never calls the callback function ( since there will be no more incoming data ).
Now I actually don't know how I could determine that all data has been sent. Actually I would expect the error flag to be set.
Now this is very similar to the implementation of the Boost Example for an Async HTTP client. Also it is done the same way in the Example Boost Async HTTP Client. I implemented this in another call and there it actually works.
Now in my opinion it should make no difference for the async_read call wether it is HTTP or RTSP - end of frame is end of frame, if there is no more data to read.
I'm also aware that according to the boost documentation I am using
void async_read(
AsyncReadStream & s,
basic_streambuf< Allocator > & b,
CompletionCondition completion_condition,
ReadHandler handler);
which means the function will continue until
The supplied buffer is full (that is, it has reached maximum size).
The completion_condition function object returns 0.
So if there is no more data to read, it just continues.
But I also tried the overloaded function without the CompletionCondition parameter, which should return when an error occurs ( EOF !!! ) - But this just won't callback either...
Any suggestions? I just don't get what I'm doing wrong...
I have written an RTSP client and server library using boost asio and can offer the following advice:
The RTSP message syntax is generic: there is no need for different DESCRIBE and SETUP handlers. In general
write an RTSP request
to read the response do a boost::asio::async_read_until("\r\n\r\n")
then check for the Content-Length header
if content_length > 0 do a boost::asio::transfer_at_least(content_length)
Further, why are you expecting an EOF? The connection is still open: the server is waiting for either another SETUP or a PLAY request and typically won't close the connection until the RTSP TCP connection has been timed out, which has a default value of 60 seconds according to RFC2326.
If in your application, you have completed interaction with the RTSP server, close the connection after you have read the response.

Losing characters in TCP Telnet transmission

I'm using Winsock to send commands through Telnet ; but for some reason when I try to send a string, a few characters get dropped occasionally. I use send:
int SendData(const string & text)
{
send(hSocket,text.c_str(),static_cast<int>(text.size()),0);
Sleep(100);
send(hSocket,"\r",1,0);
Sleep(100);
return 0;
}
Any suggestions?
Update:
I checked and the error still occurs even if all the characters are sent. So I decided to change the Send function so that it sends individual characters and checks if they have been sent:
void SafeSend(const string &text)
{
char char_text[1];
for(size_t i = 0; i <text.size(); ++i)
{
char_text[0] = text[i];
while(send(hSocket,char_text,1,0) != 1);
}
}
Also, it drops characters in a peculiar way ; i.e. in the middle of the sentence. E.g.
set variable [fp]exit_flag = true
is sent as
ariable [fp]exit_flag = true
Or
set variable [fp]app_flag = true
is sent as
setrable [fp]app_flag = true
As mentioned in the comments you absolutely need to check the return value of send as it can return after sending only a part of your buffer.
You nearly always want to call send in a loop similar to the following (not tested as I don't have a Windows development environment available at the moment):
bool SendString(const std::string& text) {
int remaining = text.length();
const char* buf = text.data();
while (remaining > 0) {
int sent = send(hSocket, buf, remaining, 0);
if (sent == SOCKET_ERROR) {
/* Error occurred check WSAGetLastError() */
return false;
}
remaining -= sent;
buf += sent;
}
return true;
}
Update:
This is not relevant for the OP, but calls to recv should also structured in the same way as above.
To debug the problem further, Wireshark (or equivalent software) is excellent in tracking down the source of the problem.
Filter the packets you want to look at (it has lots of options) and check if they include what you think they include.
Also note that telnet is a protocol with numerous RFCs. Most of the time you can get away with just sending raw text, but it's not really guaranteed to work.
You mention that the windows telnet client sends different bytes from you, capture a minimal sequence from both clients and compare them. Use the RFCs to figure out what the other client does different and why. You can use "View -> Packet Bytes" to bring up the data of the packet and can easily inspect and copy/paste the hex dump.