Skip to content

Commit ebc7396

Browse files
authored
Example of coroutines with boost (#474)
* Example of coroutines with boost Thanks for your videos Just wanted to share my approach of using boost asio with coroutines Coroutines are stackfull and also from boost (there are also stackless from cpp-20 - the channel "Talking Async" on youtube shows examples of use) No idea how it affects performance, but it is more readable (at least for me) and does not use explicit shared pointers(which do worsen performance) * adding threads vector of threads calling ioc.run()
1 parent 175d0c0 commit ebc7396

File tree

1 file changed

+113
-20
lines changed

1 file changed

+113
-20
lines changed

lessons/263/app-cpp/src/main.cpp

Lines changed: 113 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,135 @@
1-
#include <algorithm>
21
#include <boost/beast/core.hpp>
32
#include <boost/beast/http.hpp>
4-
#include <cstdlib>
5-
#include <iostream>
6-
#include <memory>
7-
#include <thread>
8-
#include <vector>
3+
#include <boost/asio/spawn.hpp> // need to link with boost_coroutine
94

10-
#include "listener.hpp"
11-
#include "spdlog/spdlog.h"
5+
#include <boost/json.hpp>
6+
#include <boost/json/src.hpp>
127

8+
#include <thread>
139

1410
namespace beast = boost::beast;
1511
namespace http = beast::http;
1612
namespace net = boost::asio;
17-
using tcp = asio::ip::tcp;
13+
using tcp = net::ip::tcp;
1814

15+
namespace json = boost::json;
16+
17+
void fail(beast::error_code const &ec, const char* const what) {
18+
fprintf(stderr, "error: %s, %s\n", what, ec.message().c_str());
19+
}
1920

2021
int main() {
21-
auto const address = net::ip::make_address("0.0.0.0");
22-
constexpr auto port = static_cast<unsigned short>(8080);
23-
// 2 Threads, same as Rust
24-
constexpr auto threads = 2;
22+
beast::error_code ec;
23+
net::io_context ioc;
24+
25+
tcp::endpoint endpoint(tcp::v4(), 8080);
26+
27+
tcp::acceptor acceptor(ioc);
28+
acceptor.open(endpoint.protocol(), ec);
29+
if (ec) {
30+
fail(ec, "open");
31+
return 1;
32+
}
33+
34+
acceptor.set_option(net::socket_base::reuse_address(true), ec);
35+
if (ec) {
36+
fail(ec, "set_option");
37+
return 1;
38+
}
39+
40+
acceptor.bind(endpoint, ec);
41+
if (ec) {
42+
fail(ec, "bind");
43+
return 1;
44+
}
45+
46+
acceptor.listen(net::socket_base::max_listen_connections, ec);
47+
if (ec) {
48+
fail(ec, "listen");
49+
return 1;
50+
}
2551

26-
// The io_context is required for all I/O
27-
net::io_context ioc{threads};
52+
net::spawn(acceptor.get_executor(), [&](net::yield_context const &yield) {
53+
while (acceptor.is_open()) {
54+
tcp::socket socket(ioc);
55+
acceptor.async_accept(socket, yield[ec]);
56+
if (ec) {
57+
fail(ec, "accept");
58+
break;
59+
}
2860

29-
// Create and launch a listening port
30-
std::make_shared<listener>(ioc, tcp::endpoint{address, port})->run();
61+
net::spawn(acceptor.get_executor(), [s = std::move(socket)](net::yield_context const &yield) mutable {
62+
beast::tcp_stream stream(std::move(s));
63+
64+
beast::error_code ec;
65+
beast::flat_buffer buffer;
66+
while (true) { // TODO not sure how keep_alive should work
67+
http::request_parser<http::string_body> requestParser;
68+
http::async_read(stream, buffer, requestParser, yield[ec]);
69+
if (ec) {
70+
fail(ec, "read");
71+
return;
72+
}
73+
74+
auto const &request = requestParser.get();
75+
76+
http::response<http::dynamic_body> response;
77+
response.version(request.version());
78+
response.keep_alive(request.keep_alive());
79+
80+
if (request.method() == http::verb::get && request.target() == "/api/v3/ticker/bookTicker") {
81+
response.result(http::status::ok);
82+
response.set(http::field::content_type, "application/json");
83+
// TODO without the vector for simplicity of that example
84+
beast::ostream(response.body()) << json::serialize(json::array{
85+
json::object{
86+
{"symbol", "LTCBTC"},
87+
{"bidPrice", "4.00000000"},
88+
{"bidQty", "431.00000000"},
89+
{"askPrice", "4.00000200"},
90+
{"askQty", "9.00000000"}
91+
},
92+
json::object{
93+
{"symbol", "ETHBTC"},
94+
{"bidPrice", "0.07946700"},
95+
{"bidQty", "49.00000000"},
96+
{"askPrice", "100000.00000000"},
97+
{"askQty", "1000.00000000"}
98+
},
99+
});
100+
} else {
101+
response.result(http::status::not_found);
102+
response.set(http::field::content_type, "text/plain");
103+
beast::ostream(response.body()) << "Not Found";
104+
}
105+
106+
response.prepare_payload();
107+
108+
http::async_write(stream, response, yield[ec]);
109+
if (ec) {
110+
fail(ec, "write");
111+
return;
112+
}
113+
114+
// TODO not sure how keep_alive should work
115+
// printf("%d\n", request.keep_alive()); // always return true (through curl)
116+
// from docs: The value depends on the *version* in the message
117+
auto const con_header = request.find("Connection");
118+
if (con_header == request.end() || con_header->value() != "keep-alive")
119+
break;
120+
}
121+
});
122+
}
123+
});
124+
125+
constexpr auto threads = 2;
31126

32-
// Run the I/O service on the requested number of threads
33127
std::vector<std::thread> v;
34128
v.reserve(threads - 1);
35129
for (auto i = threads - 1; i > 0; --i)
36130
v.emplace_back([&ioc] { ioc.run(); });
37131

38-
spdlog::info("Starting a server on port: {}", port);
39132
ioc.run();
40133

41-
return EXIT_SUCCESS;
134+
return 0;
42135
}

0 commit comments

Comments
 (0)