2

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.

Kyu96
  • 1,159
  • 2
  • 17
  • 35

2 Answers2

2

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.

user7860670
  • 35,849
  • 4
  • 58
  • 84
  • Thanks for the reply. Yes, I know about `std::vector<>` . Can you maybe provide a basic example of how the pending buffers could look like? – Kyu96 Apr 26 '19 at 09:28
  • @Kyu96 for example `::std::list<::std::vector<::std::uint8_t>>` you push new buffer prior to `async_read` and then pop it when callback is invoked – user7860670 Apr 26 '19 at 12:53
1

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
        {

        });
}
rafix07
  • 20,001
  • 3
  • 20
  • 33
  • Thanks for your reply. I'll definitely go with the first option. Do I need to delete buf by hand then once the async_read finished? – Kyu96 Apr 26 '19 at 15:21
  • 1
    @Kyu96 No, when handler created by `bind` is deleted, shared_ptr will delete buf, you don't need to do it manually. – rafix07 Apr 26 '19 at 15:41