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
andFUTURES_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
andFUTURES_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.