Skip to content

Commit 4c203f9

Browse files
asio echo_server example
1 parent 326ed49 commit 4c203f9

3 files changed

Lines changed: 142 additions & 140 deletions

File tree

example/asio/CMakeLists.txt

Lines changed: 4 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -7,137 +7,13 @@
77
# Official repository: https://github.com/cppalliance/capy
88
#
99

10-
#-------------------------------------------------
11-
#
12-
# Find or fetch Boost.Asio
13-
#
14-
#-------------------------------------------------
15-
if (NOT TARGET Boost::asio)
16-
# Use BOOST_SRC_DIR if already defined, else check env, else use default
17-
if (NOT DEFINED BOOST_SRC_DIR)
18-
if (DEFINED ENV{BOOST_SRC_DIR})
19-
set(BOOST_SRC_DIR "$ENV{BOOST_SRC_DIR}" CACHE STRING "Boost source dir")
20-
else ()
21-
set(BOOST_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../.." CACHE STRING "Boost source dir")
22-
endif ()
23-
endif ()
24-
25-
# Find absolute BOOST_SRC_DIR
26-
if (NOT IS_ABSOLUTE ${BOOST_SRC_DIR})
27-
set(BOOST_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${BOOST_SRC_DIR}")
28-
endif ()
29-
30-
# Validate BOOST_SRC_DIR
31-
set(BOOST_SRC_DIR_IS_VALID ON)
32-
foreach (F "CMakeLists.txt" "Jamroot" "boost-build.jam" "bootstrap.sh" "libs")
33-
if (NOT EXISTS "${BOOST_SRC_DIR}/${F}")
34-
set(BOOST_SRC_DIR_IS_VALID OFF)
35-
break()
36-
endif ()
37-
endforeach ()
38-
39-
if (BOOST_SRC_DIR_IS_VALID)
40-
message(STATUS "Building with valid Boost src dir")
41-
# From BOOST_SRC_DIR
42-
set(BOOST_INCLUDE_LIBRARIES asio)
43-
set(BOOST_EXCLUDE_LIBRARIES capy)
44-
set(PREV_BUILD_TESTING ${BUILD_TESTING})
45-
set(BUILD_TESTING OFF CACHE BOOL "Build the tests." FORCE)
46-
add_subdirectory(${BOOST_SRC_DIR} ${CMAKE_CURRENT_BINARY_DIR}/_deps/boost EXCLUDE_FROM_ALL)
47-
set(BUILD_TESTING ${PREV_BUILD_TESTING} CACHE BOOL "Build the tests." FORCE)
48-
else ()
49-
# Try installed Boost package
50-
find_package(Boost QUIET)
51-
if (Boost_FOUND)
52-
message(STATUS "Using installed Boost package for asio example")
53-
if (NOT TARGET Boost::asio)
54-
add_library(Boost::asio ALIAS Boost::headers)
55-
endif ()
56-
else ()
57-
# Fallback: FetchContent
58-
message(STATUS "No local Boost found, using FetchContent to download Boost")
59-
include(FetchContent)
60-
61-
# Policy CMP0135 (introduced in CMake 3.24) controls the timestamp behavior
62-
# for files extracted from archives downloaded via FetchContent/ExternalProject.
63-
#
64-
# OLD behavior: Extracted files keep their original timestamps from the archive.
65-
# This can cause build issues because archive timestamps are often
66-
# older than dependent build files, triggering unnecessary rebuilds
67-
# or causing "file has modification time in the future" warnings.
68-
#
69-
# NEW behavior: Extracted files get the current time (download time) as their
70-
# timestamp, which avoids these timing-related build problems.
71-
#
72-
# We set NEW if the policy exists. On older CMake versions (< 3.24), the policy
73-
# doesn't exist, so this block is skipped and FetchContent works normally.
74-
# This avoids using DOWNLOAD_EXTRACT_TIMESTAMP directly in FetchContent_Declare,
75-
# which would cause a parse error on CMake < 3.24 (unrecognized keyword).
76-
if (POLICY CMP0135)
77-
cmake_policy(SET CMP0135 NEW)
78-
endif()
79-
80-
set(BOOST_INCLUDE_LIBRARIES asio)
81-
FetchContent_Declare(
82-
boost
83-
URL https://github.com/boostorg/boost/releases/download/boost-1.88.0/boost-1.88.0-cmake.tar.xz
84-
)
85-
set(PREV_BUILD_TESTING ${BUILD_TESTING})
86-
set(BUILD_TESTING OFF CACHE BOOL "Build the tests." FORCE)
87-
FetchContent_MakeAvailable(boost)
88-
set(BUILD_TESTING ${PREV_BUILD_TESTING} CACHE BOOL "Build the tests." FORCE)
89-
endif ()
90-
endif ()
91-
endif ()
92-
93-
#-------------------------------------------------
94-
#
95-
# Example executables
96-
#
97-
#-------------------------------------------------
98-
file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp
99-
CMakeLists.txt
100-
Jamfile)
101-
102-
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES})
103-
104-
# any_stream example (coroutine-based)
105-
add_executable(capy_example_any_stream
106-
any_stream.cpp
107-
api/capy_streams.cpp
108-
api/capy_streams.hpp)
109-
110-
set_property(TARGET capy_example_any_stream
111-
PROPERTY FOLDER "examples")
112-
113-
target_link_libraries(capy_example_any_stream
114-
Boost::capy
115-
Boost::asio)
116-
117-
# asio_callbacks example (callback-based with uni_stream)
118-
add_executable(capy_example_asio_callbacks
119-
asio_callbacks.cpp
120-
api/capy_streams.cpp
121-
api/capy_streams.hpp
122-
api/uni_stream.hpp)
123-
124-
set_property(TARGET capy_example_asio_callbacks
125-
PROPERTY FOLDER "examples")
126-
127-
target_link_libraries(capy_example_asio_callbacks
128-
Boost::capy
129-
Boost::asio)
130-
13110
# use_capy example (use_capy completion token)
132-
add_executable(capy_example_use_capy
133-
use_capy_example.cpp
134-
api/capy_streams.cpp
135-
api/capy_streams.hpp
136-
api/use_capy.hpp)
11+
add_executable(capy_example_asio_echo_server
12+
echo_server.cpp)
13713

138-
set_property(TARGET capy_example_use_capy
14+
set_property(TARGET capy_example_asio_echo_server
13915
PROPERTY FOLDER "examples")
14016

141-
target_link_libraries(capy_example_use_capy
17+
target_link_libraries(capy_example_asio_echo_server
14218
Boost::capy
14319
Boost::asio)

example/asio/Jamfile

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,7 @@ project
1414
<include>.
1515
;
1616

17-
exe any_stream :
18-
any_stream.cpp
19-
api/capy_streams.cpp
17+
exe echo_server :
18+
echo_server.cpp
2019
;
2120

22-
exe asio_callbacks :
23-
asio_callbacks.cpp
24-
api/capy_streams.cpp
25-
;
26-
27-
exe use_capy_example :
28-
use_capy_example.cpp
29-
api/capy_streams.cpp
30-
;

example/asio/echo_server.cpp

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//
2+
// Copyright (c) 2026 Klemens Morgenstern
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/cppalliance/capy
8+
//
9+
10+
//
11+
// Echo Server Example (Boost.Asio)
12+
//
13+
// A complete echo server using Boost.Asio TCP sockets with capy coroutines.
14+
// Demonstrates how to use capy::as_io_awaitable to await ASIO async operations.
15+
//
16+
17+
#include <boost/capy/asio/boost.hpp>
18+
#include <boost/capy/io_task.hpp>
19+
#include <boost/capy/ex/run_async.hpp>
20+
21+
#include <boost/asio/ip/tcp.hpp>
22+
#include <boost/asio/read.hpp>
23+
#include <boost/asio/write.hpp>
24+
25+
#include <cstddef>
26+
#include <cstdlib>
27+
#include <iostream>
28+
29+
namespace net = boost::asio;
30+
namespace capy = boost::capy;
31+
32+
// Socket type with as_io_awaitable as the default completion token.
33+
// This allows omitting the completion token in async calls.
34+
using tcp_socket = capy::as_io_awaitable_t::as_default_on_t<
35+
net::ip::tcp::socket>;
36+
37+
using tcp_acceptor = capy::as_io_awaitable_t::as_default_on_t<
38+
net::ip::tcp::acceptor>;
39+
40+
// Handle a single client session.
41+
// Reads data from the socket and echoes it back until the connection closes.
42+
capy::io_task<> echo_session(tcp_socket sock)
43+
{
44+
char buf[1024];
45+
46+
for (;;)
47+
{
48+
// Read some data from the client.
49+
auto res = co_await sock.async_read_some(net::buffer(buf));
50+
51+
if (boost::system::error_code ec = res.ec; ec)
52+
{
53+
if (ec != net::error::eof)
54+
std::cerr << "Read error: " << ec.message() << "\n";
55+
break;
56+
}
57+
58+
// Write the data back to the client.
59+
res = co_await net::async_write(
60+
sock, net::buffer(buf, std::get<0>(res.values)));
61+
62+
if (res.ec)
63+
{
64+
std::cerr << "Write error: " << res.ec.message() << "\n";
65+
break;
66+
}
67+
}
68+
69+
boost::system::error_code ec;
70+
sock.shutdown(net::ip::tcp::socket::shutdown_both, ec);
71+
sock.close(ec);
72+
73+
co_return {};
74+
}
75+
76+
// Accept loop - accepts connections and spawns echo sessions.
77+
capy::io_task<> accept_loop(tcp_acceptor& acceptor)
78+
{
79+
auto exec = capy::wrap_asio_executor(acceptor.get_executor());
80+
auto ep = acceptor.local_endpoint();
81+
std::cout << "Listening on port " << ep.port() << "\n";
82+
83+
for (;;)
84+
{
85+
// Accept a new connection.
86+
auto [ec, sock] = co_await acceptor.async_accept();
87+
88+
if (ec)
89+
{
90+
std::cerr << "Accept error: " << ec.message() << "\n";
91+
continue;
92+
}
93+
94+
auto remote = sock.remote_endpoint();
95+
std::cout << "Connection from " << remote.address()
96+
<< ":" << remote.port() << "\n";
97+
98+
// Spawn an echo session for this connection.
99+
// Convert the socket to use as_io_awaitable as default.
100+
capy::run_async(exec)(
101+
echo_session(capy::as_io_awaitable_t::as_default_on(std::move(sock))));
102+
}
103+
}
104+
105+
int main(int argc, char* argv[])
106+
{
107+
unsigned short port = 8080;
108+
if (argc > 1)
109+
port = static_cast<unsigned short>(std::atoi(argv[1]));
110+
111+
try
112+
{
113+
net::io_context ioc;
114+
115+
// Create the acceptor with as_io_awaitable as default.
116+
tcp_acceptor acceptor(
117+
ioc,
118+
net::ip::tcp::endpoint(net::ip::tcp::v4(), port));
119+
120+
// Wrap the ASIO executor for use with capy.
121+
auto exec = capy::wrap_asio_executor(ioc.get_executor());
122+
123+
// Spawn the accept loop.
124+
capy::run_async(exec)(accept_loop(acceptor));
125+
126+
// Run the I/O context.
127+
ioc.run();
128+
}
129+
catch (std::exception const& e)
130+
{
131+
std::cerr << "Error: " << e.what() << "\n";
132+
return 1;
133+
}
134+
135+
return 0;
136+
}

0 commit comments

Comments
 (0)