Error handling

97

Synchronous functions

Every synchronous I/O function in Boost.Asio has two overloads in order to deal with errors: the first one throws an exception, and the second one returns an error by reference (return value approach).

When Boost.Asio function throws an exception, it throws an instance of boost::system::system_error which is inherited from the std::runtime_error exception class.

To return an error by reference, an instance of boost::system::error_code is used.

Exception approach

try
{
    socket.connect(endpoint);
}
catch(boost::system::system_error const& e)
{
    std::cerr << e.what() << "\n";
}

You can retrive the error_code from the system_error by calling code() member function:

catch(boost::system::system_error const& e)
{
    boost::system::error_code error = e.code();
    std::cerr << error.message() << "\n";
}

Return value approach

If you don't want to mess with exceptions, you can use return value overload:

boost::system::error_code error;
socket.connect(endpoint, error);

if(error)
{
    std::cerr << error.message() << "\n";
}

Asynchronous functions

Asynchronous I/O functions don't throw exceptions. They pass boost::system::error_code into a completion handler instead. So, to check if the operation has been successfully completed you should write something like:

socket.async_connect(endpoint, [&] (boost::system::error_code error)
{
    if(!error)
    {
        // Asynchronous operation has been successfully completed
    }
    else
    {
        // Something went wrong
        std::cerr << error.message() << "\n";
    }
});

error_code

To get a human-readable error description from the boost::system::error_code you should call message() member funtion which will return std::string with the error description.

If you don't want to allocate additional std::string instance then you can use message(char const* buffer, std::size_t size) overload.

You can obtain the underlying OS-level error code (which has type int). To do this call value() member function.

If a remote peer close the connection then end-of-file error will be generated. In some cases you don't want to treat end-of-file error code as an application-level error. For example, if you receive some seamless data from the remote peer, and the peer close the connection, it may be considered as a normal control flow. In that case you could write:

socket.async_receive(buffer, [&] (boost::system::error_code error, std::size_t bytes_transferred)
{
    if(!error)
    {
        // Asynchronous operation has been successfully completed
        // Connection is still alive
    }
    else if(error == boost::asio::error::eof)
    {
        // Connection was closed by the remote peer
        // Still the buffer holds "bytes_transferred" bytes of the received data
    }
    else
    {
        // Something went wrong
        std::cerr << error.message() << "\n";
    }
});

Should you pass boost::system::error_code into functions by value or by reference? If you look at Boost.Asio documentation then you'll see that the library's author use pass-by-reference approach. However, currentry error_code object consists of one int, one bool and one raw pointer. It is adviced to pass std::string_view by value, which consists of one std::size_t and one raw pointer, which should have the same size (or almost the same size, depends on the target platform) as boost::system::error_code. So, it's really up to you. In my own code I pass boost::system::error_code by value.

Share this page:

Learning plan

What is server anyway? The most simple example
It's time to say “goodbye” to a synchronous I/O
The first simple asynchronous TCP server
6. Error handling
How to handle Boost.Asio errors
There are several new things we should learn before jumping into a bigger example of a server
A bigger example of a server where you'll need to apply everything you've learned so far
Principles you should take into consideration during the development of your applications