├── .gitignore ├── README.md ├── episode1 ├── .gitignore ├── Makefile ├── step_0.cpp ├── step_1.cpp ├── step_2.cpp ├── step_3.cpp ├── step_4.cpp ├── step_5.cpp ├── step_6.cpp └── step_7.cpp └── episode2 ├── .gitignore ├── Makefile ├── step_0.cpp ├── step_1.cpp ├── step_2.cpp ├── step_3.cpp ├── step_4.cpp ├── step_5.cpp ├── step_6.cpp ├── step_7.cpp └── step_8.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Talking Async 2 | 3 | Example programs for the Talking Async videos, which can be found on [YouTube](https://www.youtube.com/channel/UCmechqi1MyF9QWOMyWtpMGw). 4 | 5 | * Episode 1: [Why C++20 is the Awesomest Language for Network Programming](https://www.youtube.com/watch?v=icgnqFM-aY4) 6 | * Episode 2: [Cancellation in depth](https://youtu.be/watch?v=hHk5OXlKVFg) 7 | 8 | These examples require Asio 1.19+. The latest release may be obtained from [https://think-async.com/Asio](https://think-async.com/Asio). 9 | -------------------------------------------------------------------------------- /episode1/.gitignore: -------------------------------------------------------------------------------- 1 | step_* 2 | -------------------------------------------------------------------------------- /episode1/Makefile: -------------------------------------------------------------------------------- 1 | CXX=g++ 2 | CXXFLAGS=-std=c++20 -Wall -Wextra -fno-inline -I$(ASIO_ROOT)/include -g -DASIO_ENABLE_HANDLER_TRACKING 3 | SOURCE=$(wildcard *.cpp) 4 | PROGRAMS=$(SOURCE:.cpp=) 5 | DSYM=$(SOURCE:.cpp=.dSYM) 6 | 7 | all: $(PROGRAMS) 8 | 9 | clean: 10 | rm -rf $(PROGRAMS) $(DSYM) 11 | -------------------------------------------------------------------------------- /episode1/step_0.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using asio::buffer; 7 | using asio::ip::tcp; 8 | 9 | class proxy 10 | : public std::enable_shared_from_this 11 | { 12 | public: 13 | proxy(tcp::socket client) 14 | : client_(std::move(client)), 15 | server_(client_.get_executor()) 16 | { 17 | } 18 | 19 | void connect_to_server(tcp::endpoint target) 20 | { 21 | auto self = shared_from_this(); 22 | server_.async_connect( 23 | target, 24 | [self](std::error_code error) 25 | { 26 | if (!error) 27 | { 28 | self->read_from_client(); 29 | self->read_from_server(); 30 | } 31 | } 32 | ); 33 | } 34 | 35 | private: 36 | void stop() 37 | { 38 | client_.close(); 39 | server_.close(); 40 | } 41 | 42 | void read_from_client() 43 | { 44 | auto self = shared_from_this(); 45 | client_.async_read_some( 46 | buffer(data_from_client_), 47 | [self](std::error_code error, std::size_t n) 48 | { 49 | if (!error) 50 | { 51 | self->write_to_server(n); 52 | } 53 | else 54 | { 55 | self->stop(); 56 | } 57 | } 58 | ); 59 | } 60 | 61 | void write_to_server(std::size_t n) 62 | { 63 | auto self = shared_from_this(); 64 | async_write( 65 | server_, 66 | buffer(data_from_client_, n), 67 | [self](std::error_code ec, std::size_t /*n*/) 68 | { 69 | if (!ec) 70 | { 71 | self->read_from_client(); 72 | } 73 | else 74 | { 75 | self->stop(); 76 | } 77 | } 78 | ); 79 | } 80 | 81 | void read_from_server() 82 | { 83 | auto self = shared_from_this(); 84 | server_.async_read_some( 85 | asio::buffer(data_from_server_), 86 | [self](std::error_code error, std::size_t n) 87 | { 88 | if (!error) 89 | { 90 | self->write_to_client(n); 91 | } 92 | else 93 | { 94 | self->stop(); 95 | } 96 | } 97 | ); 98 | } 99 | 100 | void write_to_client(std::size_t n) 101 | { 102 | auto self = shared_from_this(); 103 | async_write( 104 | client_, 105 | buffer(data_from_server_, n), 106 | [self](std::error_code ec, std::size_t /*n*/) 107 | { 108 | if (!ec) 109 | { 110 | self->read_from_server(); 111 | } 112 | else 113 | { 114 | self->stop(); 115 | } 116 | } 117 | ); 118 | } 119 | 120 | tcp::socket client_; 121 | tcp::socket server_; 122 | std::array data_from_client_; 123 | std::array data_from_server_; 124 | }; 125 | 126 | void listen(tcp::acceptor& acceptor, tcp::endpoint target) 127 | { 128 | acceptor.async_accept( 129 | [&acceptor, target](std::error_code error, tcp::socket client) 130 | { 131 | if (!error) 132 | { 133 | std::make_shared( 134 | std::move(client) 135 | )->connect_to_server(target); 136 | } 137 | 138 | listen(acceptor, target); 139 | } 140 | ); 141 | } 142 | 143 | int main(int argc, char* argv[]) 144 | { 145 | try 146 | { 147 | if (argc != 5) 148 | { 149 | std::cerr << "Usage: proxy"; 150 | std::cerr << " "; 151 | std::cerr << " \n"; 152 | return 1; 153 | } 154 | 155 | asio::io_context ctx; 156 | 157 | auto listen_endpoint = 158 | *tcp::resolver(ctx).resolve( 159 | argv[1], 160 | argv[2], 161 | tcp::resolver::passive 162 | ); 163 | 164 | auto target_endpoint = 165 | *tcp::resolver(ctx).resolve( 166 | argv[3], 167 | argv[4] 168 | ); 169 | 170 | tcp::acceptor acceptor(ctx, listen_endpoint); 171 | 172 | listen(acceptor, target_endpoint); 173 | 174 | ctx.run(); 175 | } 176 | catch (std::exception& e) 177 | { 178 | std::cerr << "Exception: " << e.what() << "\n"; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /episode1/step_1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using asio::awaitable; 7 | using asio::buffer; 8 | using asio::co_spawn; 9 | using asio::detached; 10 | using asio::ip::tcp; 11 | using asio::use_awaitable; 12 | 13 | struct proxy_state 14 | { 15 | proxy_state(tcp::socket client) 16 | : client(std::move(client)) 17 | { 18 | } 19 | 20 | tcp::socket client; 21 | tcp::socket server{client.get_executor()}; 22 | }; 23 | 24 | using proxy_state_ptr = std::shared_ptr; 25 | 26 | awaitable client_to_server(proxy_state_ptr state) 27 | { 28 | try 29 | { 30 | std::array data; 31 | 32 | for (;;) 33 | { 34 | auto n = co_await state->client.async_read_some(buffer(data), use_awaitable); 35 | 36 | co_await async_write(state->server, buffer(data, n), use_awaitable); 37 | } 38 | } 39 | catch (const std::exception& e) 40 | { 41 | state->client.close(); 42 | state->server.close(); 43 | } 44 | } 45 | 46 | awaitable server_to_client(proxy_state_ptr state) 47 | { 48 | try 49 | { 50 | std::array data; 51 | 52 | for (;;) 53 | { 54 | auto n = co_await state->server.async_read_some(buffer(data), use_awaitable); 55 | 56 | co_await async_write(state->client, buffer(data, n), use_awaitable); 57 | } 58 | } 59 | catch (const std::exception& e) 60 | { 61 | state->client.close(); 62 | state->server.close(); 63 | } 64 | } 65 | 66 | awaitable proxy(tcp::socket client, tcp::endpoint target) 67 | { 68 | auto state = std::make_shared(std::move(client)); 69 | 70 | co_await state->server.async_connect(target, use_awaitable); 71 | 72 | auto ex = state->client.get_executor(); 73 | co_spawn(ex, client_to_server(state), detached); 74 | 75 | co_await server_to_client(state); 76 | } 77 | 78 | awaitable listen(tcp::acceptor& acceptor, tcp::endpoint target) 79 | { 80 | for (;;) 81 | { 82 | auto client = co_await acceptor.async_accept(use_awaitable); 83 | 84 | auto ex = client.get_executor(); 85 | co_spawn(ex, proxy(std::move(client), target), detached); 86 | } 87 | } 88 | 89 | int main(int argc, char* argv[]) 90 | { 91 | try 92 | { 93 | if (argc != 5) 94 | { 95 | std::cerr << "Usage: proxy"; 96 | std::cerr << " "; 97 | std::cerr << " \n"; 98 | return 1; 99 | } 100 | 101 | asio::io_context ctx; 102 | 103 | auto listen_endpoint = 104 | *tcp::resolver(ctx).resolve( 105 | argv[1], 106 | argv[2], 107 | tcp::resolver::passive 108 | ); 109 | 110 | auto target_endpoint = 111 | *tcp::resolver(ctx).resolve( 112 | argv[3], 113 | argv[4] 114 | ); 115 | 116 | tcp::acceptor acceptor(ctx, listen_endpoint); 117 | 118 | co_spawn(ctx, listen(acceptor, target_endpoint), detached); 119 | 120 | ctx.run(); 121 | } 122 | catch (std::exception& e) 123 | { 124 | std::cerr << "Exception: " << e.what() << "\n"; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /episode1/step_2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using asio::awaitable; 8 | using asio::buffer; 9 | using asio::co_spawn; 10 | using asio::detached; 11 | using asio::ip::tcp; 12 | 13 | constexpr auto use_nothrow_awaitable = asio::experimental::as_tuple(asio::use_awaitable); 14 | 15 | struct proxy_state 16 | { 17 | proxy_state(tcp::socket client) 18 | : client(std::move(client)) 19 | { 20 | } 21 | 22 | tcp::socket client; 23 | tcp::socket server{client.get_executor()}; 24 | }; 25 | 26 | using proxy_state_ptr = std::shared_ptr; 27 | 28 | awaitable client_to_server(proxy_state_ptr state) 29 | { 30 | std::array data; 31 | 32 | for (;;) 33 | { 34 | auto [e1, n1] = co_await state->client.async_read_some(buffer(data), use_nothrow_awaitable); 35 | if (e1) 36 | break; 37 | 38 | auto [e2, n2] = co_await async_write(state->server, buffer(data, n1), use_nothrow_awaitable); 39 | if (e2) 40 | break; 41 | } 42 | 43 | state->client.close(); 44 | state->server.close(); 45 | } 46 | 47 | awaitable server_to_client(proxy_state_ptr state) 48 | { 49 | std::array data; 50 | 51 | for (;;) 52 | { 53 | auto [e1, n1] = co_await state->server.async_read_some(buffer(data), use_nothrow_awaitable); 54 | if (e1) 55 | break; 56 | 57 | auto [e2, n2] = co_await async_write(state->client, buffer(data, n1), use_nothrow_awaitable); 58 | if (e2) 59 | break; 60 | } 61 | 62 | state->client.close(); 63 | state->server.close(); 64 | } 65 | 66 | awaitable proxy(tcp::socket client, tcp::endpoint target) 67 | { 68 | auto state = std::make_shared(std::move(client)); 69 | 70 | auto [e] = co_await state->server.async_connect(target, use_nothrow_awaitable); 71 | if (!e) 72 | { 73 | auto ex = state->client.get_executor(); 74 | co_spawn(ex, client_to_server(state), detached); 75 | 76 | co_await server_to_client(state); 77 | } 78 | } 79 | 80 | awaitable listen(tcp::acceptor& acceptor, tcp::endpoint target) 81 | { 82 | for (;;) 83 | { 84 | auto [e, client] = co_await acceptor.async_accept(use_nothrow_awaitable); 85 | if (e) 86 | break; 87 | 88 | auto ex = client.get_executor(); 89 | co_spawn(ex, proxy(std::move(client), target), detached); 90 | } 91 | } 92 | 93 | int main(int argc, char* argv[]) 94 | { 95 | try 96 | { 97 | if (argc != 5) 98 | { 99 | std::cerr << "Usage: proxy"; 100 | std::cerr << " "; 101 | std::cerr << " \n"; 102 | return 1; 103 | } 104 | 105 | asio::io_context ctx; 106 | 107 | auto listen_endpoint = 108 | *tcp::resolver(ctx).resolve( 109 | argv[1], 110 | argv[2], 111 | tcp::resolver::passive 112 | ); 113 | 114 | auto target_endpoint = 115 | *tcp::resolver(ctx).resolve( 116 | argv[3], 117 | argv[4] 118 | ); 119 | 120 | tcp::acceptor acceptor(ctx, listen_endpoint); 121 | 122 | co_spawn(ctx, listen(acceptor, target_endpoint), detached); 123 | 124 | ctx.run(); 125 | } 126 | catch (std::exception& e) 127 | { 128 | std::cerr << "Exception: " << e.what() << "\n"; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /episode1/step_3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using asio::awaitable; 8 | using asio::buffer; 9 | using asio::co_spawn; 10 | using asio::detached; 11 | using asio::ip::tcp; 12 | using std::chrono::steady_clock; 13 | using namespace std::literals::chrono_literals; 14 | 15 | constexpr auto use_nothrow_awaitable = asio::experimental::as_tuple(asio::use_awaitable); 16 | 17 | struct proxy_state 18 | { 19 | proxy_state(tcp::socket client) 20 | : client(std::move(client)) 21 | { 22 | } 23 | 24 | tcp::socket client; 25 | tcp::socket server{client.get_executor()}; 26 | steady_clock::time_point deadline; 27 | }; 28 | 29 | using proxy_state_ptr = std::shared_ptr; 30 | 31 | awaitable client_to_server(proxy_state_ptr state) 32 | { 33 | std::array data; 34 | 35 | for (;;) 36 | { 37 | state->deadline = std::max(state->deadline, steady_clock::now() + 5s); 38 | 39 | auto [e1, n1] = co_await state->client.async_read_some(buffer(data), use_nothrow_awaitable); 40 | if (e1) 41 | break; 42 | 43 | auto [e2, n2] = co_await async_write(state->server, buffer(data, n1), use_nothrow_awaitable); 44 | if (e2) 45 | break; 46 | } 47 | 48 | state->client.close(); 49 | state->server.close(); 50 | } 51 | 52 | awaitable server_to_client(proxy_state_ptr state) 53 | { 54 | std::array data; 55 | 56 | for (;;) 57 | { 58 | state->deadline = std::max(state->deadline, steady_clock::now() + 5s); 59 | 60 | auto [e1, n1] = co_await state->server.async_read_some(buffer(data), use_nothrow_awaitable); 61 | if (e1) 62 | break; 63 | 64 | auto [e2, n2] = co_await async_write(state->client, buffer(data, n1), use_nothrow_awaitable); 65 | if (e2) 66 | break; 67 | } 68 | 69 | state->client.close(); 70 | state->server.close(); 71 | } 72 | 73 | awaitable watchdog(proxy_state_ptr state) 74 | { 75 | asio::steady_timer timer(state->client.get_executor()); 76 | 77 | auto now = steady_clock::now(); 78 | while (state->deadline > now) 79 | { 80 | timer.expires_at(state->deadline); 81 | co_await timer.async_wait(use_nothrow_awaitable); 82 | now = steady_clock::now(); 83 | } 84 | 85 | state->client.close(); 86 | state->server.close(); 87 | } 88 | 89 | awaitable proxy(tcp::socket client, tcp::endpoint target) 90 | { 91 | auto state = std::make_shared(std::move(client)); 92 | 93 | auto [e] = co_await state->server.async_connect(target, use_nothrow_awaitable); 94 | if (!e) 95 | { 96 | auto ex = state->client.get_executor(); 97 | co_spawn(ex, client_to_server(state), detached); 98 | co_spawn(ex, server_to_client(state), detached); 99 | 100 | co_await watchdog(state); 101 | } 102 | } 103 | 104 | awaitable listen(tcp::acceptor& acceptor, tcp::endpoint target) 105 | { 106 | for (;;) 107 | { 108 | auto [e, client] = co_await acceptor.async_accept(use_nothrow_awaitable); 109 | if (e) 110 | break; 111 | 112 | auto ex = client.get_executor(); 113 | co_spawn(ex, proxy(std::move(client), target), detached); 114 | } 115 | } 116 | 117 | int main(int argc, char* argv[]) 118 | { 119 | try 120 | { 121 | if (argc != 5) 122 | { 123 | std::cerr << "Usage: proxy"; 124 | std::cerr << " "; 125 | std::cerr << " \n"; 126 | return 1; 127 | } 128 | 129 | asio::io_context ctx; 130 | 131 | auto listen_endpoint = 132 | *tcp::resolver(ctx).resolve( 133 | argv[1], 134 | argv[2], 135 | tcp::resolver::passive 136 | ); 137 | 138 | auto target_endpoint = 139 | *tcp::resolver(ctx).resolve( 140 | argv[3], 141 | argv[4] 142 | ); 143 | 144 | tcp::acceptor acceptor(ctx, listen_endpoint); 145 | 146 | co_spawn(ctx, listen(acceptor, target_endpoint), detached); 147 | 148 | ctx.run(); 149 | } 150 | catch (std::exception& e) 151 | { 152 | std::cerr << "Exception: " << e.what() << "\n"; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /episode1/step_4.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using asio::awaitable; 9 | using asio::buffer; 10 | using asio::co_spawn; 11 | using asio::detached; 12 | using asio::ip::tcp; 13 | using namespace asio::experimental::awaitable_operators; 14 | using std::chrono::steady_clock; 15 | using namespace std::literals::chrono_literals; 16 | 17 | constexpr auto use_nothrow_awaitable = asio::experimental::as_tuple(asio::use_awaitable); 18 | 19 | struct proxy_state 20 | { 21 | proxy_state(tcp::socket client) 22 | : client(std::move(client)) 23 | { 24 | } 25 | 26 | tcp::socket client; 27 | tcp::socket server{client.get_executor()}; 28 | steady_clock::time_point deadline; 29 | }; 30 | 31 | using proxy_state_ptr = std::shared_ptr; 32 | 33 | awaitable client_to_server(proxy_state_ptr state) 34 | { 35 | std::array data; 36 | 37 | for (;;) 38 | { 39 | state->deadline = std::max(state->deadline, steady_clock::now() + 5s); 40 | 41 | auto [e1, n1] = co_await state->client.async_read_some(buffer(data), use_nothrow_awaitable); 42 | if (e1) 43 | co_return; 44 | 45 | auto [e2, n2] = co_await async_write(state->server, buffer(data, n1), use_nothrow_awaitable); 46 | if (e2) 47 | co_return; 48 | } 49 | } 50 | 51 | awaitable server_to_client(proxy_state_ptr state) 52 | { 53 | std::array data; 54 | 55 | for (;;) 56 | { 57 | state->deadline = std::max(state->deadline, steady_clock::now() + 5s); 58 | 59 | auto [e1, n1] = co_await state->server.async_read_some(buffer(data), use_nothrow_awaitable); 60 | if (e1) 61 | co_return; 62 | 63 | auto [e2, n2] = co_await async_write(state->client, buffer(data, n1), use_nothrow_awaitable); 64 | if (e2) 65 | co_return; 66 | } 67 | } 68 | 69 | awaitable watchdog(proxy_state_ptr state) 70 | { 71 | asio::steady_timer timer(state->client.get_executor()); 72 | 73 | auto now = steady_clock::now(); 74 | while (state->deadline > now) 75 | { 76 | timer.expires_at(state->deadline); 77 | co_await timer.async_wait(use_nothrow_awaitable); 78 | now = steady_clock::now(); 79 | } 80 | } 81 | 82 | awaitable proxy(tcp::socket client, tcp::endpoint target) 83 | { 84 | auto state = std::make_shared(std::move(client)); 85 | 86 | auto [e] = co_await state->server.async_connect(target, use_nothrow_awaitable); 87 | if (!e) 88 | { 89 | co_await ( 90 | client_to_server(state) || 91 | server_to_client(state) || 92 | watchdog(state) 93 | ); 94 | 95 | state->client.close(); 96 | state->server.close(); 97 | } 98 | } 99 | 100 | awaitable listen(tcp::acceptor& acceptor, tcp::endpoint target) 101 | { 102 | for (;;) 103 | { 104 | auto [e, client] = co_await acceptor.async_accept(use_nothrow_awaitable); 105 | if (e) 106 | break; 107 | 108 | auto ex = client.get_executor(); 109 | co_spawn(ex, proxy(std::move(client), target), detached); 110 | } 111 | } 112 | 113 | int main(int argc, char* argv[]) 114 | { 115 | try 116 | { 117 | if (argc != 5) 118 | { 119 | std::cerr << "Usage: proxy"; 120 | std::cerr << " "; 121 | std::cerr << " \n"; 122 | return 1; 123 | } 124 | 125 | asio::io_context ctx; 126 | 127 | auto listen_endpoint = 128 | *tcp::resolver(ctx).resolve( 129 | argv[1], 130 | argv[2], 131 | tcp::resolver::passive 132 | ); 133 | 134 | auto target_endpoint = 135 | *tcp::resolver(ctx).resolve( 136 | argv[3], 137 | argv[4] 138 | ); 139 | 140 | tcp::acceptor acceptor(ctx, listen_endpoint); 141 | 142 | co_spawn(ctx, listen(acceptor, target_endpoint), detached); 143 | 144 | ctx.run(); 145 | } 146 | catch (std::exception& e) 147 | { 148 | std::cerr << "Exception: " << e.what() << "\n"; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /episode1/step_5.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using asio::awaitable; 9 | using asio::buffer; 10 | using asio::co_spawn; 11 | using asio::detached; 12 | using asio::ip::tcp; 13 | namespace this_coro = asio::this_coro; 14 | using namespace asio::experimental::awaitable_operators; 15 | using std::chrono::steady_clock; 16 | using namespace std::literals::chrono_literals; 17 | 18 | constexpr auto use_nothrow_awaitable = asio::experimental::as_tuple(asio::use_awaitable); 19 | 20 | awaitable transfer(tcp::socket& from, tcp::socket& to, steady_clock::time_point& deadline) 21 | { 22 | std::array data; 23 | 24 | for (;;) 25 | { 26 | deadline = std::max(deadline, steady_clock::now() + 5s); 27 | 28 | auto [e1, n1] = co_await from.async_read_some(buffer(data), use_nothrow_awaitable); 29 | if (e1) 30 | co_return; 31 | 32 | auto [e2, n2] = co_await async_write(to, buffer(data, n1), use_nothrow_awaitable); 33 | if (e2) 34 | co_return; 35 | } 36 | } 37 | 38 | awaitable watchdog(steady_clock::time_point& deadline) 39 | { 40 | asio::steady_timer timer(co_await this_coro::executor); 41 | 42 | auto now = steady_clock::now(); 43 | while (deadline > now) 44 | { 45 | timer.expires_at(deadline); 46 | co_await timer.async_wait(use_nothrow_awaitable); 47 | now = steady_clock::now(); 48 | } 49 | } 50 | 51 | awaitable proxy(tcp::socket client, tcp::endpoint target) 52 | { 53 | tcp::socket server(client.get_executor()); 54 | steady_clock::time_point deadline{}; 55 | 56 | auto [e] = co_await server.async_connect(target, use_nothrow_awaitable); 57 | if (!e) 58 | { 59 | co_await ( 60 | transfer(client, server, deadline) || 61 | transfer(server, client, deadline) || 62 | watchdog(deadline) 63 | ); 64 | } 65 | } 66 | 67 | awaitable listen(tcp::acceptor& acceptor, tcp::endpoint target) 68 | { 69 | for (;;) 70 | { 71 | auto [e, client] = co_await acceptor.async_accept(use_nothrow_awaitable); 72 | if (e) 73 | break; 74 | 75 | auto ex = client.get_executor(); 76 | co_spawn(ex, proxy(std::move(client), target), detached); 77 | } 78 | } 79 | 80 | int main(int argc, char* argv[]) 81 | { 82 | try 83 | { 84 | if (argc != 5) 85 | { 86 | std::cerr << "Usage: proxy"; 87 | std::cerr << " "; 88 | std::cerr << " \n"; 89 | return 1; 90 | } 91 | 92 | asio::io_context ctx; 93 | 94 | auto listen_endpoint = 95 | *tcp::resolver(ctx).resolve( 96 | argv[1], 97 | argv[2], 98 | tcp::resolver::passive 99 | ); 100 | 101 | auto target_endpoint = 102 | *tcp::resolver(ctx).resolve( 103 | argv[3], 104 | argv[4] 105 | ); 106 | 107 | tcp::acceptor acceptor(ctx, listen_endpoint); 108 | 109 | co_spawn(ctx, listen(acceptor, target_endpoint), detached); 110 | 111 | ctx.run(); 112 | } 113 | catch (std::exception& e) 114 | { 115 | std::cerr << "Exception: " << e.what() << "\n"; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /episode1/step_6.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using asio::awaitable; 9 | using asio::buffer; 10 | using asio::co_spawn; 11 | using asio::detached; 12 | using asio::ip::tcp; 13 | namespace this_coro = asio::this_coro; 14 | using namespace asio::experimental::awaitable_operators; 15 | using std::chrono::steady_clock; 16 | using namespace std::literals::chrono_literals; 17 | 18 | constexpr auto use_nothrow_awaitable = asio::experimental::as_tuple(asio::use_awaitable); 19 | 20 | awaitable transfer(tcp::socket& from, tcp::socket& to, steady_clock::time_point& deadline) 21 | { 22 | std::array data; 23 | 24 | for (;;) 25 | { 26 | deadline = std::max(deadline, steady_clock::now() + 5s); 27 | 28 | auto [e1, n1] = co_await from.async_read_some(buffer(data), use_nothrow_awaitable); 29 | if (e1) 30 | co_return; 31 | 32 | auto [e2, n2] = co_await async_write(to, buffer(data, n1), use_nothrow_awaitable); 33 | if (e2) 34 | co_return; 35 | } 36 | } 37 | 38 | awaitable watchdog(steady_clock::time_point& deadline) 39 | { 40 | asio::steady_timer timer(co_await this_coro::executor); 41 | 42 | auto now = steady_clock::now(); 43 | while (deadline > now) 44 | { 45 | timer.expires_at(deadline); 46 | co_await timer.async_wait(use_nothrow_awaitable); 47 | now = steady_clock::now(); 48 | } 49 | } 50 | 51 | awaitable proxy(tcp::socket client, tcp::endpoint target) 52 | { 53 | tcp::socket server(client.get_executor()); 54 | steady_clock::time_point client_to_server_deadline{}; 55 | steady_clock::time_point server_to_client_deadline{}; 56 | 57 | auto [e] = co_await server.async_connect(target, use_nothrow_awaitable); 58 | if (!e) 59 | { 60 | co_await ( 61 | ( 62 | transfer(client, server, client_to_server_deadline) || 63 | watchdog(client_to_server_deadline) 64 | ) 65 | && 66 | ( 67 | transfer(server, client, server_to_client_deadline) || 68 | watchdog(server_to_client_deadline) 69 | ) 70 | ); 71 | } 72 | } 73 | 74 | awaitable listen(tcp::acceptor& acceptor, tcp::endpoint target) 75 | { 76 | for (;;) 77 | { 78 | auto [e, client] = co_await acceptor.async_accept(use_nothrow_awaitable); 79 | if (e) 80 | break; 81 | 82 | auto ex = client.get_executor(); 83 | co_spawn(ex, proxy(std::move(client), target), detached); 84 | } 85 | } 86 | 87 | int main(int argc, char* argv[]) 88 | { 89 | try 90 | { 91 | if (argc != 5) 92 | { 93 | std::cerr << "Usage: proxy"; 94 | std::cerr << " "; 95 | std::cerr << " \n"; 96 | return 1; 97 | } 98 | 99 | asio::io_context ctx; 100 | 101 | auto listen_endpoint = 102 | *tcp::resolver(ctx).resolve( 103 | argv[1], 104 | argv[2], 105 | tcp::resolver::passive 106 | ); 107 | 108 | auto target_endpoint = 109 | *tcp::resolver(ctx).resolve( 110 | argv[3], 111 | argv[4] 112 | ); 113 | 114 | tcp::acceptor acceptor(ctx, listen_endpoint); 115 | 116 | co_spawn(ctx, listen(acceptor, target_endpoint), detached); 117 | 118 | ctx.run(); 119 | } 120 | catch (std::exception& e) 121 | { 122 | std::cerr << "Exception: " << e.what() << "\n"; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /episode1/step_7.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using asio::awaitable; 9 | using asio::buffer; 10 | using asio::co_spawn; 11 | using asio::detached; 12 | using asio::ip::tcp; 13 | namespace this_coro = asio::this_coro; 14 | using namespace asio::experimental::awaitable_operators; 15 | using std::chrono::steady_clock; 16 | using namespace std::literals::chrono_literals; 17 | 18 | constexpr auto use_nothrow_awaitable = asio::experimental::as_tuple(asio::use_awaitable); 19 | 20 | awaitable timeout(steady_clock::duration duration) 21 | { 22 | asio::steady_timer timer(co_await this_coro::executor); 23 | timer.expires_after(duration); 24 | co_await timer.async_wait(use_nothrow_awaitable); 25 | } 26 | 27 | awaitable transfer(tcp::socket& from, tcp::socket& to) 28 | { 29 | std::array data; 30 | 31 | for (;;) 32 | { 33 | auto result1 = co_await ( 34 | from.async_read_some(buffer(data), use_nothrow_awaitable) || 35 | timeout(5s) 36 | ); 37 | 38 | if (result1.index() == 1) 39 | co_return; // timed out 40 | 41 | auto [e1, n1] = std::get<0>(result1); 42 | if (e1) 43 | break; 44 | 45 | auto result2 = co_await ( 46 | async_write(to, buffer(data, n1), use_nothrow_awaitable) || 47 | timeout(1s) 48 | ); 49 | 50 | if (result2.index() == 1) 51 | co_return; // timed out 52 | 53 | auto [e2, n2] = std::get<0>(result2); 54 | if (e2) 55 | break; 56 | } 57 | } 58 | 59 | awaitable proxy(tcp::socket client, tcp::endpoint target) 60 | { 61 | tcp::socket server(client.get_executor()); 62 | 63 | auto [e] = co_await server.async_connect(target, use_nothrow_awaitable); 64 | if (!e) 65 | { 66 | co_await ( 67 | transfer(client, server) || 68 | transfer(server, client) 69 | ); 70 | } 71 | } 72 | 73 | awaitable listen(tcp::acceptor& acceptor, tcp::endpoint target) 74 | { 75 | for (;;) 76 | { 77 | auto [e, client] = co_await acceptor.async_accept(use_nothrow_awaitable); 78 | if (e) 79 | break; 80 | 81 | auto ex = client.get_executor(); 82 | co_spawn(ex, proxy(std::move(client), target), detached); 83 | } 84 | } 85 | 86 | int main(int argc, char* argv[]) 87 | { 88 | try 89 | { 90 | if (argc != 5) 91 | { 92 | std::cerr << "Usage: proxy"; 93 | std::cerr << " "; 94 | std::cerr << " \n"; 95 | return 1; 96 | } 97 | 98 | asio::io_context ctx; 99 | 100 | auto listen_endpoint = 101 | *tcp::resolver(ctx).resolve( 102 | argv[1], 103 | argv[2], 104 | tcp::resolver::passive 105 | ); 106 | 107 | auto target_endpoint = 108 | *tcp::resolver(ctx).resolve( 109 | argv[3], 110 | argv[4] 111 | ); 112 | 113 | tcp::acceptor acceptor(ctx, listen_endpoint); 114 | 115 | co_spawn(ctx, listen(acceptor, target_endpoint), detached); 116 | 117 | ctx.run(); 118 | } 119 | catch (std::exception& e) 120 | { 121 | std::cerr << "Exception: " << e.what() << "\n"; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /episode2/.gitignore: -------------------------------------------------------------------------------- 1 | *.dSYM 2 | step_[0-9] 3 | -------------------------------------------------------------------------------- /episode2/Makefile: -------------------------------------------------------------------------------- 1 | CXX=g++ 2 | CXXFLAGS=-std=c++20 -Wall -Wextra -fno-inline -I$(ASIO_ROOT)/include -g -DASIO_ENABLE_HANDLER_TRACKING 3 | SOURCE=$(wildcard *.cpp) 4 | PROGRAMS=$(SOURCE:.cpp=) 5 | DSYM=$(SOURCE:.cpp=.dSYM) 6 | 7 | all: $(PROGRAMS) 8 | 9 | clean: 10 | rm -rf $(PROGRAMS) $(DSYM) 11 | -------------------------------------------------------------------------------- /episode2/step_0.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using asio::buffer; 7 | using asio::ip::tcp; 8 | 9 | class proxy 10 | : public std::enable_shared_from_this 11 | { 12 | public: 13 | proxy(tcp::socket client) 14 | : client_(std::move(client)), 15 | server_(client_.get_executor()) 16 | { 17 | } 18 | 19 | void connect_to_server(tcp::endpoint target) 20 | { 21 | auto self = shared_from_this(); 22 | server_.async_connect( 23 | target, 24 | [self](std::error_code error) 25 | { 26 | if (!error) 27 | { 28 | self->read_from_client(); 29 | self->read_from_server(); 30 | } 31 | } 32 | ); 33 | } 34 | 35 | private: 36 | void stop() 37 | { 38 | client_.close(); 39 | server_.close(); 40 | } 41 | 42 | void read_from_client() 43 | { 44 | auto self = shared_from_this(); 45 | client_.async_read_some( 46 | buffer(data_from_client_), 47 | [self](std::error_code error, std::size_t n) 48 | { 49 | if (!error) 50 | { 51 | self->write_to_server(n); 52 | } 53 | else 54 | { 55 | self->stop(); 56 | } 57 | } 58 | ); 59 | } 60 | 61 | void write_to_server(std::size_t n) 62 | { 63 | auto self = shared_from_this(); 64 | async_write( 65 | server_, 66 | buffer(data_from_client_, n), 67 | [self](std::error_code error, std::size_t /*n*/) 68 | { 69 | if (!error) 70 | { 71 | self->read_from_client(); 72 | } 73 | else 74 | { 75 | self->stop(); 76 | } 77 | } 78 | ); 79 | } 80 | 81 | void read_from_server() 82 | { 83 | auto self = shared_from_this(); 84 | server_.async_read_some( 85 | buffer(data_from_server_), 86 | [self](std::error_code error, std::size_t n) 87 | { 88 | if (!error) 89 | { 90 | self->write_to_client(n); 91 | } 92 | else 93 | { 94 | self->stop(); 95 | } 96 | } 97 | ); 98 | } 99 | 100 | void write_to_client(std::size_t n) 101 | { 102 | auto self = shared_from_this(); 103 | async_write( 104 | client_, 105 | buffer(data_from_server_, n), 106 | [self](std::error_code error, std::size_t /*n*/) 107 | { 108 | if (!error) 109 | { 110 | self->read_from_server(); 111 | } 112 | else 113 | { 114 | self->stop(); 115 | } 116 | } 117 | ); 118 | } 119 | 120 | tcp::socket client_; 121 | tcp::socket server_; 122 | std::array data_from_client_; 123 | std::array data_from_server_; 124 | }; 125 | 126 | void listen(tcp::acceptor& acceptor, tcp::endpoint target) 127 | { 128 | acceptor.async_accept( 129 | [&acceptor, target](std::error_code error, tcp::socket client) 130 | { 131 | if (!error) 132 | { 133 | std::make_shared( 134 | std::move(client) 135 | )->connect_to_server(target); 136 | } 137 | 138 | listen(acceptor, target); 139 | } 140 | ); 141 | } 142 | 143 | int main(int argc, char* argv[]) 144 | { 145 | try 146 | { 147 | if (argc != 5) 148 | { 149 | std::cerr << "Usage: proxy"; 150 | std::cerr << " "; 151 | std::cerr << " \n"; 152 | return 1; 153 | } 154 | 155 | asio::io_context ctx; 156 | 157 | auto listen_endpoint = 158 | *tcp::resolver(ctx).resolve( 159 | argv[1], 160 | argv[2], 161 | tcp::resolver::passive 162 | ); 163 | 164 | auto target_endpoint = 165 | *tcp::resolver(ctx).resolve( 166 | argv[3], 167 | argv[4] 168 | ); 169 | 170 | tcp::acceptor acceptor(ctx, listen_endpoint); 171 | 172 | listen(acceptor, target_endpoint); 173 | 174 | ctx.run(); 175 | } 176 | catch (std::exception& e) 177 | { 178 | std::cerr << "Exception: " << e.what() << "\n"; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /episode2/step_1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using asio::buffer; 7 | using asio::ip::tcp; 8 | using std::chrono::steady_clock; 9 | using namespace std::literals::chrono_literals; 10 | 11 | class proxy 12 | : public std::enable_shared_from_this 13 | { 14 | public: 15 | proxy(tcp::socket client) 16 | : client_(std::move(client)), 17 | server_(client_.get_executor()), 18 | watchdog_timer_(client_.get_executor()) 19 | { 20 | } 21 | 22 | void connect_to_server(tcp::endpoint target) 23 | { 24 | auto self = shared_from_this(); 25 | server_.async_connect( 26 | target, 27 | [self](std::error_code error) 28 | { 29 | if (!error) 30 | { 31 | self->read_from_client(); 32 | self->read_from_server(); 33 | self->watchdog(); 34 | } 35 | } 36 | ); 37 | } 38 | 39 | private: 40 | void stop() 41 | { 42 | client_.close(); 43 | server_.close(); 44 | watchdog_timer_.cancel(); 45 | } 46 | 47 | bool is_stopped() const 48 | { 49 | return !client_.is_open() && !server_.is_open(); 50 | } 51 | 52 | void read_from_client() 53 | { 54 | deadline_ = std::max(deadline_, steady_clock::now() + 5s); 55 | 56 | auto self = shared_from_this(); 57 | client_.async_read_some( 58 | buffer(data_from_client_), 59 | [self](std::error_code error, std::size_t n) 60 | { 61 | if (!error) 62 | { 63 | self->write_to_server(n); 64 | } 65 | else 66 | { 67 | self->stop(); 68 | } 69 | } 70 | ); 71 | } 72 | 73 | void write_to_server(std::size_t n) 74 | { 75 | auto self = shared_from_this(); 76 | async_write( 77 | server_, 78 | buffer(data_from_client_, n), 79 | [self](std::error_code error, std::size_t /*n*/) 80 | { 81 | if (!error) 82 | { 83 | self->read_from_client(); 84 | } 85 | else 86 | { 87 | self->stop(); 88 | } 89 | } 90 | ); 91 | } 92 | 93 | void read_from_server() 94 | { 95 | deadline_ = std::max(deadline_, steady_clock::now() + 5s); 96 | 97 | auto self = shared_from_this(); 98 | server_.async_read_some( 99 | buffer(data_from_server_), 100 | [self](std::error_code error, std::size_t n) 101 | { 102 | if (!error) 103 | { 104 | self->write_to_client(n); 105 | } 106 | else 107 | { 108 | self->stop(); 109 | } 110 | } 111 | ); 112 | } 113 | 114 | void write_to_client(std::size_t n) 115 | { 116 | auto self = shared_from_this(); 117 | async_write( 118 | client_, 119 | buffer(data_from_server_, n), 120 | [self](std::error_code error, std::size_t /*n*/) 121 | { 122 | if (!error) 123 | { 124 | self->read_from_server(); 125 | } 126 | else 127 | { 128 | self->stop(); 129 | } 130 | } 131 | ); 132 | } 133 | 134 | void watchdog() 135 | { 136 | auto self = shared_from_this(); 137 | watchdog_timer_.expires_at(deadline_); 138 | watchdog_timer_.async_wait( 139 | [self](std::error_code /*error*/) 140 | { 141 | if (!self->is_stopped()) 142 | { 143 | auto now = steady_clock::now(); 144 | if (self->deadline_ > now) 145 | { 146 | self->watchdog(); 147 | } 148 | else 149 | { 150 | self->stop(); 151 | } 152 | } 153 | } 154 | ); 155 | } 156 | 157 | tcp::socket client_; 158 | tcp::socket server_; 159 | std::array data_from_client_; 160 | std::array data_from_server_; 161 | steady_clock::time_point deadline_; 162 | asio::steady_timer watchdog_timer_; 163 | }; 164 | 165 | void listen(tcp::acceptor& acceptor, tcp::endpoint target) 166 | { 167 | acceptor.async_accept( 168 | [&acceptor, target](std::error_code error, tcp::socket client) 169 | { 170 | if (!error) 171 | { 172 | std::make_shared( 173 | std::move(client) 174 | )->connect_to_server(target); 175 | } 176 | 177 | listen(acceptor, target); 178 | } 179 | ); 180 | } 181 | 182 | int main(int argc, char* argv[]) 183 | { 184 | try 185 | { 186 | if (argc != 5) 187 | { 188 | std::cerr << "Usage: proxy"; 189 | std::cerr << " "; 190 | std::cerr << " \n"; 191 | return 1; 192 | } 193 | 194 | asio::io_context ctx; 195 | 196 | auto listen_endpoint = 197 | *tcp::resolver(ctx).resolve( 198 | argv[1], 199 | argv[2], 200 | tcp::resolver::passive 201 | ); 202 | 203 | auto target_endpoint = 204 | *tcp::resolver(ctx).resolve( 205 | argv[3], 206 | argv[4] 207 | ); 208 | 209 | tcp::acceptor acceptor(ctx, listen_endpoint); 210 | 211 | listen(acceptor, target_endpoint); 212 | 213 | ctx.run(); 214 | } 215 | catch (std::exception& e) 216 | { 217 | std::cerr << "Exception: " << e.what() << "\n"; 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /episode2/step_2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using asio::buffer; 7 | using asio::ip::tcp; 8 | using std::chrono::steady_clock; 9 | using namespace std::literals::chrono_literals; 10 | 11 | class proxy 12 | : public std::enable_shared_from_this 13 | { 14 | public: 15 | proxy(tcp::socket client) 16 | : client_(std::move(client)), 17 | server_(client_.get_executor()), 18 | watchdog_timer_(client_.get_executor()), 19 | stopped_(false) 20 | { 21 | } 22 | 23 | void connect_to_server(tcp::endpoint target) 24 | { 25 | auto self = shared_from_this(); 26 | server_.async_connect( 27 | target, 28 | [self](std::error_code error) 29 | { 30 | if (!error) 31 | { 32 | self->read_from_client(); 33 | self->read_from_server(); 34 | self->watchdog(); 35 | } 36 | } 37 | ); 38 | } 39 | 40 | private: 41 | void stop() 42 | { 43 | client_.cancel(); 44 | server_.cancel(); 45 | watchdog_timer_.cancel(); 46 | stopped_ = true; 47 | } 48 | 49 | bool is_stopped() const 50 | { 51 | return !client_.is_open() && !server_.is_open(); 52 | } 53 | 54 | void read_from_client() 55 | { 56 | deadline_ = std::max(deadline_, steady_clock::now() + 5s); 57 | 58 | auto self = shared_from_this(); 59 | client_.async_read_some( 60 | buffer(data_from_client_), 61 | [self](std::error_code error, std::size_t n) 62 | { 63 | if (!error && !self->is_stopped()) 64 | { 65 | self->write_to_server(n); 66 | } 67 | else 68 | { 69 | self->stop(); 70 | } 71 | } 72 | ); 73 | } 74 | 75 | void write_to_server(std::size_t n) 76 | { 77 | auto self = shared_from_this(); 78 | async_write( 79 | server_, 80 | buffer(data_from_client_, n), 81 | [self](std::error_code error, std::size_t n) -> std::size_t 82 | { 83 | auto completion_condition = asio::transfer_all(); 84 | if (!self->is_stopped()) 85 | { 86 | return completion_condition(error, n); 87 | } 88 | else 89 | { 90 | return 0; 91 | } 92 | }, 93 | [self](std::error_code error, std::size_t /*n*/) 94 | { 95 | if (!error && !self->is_stopped()) 96 | { 97 | self->read_from_client(); 98 | } 99 | else 100 | { 101 | self->stop(); 102 | } 103 | } 104 | ); 105 | } 106 | 107 | void read_from_server() 108 | { 109 | deadline_ = std::max(deadline_, steady_clock::now() + 5s); 110 | 111 | auto self = shared_from_this(); 112 | server_.async_read_some( 113 | buffer(data_from_server_), 114 | [self](std::error_code error, std::size_t n) 115 | { 116 | if (!error && !self->is_stopped()) 117 | { 118 | self->write_to_client(n); 119 | } 120 | else 121 | { 122 | self->stop(); 123 | } 124 | } 125 | ); 126 | } 127 | 128 | void write_to_client(std::size_t n) 129 | { 130 | auto self = shared_from_this(); 131 | async_write( 132 | client_, 133 | buffer(data_from_server_, n), 134 | [self](std::error_code error, std::size_t n) -> std::size_t 135 | { 136 | auto completion_condition = asio::transfer_all(); 137 | if (!self->is_stopped()) 138 | { 139 | return completion_condition(error, n); 140 | } 141 | else 142 | { 143 | return 0; 144 | } 145 | }, 146 | [self](std::error_code error, std::size_t /*n*/) 147 | { 148 | if (!error && !self->is_stopped()) 149 | { 150 | self->read_from_server(); 151 | } 152 | else 153 | { 154 | self->stop(); 155 | } 156 | } 157 | ); 158 | } 159 | 160 | void watchdog() 161 | { 162 | auto self = shared_from_this(); 163 | watchdog_timer_.expires_at(deadline_); 164 | watchdog_timer_.async_wait( 165 | [self](std::error_code /*error*/) 166 | { 167 | if (!self->is_stopped()) 168 | { 169 | auto now = steady_clock::now(); 170 | if (self->deadline_ > now) 171 | { 172 | self->watchdog(); 173 | } 174 | else 175 | { 176 | self->stop(); 177 | } 178 | } 179 | } 180 | ); 181 | } 182 | 183 | tcp::socket client_; 184 | tcp::socket server_; 185 | std::array data_from_client_; 186 | std::array data_from_server_; 187 | steady_clock::time_point deadline_; 188 | asio::steady_timer watchdog_timer_; 189 | bool stopped_; 190 | }; 191 | 192 | void listen(tcp::acceptor& acceptor, tcp::endpoint target) 193 | { 194 | acceptor.async_accept( 195 | [&acceptor, target](std::error_code error, tcp::socket client) 196 | { 197 | if (!error) 198 | { 199 | std::make_shared( 200 | std::move(client) 201 | )->connect_to_server(target); 202 | } 203 | 204 | listen(acceptor, target); 205 | } 206 | ); 207 | } 208 | 209 | int main(int argc, char* argv[]) 210 | { 211 | try 212 | { 213 | if (argc != 5) 214 | { 215 | std::cerr << "Usage: proxy"; 216 | std::cerr << " "; 217 | std::cerr << " \n"; 218 | return 1; 219 | } 220 | 221 | asio::io_context ctx; 222 | 223 | auto listen_endpoint = 224 | *tcp::resolver(ctx).resolve( 225 | argv[1], 226 | argv[2], 227 | tcp::resolver::passive 228 | ); 229 | 230 | auto target_endpoint = 231 | *tcp::resolver(ctx).resolve( 232 | argv[3], 233 | argv[4] 234 | ); 235 | 236 | tcp::acceptor acceptor(ctx, listen_endpoint); 237 | 238 | listen(acceptor, target_endpoint); 239 | 240 | ctx.run(); 241 | } 242 | catch (std::exception& e) 243 | { 244 | std::cerr << "Exception: " << e.what() << "\n"; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /episode2/step_3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using asio::buffer; 7 | using asio::ip::tcp; 8 | using std::chrono::steady_clock; 9 | using namespace std::literals::chrono_literals; 10 | 11 | class proxy 12 | : public std::enable_shared_from_this 13 | { 14 | public: 15 | proxy(tcp::socket client) 16 | : client_(std::move(client)), 17 | server_(client_.get_executor()), 18 | watchdog_timer_(client_.get_executor()), 19 | heartbeat_timer_(client_.get_executor()) 20 | { 21 | } 22 | 23 | void connect_to_server(tcp::endpoint target) 24 | { 25 | auto self = shared_from_this(); 26 | server_.async_connect( 27 | target, 28 | [self](std::error_code error) 29 | { 30 | if (!error) 31 | { 32 | self->read_from_client(); 33 | self->read_from_server(); 34 | self->watchdog(); 35 | self->heartbeat(); 36 | } 37 | } 38 | ); 39 | } 40 | 41 | private: 42 | void stop() 43 | { 44 | client_.close(); 45 | server_.close(); 46 | watchdog_timer_.cancel(); 47 | heartbeat_timer_.cancel(); 48 | } 49 | 50 | bool is_stopped() const 51 | { 52 | return !client_.is_open() && !server_.is_open(); 53 | } 54 | 55 | void read_from_client() 56 | { 57 | deadline_ = std::max(deadline_, steady_clock::now() + 5s); 58 | 59 | auto self = shared_from_this(); 60 | client_.async_read_some( 61 | buffer(data_from_client_), 62 | [self](std::error_code error, std::size_t n) 63 | { 64 | if (!error) 65 | { 66 | self->write_to_server(n); 67 | } 68 | else 69 | { 70 | self->stop(); 71 | } 72 | } 73 | ); 74 | } 75 | 76 | void write_to_server(std::size_t n) 77 | { 78 | auto self = shared_from_this(); 79 | async_write( 80 | server_, 81 | buffer(data_from_client_, n), 82 | [self](std::error_code error, std::size_t /*n*/) 83 | { 84 | if (!error) 85 | { 86 | self->read_from_client(); 87 | } 88 | else 89 | { 90 | self->stop(); 91 | } 92 | } 93 | ); 94 | } 95 | 96 | void read_from_server() 97 | { 98 | auto self = shared_from_this(); 99 | server_.async_read_some( 100 | buffer(data_from_server_), 101 | asio::bind_cancellation_slot( 102 | heartbeat_signal_.slot(), 103 | [self](std::error_code error, std::size_t n) 104 | { 105 | if (!error) 106 | { 107 | self->num_heartbeats_ = 0; 108 | self->write_to_client(n); 109 | } 110 | else if (error == asio::error::operation_aborted) 111 | { 112 | ++self->num_heartbeats_; 113 | self->write_heartbeat_to_client(); 114 | } 115 | else 116 | { 117 | self->stop(); 118 | } 119 | } 120 | ) 121 | ); 122 | } 123 | 124 | void write_to_client(std::size_t n) 125 | { 126 | auto self = shared_from_this(); 127 | async_write( 128 | client_, 129 | buffer(data_from_server_, n), 130 | [self](std::error_code error, std::size_t /*n*/) 131 | { 132 | if (!error) 133 | { 134 | self->read_from_server(); 135 | } 136 | else 137 | { 138 | self->stop(); 139 | } 140 | } 141 | ); 142 | } 143 | 144 | void write_heartbeat_to_client() 145 | { 146 | std::size_t n = asio::buffer_copy( 147 | buffer(data_from_server_), 148 | std::array{ 149 | buffer("\r\n") 152 | } 153 | ); 154 | 155 | write_to_client(n); 156 | } 157 | 158 | void watchdog() 159 | { 160 | auto self = shared_from_this(); 161 | watchdog_timer_.expires_at(deadline_); 162 | watchdog_timer_.async_wait( 163 | [self](std::error_code /*error*/) 164 | { 165 | if (!self->is_stopped()) 166 | { 167 | auto now = steady_clock::now(); 168 | if (self->deadline_ > now) 169 | { 170 | self->watchdog(); 171 | } 172 | else 173 | { 174 | self->stop(); 175 | } 176 | } 177 | } 178 | ); 179 | } 180 | 181 | void heartbeat() 182 | { 183 | auto self = shared_from_this(); 184 | heartbeat_timer_.expires_after(1s); 185 | heartbeat_timer_.async_wait( 186 | [self](std::error_code /*error*/) 187 | { 188 | if (!self->is_stopped()) 189 | { 190 | self->heartbeat_signal_.emit(asio::cancellation_type::total); 191 | self->heartbeat(); 192 | } 193 | } 194 | ); 195 | } 196 | 197 | tcp::socket client_; 198 | tcp::socket server_; 199 | std::array data_from_client_; 200 | std::array data_from_server_; 201 | steady_clock::time_point deadline_; 202 | asio::steady_timer watchdog_timer_; 203 | asio::steady_timer heartbeat_timer_; 204 | asio::cancellation_signal heartbeat_signal_; 205 | std::size_t num_heartbeats_ = 0; 206 | }; 207 | 208 | void listen(tcp::acceptor& acceptor, tcp::endpoint target) 209 | { 210 | acceptor.async_accept( 211 | [&acceptor, target](std::error_code error, tcp::socket client) 212 | { 213 | if (!error) 214 | { 215 | std::make_shared( 216 | std::move(client) 217 | )->connect_to_server(target); 218 | } 219 | 220 | listen(acceptor, target); 221 | } 222 | ); 223 | } 224 | 225 | int main(int argc, char* argv[]) 226 | { 227 | try 228 | { 229 | if (argc != 5) 230 | { 231 | std::cerr << "Usage: proxy"; 232 | std::cerr << " "; 233 | std::cerr << " \n"; 234 | return 1; 235 | } 236 | 237 | asio::io_context ctx; 238 | 239 | auto listen_endpoint = 240 | *tcp::resolver(ctx).resolve( 241 | argv[1], 242 | argv[2], 243 | tcp::resolver::passive 244 | ); 245 | 246 | auto target_endpoint = 247 | *tcp::resolver(ctx).resolve( 248 | argv[3], 249 | argv[4] 250 | ); 251 | 252 | tcp::acceptor acceptor(ctx, listen_endpoint); 253 | 254 | listen(acceptor, target_endpoint); 255 | 256 | ctx.run(); 257 | } 258 | catch (std::exception& e) 259 | { 260 | std::cerr << "Exception: " << e.what() << "\n"; 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /episode2/step_4.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using asio::buffer; 8 | using asio::ip::tcp; 9 | using std::chrono::steady_clock; 10 | using namespace std::literals::chrono_literals; 11 | 12 | class proxy 13 | : public std::enable_shared_from_this 14 | { 15 | public: 16 | proxy(tcp::socket client) 17 | : client_(std::move(client)), 18 | server_(client_.get_executor()), 19 | watchdog_timer_(client_.get_executor()), 20 | heartbeat_timer_(client_.get_executor()) 21 | { 22 | } 23 | 24 | void connect_to_server(tcp::endpoint target) 25 | { 26 | auto self = shared_from_this(); 27 | server_.async_connect( 28 | target, 29 | [self](std::error_code error) 30 | { 31 | if (!error) 32 | { 33 | self->read_from_client(); 34 | self->read_from_server(); 35 | self->watchdog(); 36 | } 37 | } 38 | ); 39 | } 40 | 41 | private: 42 | void stop() 43 | { 44 | client_.close(); 45 | server_.close(); 46 | watchdog_timer_.cancel(); 47 | } 48 | 49 | bool is_stopped() const 50 | { 51 | return !client_.is_open() && !server_.is_open(); 52 | } 53 | 54 | void read_from_client() 55 | { 56 | deadline_ = std::max(deadline_, steady_clock::now() + 5s); 57 | 58 | auto self = shared_from_this(); 59 | client_.async_read_some( 60 | buffer(data_from_client_), 61 | [self](std::error_code error, std::size_t n) 62 | { 63 | if (!error) 64 | { 65 | self->write_to_server(n); 66 | } 67 | else 68 | { 69 | self->stop(); 70 | } 71 | } 72 | ); 73 | } 74 | 75 | void write_to_server(std::size_t n) 76 | { 77 | auto self = shared_from_this(); 78 | async_write( 79 | server_, 80 | buffer(data_from_client_, n), 81 | [self](std::error_code error, std::size_t /*n*/) 82 | { 83 | if (!error) 84 | { 85 | self->read_from_client(); 86 | } 87 | else 88 | { 89 | self->stop(); 90 | } 91 | } 92 | ); 93 | } 94 | 95 | void read_from_server() 96 | { 97 | heartbeat_timer_.expires_after(1s); 98 | 99 | auto self = shared_from_this(); 100 | asio::experimental::make_parallel_group( 101 | [this](auto token) 102 | { 103 | return server_.async_read_some( 104 | buffer(data_from_server_), 105 | token 106 | ); 107 | }, 108 | [this](auto token) 109 | { 110 | return heartbeat_timer_.async_wait(token); 111 | } 112 | ).async_wait( 113 | asio::experimental::wait_for_one(), 114 | [self]( 115 | std::array order, 116 | std::error_code read_error, std::size_t n, 117 | std::error_code /*timer_error*/) 118 | { 119 | switch (order[0]) 120 | { 121 | case 0: // read 122 | if (!read_error) 123 | { 124 | self->num_heartbeats_ = 0; 125 | self->write_to_client(n); 126 | } 127 | else 128 | { 129 | self->stop(); 130 | } 131 | break; 132 | case 1: // timer 133 | ++self->num_heartbeats_; 134 | self->write_heartbeat_to_client(); 135 | break; 136 | } 137 | } 138 | ); 139 | } 140 | 141 | void write_to_client(std::size_t n) 142 | { 143 | auto self = shared_from_this(); 144 | async_write( 145 | client_, 146 | buffer(data_from_server_, n), 147 | [self](std::error_code error, std::size_t /*n*/) 148 | { 149 | if (!error) 150 | { 151 | self->read_from_server(); 152 | } 153 | else 154 | { 155 | self->stop(); 156 | } 157 | } 158 | ); 159 | } 160 | 161 | void write_heartbeat_to_client() 162 | { 163 | std::size_t n = asio::buffer_copy( 164 | buffer(data_from_server_), 165 | std::array{ 166 | buffer("\r\n") 169 | } 170 | ); 171 | 172 | write_to_client(n); 173 | } 174 | 175 | void watchdog() 176 | { 177 | auto self = shared_from_this(); 178 | watchdog_timer_.expires_at(deadline_); 179 | watchdog_timer_.async_wait( 180 | [self](std::error_code /*error*/) 181 | { 182 | if (!self->is_stopped()) 183 | { 184 | auto now = steady_clock::now(); 185 | if (self->deadline_ > now) 186 | { 187 | self->watchdog(); 188 | } 189 | else 190 | { 191 | self->stop(); 192 | } 193 | } 194 | } 195 | ); 196 | } 197 | 198 | tcp::socket client_; 199 | tcp::socket server_; 200 | std::array data_from_client_; 201 | std::array data_from_server_; 202 | steady_clock::time_point deadline_; 203 | asio::steady_timer watchdog_timer_; 204 | asio::steady_timer heartbeat_timer_; 205 | std::size_t num_heartbeats_ = 0; 206 | }; 207 | 208 | void listen(tcp::acceptor& acceptor, tcp::endpoint target) 209 | { 210 | acceptor.async_accept( 211 | [&acceptor, target](std::error_code error, tcp::socket client) 212 | { 213 | if (!error) 214 | { 215 | std::make_shared( 216 | std::move(client) 217 | )->connect_to_server(target); 218 | } 219 | 220 | listen(acceptor, target); 221 | } 222 | ); 223 | } 224 | 225 | int main(int argc, char* argv[]) 226 | { 227 | try 228 | { 229 | if (argc != 5) 230 | { 231 | std::cerr << "Usage: proxy"; 232 | std::cerr << " "; 233 | std::cerr << " \n"; 234 | return 1; 235 | } 236 | 237 | asio::io_context ctx; 238 | 239 | auto listen_endpoint = 240 | *tcp::resolver(ctx).resolve( 241 | argv[1], 242 | argv[2], 243 | tcp::resolver::passive 244 | ); 245 | 246 | auto target_endpoint = 247 | *tcp::resolver(ctx).resolve( 248 | argv[3], 249 | argv[4] 250 | ); 251 | 252 | tcp::acceptor acceptor(ctx, listen_endpoint); 253 | 254 | listen(acceptor, target_endpoint); 255 | 256 | ctx.run(); 257 | } 258 | catch (std::exception& e) 259 | { 260 | std::cerr << "Exception: " << e.what() << "\n"; 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /episode2/step_5.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using asio::awaitable; 7 | using asio::buffer; 8 | using asio::co_spawn; 9 | using asio::detached; 10 | using asio::use_awaitable; 11 | namespace this_coro = asio::this_coro; 12 | using namespace asio::experimental::awaitable_operators; 13 | using std::chrono::steady_clock; 14 | using namespace std::literals::chrono_literals; 15 | 16 | template 17 | auto async_wait_for_signal( 18 | asio::signal_set& sigset, 19 | CompletionToken&& token) 20 | { 21 | return asio::async_initiate( 23 | [&sigset](auto handler) 24 | { 25 | auto executor = 26 | asio::get_associated_executor( 27 | handler, 28 | sigset.get_executor() 29 | ); 30 | 31 | auto intermediate_handler = 32 | [handler = std::move(handler)]( 33 | std::error_code error, 34 | int signo 35 | ) mutable 36 | { 37 | std::string signame; 38 | switch (signo) 39 | { 40 | case SIGABRT: signame = "SIGABRT"; break; 41 | case SIGFPE: signame = "SIGFPE"; break; 42 | case SIGILL: signame = "SIGILL"; break; 43 | case SIGINT: signame = "SIGINT"; break; 44 | case SIGSEGV: signame = "SIGSEGV"; break; 45 | case SIGTERM: signame = "SIGTERM"; break; 46 | default: signame = ""; break; 47 | } 48 | 49 | std::move(handler)(error, signame); 50 | }; 51 | 52 | sigset.async_wait( 53 | asio::bind_executor( 54 | executor, 55 | std::move(intermediate_handler) 56 | ) 57 | ); 58 | }, 59 | token 60 | ); 61 | } 62 | 63 | awaitable timed_wait_for_signal() 64 | { 65 | asio::signal_set sigset(co_await this_coro::executor, SIGINT, SIGTERM); 66 | asio::steady_timer timer(co_await this_coro::executor, 5s); 67 | 68 | auto result = co_await ( 69 | async_wait_for_signal(sigset, use_awaitable) || 70 | timer.async_wait(use_awaitable) 71 | ); 72 | 73 | switch (result.index()) 74 | { 75 | case 0: 76 | std::cout << "signal finished first: " << std::get<0>(result) << "\n"; 77 | break; 78 | case 1: 79 | std::cout << "timer finished first\n"; 80 | break; 81 | } 82 | } 83 | 84 | int main() 85 | { 86 | asio::io_context ctx; 87 | co_spawn(ctx, timed_wait_for_signal(), detached); 88 | ctx.run(); 89 | } 90 | -------------------------------------------------------------------------------- /episode2/step_6.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using asio::awaitable; 7 | using asio::buffer; 8 | using asio::co_spawn; 9 | using asio::detached; 10 | using asio::use_awaitable; 11 | namespace this_coro = asio::this_coro; 12 | using namespace asio::experimental::awaitable_operators; 13 | using std::chrono::steady_clock; 14 | using namespace std::literals::chrono_literals; 15 | 16 | template 17 | auto async_wait_for_signal( 18 | asio::signal_set& sigset, 19 | CompletionToken&& token) 20 | { 21 | return asio::async_initiate( 23 | [&sigset](auto handler) 24 | { 25 | auto cancellation_slot = 26 | asio::get_associated_cancellation_slot( 27 | handler, 28 | asio::cancellation_slot() 29 | ); 30 | 31 | if (cancellation_slot.is_connected()) 32 | { 33 | cancellation_slot.assign( 34 | [&sigset](asio::cancellation_type /*type*/) 35 | { 36 | sigset.cancel(); 37 | } 38 | ); 39 | } 40 | 41 | auto executor = 42 | asio::get_associated_executor( 43 | handler, 44 | sigset.get_executor() 45 | ); 46 | 47 | auto intermediate_handler = 48 | [handler = std::move(handler)]( 49 | std::error_code error, 50 | int signo 51 | ) mutable 52 | { 53 | std::string signame; 54 | switch (signo) 55 | { 56 | case SIGABRT: signame = "SIGABRT"; break; 57 | case SIGFPE: signame = "SIGFPE"; break; 58 | case SIGILL: signame = "SIGILL"; break; 59 | case SIGINT: signame = "SIGINT"; break; 60 | case SIGSEGV: signame = "SIGSEGV"; break; 61 | case SIGTERM: signame = "SIGTERM"; break; 62 | default: signame = ""; break; 63 | } 64 | 65 | std::move(handler)(error, signame); 66 | }; 67 | 68 | sigset.async_wait( 69 | asio::bind_executor( 70 | executor, 71 | std::move(intermediate_handler) 72 | ) 73 | ); 74 | }, 75 | token 76 | ); 77 | } 78 | 79 | awaitable timed_wait_for_signal() 80 | { 81 | asio::signal_set sigset(co_await this_coro::executor, SIGINT, SIGTERM); 82 | asio::steady_timer timer(co_await this_coro::executor, 5s); 83 | 84 | auto result = co_await ( 85 | async_wait_for_signal(sigset, use_awaitable) || 86 | timer.async_wait(use_awaitable) 87 | ); 88 | 89 | switch (result.index()) 90 | { 91 | case 0: 92 | std::cout << "signal finished first: " << std::get<0>(result) << "\n"; 93 | break; 94 | case 1: 95 | std::cout << "timer finished first\n"; 96 | break; 97 | } 98 | } 99 | 100 | int main() 101 | { 102 | asio::io_context ctx; 103 | co_spawn(ctx, timed_wait_for_signal(), detached); 104 | ctx.run(); 105 | } 106 | -------------------------------------------------------------------------------- /episode2/step_7.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using asio::awaitable; 8 | using asio::co_spawn; 9 | using asio::detached; 10 | using asio::dynamic_buffer; 11 | using asio::ip::tcp; 12 | using asio::use_awaitable; 13 | namespace this_coro = asio::this_coro; 14 | using namespace asio::experimental::awaitable_operators; 15 | using std::chrono::steady_clock; 16 | using namespace std::literals::chrono_literals; 17 | 18 | constexpr auto use_nothrow_awaitable = asio::experimental::as_tuple(asio::use_awaitable); 19 | 20 | template 21 | class message_reader 22 | { 23 | public: 24 | message_reader(Stream& stream) 25 | : stream_(stream) 26 | { 27 | } 28 | 29 | awaitable read_message() 30 | { 31 | auto [e, n] = 32 | co_await asio::async_read_until( 33 | stream_, 34 | dynamic_buffer(message_buffer_), 35 | '|', 36 | use_nothrow_awaitable 37 | ); 38 | 39 | if (e) 40 | { 41 | co_return std::string(); 42 | } 43 | 44 | std::string message(message_buffer_.substr(0, n)); 45 | message_buffer_.erase(0, n); 46 | co_return message; 47 | } 48 | 49 | private: 50 | Stream& stream_; 51 | std::string message_buffer_; 52 | }; 53 | 54 | awaitable session(tcp::socket client) 55 | { 56 | message_reader reader(client); 57 | 58 | for (;;) 59 | { 60 | std::string message = co_await reader.read_message(); 61 | 62 | if (!message.empty()) 63 | { 64 | std::cout << "received: " << message << "\n"; 65 | } 66 | else 67 | { 68 | co_return; 69 | } 70 | } 71 | } 72 | 73 | awaitable listen(tcp::acceptor& acceptor) 74 | { 75 | for (;;) 76 | { 77 | auto [e, client] = co_await acceptor.async_accept(use_nothrow_awaitable); 78 | if (e) 79 | break; 80 | 81 | auto ex = client.get_executor(); 82 | co_spawn(ex, session(std::move(client)), detached); 83 | } 84 | } 85 | 86 | int main(int argc, char* argv[]) 87 | { 88 | try 89 | { 90 | if (argc != 3) 91 | { 92 | std::cerr << "Usage: message_server"; 93 | std::cerr << " \n"; 94 | return 1; 95 | } 96 | 97 | asio::io_context ctx; 98 | 99 | auto listen_endpoint = 100 | *tcp::resolver(ctx).resolve( 101 | argv[1], 102 | argv[2], 103 | tcp::resolver::passive 104 | ); 105 | 106 | tcp::acceptor acceptor(ctx, listen_endpoint); 107 | 108 | co_spawn(ctx, listen(acceptor), detached); 109 | 110 | ctx.run(); 111 | } 112 | catch (std::exception& e) 113 | { 114 | std::cerr << "Exception: " << e.what() << "\n"; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /episode2/step_8.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using asio::awaitable; 8 | using asio::cancellation_type; 9 | using asio::co_spawn; 10 | using asio::detached; 11 | using asio::dynamic_buffer; 12 | using asio::ip::tcp; 13 | using asio::use_awaitable; 14 | namespace this_coro = asio::this_coro; 15 | using namespace asio::experimental::awaitable_operators; 16 | using std::chrono::steady_clock; 17 | using namespace std::literals::chrono_literals; 18 | 19 | constexpr auto use_nothrow_awaitable = asio::experimental::as_tuple(asio::use_awaitable); 20 | 21 | template 22 | class message_reader 23 | { 24 | public: 25 | message_reader(Stream& stream) 26 | : stream_(stream) 27 | { 28 | } 29 | 30 | awaitable read_message() 31 | { 32 | co_await this_coro::reset_cancellation_state( 33 | [](cancellation_type requested) 34 | { 35 | if ((requested & cancellation_type::total) != cancellation_type::none) 36 | { 37 | return cancellation_type::partial; 38 | } 39 | else 40 | { 41 | return requested; 42 | } 43 | } 44 | ); 45 | 46 | auto [e, n] = 47 | co_await asio::async_read_until( 48 | stream_, 49 | dynamic_buffer(message_buffer_), 50 | '|', 51 | use_nothrow_awaitable 52 | ); 53 | 54 | if ((co_await this_coro::cancellation_state).cancelled() != cancellation_type::none) 55 | { 56 | co_return std::string(); 57 | } 58 | 59 | co_await this_coro::reset_cancellation_state(); // Reset to default, which is terminal only. 60 | 61 | if (e) 62 | { 63 | co_return std::string(); 64 | } 65 | 66 | std::string message(message_buffer_.substr(0, n)); 67 | message_buffer_.erase(0, n); 68 | co_return message; 69 | } 70 | 71 | private: 72 | Stream& stream_; 73 | std::string message_buffer_; 74 | }; 75 | 76 | awaitable timeout(steady_clock::duration duration) 77 | { 78 | asio::steady_timer timer(co_await this_coro::executor); 79 | timer.expires_after(duration); 80 | co_await timer.async_wait(use_nothrow_awaitable); 81 | } 82 | 83 | awaitable session(tcp::socket client) 84 | { 85 | message_reader reader(client); 86 | 87 | for (;;) 88 | { 89 | auto result = co_await ( 90 | reader.read_message() || 91 | timeout(5s) 92 | ); 93 | 94 | switch (result.index()) 95 | { 96 | case 0: 97 | if (!std::get<0>(result).empty()) 98 | { 99 | std::cout << "received: " << std::get<0>(result) << "\n"; 100 | } 101 | else 102 | { 103 | co_return; 104 | } 105 | break; 106 | case 1: 107 | std::cout << "timed out\n"; 108 | break; 109 | } 110 | } 111 | } 112 | 113 | awaitable listen(tcp::acceptor& acceptor) 114 | { 115 | for (;;) 116 | { 117 | auto [e, client] = co_await acceptor.async_accept(use_nothrow_awaitable); 118 | if (e) 119 | break; 120 | 121 | auto ex = client.get_executor(); 122 | co_spawn(ex, session(std::move(client)), detached); 123 | } 124 | } 125 | 126 | int main(int argc, char* argv[]) 127 | { 128 | try 129 | { 130 | if (argc != 3) 131 | { 132 | std::cerr << "Usage: message_server"; 133 | std::cerr << " \n"; 134 | return 1; 135 | } 136 | 137 | asio::io_context ctx; 138 | 139 | auto listen_endpoint = 140 | *tcp::resolver(ctx).resolve( 141 | argv[1], 142 | argv[2], 143 | tcp::resolver::passive 144 | ); 145 | 146 | tcp::acceptor acceptor(ctx, listen_endpoint); 147 | 148 | co_spawn(ctx, listen(acceptor), detached); 149 | 150 | ctx.run(); 151 | } 152 | catch (std::exception& e) 153 | { 154 | std::cerr << "Exception: " << e.what() << "\n"; 155 | } 156 | } 157 | --------------------------------------------------------------------------------