Skip to content

Asio

Asio is the predominant C++ library for asynchronous network programming.

Some applications that use Asio are...

Asio is also the basis for the "C++ Extensions for Networking", which benefited from its 15 years of existing practice.

This library integrates with Asio to provide interoperability with Asio executors, future types for networking facilities, and as a form to extend existing Asio applications through future completion tokens.

Integration

Asio is available as both a standalone library or as part of the Boost libraries. This library works with both versions of Asio.

If this library has been integrated through CMake, the build script will already identify and integrate Asio. All you need to do is link the appropriate target:

  • Futures with the preferred version of Asio
add_executable(your_target main.cpp)
target_link_libraries(your_target PUBLIC futures::futures)

The preferred version of Asio is the version found when configuring the targets. If both versions are available, the CMake options FUTURES_PREFER_STANDALONE_ASIO and FUTURES_PREFER_BOOST can be used to influence that choice.

  • Futures with standalone Asio
add_executable(your_target main.cpp)
target_link_libraries(your_target PUBLIC futures::futures_asio)

This target is only available when the standalone Asio library is available. If no versions of Asio are available, the configuration step will attempt to download a compatible version of the standalone Asio library.

  • Futures with Boost.Asio
add_executable(your_target main.cpp)
target_link_libraries(your_target PUBLIC futures::futures_boost)

This target is only available when the Boost.Asio library is available. If Boost cannot be found, the configuration step will attempt to download a compatible version of the standalone Asio library.

When using this library as header-only or using another build system, use the appropriate macros to choose the Asio version should be set:

  • The macros FUTURES_HAS_STANDALONE_ASIO and FUTURES_HAS_BOOST can be used to indicate that Asio or Boost are available.
    • Build systems should be automatically setting these macros
    • If both are undefined, the bundled version of the libraries will be used
  • When both are available, the macros FUTURES_PREFER_STANDALONE_ASIO and FUTURES_PREFER_BOOST can be used to indicate whether Asio or Boost.Asio is preferred.

When the macros are undefined, the following steps will be performed to infer their values:

  • Identify whether any of dependencies has already been included before futures by checking for the existence of macros.
  • From C++17, the library can automatically identify the availability of dependencies with the __has_include macro and set the appropriate flags accordingly.
  • On other compilers, the library can automatically identify the availability of dependencies with local fake fake headers.
  • If no dependency headers can be found, the library defaults to the bundled version of the libraries.

Asio is provided as both a header-only or compiled library. To use the compiled version of Asio, the macro ASIO_SEPARATE_COMPILATION should be defined.

Asio executors

The Asio library resolves around the io_context execution context. An io_context can be used as any other executor in this library to create futures:

futures::asio::io_context io;
using io_executor = futures::asio::io_context::executor_type;
io_executor ex = io.get_executor();
cfuture<int, io_executor> f = async(ex, []() { return 2; });

However, the io_context executors have two important properties:

1) An io_context is a task queue

We need to explicitly ask io_context to execute all its pending tasks:

io.run();
assert(f.get() == 2);

Tasks are pushed into the io_context queue which prevents them from being executed immediately. By executing the tasks in the queue, the future value was immediately set.

This effectively defers execution until we pop tasks from the queue. The strategy allows parallel tasks on a single thread.

2) An io_context supports networking tasks

Say we have a server acceptor listening for connections.

asio::io_context io;
asio::ip::tcp::acceptor acceptor(io,
asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 13));
asio::ip::tcp::socket client_socket(io);
acceptor.async_accept(client_socket, [](const asio::error_code&) {
    // handle connection
});

The listening task goes to the io_context. By executing the task, we effectively listen for the connections:

io.run();

In a practical application, we would probably handle this connection by pushing more tasks to the executor to read and write from the client through the client socket.