cpp: c++ network silice
Created on Oct 9, 2024
c++ boost::asio, boost::beast networking silice
The tcp resolver type.
resolver.async_resolve(...
Asynchronous perform forward or reverse resolution of a query or an endpoint to entries.
Commonly used on client side.
The tcp acceptor type.
acceptor.async_accept(...
Asynchronous start an accept.
Commonly used on server side.
A TCP/IP stream socket with timeouts and a polymorphic executor.
Commonly used on both server side and client side.
__tcp_stream.async_connect(ep_or_ep_sequence_or_ep_iterators, ...
Connect the stream to the specified endpoint asynchronously.
Commonly used on client side.
http::async_write(...
Write a complete message to a stream asynchronously.
Commonly used on both server side and client side.
http::async_read(...
Read a complete message from a stream asynchronously.
Commonly used on both server side and client side.
Server side code can be divided into two stages:
c++ example
#include <boost/asio.hpp> #include <boost/beast.hpp> #include <iostream> using std::string_literals::operator""s; namespace asio = boost::asio; namespace beast = boost::beast; namespace http = beast::http; using asio::ip::tcp; namespace space { class http_session: virtual public std::enable_shared_from_this< space::http_session > { protected: beast::tcp_stream __tcp_stream; http::request<http::string_body> __request; http::response<http::string_body> __response; beast::flat_buffer __buffer; public: virtual ~http_session() { __tcp_stream.close(); std::clog << "Session is ended.\n"; } public: http_session(tcp::socket && socket__): __tcp_stream{std::move(socket__)} { std::clog << "Session is started.\n"; } public: void run() { std::cout << "Session is running ...\n"; this->read_write(); } protected: void read_write() { this->read(); } void read() { http::async_read( __tcp_stream, __buffer, __request, [self=this->shared_from_this()] (std::error_code ec, unsigned long bytes) { if (ec) throw std::system_error{ec, "http::asyn_read error."}; std::clog << "Request read: " << bytes << " bytes.\n"; self->write(); } ); } void write() { __response.body() = "<html><body>Hello, c++ http server!</body></html>"; __response.keep_alive(__request.keep_alive()); http::async_write( __tcp_stream, __response, [self=this->shared_from_this()] (std::error_code ec, unsigned long bytes) { if (ec) throw std::system_error{ec, "http::async write error."}; std::clog << "Response written: " << bytes << " bytes.\n"; } ); } }; class http_server: virtual public std::enable_shared_from_this< space::http_server > { protected: asio::io_context __io_ctx; const std::string __address; const unsigned short __port; tcp::acceptor __acceptor; http::request<http::string_body> __request; http::response<http::string_body> __response; public: virtual ~http_server() { } public: http_server( const std::string_view address__, const unsigned short port__ ): __io_ctx{}, __address{address__}, __port{port__}, __acceptor{ __io_ctx, tcp::endpoint{ asio::ip::make_address(__address), __port } } { } public: void run() { this->start(); __io_ctx.run(); } protected: void start() { this->accept(); } void accept() { __acceptor.async_accept( [self=this->shared_from_this()] (std::error_code ec, tcp::socket socket) { if (ec) throw std::system_error{ec, "Accept Error."}; std::clog << "Accept OK.\n"; std::make_shared<space::http_session>(std::move(socket))->run(); self->accept(); } ); } }; } // namespace space int main(int argc, char * argv[]) try { if (argc != 3) throw std::runtime_error{""s + argv[0] + " <bind address> <bind port>"}; std::make_shared<space::http_server>(argv[1], std::stoi(argv[2]))->run(); } catch (const std::exception & e) { std::cerr << "ERROR:\n" << e.what() << std::endl; }
Client side code can be divided into some stages:
c++ example
#include <boost/beast.hpp> #include <boost/asio.hpp> #include <iostream> namespace asio = boost::asio; namespace beast = boost::beast; namespace http = beast::http; using asio::ip::tcp; using std::string_literals::operator""s; namespace space { class http_client_session: virtual public std::enable_shared_from_this< space::http_client_session > { protected: asio::io_context __io_ctx; const std::string __target; const std::string __protocol; tcp::resolver __resolver; beast::tcp_stream __tcp_stream; http::request<http::string_body> __request; http::response<http::string_body> __response; beast::flat_buffer __buffer; public: virtual ~http_client_session() { } public: http_client_session(const std::string_view target__, const std::string_view protocol__): __io_ctx{}, __target{target__}, __protocol{protocol__}, __resolver{__io_ctx}, __tcp_stream{__io_ctx} { } public: void run() { this->start(); __io_ctx.run(); } protected: void start() { this->resolve(); } void resolve() { __resolver.async_resolve( __target, __protocol, [self=this->shared_from_this()] ( std::error_code ec, tcp::resolver::results_type results ) { if (ec) throw std::system_error{ec, "__resolver.async_resolve Error."}; std::clog << "Resolve OK.\n"; self->connect(std::move(results)); } ); } void connect(tcp::resolver::results_type && results__) { __tcp_stream.expires_after(std::chrono::seconds(3)); __tcp_stream.async_connect( results__, [self=this->shared_from_this()] ( std::error_code ec, tcp::endpoint ep ) { if (ec) throw std::system_error{ec, "__tcp_stream.async_connect error."}; std::clog << "Connect OK.\n"; self->write_read(); } ); } protected: void write_read() { this->write(); } void write() { __request.method(http::verb::get); __request.set(http::field::host, __target); __request.target("/"); __tcp_stream.expires_after(std::chrono::seconds(3)); http::async_write( __tcp_stream, __request, [self=this->shared_from_this()] ( std::error_code ec, unsigned long bytes ) { if (ec) throw std::system_error{ec, "http::async_write error."}; self->read(); } ); } void read() { __tcp_stream.expires_after(std::chrono::seconds(3)); http::async_read( __tcp_stream, __buffer, __response, [self=this->shared_from_this()] ( std::error_code ec, unsigned long bytes ) { if (ec) throw std::system_error{ec, "http::async_read error."}; std::cout << self->__response.body() << std::endl; } ); } }; // class http_client_session } // namespace space int main(int argc, char * argv[]) try { if (argc != 3) throw std::runtime_error{""s + argv[0] + " <remote host> <protocol (a port number)>"}; std::make_shared<space::http_client_session>(argv[1], argv[2])->run(); } catch (const std::exception & e) { std::cerr << "ERROR:\n" << e.what() << std::endl; }
c++ coroutines
boost::asio::co_spawn
Spawn a new coroutine-based thread of execution.
asio::co_spawn(x__, awaitable__, token__ = DEFAULT, constraint__ = 0);
The return type of a coroutine
A completion token object that reprensents the currently executing coroutine.
co_await asio::this_coro::executor;
As the name described, asio::this_coro::executor is an awaitable object that returns the executor of the current coroutine.
co_await
asio::this_coro::executor;
gets the executor that the awaitable object returns.
Get it step by step
First of all, all coroutines can not be used directly in main:
A coroutine execution thread must be spawned as the start point of all other coroutines, which is done by boost::asio::co_spawn.
main calls asio::co_spawn, asio::co_spawn calls coroutines.
boost::asio::co_spawn(io_context, yyzz, asio::detached); io_context.run();
boost::asio::co_spawn(io_context, yyzz(), asio::detached); io_context.run();
Both work.
Both yyzz and yyzz() are not actual value. Their relationships are:
lambda returns awaitable object,
awaitable object returns actual value .
The return type of a coroutine is boost::asio::awaitable, and can not be auto-deduced.
EX.:
class server { public: asio::awaitable<void> run() { co_return; } };
completion-token: boost::asio::use_awaitable
In most cases, boost::asio::use_awaitable takes the handler place, as a completion token. And then the calling returns an awaitable object.
EX.:
auto awaitable_object = http::async_write( tcp_stream, request, boost::asio::use_awaitable ); co_await awaitable_object;
In most cases, we do not want the awaitable object, we want the actual object, then:
unsigned long bytes = co_await http::async_write(tcp_stream, request, asio::use_awaitable);
Further on, we don't need both, we only need the write action:
co_await http::async_write(tcp_stream, request, asio::use_awaitable);
The boost::asio::use_awaitable takes pace of the async handler, the async handler:
http::async_write( tcp_stream, request, [] (std::error_code ec, unsigned long byte) // This lambda is the so-called handler. { if (! ec) // if no error ... } );
asio::this_coro::executor
As the name described, get the executor from this-coro, it returns an awaitable object, we need to get the executor from the awaitable object further on:
auto executor = co_await asio::this_coro::executor;
But be careful, co_await can not be used as the constructor parameter.
Server side code can be divided into two stages:
Server side c++ example code.
#include <boost/asio.hpp> #include <boost/asio/detached.hpp> #include <iostream> #include <memory> #include <boost/beast.hpp> using std::string_literals::operator""s; namespace space { class http_session: virtual public std::enable_shared_from_this<space::http_session> { protected: boost::beast::tcp_stream __stream; protected: boost::beast::http::request<boost::beast::http::string_body> __request; boost::beast::http::response<boost::beast::http::string_body> __response; boost::beast::flat_buffer __buffer; public: virtual ~http_session() { __stream.close(); std::cout << "Session is ended.\n"; } public: http_session(boost::asio::ip::tcp::socket && socket__): __stream{std::move(socket__)} { std::cout << "Session is created.\n"; } public: boost::asio::awaitable<void> run() { co_await this->read_write(); } boost::asio::awaitable<void> read_write() { co_await boost::beast::http::async_read(__stream, __buffer, __request, boost::asio::use_awaitable); __response.body() = R"( <html> <head> <title>Hello, c++!</title> </head> <body> <h1>Hello, c++ !!</h1> </body> </html> )"; co_await boost::beast::http::async_write(__stream, __response, boost::asio::use_awaitable); } }; // class http_session class http_server: virtual public std::enable_shared_from_this< space::http_server > { protected: const std::string __address; const unsigned short __port; boost::asio::ip::tcp::acceptor __acceptor; public: virtual ~http_server() { } public: http_server( auto executor, const std::string_view address__, const unsigned short port__ ): __address{address__}, __port{port__}, __acceptor{ executor, boost::asio::ip::tcp::endpoint{ boost::asio::ip::make_address(__address), __port } } { } public: boost::asio::awaitable<void> operator()() { co_return; } public: boost::asio::awaitable<void> run() { co_await this->accept(); } boost::asio::awaitable<void> accept() { boost::asio::ip::tcp::socket socket = co_await __acceptor.async_accept(boost::asio::use_awaitable); co_await std::make_shared<space::http_session>(std::move(socket))->run(); co_await this->accept(); } }; // class http_server } // namespace space int main(int argc, char * argv[]) try { if (argc != 3) throw std::runtime_error{""s + argv[0] + " <bind address> <bind port>"}; auto io_context = boost::asio::io_context{}; boost::asio::co_spawn( io_context, [] (const std::string_view host, const unsigned short port) -> boost::asio::awaitable<void> { co_await std::make_shared<space::http_server>( co_await boost::asio::this_coro::executor, host, port ) ->run(); } (argv[1], static_cast<unsigned short>(std::stoi(argv[2]))), boost::asio::detached ); io_context.run(); } catch (const std::exception & e) { std::cerr << "ERROR:\n" << e.what() << std::endl; }
Client side code can be divided into some stages:
Client side c++ example code.
(exceptions or error codes might not work)
#include <boost/beast.hpp> #include <boost/asio.hpp> #include <boost/asio/detached.hpp> #include <boost/asio/co_spawn.hpp> #include <iostream> namespace asio = boost::asio; namespace beast = boost::beast; namespace http = beast::http; using asio::ip::tcp; using std::string_literals::operator""s; namespace space { class http_session: virtual public std::enable_shared_from_this< space::http_session > { protected: const std::string __target; const std::string __protocol; asio::ip::tcp::resolver __resolver; beast::tcp_stream __stream; protected: http::request<http::string_body> __request; http::response<http::string_body> __response; beast::flat_buffer __buffer; public: virtual ~http_session() { __stream.close(); } public: http_session(auto executor__, const std::string_view target__, const std::string_view protocol__): __target{target__}, __protocol{protocol__}, __resolver{executor__}, __stream{executor__} { } public: asio::awaitable<void> run() { co_await this->Run(); } protected: asio::awaitable<void> Run() { tcp::resolver::results_type results = co_await this->resolve(); [[maybe_unused]] tcp::endpoint ep = co_await this->connect(std::move(results)); co_await this->write_read(); } protected: asio::awaitable<tcp::resolver::results_type> resolve() { co_return co_await __resolver.async_resolve( __target, __protocol, asio::use_awaitable ); } asio::awaitable<tcp::endpoint> connect(tcp::resolver::results_type && results) { __stream.expires_after(std::chrono::seconds(3)); co_return co_await __stream.async_connect(results, asio::use_awaitable); } asio::awaitable<void> write_read() { __request.method(http::verb::get); __request.version(11); __request.target("/"); __request.set(http::field::host, __target); __stream.expires_after(std::chrono::seconds(3)); co_await http::async_write(__stream, __request, asio::use_awaitable); __stream.expires_after(std::chrono::seconds(3)); co_await http::async_read(__stream, __buffer, __response, asio::use_awaitable); if (true) { std::cout << __response.body() << std::endl; } else { std::cout << __response << std::endl; // works too. std::cout << __response.body().data() << std::endl; // works too. } } }; } // namespace space int main(int argc, char * argv[]) try { if (argc != 3) throw std::runtime_error{""s + argv[0] + " <remote host> <remote port>"}; auto io_context = asio::io_context{}; asio::co_spawn( io_context, [&] -> asio::awaitable<void> { co_await std::make_shared<space::http_session>(co_await asio::this_coro::executor, argv[1], argv[2])->run(); }, asio::detached ); io_context.run(); } catch (const std::exception & e) { std::cerr << "ERROR:\n" << e.what() << std::endl; }
c++ std::exception:
std::cout.write(err.data(), err.size());
std::cout << std::endl;
caught:
=================================== # The c++ programming language. # # # # Join c++ Discord: yZcauUAUyC # # Deck # ===================================