This short article explains a tiny C++ program that uses standalone Asio and C++20 coroutines to run multiple I/O tasks concurrently: a TCP echo server plus a couple of periodic timers. It also explains the provided g++ command line and a few Windows-specific macros that appear at the top of main.cpp.
The program is a compact example showing how to mix network I/O and timers using co_await / co_spawn:
listener(port)— anawaitablecoroutine that accepts incoming TCP connections on the given port. For each accepted socket it spawns anecho_sessioncoroutine.echo_session(socket)— reads lines (up to'\n') from the socket usingasync_read_until, prints the received text to stdout, and writes the same data back (an echo).ticker(id, interval)— a periodic task implemented withsteady_timerthat prints a tick message everyintervalseconds.main()— creates anasio::io_context, spawns thelistenerand twotickercoroutines, hooks asignal_setto gracefully stop onSIGINT/SIGTERM, and runs the event loop.
The structure demonstrates how multiple asynchronous I/O operations — socket accept/read/write and timers — can coexist naturally using coroutine syntax, giving readable sequential style without blocking the thread.
# Linux
g++ -std=c++20 -Ipath/to/asio/include main.cpp -pthread -o asio_coroutines_server
# Windows
g++ -std=c++20 -Ipath/to/asio/include main.cpp -pthread -D_WIN32_WINNT=0x0601 -lws2_32 -lmswsock -o asio_coroutines_server-std=c++20— use C++20 (required forco_awaitcoroutine support).-Iasio-1.36.0/include/— add the Asio header directory (standalone Asio is header-only).main.cpp— source file.-pthread— enable pthreads (POSIX threads). On some toolchains this still helps forio_contextand thread safety.-D_WIN32_WINNT=0x0601— define the minimum Windows target version (here0x0601→ Windows 7). This controls which Windows API symbols are visible when including Windows headers.-lws2_32 -lmswsock— link against Winsock libraries on Windows (needed for network functions).
Important: don’t accidentally pass -D_WIN32_WINNT without a value — that breaks Windows headers and causes cryptic compile errors (e.g., undefined FINDEX_INFO_LEVELS). Define it with a value (or define it in the source before including Windows headers).
At the top of main.cpp you see:
#if defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0601
#endif
#endif
#define ASIO_STANDALONE
#include <asio.hpp>WIN32_LEAN_AND_MEANreduces the size ofwindows.hto avoid pulling in rarely-used APIs.NOMINMAXprevents Windows headers from definingmin/maxmacros that conflict with C++ standard functions._WIN32_WINNTsets the Windows platform level if it wasn’t already defined on the command line.ASIO_STANDALONEtells the headers that you’re using standalone Asio (not Boost.Asio).
Defining those macros in source makes the build more robust against toolchain differences. If you also set _WIN32_WINNT on the compiler command line, command-line definitions take precedence.
- Make sure standalone Asio headers are available in
asio-1.36.0/include/(or update the-Ipath). - Compile with the command above (adjust include path and possibly change/remove
-pthreadon Windows toolchains if not needed). - Run the produced executable. It will print that it’s listening (port
12345in the example). - Use
nc(netcat) ortelnetfrom another terminal to connect:
nc localhost 12345
# type: Hello<Enter>
# you'll see the same line echoed backOpen multiple clients simultaneously to observe concurrent sessions. The console will also show ticker messages every 5s and 7s.
- This sample is single-threaded (
io_context{1}) and usesco_spawn(..., asio::detached)to run coroutines without awaiting their completion. You can scale to multiple threads by runningio_context.run()on multiple threads or using a thread pool. - For TLS, replace
tcp::socketwithasio::ssl::stream<tcp::socket>and initialize an SSL context. - Be careful with Windows toolchains: ensure you use a consistent compiler (MSYS2/Mingw-w64 target) and avoid mixing incompatible headers.
- If you get weird Windows header errors, double-check you didn’t accidentally pass
-D_WIN32_WINNTwithout a value.
This tiny program is a clear, idiomatic demonstration of mixing sockets and timers with C++20 coroutines and standalone Asio. It shows how co_await and co_spawn let asynchronous code be written in straightforward sequential style, while remaining non-blocking and efficient. Use it as a starting point for building coroutine-based network services, timed tasks, or more advanced asynchronous workflows.