Read and write data properly

138

We've already learned how to read and write data from or into a socket, however we did it quite briefly. Now it's time to learn how to read and write data in details. In this lesson we will discuss how do socket I/O member functions work.

I/O member functions of a socket are socket::async_send and socket::async_receive. In fact, you should considered these functions as low-level and avoid using them directly in your application code unless you're certain that you need them. It's more likely that you could build some library-level facilities on top of these functions.

Look at the following example. We've seen something like that several times already:

char outgoing[] = "Some outgoing message";
char incoming[256];

auto on_complete = [] (error_code error, std::size_t bytes_transferred)
{
    std::cout << error.message() << ", " << bytes_transferred << "\n";
};

socket.async_send(io::buffer(outgoing), on_complete);
socket.async_receive(io::buffer(incoming), on_complete);

Everything looks familiar so far. The main subtlety about socket I/O member functions is this: when the completion handler is invoked, it is not guaranteed that all of the data was sent or received. For example, you could schedule async_send for the 150 bytes length buffer, this operation could complete successfully, however bytes_transferred value could be less than 150. Lets say, 64. This would mean that the first 64 bytes has been successfuly sent, and if you still want to deliver all of 150 bytes then you should schedule the next async_send with another buffer view, which should start with the offset of 64 bytes from the original buffer and has length equal to 150 - 64 = 86 bytes. In other words, you have to maintain delivery integrity by yourself with a chain of asynchronous operations. Such delivery could look like that (unrelated code is omitted):

void application::send(io::const_buffer data)
{
    socket.async_send(data, std::bind(&application::on_send, this, _1, _2, data));
}

void application::on_send(error_code error, std::size_t bytes_transferred, io::const_buffer data)
{
    if(!error)
    {
        // bytes_transferred amount of data was successfully sent
        if(auto rest = data + bytes_transferred; rest.size())
        {
            // If the rest of the data is not empty
            // then we should send it in the next async_send operation
            send(rest);
        }
        else
        {
            std::cout << "Message delivered";
        }
    }
    else
    {
        std::cerr << error.message();
    }
};

io::io_context io_context;
application app(io_context);
app.connect([&]
{
    app.send(io::buffer("Some outgoing message"));
});
io_context.run();

Note this line:

auto rest = data + bytes_transferred;

This is something new! You can add an integer to the buffer view. The result is a new buffer view which pointer is shifted forward by the value of this integer, and the size is reduced by the same value. The same could be written like that:

auto rest = io::buffer
(
    static_cast<std::uint8_t const*>(data.data()) + bytes_transferred,
    data.size() - bytes_transferred
);

As you can see, a chain of socket::async_send continues until the whole data was sent. In the same way socket::async_receive member function works. You pass there a mutable buffer view and it receives some amount of data, less or equal to a given buffer size. And if you need to fill the whole buffer then you have to maintain a chain of asynchronous calls by yourself.

Both socket::async_send and socket::async_receive accept in fact a buffer sequence. A single buffer view is converted to a buffer sequence implicitly at this point. Just remember that you can pass buffer sequence to those functions as well.

So, socket I/O member functions are low-level things and usually should be used to build higher-level facilities on top of them. It's very unlikely that you want to use them directly in your application code.

Boost.Asio offers higher level I/O free functions which has more convenient behavior. We will learn these functions in the next lesson.

Share this page:

Learning plan

How to read data from Boost.Asio dynamic buffers
How to work with Boost.Asio dynamic buffers manually
Let's briefly summarize everything we've learned about different Boost.Asio buffers
25. Read and write data properly
How to deal with read and write functions properly to gain desired I/O behavior
How to deal with Boost.Asio I/O free functions: async_read, async_read_until and async_write
Several additional tips on dealing with Boost.Asio I/O free functions
How to deal with secure connections with Boost.Asio and OpenSSL