Kyu96
Kyu96

Reputation: 1349

boost::asio problem passing dynamically sized data to async handler

I am processing custom tcp data packet with boost. Since all operations are asynchronously a handler must be called to process the data. The main problem is that I don't know how to pass the data to the handler when the size is not known at compiletime? For example, say you receive the header bytes, parse them which tells you the length of the body:

int length = header.body_size();

I somehow need to allocate an array with the size of the body and then call the handler (which is a class member, not a static function) and pass the data to it. How do I do that properly?

I tried different things such as but always ended up getting a segfault or I had to provide a fixed size for the body buffer which is not what I want. An attempt I made can be found below.

After receiving the header information:

char data[header.body_size()];
boost::asio::async_read(_socket, boost::asio::buffer(data, header.body_size()),
                                    boost::bind(&TCPClient::handle_read_body, this, boost::asio::placeholders::error,
                                                boost::asio::placeholders::bytes_transferred, data));

The handler:

void TCPClient::handle_read_body(const boost::system::error_code &error, std::size_t bytes_transferred,
                                 const char *buffer) {

    Logger::log_info("Reading body. Body size: " + std::to_string(bytes_transferred));
}

This example throws a segfault.

How can I allocate a buffer for the body after knowing the size? And how can I then call the handler and passing over the error_code, the bytes_transferred and the body data?

An example snippet would be really appreciated since the boost-chat examples that do this are not very clear to me.

Upvotes: 2

Views: 1393

Answers (2)

rafix07
rafix07

Reputation: 20936

All you need to do is to create buffer onto heap instead of stack. In place of VLA - char [sizeAtRuntime] you can use std::string or std::vector with std::shared_ptr. By using string/vector you can set buffer to have any size and by using shared_ptr you can prolong lifetime of your buffer.

Version with bind:

void foo()
{
    std::shared_ptr<std::vector<char>> buf = std::make_shared<std::vector<char>>(); // buf is local
    buf->resize( header.body_size() );
    // ditto with std::string

    boost::asio::async_read(_socket, boost::asio::buffer(*buf),
         boost::bind(&TCPClient::handle_read_body, 
                     this, boost::asio::placeholders::error,
                     boost::asio::placeholders::bytes_transferred, 
                     buf)); // buf is passed by value
}

void handle_read_body(const boost::system::error_code&, 
                      size_t, 
                      std::shared_ptr<std::vector<char>>)
{

}

in above example buf is created onto stack and points to vector onto heap, because bind takes its arguments by value, so buf is copied and reference counter is increased - it means your buffer still exists when async_read ends and foo ends.

You can achive the same behaviour with lambda, then buf should be captured by value:

void foo()
{
    std::shared_ptr<std::vector<char>> buf = std::make_shared<std::vector<char>>(); // buf is local
    buf->resize( header.body_size() );
    // ditto with std::string

    boost::asio::async_read(_socket, boost::asio::buffer(*buf),
                                    boost::bind(&TCPClient::handle_read_body, this, boost::asio::placeholders::error,
                                                boost::asio::placeholders::bytes_transferred, buf)); // buf is passed by value

    boost::asio::async_read(_socket, boost::asio::buffer(*buf),
        [buf](const boost::system::error_code& , size_t)
         ^^^ capture buf by value, increates reference counter of shared_ptr
        {

        });
}

Upvotes: 1

user7860670
user7860670

Reputation: 37587

char data[header.body_size()]; is not standard in C++ and will become invalid once it goes out of scope while async_read requires buffer to remain alive until completion callback is invoked. So you should probably add a field to TCPClient holding a list of data buffers (probably of std::vector kind) pending to be received.

Upvotes: 2

Related Questions