2
votes

I have searched other posts, but didn't found anything relevant. Now, I have a protocol consisting of header and body. Protocol is like: Z24,91009802,123456789ABCDEF Where Z24, is the header. Z is message type, 24 is remaining bytes to read. Remaining bytes is variable, so I read until first ',' is found.

void handle_handshake(const boost::system::error_code& error)
{
    if (!error)
    {
        boost::asio::async_read_until(
            socket_,
            inputStreamBuffer_,
            ',',
            boost::bind(
                &session::doReadHeader, this,
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred)
        );
    }
    else
    {
        delete this;
    }
}

void doReadHeader(
    const boost::system::error_code& error,
    size_t bytes_transferred)
{
    if (!error)
    {
        istream is(&inputStreamBuffer_);
        vector<char> v(bytes_transferred);
        is.read(&(v[0]),bytes_transferred);
        request_.append(v.begin(),v.end());

        cout << "request_=#" << request_ << "#" << endl;
        int nBytes=string_to_llint(request_.substr(1,request_.size()-2));
        cout << "nBytes=" << nBytes << endl;
        cout << "size=" << inputStreamBuffer_.size() << endl;

        boost::asio::async_read(
            socket_,
            inputStreamBuffer_,
            boost::asio::transfer_at_least(nBytes),
            boost::bind(
                &session::doReadBody, this,
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred)
        );
    }
    else
    {
        delete this;
    }
}

void doReadBody(
    const boost::system::error_code& error,
    size_t bytes_transferred)
{
    if (!error)
    {
        istream is(&inputStreamBuffer_);
        vector<char> v(bytes_transferred);
        is.read(&(v[0]),bytes_transferred);
        request_.append(v.begin(),v.end());

        string response=cardIssueProcessor_.process(request_);
        cout << "request=#" << request_ << "#" << endl;
        cout << "response=#" << response << "#" << endl;
        request_.clear();

        boost::asio::async_write(
            socket_,
            boost::asio::buffer(response, response.size()),
            boost::bind(
                &session::doWriteResponse, this,
                boost::asio::placeholders::error)
        );
    }
    else
    {
        delete this;
    }
}

Now, the header is read. But reading the footer blocks. Apparently the entire message is read in the header call. When I do the second async_read() with boost::asio::transfer_at_least(nBytes), nBytes are already in inputStreamBuffer_, but I think the call doesn't check this?

This is dump from the output:

request_=#Z24,# nBytes=24 size=24

What is the problem, or how can I workaround it. I am a boost newbie, so all help appreciated. Thank you.

EDIT: I tried to check the buffer fullness, and don't make async_read() call for the body if it happens to be already read by previous call. It kind of works, but is it the right solution?

void doReadHeader(
    const boost::system::error_code& error,
    size_t bytes_transferred)
{
    if (!error)
    {
        istream is(&inputStreamBuffer_);
        vector<char> v(bytes_transferred);
        is.read(&(v[0]),bytes_transferred);
        request_.assign(v.begin(),v.end());

        cout << "request_=#" << request_ << "#" << endl;
        int nBytes=string_to_llint(request_.substr(1,request_.size()-2));
        cout << "nBytes=" << nBytes << endl;
        cout << "size=" << inputStreamBuffer_.size() << endl;

        size_t toReadBytes=nBytes-inputStreamBuffer_.size();
        if (toReadBytes>0)
        {
            boost::asio::async_read(
                socket_,
                inputStreamBuffer_,
                boost::asio::transfer_at_least(toReadBytes),
                boost::bind(
                    &session::doReadBody, this,
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred)
            );
        }
        else
        {
            doReadBody(error,nBytes);
        }
    }
    else
    {
        delete this;
    }
}
2
I tried to check the buffer fillness, before doing async_read() for the body. I kind of works. Is it the right solution?Dragomir Ivanov
I'm not sure if your requirements allow you to do this, but it's usually easier to use an integer value of a defined byte size to represent the remaining bytes to read instead of a variable length string.devyndraen

2 Answers

2
votes

The Boost ASIO documentation indicates that the async_read_until call may read data into the buffer that is beyond the delimiter (see the Remarks section). That being said, your solution for checking whether the buffer has more data is a good solution given your input.

As I mentioned in my comment above, if your requirements will allow you to do so, using an integral value for the remaining bytes instead of a string is probably going to make your life easier and the code a bit cleaner and less error prone.

1
votes

async_read_until can read bytes past the delimiter

Remarks

After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter. An application will typically leave that data in the streambuf for a subsequent async_read_until operation to examine.