Skip to content

Commit d5a6d8f

Browse files
committed
feat: add echo-server example with preallocated worker pool
- Add echo_server.cpp with worker struct containing socket and string buffer - Preallocate workers at startup based on command-line max-workers argument - Implement run_session coroutine for echo loop until client disconnect - Implement accept_loop coroutine to assign connections to free workers - Add CMakeLists.txt and Jamfile for CMake and B2 builds - Add README.md with usage instructions and design overview - Update parent example/CMakeLists.txt and Jamfile to include echo-server
1 parent 3eee7dc commit d5a6d8f

6 files changed

Lines changed: 283 additions & 0 deletions

File tree

example/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
#
99

1010
add_subdirectory(client)
11+
add_subdirectory(echo-server)

example/Jamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
#
99

1010
build-project client ;
11+
build-project echo-server ;

example/echo-server/CMakeLists.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#
2+
# Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
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/corosio
8+
#
9+
10+
file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp
11+
CMakeLists.txt
12+
Jamfile)
13+
14+
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES})
15+
16+
add_executable(corosio_example_echo_server ${PFILES})
17+
18+
set_property(TARGET corosio_example_echo_server
19+
PROPERTY FOLDER "examples")
20+
21+
target_link_libraries(corosio_example_echo_server
22+
Boost::corosio)

example/echo-server/Jamfile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#
2+
# Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
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/corosio
8+
#
9+
10+
project
11+
: requirements
12+
<library>/boost/corosio//boost_corosio
13+
<include>.
14+
;
15+
16+
exe echo_server :
17+
[ glob *.cpp ]
18+
;

example/echo-server/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Echo Server Example
2+
3+
A TCP echo server using a preallocated worker pool. Each worker contains a socket and buffer that persist for the lifetime of the server.
4+
5+
## Usage
6+
7+
```bash
8+
echo_server <port> <max-workers>
9+
```
10+
11+
### Example
12+
13+
```bash
14+
# Start server on port 8080 with 10 workers
15+
echo_server 8080 10
16+
```
17+
18+
## What It Does
19+
20+
1. Parses the port and max-workers from command-line arguments
21+
2. Preallocates a pool of workers, each with its own socket and buffer
22+
3. Listens for incoming TCP connections
23+
4. Accepts connections into free workers
24+
5. Each worker reads data and echoes it back until the client disconnects
25+
6. When a client disconnects, the worker becomes available for a new connection
26+
27+
## Design
28+
29+
- **Preallocated resources**: All workers are created at startup with reserved buffers
30+
- **No dynamic allocation**: During normal operation, no memory allocation occurs
31+
- **Worker reuse**: Sockets are closed and reused for new connections
32+
- **Simple scheduling**: First available worker handles the next connection
33+
34+
## Testing
35+
36+
You can test the echo server using netcat or telnet:
37+
38+
```bash
39+
# In one terminal, start the server
40+
./echo_server 8080 10
41+
42+
# In another terminal, connect with netcat
43+
nc localhost 8080
44+
# Type messages and see them echoed back
45+
```
46+
47+
## Building
48+
49+
### CMake
50+
51+
```bash
52+
cmake -B build -DBOOST_COROSIO_BUILD_EXAMPLES=ON
53+
cmake --build build --target corosio_example_echo_server
54+
./build/example/echo-server/corosio_example_echo_server 8080 10
55+
```
56+
57+
### B2 (BJam)
58+
59+
```bash
60+
b2 example/echo-server
61+
./bin/echo_server 8080 10
62+
```
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
//
2+
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
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/corosio
8+
//
9+
10+
#include <boost/corosio.hpp>
11+
#include <boost/corosio/acceptor.hpp>
12+
#include <boost/capy/task.hpp>
13+
#include <boost/capy/async_run.hpp>
14+
#include <boost/capy/buffers.hpp>
15+
#include <boost/capy/error.hpp>
16+
17+
#include <cstdlib>
18+
#include <iostream>
19+
#include <string>
20+
#include <vector>
21+
22+
namespace corosio = boost::corosio;
23+
namespace capy = boost::capy;
24+
25+
// Preallocated worker that handles one connection at a time
26+
struct worker
27+
{
28+
corosio::socket sock;
29+
std::string buf;
30+
bool in_use = false;
31+
32+
explicit worker(corosio::io_context& ioc)
33+
: sock(ioc)
34+
{
35+
buf.reserve(4096);
36+
}
37+
38+
worker(worker&&) = default;
39+
worker& operator=(worker&&) = default;
40+
};
41+
42+
// Echo session coroutine for a single worker
43+
// Reads data and echoes it back until the client disconnects
44+
capy::task<void>
45+
run_session(worker& w)
46+
{
47+
w.in_use = true;
48+
49+
for (;;)
50+
{
51+
w.buf.clear();
52+
w.buf.resize(4096);
53+
54+
// Read some data
55+
auto [ec, n] = co_await w.sock.read_some(
56+
capy::mutable_buffer(w.buf.data(), w.buf.size()));
57+
58+
if (ec || n == 0)
59+
break;
60+
61+
w.buf.resize(n);
62+
63+
// Echo it back
64+
auto [wec, wn] = co_await corosio::write(
65+
w.sock, capy::const_buffer(w.buf.data(), w.buf.size()));
66+
67+
if (wec)
68+
break;
69+
}
70+
71+
w.sock.close();
72+
w.in_use = false;
73+
}
74+
75+
// Accept loop coroutine
76+
// Accepts connections and assigns them to free workers
77+
capy::task<void>
78+
accept_loop(
79+
corosio::io_context& ioc,
80+
corosio::acceptor& acc,
81+
std::vector<worker>& workers)
82+
{
83+
for (;;)
84+
{
85+
// Find a free worker
86+
worker* free_worker = nullptr;
87+
for (auto& w : workers)
88+
{
89+
if (!w.in_use)
90+
{
91+
free_worker = &w;
92+
break;
93+
}
94+
}
95+
96+
if (!free_worker)
97+
{
98+
// All workers busy - this simple example just logs and continues
99+
// A production server might queue or reject connections
100+
std::cerr << "All workers busy, waiting...\n";
101+
// We need to accept anyway to not leave the client hanging
102+
// Create a temporary socket just to accept and close
103+
corosio::socket temp(ioc);
104+
auto [ec] = co_await acc.accept(temp);
105+
if (ec)
106+
{
107+
std::cerr << "Accept error: " << ec.message() << "\n";
108+
break;
109+
}
110+
temp.close();
111+
continue;
112+
}
113+
114+
// Accept into the free worker's socket
115+
auto [ec] = co_await acc.accept(free_worker->sock);
116+
if (ec)
117+
{
118+
std::cerr << "Accept error: " << ec.message() << "\n";
119+
break;
120+
}
121+
122+
// Spawn the session coroutine
123+
capy::async_run(ioc.get_executor())(run_session(*free_worker));
124+
}
125+
}
126+
127+
int
128+
main(int argc, char* argv[])
129+
{
130+
if (argc != 3)
131+
{
132+
std::cerr <<
133+
"Usage: echo_server <port> <max-workers>\n"
134+
"Example:\n"
135+
" echo_server 8080 10\n";
136+
return EXIT_FAILURE;
137+
}
138+
139+
// Parse port
140+
int port_int = std::atoi(argv[1]);
141+
if (port_int <= 0 || port_int > 65535)
142+
{
143+
std::cerr << "Invalid port: " << argv[1] << "\n";
144+
return EXIT_FAILURE;
145+
}
146+
auto port = static_cast<std::uint16_t>(port_int);
147+
148+
// Parse max workers
149+
int max_workers = std::atoi(argv[2]);
150+
if (max_workers <= 0)
151+
{
152+
std::cerr << "Invalid max-workers: " << argv[2] << "\n";
153+
return EXIT_FAILURE;
154+
}
155+
156+
// Create I/O context
157+
corosio::io_context ioc;
158+
159+
// Preallocate workers
160+
std::vector<worker> workers;
161+
workers.reserve(max_workers);
162+
for (int i = 0; i < max_workers; ++i)
163+
workers.emplace_back(ioc);
164+
165+
// Create acceptor and listen
166+
corosio::acceptor acc(ioc);
167+
acc.listen(corosio::endpoint(port));
168+
169+
std::cout << "Echo server listening on port " << port
170+
<< " with " << max_workers << " workers\n";
171+
172+
// Start the accept loop
173+
capy::async_run(ioc.get_executor())(accept_loop(ioc, acc, workers));
174+
175+
// Run the event loop
176+
ioc.run();
177+
178+
return EXIT_SUCCESS;
179+
}

0 commit comments

Comments
 (0)