├── lib ├── CMakeLists.txt ├── util │ ├── main.spec.cpp │ ├── net.hpp │ ├── poly_handler.spec.cpp │ ├── CMakeLists.txt │ ├── st │ │ ├── detail │ │ │ └── stop_token.hpp │ │ └── stop_token.hpp │ ├── async_queue.spec.cpp │ ├── async_queue.hpp │ ├── detail │ │ └── async_queue_impl.hpp │ └── poly_handler.hpp └── config │ ├── boost_json_impl.cpp │ ├── net.hpp │ └── CMakeLists.txt ├── pre-cxx20 ├── blog-2020-09 │ ├── json.hpp │ ├── ssl.hpp │ ├── beast.hpp │ ├── websocket.hpp │ ├── CMakeLists.txt │ ├── net.hpp │ ├── main.cpp │ ├── application.hpp │ ├── application.cpp │ ├── sigint_state.hpp │ ├── fmex_connection.hpp │ ├── wss_transport.hpp │ ├── sigint_state.cpp │ ├── fmex_connection.cpp │ └── wss_transport.cpp ├── CMakeLists.txt ├── memory-test │ ├── CMakeLists.txt │ ├── client.cpp │ └── server.cpp ├── mime_reader │ ├── CMakeLists.txt │ ├── config.hpp │ └── main.cpp ├── chatterbox │ ├── CMakeLists.txt │ ├── config.hpp │ ├── main.cpp │ ├── app.hpp │ ├── app.cpp │ ├── connection_pool.hpp │ ├── connection.hpp │ ├── connection_pool.cpp │ ├── connection.cpp │ └── console.hpp ├── echo_server │ ├── CMakeLists.txt │ ├── config.hpp │ ├── main.cpp │ ├── app.hpp │ ├── app.cpp │ ├── server.hpp │ ├── connection.hpp │ ├── server.cpp │ └── connection.cpp └── fmex_client │ ├── CMakeLists.txt │ ├── config.hpp │ ├── stop_register.hpp │ ├── stop_register.cpp │ ├── main.cpp │ ├── connection_base.hpp │ ├── fmex_connection.hpp │ ├── connect_transport_op.hpp │ └── connection_base.cpp ├── cxx20 ├── CMakeLists.txt ├── config.hpp ├── main.cpp ├── app.hpp ├── app.cpp ├── server.hpp ├── connection.cpp ├── server.cpp ├── connection.hpp └── states.hpp ├── LICENCE_1_0.txt ├── .gitignore ├── CMakeLists.txt ├── .clang-format ├── cmake ├── BuildCMakeContent.cmake └── RequireBoost.cmake └── README.md /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(config) 2 | add_subdirectory(util) 3 | -------------------------------------------------------------------------------- /lib/util/main.spec.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | 4 | -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/json.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace project 6 | { 7 | namespace json = boost::json; 8 | } -------------------------------------------------------------------------------- /lib/config/boost_json_impl.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Including this file builds the implementation of boost::json in this 3 | // compilation unit 4 | // 5 | #include 6 | -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/ssl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "net.hpp" 4 | 5 | #include 6 | 7 | namespace project 8 | { 9 | namespace ssl = asio::ssl; 10 | } -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/beast.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "net.hpp" 4 | #include 5 | 6 | namespace project 7 | { 8 | namespace beast = boost::beast; 9 | } 10 | -------------------------------------------------------------------------------- /cxx20/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project(Cxx20) 3 | 4 | add_executable(cxx20 main.cpp app.cpp connection.cpp server.cpp) 5 | target_link_libraries(cxx20 PUBLIC beast_fun_times_config Boost::system beast_fun_times::util) 6 | 7 | -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/websocket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "beast.hpp" 4 | 5 | #include 6 | 7 | namespace project 8 | { 9 | namespace websocket = beast::websocket; 10 | } -------------------------------------------------------------------------------- /pre-cxx20/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(blog-2020-09) 2 | add_subdirectory(chatterbox) 3 | add_subdirectory(echo_server) 4 | add_subdirectory(fmex_client) 5 | add_subdirectory(memory-test) 6 | add_subdirectory(mime_reader) 7 | -------------------------------------------------------------------------------- /lib/util/net.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config/net.hpp" 4 | 5 | namespace beast_fun_times::util 6 | { 7 | namespace net = config::net; 8 | using config::error_code; 9 | using config::system_error; 10 | } -------------------------------------------------------------------------------- /pre-cxx20/memory-test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(memory-test) 2 | 3 | link_libraries(Boost::boost Boost::system Threads::Threads) 4 | add_executable(memory-test-server server.cpp) 5 | add_executable(memory-test-client client.cpp) 6 | -------------------------------------------------------------------------------- /pre-cxx20/mime_reader/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(mime_reader) 2 | 3 | add_executable(mime_reader main.cpp config.hpp) 4 | target_link_libraries(mime_reader beast_fun_times_config Boost::boost Boost::system Threads::Threads spdlog::spdlog) 5 | -------------------------------------------------------------------------------- /pre-cxx20/chatterbox/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(pre_cxx20_chatterbox) 2 | 3 | add_executable(pre_cxx20_chatterbox main.cpp app.cpp connection.cpp connection_pool.cpp) 4 | target_link_libraries(pre_cxx20_chatterbox PUBLIC beast_fun_times_config Boost::system Boost::thread) 5 | -------------------------------------------------------------------------------- /lib/config/net.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace beast_fun_times::config 6 | { 7 | namespace net = boost::asio; 8 | using error_code = boost::system::error_code; 9 | using system_error = boost::system::system_error; 10 | } -------------------------------------------------------------------------------- /pre-cxx20/echo_server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project(pre_cxx20_echo_server) 3 | 4 | add_executable(pre_cxx20_echo_server main.cpp app.cpp connection.cpp server.cpp) 5 | target_link_libraries(pre_cxx20_echo_server PUBLIC 6 | beast_fun_times_config Boost::system Threads::Threads) 7 | 8 | -------------------------------------------------------------------------------- /pre-cxx20/echo_server/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace project { 7 | namespace net = boost::asio; 8 | namespace beast = boost::beast; 9 | namespace http = beast::http; 10 | namespace websocket = beast::websocket; 11 | using error_code = beast::error_code; 12 | 13 | } // namespace project -------------------------------------------------------------------------------- /pre-cxx20/chatterbox/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace project 7 | { 8 | namespace asio = boost::asio; 9 | namespace beast = boost::beast; 10 | namespace http = beast::http; 11 | namespace net = asio; 12 | namespace websocket = beast::websocket; 13 | using error_code = beast::error_code; 14 | 15 | } -------------------------------------------------------------------------------- /cxx20/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace project 7 | { 8 | namespace net = boost::asio; 9 | namespace beast = boost::beast; 10 | namespace http = beast::http; 11 | namespace websocket = beast::websocket; 12 | using error_code = beast::error_code; 13 | using system_error = beast::system_error; 14 | 15 | } -------------------------------------------------------------------------------- /pre-cxx20/mime_reader/config.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace program { 8 | namespace beast = boost::beast; 9 | namespace http = beast::http; 10 | namespace asio = boost::asio; 11 | namespace net = asio; 12 | 13 | using boost::system::error_code; 14 | using boost::system::system_error; 15 | } // namespace program -------------------------------------------------------------------------------- /cxx20/main.cpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | #include "app.hpp" 3 | 4 | #include 5 | 6 | int main() 7 | { 8 | using namespace project; 9 | 10 | net::io_context ioc; 11 | 12 | auto the_app = app(ioc.get_executor()); 13 | the_app.run(); // initiate async ops 14 | 15 | try 16 | { 17 | ioc.run(); 18 | } 19 | catch(std::exception& e) 20 | { 21 | std::cout << "program bombed: " << e.what(); 22 | } 23 | } -------------------------------------------------------------------------------- /pre-cxx20/chatterbox/main.cpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | #include "app.hpp" 3 | 4 | #include 5 | 6 | int main() 7 | { 8 | using namespace project; 9 | 10 | net::io_context ioc; 11 | 12 | auto the_app = app(ioc.get_executor()); 13 | the_app.run(); // initiate async ops 14 | 15 | try 16 | { 17 | ioc.run(); 18 | } 19 | catch(std::exception& e) 20 | { 21 | std::cout << "program bombed: " << e.what(); 22 | } 23 | } -------------------------------------------------------------------------------- /pre-cxx20/echo_server/main.cpp: -------------------------------------------------------------------------------- 1 | #include "app.hpp" 2 | #include "config.hpp" 3 | 4 | #include 5 | 6 | int 7 | main() 8 | { 9 | using namespace project; 10 | 11 | net::io_context ioc; 12 | 13 | auto the_app = app(ioc.get_executor()); 14 | the_app.run(); // initiate async ops 15 | 16 | try 17 | { 18 | ioc.run(); 19 | } 20 | catch (std::exception &e) 21 | { 22 | std::cout << "program bombed: " << e.what(); 23 | } 24 | } -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(blog_2020_09) 2 | if (FUN_TIMES_BOOST_VERSION VERSION_GREATER "1.71.0") 3 | 4 | file(GLOB_RECURSE src_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS "*.cpp" "*.hpp") 5 | add_executable(blog_2020_09 ${src_files}) 6 | target_link_libraries(blog_2020_09 7 | PUBLIC 8 | beast_fun_times_config 9 | Boost::system 10 | fmt::fmt 11 | OpenSSL::SSL OpenSSL::Crypto 12 | Threads::Threads) 13 | endif() -------------------------------------------------------------------------------- /pre-cxx20/fmex_client/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(pre_cxx20_fmex_client) 2 | 3 | file(GLOB_RECURSE src_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS "*.cpp" "*.hpp") 4 | add_executable(pre_cxx20_fmex_client ${src_files}) 5 | target_link_libraries(pre_cxx20_fmex_client 6 | PUBLIC 7 | beast_fun_times_config 8 | Boost::system 9 | fmt::fmt 10 | nlohmann_json::nlohmann_json 11 | OpenSSL::SSL OpenSSL::Crypto 12 | Threads::Threads) 13 | -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/net.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace project 6 | { 7 | namespace asio = boost::asio; 8 | namespace net 9 | { 10 | using namespace asio; 11 | 12 | // 1.74 and 1.73 compatibility 13 | #if BOOST_ASIO_VERSION <= 101601 14 | using any_io_executor = executor; 15 | #endif 16 | } // namespace net 17 | 18 | using error_code = boost::system::error_code; 19 | using system_error = boost::system::system_error; 20 | 21 | } // namespace project -------------------------------------------------------------------------------- /cxx20/app.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config.hpp" 4 | #include "server.hpp" 5 | 6 | namespace project { 7 | /// The application object. 8 | /// There shall be one. 9 | /// So no need to be owned by a shared ptr 10 | struct app { 11 | app(net::any_io_executor exec); 12 | 13 | void run(); 14 | 15 | private: 16 | 17 | void handle_run(); 18 | 19 | // The application's executor. 20 | // In a multi-threaded application this would be a strand. 21 | net::any_io_executor exec_; 22 | net::signal_set signals_; 23 | server server_; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /pre-cxx20/echo_server/app.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config.hpp" 4 | #include "server.hpp" 5 | 6 | namespace project { 7 | /// The application object. 8 | /// There shall be one. 9 | /// So no need to be owned by a shared ptr 10 | struct app 11 | { 12 | app(net::any_io_executor exec); 13 | 14 | void 15 | run(); 16 | 17 | private: 18 | void 19 | handle_run(); 20 | 21 | // The application's executor. 22 | // In a multi-threaded application this would be a strand. 23 | net::any_io_executor exec_; 24 | net::signal_set signals_; 25 | server server_; 26 | }; 27 | } // namespace project 28 | -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/main.cpp: -------------------------------------------------------------------------------- 1 | #include "application.hpp" 2 | #include "ssl.hpp" 3 | 4 | #include 5 | 6 | int 7 | main() 8 | { 9 | using namespace project; 10 | 11 | auto ioc = net::io_context(); 12 | auto ssl_ctx = ssl::context(ssl::context::tlsv12_client); 13 | ssl_ctx.set_default_verify_paths(); 14 | ssl_ctx.set_verify_mode(ssl::context::verify_peer); 15 | 16 | try 17 | { 18 | auto app = application(ioc.get_executor(), ssl_ctx); 19 | app.start(); 20 | ioc.run(); 21 | } 22 | catch (std::exception &e) 23 | { 24 | fmt::print(stderr, "Fatal: uncaught exception: {}", e.what()); 25 | std::exit(100); 26 | } 27 | 28 | return 0; 29 | } -------------------------------------------------------------------------------- /pre-cxx20/echo_server/app.cpp: -------------------------------------------------------------------------------- 1 | #include "app.hpp" 2 | 3 | #include 4 | 5 | namespace project { 6 | app::app(net::any_io_executor exec) 7 | : exec_(exec) 8 | , signals_(exec, SIGINT, SIGHUP) 9 | , server_(exec) 10 | { 11 | } 12 | 13 | void 14 | app::handle_run() 15 | { 16 | signals_.async_wait([this](error_code ec, int sig) { 17 | if (!ec) 18 | { 19 | std::cout << "signal: " << sig << std::endl; 20 | server_.stop(); 21 | } 22 | }); 23 | 24 | server_.run(); 25 | } 26 | 27 | void 28 | app::run() 29 | { 30 | // get onto the correct executor. It saves confusion later. 31 | net::dispatch(net::bind_executor(exec_, [this] { handle_run(); })); 32 | } 33 | } // namespace project 34 | -------------------------------------------------------------------------------- /cxx20/app.cpp: -------------------------------------------------------------------------------- 1 | #include "app.hpp" 2 | #include 3 | 4 | namespace project { 5 | app::app(net::any_io_executor exec) : exec_(exec), signals_(exec, SIGINT, SIGHUP), 6 | server_(exec) {} 7 | 8 | void app::handle_run() { 9 | signals_.async_wait([this](error_code ec, int sig) { 10 | if (!ec) { 11 | std::cout << "signal: " << sig << std::endl; 12 | server_.stop(); 13 | } 14 | }); 15 | 16 | server_.run(); 17 | } 18 | 19 | void app::run() { 20 | // get onto the correct executor. It saves confusion later. 21 | net::dispatch(net::bind_executor(exec_, [this] { 22 | handle_run(); 23 | })); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/application.hpp: -------------------------------------------------------------------------------- 1 | #include "fmex_connection.hpp" 2 | #include "net.hpp" 3 | #include "sigint_state.hpp" 4 | #include "ssl.hpp" 5 | 6 | #include 7 | 8 | namespace project 9 | { 10 | struct application 11 | { 12 | using executor_type = net::io_context::executor_type; 13 | 14 | application(net::io_context::executor_type const &exec, 15 | ssl::context & ssl_ctx); 16 | 17 | void 18 | start(); 19 | 20 | private: 21 | auto 22 | get_executor() const -> executor_type const &; 23 | 24 | executor_type exec_; 25 | ssl::context & ssl_ctx_; 26 | sigint_state sigint_state_; 27 | fmex_connection fmex_connection_; 28 | }; 29 | } // namespace project -------------------------------------------------------------------------------- /pre-cxx20/chatterbox/app.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config.hpp" 4 | #include "connection_pool.hpp" 5 | #include "console.hpp" 6 | 7 | namespace project { 8 | /// The application object. 9 | /// There shall be one. 10 | /// So no need to be owned by a shared ptr 11 | struct app { 12 | app(net::any_io_executor exec); 13 | 14 | void run(); 15 | 16 | private: 17 | 18 | void handle_run(); 19 | void initiate_console(); 20 | void handle_console(error_code ec, console_event event); 21 | 22 | // The application's executor. 23 | // In a multi-threaded application this would be a strand. 24 | net::any_io_executor exec_; 25 | net::signal_set signals_; 26 | connection_pool clients_; 27 | console console_; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /pre-cxx20/fmex_client/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define FMT_HEADER_ONLY 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace project 15 | { 16 | namespace beast = boost::beast; 17 | 18 | namespace http = beast::http; 19 | 20 | namespace websocket = beast::websocket; 21 | 22 | namespace net = boost::asio; 23 | 24 | namespace ssl = boost::asio::ssl; 25 | 26 | using tcp = boost::asio::ip::tcp; 27 | 28 | using error_code = beast::error_code; 29 | 30 | using system_error = beast::system_error; 31 | 32 | using json = nlohmann::json; 33 | 34 | } // namespace project -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/application.cpp: -------------------------------------------------------------------------------- 1 | #include "application.hpp" 2 | 3 | namespace project 4 | { 5 | application::application(const net::io_context::executor_type &exec, 6 | ssl::context & ssl_ctx) 7 | : exec_(exec) 8 | , ssl_ctx_(ssl_ctx) 9 | , sigint_state_(get_executor()) 10 | , fmex_connection_(get_executor(), ssl_ctx) 11 | { 12 | } 13 | 14 | void 15 | application::start() 16 | { 17 | net::dispatch(get_executor(), [this] { 18 | fmt::print(stdout, "Application starting\n"); 19 | sigint_state_.start(); 20 | 21 | fmex_connection_.start(); 22 | sigint_state_.add_slot([this]{ 23 | fmex_connection_.stop(); 24 | }); 25 | 26 | }); 27 | } 28 | 29 | auto 30 | application::get_executor() const -> const application::executor_type & 31 | { 32 | return exec_; 33 | } 34 | } // namespace project -------------------------------------------------------------------------------- /lib/util/poly_handler.spec.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "util/poly_handler.hpp" 4 | 5 | using namespace beast_fun_times::util; 6 | using namespace std::literals; 7 | 8 | TEST_CASE("util::poly_handler") 9 | { 10 | std::string target; 11 | auto small = [&target](std::string s) 12 | { 13 | target = s; 14 | }; 15 | 16 | auto big = [&target, x="x"s, y="y"s, z="z"s](std::string s) 17 | { 18 | target = s + " " + x + y + z; 19 | }; 20 | 21 | auto f = poly_handler(); 22 | CHECK(f.has_value() == false); 23 | CHECK(bool(f) == false); 24 | 25 | CHECK_THROWS_AS(f("test"s), std::bad_function_call); 26 | 27 | f = std::move(small); 28 | CHECK(f.has_value() == true); 29 | CHECK(bool(f) == true); 30 | CHECK_NOTHROW(f("test"s)); 31 | CHECK(target == "test"); 32 | CHECK(f.has_value() == false); 33 | 34 | f = std::move(big); 35 | CHECK(f.has_value() == true); 36 | CHECK(bool(f) == true); 37 | CHECK_NOTHROW(f("test"s)); 38 | CHECK(target == "test xyz"); 39 | CHECK(f.has_value() == false); 40 | 41 | } -------------------------------------------------------------------------------- /pre-cxx20/fmex_client/stop_register.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace project 7 | { 8 | struct stop_token; 9 | 10 | using stop_id = std::uint64_t; 11 | 12 | struct stop_register 13 | { 14 | stop_token 15 | add(std::function< void() > f); 16 | 17 | void 18 | remove(stop_id id); 19 | 20 | void notify_all(); 21 | 22 | private: 23 | 24 | using stop_map = std::unordered_map< stop_id, std::function< void() > >; 25 | stop_id next_id_ = 0; 26 | stop_map map_; 27 | }; 28 | 29 | struct stop_token 30 | { 31 | explicit stop_token(stop_id id = 0, stop_register *r = nullptr) noexcept; 32 | 33 | stop_token(stop_token &&other) noexcept; 34 | 35 | stop_token & 36 | operator=(stop_token &&other) noexcept; 37 | 38 | ~stop_token() noexcept; 39 | 40 | void 41 | swap(stop_token &other) noexcept; 42 | 43 | void reset() noexcept; 44 | 45 | private: 46 | stop_id id_; 47 | stop_register *register_; 48 | }; 49 | 50 | 51 | } // namespace project -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/sigint_state.hpp: -------------------------------------------------------------------------------- 1 | #include "net.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace project 7 | { 8 | struct sigint_state 9 | { 10 | using executor_type = net::any_io_executor; 11 | 12 | auto 13 | get_executor() const -> executor_type const &; 14 | 15 | explicit sigint_state(executor_type const &exec); 16 | 17 | void 18 | start(); 19 | 20 | void 21 | stop(); 22 | 23 | void 24 | add_slot(std::function< void() > slot); 25 | 26 | private: 27 | void 28 | await_signal(); 29 | 30 | void 31 | await_timer(); 32 | 33 | void 34 | handle_signal(error_code const &ec, int sig); 35 | 36 | void 37 | handle_timer(error_code const &ec); 38 | 39 | void 40 | emit_signals(); 41 | 42 | void 43 | event_sigint(); 44 | 45 | executor_type exec_; 46 | asio::signal_set sigs_; 47 | net::high_resolution_timer timer_; 48 | std::vector< std::function< void() > > signals_; 49 | 50 | enum state_type 51 | { 52 | inactive, 53 | waiting, 54 | confirming, 55 | exit_state 56 | } state_ = inactive; 57 | }; 58 | } // namespace project -------------------------------------------------------------------------------- /LICENCE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /pre-cxx20/chatterbox/app.cpp: -------------------------------------------------------------------------------- 1 | #include "app.hpp" 2 | 3 | #include 4 | 5 | namespace project 6 | { 7 | app::app(net::any_io_executor exec) 8 | : exec_(exec) 9 | , signals_(exec, SIGINT, SIGHUP) 10 | , console_(exec) 11 | , clients_(exec) 12 | { 13 | } 14 | 15 | void app::handle_run() 16 | { 17 | signals_.async_wait([this](error_code ec, int sig) { 18 | if (!ec) 19 | { 20 | std::cout << "signal: " << sig << std::endl; 21 | clients_.stop(); 22 | console_.cancel(); 23 | } 24 | }); 25 | 26 | clients_.run(); 27 | initiate_console(); 28 | } 29 | 30 | void app::run() 31 | { 32 | // get onto the correct executor. It saves confusion later. 33 | net::dispatch(net::bind_executor(exec_, [this] { handle_run(); })); 34 | } 35 | 36 | void app::initiate_console() 37 | { 38 | console_.async_run( 39 | net::bind_executor(exec_, [this](error_code ec, console_event event) { this->handle_console(ec, event); })); 40 | } 41 | 42 | void app::handle_console(error_code ec, console_event event) 43 | { 44 | if (!ec) 45 | { 46 | switch (event) 47 | { 48 | case console_event::quit: 49 | clients_.stop(); 50 | signals_.cancel(); 51 | break; 52 | } 53 | } 54 | } 55 | 56 | } // namespace project 57 | -------------------------------------------------------------------------------- /cxx20/server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config.hpp" 4 | #include "connection.hpp" 5 | 6 | #include 7 | #include 8 | 9 | namespace project 10 | { 11 | struct endpoint_hasher 12 | { 13 | void combine(std::size_t &seed, net::ip::address const &v) const 14 | { 15 | if (v.is_v4()) 16 | boost::hash_combine(seed, v.to_v4().to_bytes()); 17 | else if (v.is_v6()) 18 | boost::hash_combine(seed, v.to_v6().to_bytes()); 19 | else if (v.is_unspecified()) 20 | boost::hash_combine(seed, 0x4751301174351161ul); 21 | else 22 | boost::hash_combine(seed, v.to_string()); 23 | } 24 | 25 | std::size_t operator()(net::ip::tcp::endpoint const &ep) const 26 | { 27 | std::size_t seed = 0; 28 | boost::hash_combine(seed, ep.port()); 29 | combine(seed, ep.address()); 30 | return seed; 31 | } 32 | }; 33 | 34 | struct server 35 | { 36 | server(net::any_io_executor exec); 37 | 38 | void run(); 39 | 40 | void stop(); 41 | 42 | private: 43 | net::awaitable< void > handle_run(); 44 | 45 | void handle_stop(); 46 | 47 | private: 48 | private: 49 | net::ip::tcp::acceptor acceptor_; 50 | std::unordered_map< net::ip::tcp::endpoint, std::weak_ptr< connection_impl >, endpoint_hasher, std::equal_to<> > 51 | connections_; 52 | error_code ec_; 53 | }; 54 | } // namespace project -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/fmex_connection.hpp: -------------------------------------------------------------------------------- 1 | #include "json.hpp" 2 | #include "wss_transport.hpp" 3 | 4 | namespace project 5 | { 6 | struct fmex_connection : wss_transport 7 | { 8 | fmex_connection(net::io_context::executor_type exec, 9 | ssl::context & ssl_ctx); 10 | 11 | void 12 | on_start() override; 13 | 14 | private: 15 | void 16 | on_transport_up() override; 17 | 18 | void 19 | on_transport_error(std::exception_ptr ep) override; 20 | 21 | void 22 | on_text_frame(std::string_view frame) override; 23 | 24 | void 25 | on_close() override; 26 | 27 | // fmex protocol management 28 | 29 | void 30 | on_hello(); 31 | 32 | void 33 | request_ticker(std::string_view ticker); 34 | 35 | // ping state 36 | // fmex requires the client to send a json ping every so often to keep 37 | // the connection alive. We will represent this concept as a state 38 | 39 | enum ping_state 40 | { 41 | ping_not_started, 42 | ping_wait, 43 | ping_sending, 44 | ping_waiting_pong, 45 | ping_finished 46 | } ping_state_ = ping_not_started; 47 | 48 | net::high_resolution_timer ping_timer_; 49 | 50 | void 51 | ping_enter_state(); 52 | 53 | void 54 | ping_enter_wait(); 55 | 56 | void 57 | ping_event_timeout(); 58 | 59 | void 60 | ping_event_pong(json::value const &frame); 61 | }; 62 | } // namespace project -------------------------------------------------------------------------------- /lib/util/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(beast_fun_times_util) 2 | set(package_name beast_fun_times) 3 | set(component_name util) 4 | 5 | get_filename_component(libs_src_dir ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) 6 | get_filename_component(libs_bin_dir ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY) 7 | 8 | file(GLOB_RECURSE cpp_files 9 | LIST_DIRECTORIES false CONFIGURE_DEPENDS 10 | "*.cpp") 11 | set(spec_cpp_files ${cpp_files}) 12 | list(FILTER spec_cpp_files INCLUDE REGEX "^.*\\.spec\\.cpp$") 13 | list(FILTER spec_cpp_files EXCLUDE REGEX "^.*/main\\.spec\\.cpp$") 14 | list(FILTER cpp_files EXCLUDE REGEX "^.*.spec.cpp$") 15 | file(GLOB_RECURSE hpp_files 16 | LIST_DIRECTORIES false CONFIGURE_DEPENDS 17 | "*.hpp") 18 | 19 | set(maybe_interface) 20 | if (${cpp_files}) 21 | add_library(${PROJECT_NAME} ${cpp_files} ${hpp_files}) 22 | set(maybe_interface PUBLIC) 23 | else () 24 | set(maybe_interface INTERFACE) 25 | add_library(${PROJECT_NAME} INTERFACE) 26 | endif () 27 | 28 | target_include_directories(${PROJECT_NAME} ${maybe_interface} 29 | $ 30 | $) 31 | 32 | target_link_libraries(${PROJECT_NAME} ${maybe_interface} beast_fun_times_config) 33 | set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME ${component_name}) 34 | add_library("${package_name}::${component_name}" ALIAS ${PROJECT_NAME}) 35 | 36 | if (${ENABLE_TESTING} AND NOT "${spec_cpp_files}" STREQUAL "") 37 | add_executable("test_${PROJECT_NAME}" main.spec.cpp ${spec_cpp_files}) 38 | target_link_libraries("test_${PROJECT_NAME}" PUBLIC ${PROJECT_NAME} Catch2::Catch2) 39 | endif () 40 | -------------------------------------------------------------------------------- /lib/util/st/detail/stop_token.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace util { namespace st { namespace detail { 9 | 10 | using stop_slot_sig = void(); 11 | using stop_slot = std::function< stop_slot_sig >; 12 | 13 | struct stop_source_impl 14 | { 15 | static constexpr auto stopped_value = 16 | std::numeric_limits< std::uint64_t >::max(); 17 | 18 | auto 19 | connect(stop_slot slot) -> std::uint64_t 20 | { 21 | auto id = next_id_; 22 | if (id == stopped_value) 23 | { 24 | slot(); 25 | } 26 | else 27 | { 28 | ++next_id_; 29 | slots_.emplace(id, std::move(slot)); 30 | } 31 | return id; 32 | } 33 | 34 | void 35 | disconnect(std::uint64_t id) 36 | { 37 | slots_.erase(id); 38 | } 39 | 40 | void 41 | stop() 42 | { 43 | next_id_ = stopped_value; 44 | auto slots = std::move(slots_); 45 | slots_.clear(); 46 | for (auto &[id, slot] : slots) 47 | { 48 | slot(); 49 | } 50 | } 51 | 52 | bool 53 | stopped() const 54 | { 55 | return next_id_ == stopped_value; 56 | } 57 | 58 | private: 59 | std::uint64_t next_id_ = 0; 60 | std::unordered_map< std::uint64_t, stop_slot > slots_; 61 | }; 62 | }}} // namespace util::st::detail -------------------------------------------------------------------------------- /pre-cxx20/fmex_client/stop_register.cpp: -------------------------------------------------------------------------------- 1 | #include "stop_register.hpp" 2 | 3 | #include 4 | 5 | namespace project 6 | { 7 | auto 8 | stop_register::add(std::function< void() > f) -> stop_token 9 | { 10 | auto id = ++next_id_; 11 | map_.emplace(id, std::move(f)); 12 | return stop_token(id, this); 13 | } 14 | 15 | void 16 | stop_register::remove(stop_id id) 17 | { 18 | map_.erase(id); 19 | } 20 | 21 | stop_token::~stop_token() noexcept 22 | { 23 | reset(); 24 | } 25 | 26 | void 27 | stop_token::swap(stop_token &other) noexcept 28 | { 29 | using std::swap; 30 | swap(id_, other.id_); 31 | swap(register_, other.register_); 32 | } 33 | 34 | stop_token & 35 | stop_token::operator=(stop_token &&other) noexcept 36 | { 37 | auto tmp = stop_token(std::move(other)); 38 | swap(tmp); 39 | return *this; 40 | } 41 | 42 | stop_token::stop_token(stop_token &&other) noexcept 43 | : id_(boost::exchange(other.id_, 0)) 44 | , register_(other.register_) 45 | { 46 | } 47 | 48 | stop_token::stop_token(stop_id id, stop_register *r) noexcept 49 | : id_(id) 50 | , register_(r) 51 | { 52 | } 53 | 54 | void 55 | stop_register::notify_all() 56 | { 57 | auto m = std::move(map_); 58 | map_.clear(); 59 | for (auto &elem : m) 60 | elem.second(); 61 | } 62 | 63 | void 64 | stop_token::reset() noexcept 65 | { 66 | if (auto id = boost::exchange(id_, 0)) 67 | register_->remove(id); 68 | } 69 | } // namespace project -------------------------------------------------------------------------------- /pre-cxx20/echo_server/server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config.hpp" 4 | #include "connection.hpp" 5 | 6 | #include 7 | #include 8 | 9 | namespace project { 10 | struct endpoint_hasher 11 | { 12 | void 13 | combine(std::size_t &seed, net::ip::address const &v) const 14 | { 15 | if (v.is_v4()) 16 | boost::hash_combine(seed, v.to_v4().to_bytes()); 17 | else if (v.is_v6()) 18 | boost::hash_combine(seed, v.to_v6().to_bytes()); 19 | else if (v.is_unspecified()) 20 | boost::hash_combine(seed, 0x4751301174351161ul); 21 | else 22 | boost::hash_combine(seed, v.to_string()); 23 | } 24 | 25 | std::size_t 26 | operator()(net::ip::tcp::endpoint const &ep) const 27 | { 28 | std::size_t seed = 0; 29 | boost::hash_combine(seed, ep.port()); 30 | combine(seed, ep.address()); 31 | return seed; 32 | } 33 | }; 34 | 35 | struct server 36 | { 37 | server(net::any_io_executor exec); 38 | 39 | void 40 | run(); 41 | 42 | void 43 | stop(); 44 | 45 | private: 46 | void 47 | handle_run(); 48 | 49 | void 50 | handle_stop(); 51 | 52 | private: 53 | void 54 | initiate_accept(); 55 | void 56 | handle_accept(error_code ec, net::ip::tcp::socket sock); 57 | 58 | private: 59 | net::ip::tcp::acceptor acceptor_; 60 | std::unordered_map< net::ip::tcp::endpoint, 61 | std::weak_ptr< connection_impl >, 62 | endpoint_hasher, 63 | std::equal_to<> > 64 | connections_; 65 | error_code ec_; 66 | }; 67 | } // namespace project -------------------------------------------------------------------------------- /lib/config/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(beast_fun_times_config) 2 | 3 | get_filename_component(libs_src_dir ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) 4 | get_filename_component(libs_bin_dir ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY) 5 | 6 | file(GLOB_RECURSE cpp_files 7 | LIST_DIRECTORIES false CONFIGURE_DEPENDS 8 | "*.cpp") 9 | set(spec_cpp_files ${cpp_files}) 10 | list(FILTER spec_cpp_files INCLUDE REGEX "^.*\\.spec\\.cpp$") 11 | list(FILTER spec_cpp_files EXCLUDE REGEX "^.*/main\\.spec\\.cpp$") 12 | list(FILTER cpp_files EXCLUDE REGEX "^.*.spec.cpp$") 13 | file(GLOB_RECURSE hpp_files 14 | LIST_DIRECTORIES false CONFIGURE_DEPENDS 15 | "*.hpp") 16 | 17 | set(maybe_interface) 18 | if (NOT "${cpp_files}" STREQUAL "") 19 | add_library(${PROJECT_NAME} ${cpp_files} ${hpp_files}) 20 | set(maybe_interface PUBLIC) 21 | else () 22 | set(maybe_interface INTERFACE) 23 | add_library(${PROJECT_NAME} INTERFACE) 24 | endif () 25 | message(STATUS "[lib] ${PROJECT_NAME} is ${maybe_interface}") 26 | 27 | target_include_directories(${PROJECT_NAME} ${maybe_interface} 28 | $ 29 | $) 30 | 31 | target_link_libraries(${PROJECT_NAME} ${maybe_interface} Boost::system Threads::Threads) 32 | target_compile_definitions(${PROJECT_NAME} ${maybe_interface} 33 | "BOOST_ASIO_NO_DEPRECATED=1" 34 | "BOOST_ASIO_DISABLE_CONCEPTS=1" 35 | "BOOST_ASIO_NO_TS_EXECUTORS=1") 36 | 37 | if (${ENABLE_TESTING} AND NOT "${spec_cpp_files}" STREQUAL "") 38 | add_executable("test_${PROJECT_NAME}" main.spec.cpp ${spec_cpp_files}) 39 | target_link_libraries("test_${PROJECT_NAME}" PUBLIC ${PROJECT_NAME} Catch2::Catch2) 40 | endif () 41 | 42 | 43 | -------------------------------------------------------------------------------- /pre-cxx20/chatterbox/connection_pool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config.hpp" 4 | #include "connection.hpp" 5 | 6 | #include 7 | #include 8 | 9 | 10 | namespace project 11 | { 12 | struct endpoint_hasher 13 | { 14 | void combine(std::size_t &seed, net::ip::address const &v) const 15 | { 16 | if (v.is_v4()) 17 | boost::hash_combine(seed, v.to_v4().to_bytes()); 18 | else if (v.is_v6()) 19 | boost::hash_combine(seed, v.to_v6().to_bytes()); 20 | else if (v.is_unspecified()) 21 | boost::hash_combine(seed, 0x4751301174351161ul); 22 | else 23 | boost::hash_combine(seed, v.to_string()); 24 | } 25 | 26 | std::size_t operator()(net::ip::tcp::endpoint const &ep) const 27 | { 28 | std::size_t seed = 0; 29 | boost::hash_combine(seed, ep.port()); 30 | combine(seed, ep.address()); 31 | return seed; 32 | } 33 | }; 34 | 35 | struct connection_pool 36 | { 37 | connection_pool(net::any_io_executor exec); 38 | 39 | void run(); 40 | 41 | void stop(); 42 | 43 | private: 44 | void handle_run(); 45 | 46 | void handle_stop(); 47 | 48 | private: 49 | void initiate_timer(); 50 | void handle_timer(error_code ec); 51 | void another_connection(); 52 | 53 | private: 54 | net::system_timer timer_; 55 | std::unordered_map< net::ip::tcp::endpoint, std::weak_ptr< connection_impl >, endpoint_hasher, std::equal_to<> > 56 | connections_; 57 | error_code ec_; 58 | }; 59 | } // namespace project -------------------------------------------------------------------------------- /cxx20/connection.cpp: -------------------------------------------------------------------------------- 1 | #include "connection.hpp" 2 | 3 | #include "states.hpp" 4 | 5 | #include 6 | 7 | namespace project 8 | { 9 | connection_impl::connection_impl(net::ip::tcp::socket sock) 10 | : chat_state< net::ip::tcp::socket >::chat_state(std::move(sock)) 11 | { 12 | } 13 | 14 | void connection_impl::run() 15 | { 16 | // callback which will happen zero or one times after websocket handshake 17 | // The rx state will not make progress until this function returns, so it should not block 18 | auto on_connect = [this]() { 19 | net::co_spawn( 20 | get_executor(), 21 | [this]() -> net::awaitable< void > { co_await dequeue_send(txqueue, stream); }, 22 | spawn_handler("tx_state")); 23 | }; 24 | 25 | // callback which will happen zero or more times, as each message is received. 26 | // The rx state will not make progress until this function returns, so it should not block 27 | auto on_message = [this](std::string message) { this->send(std::move(message)); }; 28 | 29 | net::co_spawn( 30 | this->get_executor(), 31 | [this, on_connect, on_message]() -> net::awaitable< void > { 32 | co_await run_state(*this, on_connect, on_message); 33 | }, 34 | spawn_handler("run")); 35 | } 36 | 37 | void connection_impl::stop() 38 | { 39 | net::co_spawn( 40 | get_executor(), 41 | [this]() -> net::awaitable< void > { co_await notify_error(net::error::operation_aborted); }, 42 | spawn_handler("stop")); 43 | } 44 | 45 | void connection_impl::send(std::string msg) 46 | { 47 | // this will "happen" on the correct executor 48 | txqueue.push(std::move(msg)); 49 | } 50 | 51 | } // namespace project 52 | -------------------------------------------------------------------------------- /pre-cxx20/chatterbox/connection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace project { 10 | struct connection_impl : std::enable_shared_from_this< connection_impl > 11 | { 12 | using transport = net::ip::tcp::socket; 13 | using stream = websocket::stream< transport >; 14 | 15 | connection_impl(net::any_io_executor exec); 16 | 17 | auto 18 | local_endpoint() -> net::ip::tcp::endpoint; 19 | 20 | auto 21 | get_executor() -> net::any_io_executor; 22 | 23 | void 24 | run(); 25 | void 26 | stop(); 27 | 28 | void 29 | send(std::string msg); 30 | 31 | private: 32 | void 33 | handle_run(); 34 | void 35 | handle_stop(); 36 | 37 | void 38 | handle_connect(error_code ec); 39 | void 40 | initiate_handshake(); 41 | void 42 | handle_handshake(error_code ec); 43 | 44 | void 45 | initiate_delay(); 46 | void 47 | handle_delay(error_code ec); 48 | 49 | void 50 | initiate_rx(); 51 | void 52 | handle_rx(error_code ec, std::size_t bytes_transferred); 53 | 54 | void 55 | maybe_send_next(); 56 | void 57 | initiate_tx(); 58 | void 59 | handle_tx(error_code ec); 60 | 61 | private: 62 | stream stream_; 63 | net::system_timer delay_timer_; 64 | 65 | beast::flat_buffer rxbuffer_; 66 | 67 | // elements in a std deque have a stable address, so this means we don't 68 | // need t make copies of messages 69 | std::queue< std::string, std::deque< std::string > > tx_queue_; 70 | 71 | error_code ec_; 72 | 73 | enum 74 | { 75 | handshaking, 76 | chatting, 77 | } state_ = handshaking; 78 | 79 | enum 80 | { 81 | send_idle, 82 | sending 83 | } sending_state_ = send_idle; 84 | }; 85 | } // namespace project -------------------------------------------------------------------------------- /pre-cxx20/echo_server/connection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace project { 10 | 11 | struct connection_impl : std::enable_shared_from_this< connection_impl > 12 | { 13 | using transport = net::ip::tcp::socket; 14 | using stream = websocket::stream< transport >; 15 | 16 | connection_impl(net::ip::tcp::socket sock); 17 | 18 | void 19 | run(); 20 | 21 | void 22 | stop(); 23 | 24 | void 25 | send(std::string msg); 26 | 27 | private: 28 | void 29 | handle_run(); 30 | 31 | void 32 | handle_stop( 33 | websocket::close_reason reason = websocket::close_code::going_away); 34 | 35 | void 36 | handle_accept(error_code ec); 37 | 38 | void 39 | initiate_rx(); 40 | 41 | void 42 | handle_rx(error_code ec, std::size_t bytes_transferred); 43 | 44 | void 45 | initiate_timer(); 46 | 47 | void 48 | handle_timer(); 49 | 50 | void 51 | handle_send(std::string s); 52 | 53 | void 54 | maybe_send_next(); 55 | 56 | void 57 | initiate_tx(); 58 | 59 | void 60 | handle_tx(error_code ec); 61 | 62 | private: 63 | stream stream_; 64 | net::steady_timer session_timer_; 65 | 66 | std::chrono::seconds time_remaining_; 67 | 68 | beast::flat_buffer rxbuffer_; 69 | 70 | // elements in a std deque have a stable address, so this means we don't 71 | // need t make copies of messages 72 | std::queue< std::string, std::deque< std::string > > tx_queue_; 73 | 74 | error_code ec_; 75 | 76 | enum 77 | { 78 | handshaking, 79 | chatting, 80 | closing, 81 | } state_ = handshaking; 82 | 83 | enum 84 | { 85 | send_idle, 86 | sending 87 | } sending_state_ = send_idle; 88 | }; 89 | } // namespace project -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/**/usage.statistics.xml 10 | .idea/**/dictionaries 11 | .idea/**/shelf 12 | 13 | # Generated files 14 | .idea/**/contentModel.xml 15 | 16 | # Sensitive or high-churn files 17 | .idea/**/dataSources/ 18 | .idea/**/dataSources.ids 19 | .idea/**/dataSources.local.xml 20 | .idea/**/sqlDataSources.xml 21 | .idea/**/dynamic.xml 22 | .idea/**/uiDesigner.xml 23 | .idea/**/dbnavigator.xml 24 | 25 | # Gradle 26 | .idea/**/gradle.xml 27 | .idea/**/libraries 28 | 29 | # Gradle and Maven with auto-import 30 | # When using Gradle or Maven with auto-import, you should exclude module files, 31 | # since they will be recreated, and may cause churn. Uncomment if using 32 | # auto-import. 33 | # .idea/artifacts 34 | # .idea/compiler.xml 35 | # .idea/jarRepositories.xml 36 | # .idea/modules.xml 37 | # .idea/*.iml 38 | # .idea/modules 39 | # *.iml 40 | # *.ipr 41 | 42 | # CMake 43 | cmake-build-*/ 44 | 45 | # Mongo Explorer plugin 46 | .idea/**/mongoSettings.xml 47 | 48 | # File-based project format 49 | *.iws 50 | 51 | # IntelliJ 52 | out/ 53 | 54 | # mpeltonen/sbt-idea plugin 55 | .idea_modules/ 56 | 57 | # JIRA plugin 58 | atlassian-ide-plugin.xml 59 | 60 | # Cursive Clojure plugin 61 | .idea/replstate.xml 62 | 63 | # Crashlytics plugin (for Android Studio and IntelliJ) 64 | com_crashlytics_export_strings.xml 65 | crashlytics.properties 66 | crashlytics-build.properties 67 | fabric.properties 68 | 69 | # Editor-based Rest Client 70 | .idea/httpRequests 71 | 72 | # Android studio 3.1+ serialized cache file 73 | .idea/caches/build_file_checksums.ser 74 | 75 | -------------------------------------------------------------------------------- /lib/util/async_queue.spec.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "async_queue.hpp" 4 | 5 | using namespace beast_fun_times::util; 6 | 7 | TEST_CASE("async_queue") 8 | { 9 | auto ioc = net::io_context(1); 10 | auto e = ioc.get_executor(); 11 | auto q = async_queue(e); 12 | 13 | auto ioc2 = net::io_context(1); 14 | auto e2 = ioc2.get_executor(); 15 | 16 | error_code error; 17 | std::string value; 18 | 19 | auto run = [](net::io_context& ioc) 20 | { 21 | auto s = ioc.run(); 22 | ioc.restart(); 23 | return s; 24 | }; 25 | 26 | auto poll = [](net::io_context& ioc) 27 | { 28 | auto s = ioc.poll(); 29 | ioc.restart(); 30 | return s; 31 | }; 32 | 33 | auto make_handler = [&]() 34 | { 35 | return net::bind_executor(e2, [&](error_code ec, std::string s) { 36 | error = ec; 37 | value = s; 38 | }); 39 | }; 40 | 41 | SECTION("stop") 42 | { 43 | q.async_pop(make_handler()); 44 | q.stop(); 45 | 46 | CHECK(poll(ioc) == 2); 47 | CHECK(run(ioc2) == 1); 48 | CHECK(run(ioc) == 0); 49 | 50 | CHECK(error.message() == "Operation canceled"); 51 | } 52 | 53 | SECTION("3 values") 54 | { 55 | q.push("a"); 56 | q.push("b"); 57 | q.push("c"); 58 | CHECK(poll(ioc) == 3); 59 | 60 | q.async_pop(make_handler()); 61 | CHECK(poll(ioc) == 1); 62 | CHECK(run(ioc2) == 1); 63 | CHECK(error.message() == "Success"); 64 | CHECK(value == "a"); 65 | 66 | q.async_pop(make_handler()); 67 | CHECK(poll(ioc) == 1); 68 | CHECK(run(ioc2) == 1); 69 | CHECK(error.message() == "Success"); 70 | CHECK(value == "b"); 71 | 72 | q.stop(); 73 | CHECK(poll(ioc) == 1); 74 | q.async_pop(make_handler()); 75 | CHECK(poll(ioc) == 1); 76 | CHECK(run(ioc2) == 1); 77 | CHECK(error.message() == "Operation canceled"); 78 | CHECK(value == ""); 79 | 80 | 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /pre-cxx20/chatterbox/connection_pool.cpp: -------------------------------------------------------------------------------- 1 | #include "connection_pool.hpp" 2 | 3 | #include 4 | 5 | namespace project 6 | { 7 | connection_pool::connection_pool(net::any_io_executor exec) 8 | : timer_(exec) 9 | { 10 | } 11 | 12 | void connection_pool::run() 13 | { 14 | net::dispatch(net::bind_executor(timer_.get_executor(), [this] { this->handle_run(); })); 15 | } 16 | 17 | void connection_pool::stop() 18 | { 19 | net::dispatch(net::bind_executor(timer_.get_executor(), [this] { this->handle_stop(); })); 20 | } 21 | 22 | void connection_pool::handle_run() 23 | { 24 | ec_.clear(); 25 | another_connection(); 26 | } 27 | 28 | void connection_pool::handle_timer(error_code ec) 29 | { 30 | if (ec_) 31 | std::cout << "server is stopped. abandoning\n"; 32 | else if (ec == net::error::operation_aborted) 33 | { 34 | // result of calling cancel() on acceptor 35 | } 36 | else if (ec) 37 | { 38 | // this is a real error 39 | std::cout << "timer error:" << ec.message() << "\n"; 40 | } 41 | else 42 | { 43 | another_connection(); 44 | } 45 | } 46 | void connection_pool::initiate_timer() 47 | { 48 | if (!ec_ && connections_.size() < 100) 49 | { 50 | timer_.expires_after(std::chrono::seconds(1)); 51 | timer_.async_wait( 52 | net::bind_executor(timer_.get_executor(), [this](error_code ec) { this->handle_timer(ec); })); 53 | } 54 | } 55 | void connection_pool::handle_stop() 56 | { 57 | ec_ = net::error::operation_aborted; 58 | timer_.cancel(); 59 | for (auto &[ep, weak_conn] : connections_) 60 | if (auto conn = weak_conn.lock()) 61 | { 62 | std::cout << "stopping connection on " << ep << std::endl; 63 | conn->stop(); 64 | } 65 | connections_.clear(); 66 | } 67 | 68 | void connection_pool::another_connection() { 69 | auto con = std::make_shared(net::make_strand(timer_.get_executor())); 70 | con->run(); 71 | connections_[con->local_endpoint()] = con; 72 | } 73 | } // namespace project -------------------------------------------------------------------------------- /cxx20/server.cpp: -------------------------------------------------------------------------------- 1 | #include "server.hpp" 2 | 3 | #include 4 | 5 | namespace project 6 | { 7 | server::server(net::any_io_executor exec) 8 | : acceptor_(exec) 9 | { 10 | auto ep = net::ip::tcp::endpoint(net::ip::address_v4::any(), 4321); 11 | acceptor_.open(ep.protocol()); 12 | acceptor_.set_option(net::ip::tcp::acceptor::reuse_address(true)); 13 | acceptor_.bind(ep); 14 | acceptor_.listen(); 15 | } 16 | 17 | void server::run() 18 | { 19 | net::co_spawn( 20 | acceptor_.get_executor(), 21 | [this]() -> net::awaitable< void > { co_await this->handle_run(); }, 22 | net::detached); 23 | } 24 | 25 | void server::stop() 26 | { 27 | net::dispatch(net::bind_executor(acceptor_.get_executor(), [this] { this->handle_stop(); })); 28 | } 29 | 30 | net::awaitable< void > server::handle_run() 31 | { 32 | while (!ec_) 33 | try 34 | { 35 | auto sock = co_await acceptor_.async_accept(net::use_awaitable); 36 | auto ep = sock.remote_endpoint(); 37 | auto conn = std::make_shared< connection_impl >(std::move(sock)); 38 | // cache the connection 39 | connections_[ep] = conn; 40 | conn->run(); 41 | conn->send("Welcome to my websocket server!\n"); 42 | conn->send("You are visitor number " + std::to_string(connections_.size()) + "\n"); 43 | conn->send("You connected from " + ep.address().to_string() + ":" + std::to_string(ep.port()) + "\n"); 44 | conn->send("Be good!\n"); 45 | } 46 | catch (system_error &se) 47 | { 48 | if (se.code() != net::error::connection_aborted) 49 | throw; 50 | } 51 | } 52 | 53 | void server::handle_stop() 54 | { 55 | ec_ = net::error::operation_aborted; 56 | acceptor_.cancel(); 57 | for (auto &[ep, weak_conn] : connections_) 58 | if (auto conn = weak_conn.lock()) 59 | { 60 | std::cout << "stopping connection on " << ep << std::endl; 61 | conn->stop(); 62 | } 63 | connections_.clear(); 64 | } 65 | } // namespace project -------------------------------------------------------------------------------- /lib/util/st/stop_token.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace util { namespace st { 6 | 7 | /// The stop_source and stop_token is a simple signal/slot device to 8 | /// indicate 9 | /// a one-time event. The slots are invoked on the same thread as the 10 | /// signal. 11 | 12 | using stop_slot = detail::stop_slot; 13 | struct stop_source; 14 | struct stop_token; 15 | 16 | struct stop_connection 17 | { 18 | void 19 | disconnect() 20 | { 21 | if (auto s = std::exchange(source_, {})) 22 | s->disconnect(id_); 23 | } 24 | 25 | private: 26 | friend stop_token; 27 | 28 | std::shared_ptr< detail::stop_source_impl > source_; 29 | std::uint64_t id_; 30 | 31 | stop_connection(std::shared_ptr< detail::stop_source_impl > source, 32 | std::uint64_t id) 33 | : source_(std::move(source)) 34 | , id_(id) 35 | { 36 | } 37 | }; 38 | 39 | struct stop_token 40 | { 41 | stop_token() 42 | : source_() 43 | { 44 | } 45 | 46 | bool 47 | stopped() const 48 | { 49 | return source_ ? source_->stopped() : false; 50 | } 51 | 52 | stop_connection 53 | connect(stop_slot slot) 54 | { 55 | assert(source_); 56 | return stop_connection(source_, source_->connect(std::move(slot))); 57 | } 58 | 59 | private: 60 | friend stop_source; 61 | 62 | stop_token(std::shared_ptr< detail::stop_source_impl > source) 63 | : source_(std::move(source)) 64 | { 65 | } 66 | 67 | std::shared_ptr< detail::stop_source_impl > source_; 68 | }; 69 | 70 | struct stop_source 71 | { 72 | stop_source() 73 | : impl_(std::make_shared< detail::stop_source_impl >()) 74 | { 75 | } 76 | 77 | stop_token 78 | make_token() 79 | { 80 | return stop_token(impl_); 81 | } 82 | 83 | void 84 | stop() 85 | { 86 | if (impl_) 87 | impl_->stop(); 88 | } 89 | 90 | private: 91 | std::shared_ptr< detail::stop_source_impl > impl_; 92 | }; 93 | 94 | }} // namespace util::st -------------------------------------------------------------------------------- /pre-cxx20/fmex_client/main.cpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | #include "fmex_connection.hpp" 3 | 4 | namespace project 5 | { 6 | struct app 7 | { 8 | using executor_type = net::strand< net::io_context::executor_type >; 9 | 10 | app(net::io_context::executor_type const &underlying, 11 | ssl::context & ssl_ctx) 12 | : exec_(underlying) 13 | , ssl_context_(ssl_ctx) 14 | , signals_(exec_) 15 | { 16 | } 17 | 18 | void 19 | start() 20 | { 21 | net::dispatch(exec_, [this] { 22 | signals_.add(SIGINT); 23 | signals_.async_wait([this](error_code const &ec, int sig) { 24 | on_signal(ec, sig); 25 | }); 26 | start_connections(); 27 | }); 28 | } 29 | 30 | void 31 | stop() 32 | { 33 | net::dispatch(exec_, [this] { 34 | signals_.cancel(); 35 | stop_connections(); 36 | }); 37 | } 38 | 39 | private: 40 | void 41 | on_signal(error_code const &ec, int sig) 42 | { 43 | if (ec) 44 | { 45 | if (ec == net::error::operation_aborted) 46 | return; 47 | 48 | fmt::print(stderr, "fatal error in signal handling: {}\n", ec); 49 | std::exit(100); 50 | } 51 | 52 | if (sig == SIGINT) 53 | { 54 | fmt::print(stdout, "ctrl-c detected\n"); 55 | stop_connections(); 56 | } 57 | } 58 | 59 | void 60 | start_connections() 61 | { 62 | connections_.push_back(std::make_unique< ExchangeConnection >( 63 | exec_.get_inner_executor(), ssl_context_)); 64 | connections_.back()->start(); 65 | }; 66 | void 67 | stop_connections() 68 | { 69 | for (auto &&con : connections_) 70 | con->stop(); 71 | } 72 | 73 | executor_type exec_; 74 | ssl::context & ssl_context_; 75 | boost::asio::signal_set signals_; 76 | std::vector< std::unique_ptr< ConnectionBase > > connections_; 77 | }; 78 | } // namespace project 79 | 80 | int 81 | main(int argc, char const *argv[]) 82 | { 83 | using namespace project; 84 | 85 | net::io_context ioc; 86 | 87 | ssl::context ctx { ssl::context::tlsv12_client }; 88 | 89 | auto myapp = app(ioc.get_executor(), ctx); 90 | myapp.start(); 91 | 92 | ioc.run(); 93 | 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | if (NOT DEFINED FUN_TIMES_BOOST_VERSION) 4 | set(FUN_TIMES_BOOST_VERSION "1.77.0" CACHE STRING "Boost Version") 5 | endif () 6 | 7 | include(FetchContent) 8 | include(cmake/RequireBoost.cmake) 9 | include(cmake/BuildCMakeContent.cmake) 10 | 11 | project(beast_fun_times) 12 | 13 | option(ENABLE_TESTING "" ON) 14 | 15 | if (NOT DEFINED CMAKE_CXX_STANDARD) 16 | set(CMAKE_CXX_STANDARD 17) 17 | endif () 18 | 19 | list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_BINARY_DIR}/dependencies/install) 20 | if (NOT DEFINED BOOST_ROOT) 21 | # RequireBoost(PREFIX ${CMAKE_CURRENT_BINARY_DIR}/dependencies/install VERSION 1.73.0 COMPONENTS system) 22 | set(boost_components system thread) 23 | list(APPEND boost_components container) 24 | RequireBoost(PREFIX "${CMAKE_CURRENT_BINARY_DIR}/dependencies/install/boost-${FUN_TIMES_BOOST_VERSION}" VERSION "${FUN_TIMES_BOOST_VERSION}" 25 | COMPONENTS 26 | ${boost_components}) 27 | else() 28 | message(STATUS "[dependencies] BOOST_ROOT=${BOOST_ROOT}") 29 | endif () 30 | 31 | if (ENABLE_TESTING) 32 | FetchContent_Declare(catch2 33 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 34 | GIT_TAG v2.12.1) 35 | BuildCmakeContent(catch2 Catch2 CMAKE_ARGS 36 | "-DCATCH_BUILD_TESTING=OFF" 37 | "-DCATCH_INSTALL_DOCS=OFF" 38 | "-DCATCH_INSTALL_HELPERS=OFF") 39 | find_package(Catch2 CONFIG) 40 | endif () 41 | 42 | FetchContent_Declare(fmtlib 43 | GIT_REPOSITORY git@github.com:fmtlib/fmt.git 44 | GIT_TAG 7.0.3) 45 | BuildCmakeContent(fmtlib fmt 46 | CMAKE_ARGS 47 | "-DFMT_TEST=OFF" 48 | "-DFMT_DOC=OFF") 49 | 50 | FetchContent_Declare(spdlog 51 | GIT_REPOSITORY https://github.com/gabime/spdlog.git 52 | GIT_TAG v1.8.1) 53 | BuildCMakeContent(spdlog spdlog 54 | CMAKE_ARGS 55 | -DSPDLOG_BUILD_EXAMPLE=OFF 56 | -DSPDLOG_BUILD_TESTS=OFF 57 | -DSPDLOG_INSTALL=ON 58 | -DSPDLOG_FMT_EXTERNAL=ON) 59 | 60 | 61 | FetchContent_Declare(nlohmann_jsonlib 62 | GIT_REPOSITORY git@github.com:nlohmann/json.git 63 | GIT_TAG v3.9.1) 64 | BuildCmakeContent(nlohmann_jsonlib nlohmann_json 65 | CMAKE_ARGS 66 | "-DJSON_BuildTests=OFF" 67 | "-DJSON_Install=ON") 68 | 69 | set(Boost_USE_STATIC_LIBS ON) 70 | find_package(Boost CONFIG REQUIRED COMPONENTS ${boost_components}) 71 | find_package(Threads) 72 | find_package(fmt CONFIG) 73 | find_package(spdlog CONFIG) 74 | find_package(nlohmann_json CONFIG) 75 | find_package(OpenSSL) 76 | add_subdirectory(lib) 77 | 78 | add_subdirectory(pre-cxx20) 79 | if (CMAKE_CXX_STANDARD GREATER_EQUAL 20) 80 | add_subdirectory(cxx20) 81 | endif () 82 | 83 | -------------------------------------------------------------------------------- /pre-cxx20/echo_server/server.cpp: -------------------------------------------------------------------------------- 1 | #include "server.hpp" 2 | 3 | #include 4 | 5 | namespace project { 6 | server::server(net::any_io_executor exec) 7 | : acceptor_(exec) 8 | { 9 | auto ep = net::ip::tcp::endpoint(net::ip::address_v4::any(), 4321); 10 | std::cout << "websocket chat server listening on " << ep << "\n"; 11 | acceptor_.open(ep.protocol()); 12 | acceptor_.set_option(net::ip::tcp::acceptor::reuse_address(true)); 13 | acceptor_.bind(ep); 14 | acceptor_.listen(); 15 | } 16 | 17 | void 18 | server::run() 19 | { 20 | net::dispatch(net::bind_executor(acceptor_.get_executor(), 21 | [this] { this->handle_run(); })); 22 | } 23 | 24 | void 25 | server::stop() 26 | { 27 | net::dispatch(net::bind_executor(acceptor_.get_executor(), 28 | [this] { this->handle_stop(); })); 29 | } 30 | 31 | void 32 | server::handle_run() 33 | { 34 | ec_.clear(); 35 | initiate_accept(); 36 | } 37 | 38 | void 39 | server::handle_accept(error_code ec, net::ip::tcp::socket sock) 40 | { 41 | if (ec_) 42 | std::cout << "server is stopped. abandoning\n"; 43 | else if (ec == net::error::connection_aborted) 44 | { 45 | // this is not an error condition 46 | std::cerr << "info: " << ec.message() << "\n"; 47 | initiate_accept(); 48 | } 49 | else if (ec == net::error::operation_aborted) 50 | { 51 | // result of calling cancel() on acceptor 52 | } 53 | else if (ec) 54 | { 55 | // this is a real error 56 | std::cout << "acceptor error:" << ec.message() << "\n"; 57 | } 58 | else 59 | { 60 | // no error 61 | initiate_accept(); 62 | 63 | auto ep = sock.remote_endpoint(); 64 | auto conn = std::make_shared< connection_impl >(std::move(sock)); 65 | // cache the connection 66 | connections_[ep] = conn; 67 | conn->run(); 68 | conn->send("Welcome to my websocket server!\n"); 69 | conn->send("You are visitor number " + 70 | std::to_string(connections_.size()) + "\n"); 71 | conn->send("You connected from " + ep.address().to_string() + ":" + 72 | std::to_string(ep.port()) + "\n"); 73 | conn->send("Be good!\n"); 74 | } 75 | } 76 | void 77 | server::initiate_accept() 78 | { 79 | acceptor_.async_accept([this](error_code ec, net::ip::tcp::socket sock) { 80 | this->handle_accept(ec, std::move(sock)); 81 | }); 82 | } 83 | void 84 | server::handle_stop() 85 | { 86 | ec_ = net::error::operation_aborted; 87 | acceptor_.cancel(); 88 | for (auto &[ep, weak_conn] : connections_) 89 | if (auto conn = weak_conn.lock()) 90 | { 91 | std::cout << "stopping connection on " << ep << std::endl; 92 | conn->stop(); 93 | } 94 | connections_.clear(); 95 | } 96 | } // namespace project -------------------------------------------------------------------------------- /cxx20/connection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config.hpp" 4 | #include "states.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace project 12 | { 13 | struct connection_traits 14 | { 15 | using base_executor_type = net::io_context::executor_type; 16 | using coro_executor_type = net::use_awaitable_t::executor_with_default; 17 | using socket_type = net::basic_stream_socket; 18 | 19 | }; 20 | 21 | struct connection_impl 22 | : chat_state< net::ip::tcp::socket> 23 | , std::enable_shared_from_this< connection_impl > 24 | { 25 | connection_impl(net::ip::tcp::socket sock); 26 | 27 | // 28 | // external events 29 | // 30 | 31 | /// Run the chat implementation until completion 32 | void 33 | run(); 34 | 35 | /// Gracefully stop the chat implementation 36 | void 37 | stop(); 38 | 39 | /// Queue a message to be sent at the earliest opportunity 40 | void 41 | send(std::string msg); 42 | 43 | private: 44 | /// Construct a completion handler for any coroutine running in this 45 | /// implementation 46 | /// 47 | /// Functions: 48 | /// - maintain a live shared_ptr to this implementation in order to 49 | /// sustain its lifetime 50 | /// - catch and log any exceptions that occur during the execution of 51 | /// the coroutine \param context a refernce to _static string data_ 52 | /// whose lifetime must outlive the lifetime of the function object 53 | /// returned by this function. \return a function object to be used as 54 | /// the 3rd argument to net::co_spawn 55 | auto 56 | spawn_handler(std::string_view context) 57 | { 58 | return [self = shared_from_this(), context](std::exception_ptr ep) { 59 | try 60 | { 61 | if (ep) 62 | std::rethrow_exception(ep); 63 | std::cout << context << ": done\n"; 64 | } 65 | catch (system_error &se) 66 | { 67 | if (se.code() != net::error::operation_aborted && 68 | se.code() != websocket::error::closed) 69 | { 70 | std::cout << context << ": error in " << context 71 | << " : " << se.what() << std::endl; 72 | } 73 | else 74 | { 75 | std::cout << context << ": graceful shutdown\n"; 76 | } 77 | } 78 | catch (std::exception &e) 79 | { 80 | std::cout << "error in " << context << " : " << e.what() 81 | << std::endl; 82 | } 83 | }; 84 | } 85 | }; 86 | } // namespace project -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/wss_transport.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ssl.hpp" 4 | #include "websocket.hpp" 5 | 6 | #include 7 | #include 8 | 9 | namespace project 10 | { 11 | struct wss_transport 12 | { 13 | using executor_type = net::any_io_executor; 14 | 15 | wss_transport(net::io_context::executor_type exec, 16 | ssl::context & ssl_ctx); 17 | 18 | void start(); 19 | 20 | void stop(); 21 | 22 | protected: 23 | // 24 | // internal interface for derived classes 25 | // 26 | void 27 | initiate_connect(std::string host, 28 | std::string port, 29 | std::string target); 30 | 31 | void 32 | send_text_frame(std::string frame); 33 | 34 | void 35 | initiate_close(); 36 | 37 | auto get_executor() const -> executor_type const&; 38 | 39 | private: 40 | // 41 | // virtual interface for communicating events to the derived class 42 | // 43 | 44 | virtual void on_start(); 45 | 46 | /// called when the transport layer has connected 47 | virtual void 48 | on_transport_up(); 49 | 50 | /// Called once on the first transport error to give the derived class 51 | /// a chance to clean up. (implies no on_close) 52 | virtual void 53 | on_transport_error(std::exception_ptr ep); 54 | 55 | /// Called to notify the derived class that a text frame has arrived 56 | virtual void 57 | on_text_frame(std::string_view frame); 58 | 59 | /// Called to notify the derived class that the transport has been 60 | /// gracefully closed (implies no error) 61 | virtual void 62 | on_close(); 63 | 64 | private: 65 | 66 | void event_transport_up(); 67 | 68 | void event_transport_error(error_code const& ec); 69 | void event_transport_error(std::exception_ptr ep); 70 | 71 | void start_sending(); 72 | void handle_send(error_code const& ec, std::size_t bytes_transferred); 73 | void start_reading(); 74 | void handle_read(error_code const& ec, std::size_t bytes_transferred); 75 | 76 | private: 77 | 78 | using layer_0 = beast::tcp_stream; 79 | using layer_1 = beast::ssl_stream< layer_0 >; 80 | using websock = websocket::stream< layer_1 >; 81 | 82 | executor_type exec_; 83 | websock websock_; 84 | 85 | // send_state - data to control sending data 86 | 87 | std::deque send_queue_; 88 | enum send_state 89 | { 90 | not_sending, 91 | sending 92 | } send_state_ = not_sending; 93 | 94 | // overall state of this transport 95 | 96 | enum state_type 97 | { 98 | not_started, 99 | connecting, 100 | connected, 101 | closing, 102 | finished 103 | } state_ = not_started; 104 | 105 | beast::flat_buffer rx_buffer_; 106 | 107 | // internal details 108 | 109 | struct connect_op; 110 | }; 111 | } // namespace project -------------------------------------------------------------------------------- /pre-cxx20/fmex_client/connection_base.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "config.hpp" 3 | #include "stop_register.hpp" 4 | 5 | #include 6 | #include 7 | 8 | namespace project 9 | { 10 | class ConnectionBase 11 | { 12 | public: 13 | using executor_type = net::strand< net::io_context::executor_type >; 14 | 15 | private: 16 | // create a strand to act as the default executor for all io objects in 17 | // this Exchange object 18 | executor_type exec_; 19 | 20 | websocket::stream< beast::ssl_stream< beast::tcp_stream > > ws; 21 | beast::flat_buffer buffer {}; 22 | 23 | // A queue of text frames to send. Note that a std::deque's elements 24 | // have a stable address even after insertion/removal. This means we can 25 | // reliably use front() and back() during async operations 26 | std::deque< std::string > tx_queue_ {}; 27 | 28 | // 29 | // Record the state of the "send" orthogonal region 30 | enum send_state 31 | { 32 | send_idle, 33 | send_sending, 34 | } send_state_ = send_idle; 35 | 36 | // 37 | // Create a mechanism to safely stop the connection 38 | // 39 | 40 | stop_register stop_register_; 41 | stop_token my_stop_token_; 42 | 43 | protected: 44 | void 45 | fail(const std::string &exchange, 46 | beast::error_code ec, 47 | char const * what); 48 | 49 | void 50 | fail(const std::string &exchange, char const *what); 51 | 52 | void 53 | succeed(const std::string &exchange, char const *what); 54 | 55 | protected: 56 | void 57 | notify_name(std::string arg); 58 | 59 | void 60 | notify_connect(std::string host, std::string port, std::string target); 61 | 62 | private: 63 | virtual void 64 | on_transport_up() = 0; 65 | 66 | virtual void 67 | on_error(error_code const &ec) = 0; 68 | 69 | virtual void 70 | on_text_frame(std::string_view frame) = 0; 71 | 72 | public: 73 | auto 74 | get_executor() const -> executor_type 75 | { 76 | return exec_; 77 | } 78 | 79 | const std::string name; 80 | 81 | ConnectionBase(net::io_context::executor_type const &exec, 82 | ssl::context & ctx, 83 | std::string name); 84 | 85 | virtual ~ConnectionBase() {}; 86 | 87 | void 88 | start(); 89 | 90 | void 91 | stop(); 92 | 93 | private: 94 | virtual void 95 | handle_connect_command() = 0; 96 | 97 | void 98 | initiate_close(); 99 | 100 | void 101 | enter_read_state(); 102 | 103 | void 104 | on_read(beast::error_code ec, std::size_t bytes_transferred); 105 | 106 | void 107 | on_close(beast::error_code ec); 108 | 109 | protected: 110 | // 111 | // "send" state - orthogonal region active while connected 112 | // 113 | 114 | void 115 | notify_send(std::string frame); 116 | 117 | private: 118 | void 119 | initiate_send(); 120 | 121 | void 122 | on_write(beast::error_code ec, std::size_t bytes_transferred); 123 | }; 124 | 125 | } // namespace project -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/sigint_state.cpp: -------------------------------------------------------------------------------- 1 | #include "sigint_state.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace project 7 | { 8 | using namespace std::literals; 9 | 10 | sigint_state::sigint_state(const sigint_state::executor_type &exec) 11 | : exec_(exec) 12 | , sigs_(get_executor()) 13 | , timer_(get_executor()) 14 | { 15 | } 16 | 17 | auto 18 | sigint_state::get_executor() const -> executor_type const & 19 | { 20 | return exec_; 21 | } 22 | 23 | void 24 | sigint_state::start() 25 | { 26 | net::dispatch(get_executor(), [this] { 27 | sigs_.add(SIGINT); 28 | await_signal(); 29 | state_ = waiting; 30 | fmt::print(stdout, "Press ctrl-c to interrupt.\n"); 31 | }); 32 | } 33 | 34 | void 35 | sigint_state::stop() 36 | { 37 | net::dispatch(get_executor(), [this] { sigs_.cancel(); }); 38 | } 39 | 40 | void 41 | sigint_state::handle_signal(error_code const &ec, int sig) 42 | { 43 | if (ec) 44 | { 45 | if (ec != net::error::operation_aborted) 46 | { 47 | fmt::print(stderr, "Fatal error in signal handler: {}", ec); 48 | std::exit(100); 49 | } 50 | } 51 | else 52 | { 53 | boost::ignore_unused(sig); 54 | BOOST_ASSERT(sig == SIGINT); 55 | event_sigint(); 56 | } 57 | } 58 | 59 | void 60 | sigint_state::event_sigint() 61 | { 62 | switch (state_) 63 | { 64 | case waiting: 65 | fmt::print(stderr, 66 | "Interrupt detected. Press ctrl-c again within 5 " 67 | "seconds to exit\n"); 68 | state_ = confirming; 69 | await_timer(); 70 | await_signal(); 71 | break; 72 | 73 | case confirming: 74 | fmt::print(stderr, "Interrupt confirmed. Shutting down\n"); 75 | timer_.cancel(); 76 | state_ = exit_state; 77 | emit_signals(); 78 | break; 79 | 80 | case inactive: 81 | case exit_state: 82 | break; 83 | } 84 | } 85 | 86 | void 87 | sigint_state::handle_timer(const error_code &ec) 88 | { 89 | if (ec) 90 | return; 91 | 92 | switch (state_) 93 | { 94 | case waiting: 95 | case inactive: 96 | case exit_state: 97 | break; 98 | case confirming: 99 | fmt::print(stderr, "Interrupt unconfirmed. Ignoring\n"); 100 | state_ = waiting; 101 | } 102 | } 103 | 104 | void 105 | sigint_state::await_signal() 106 | { 107 | sigs_.async_wait( 108 | [this](error_code const &ec, int sig) { handle_signal(ec, sig); }); 109 | } 110 | 111 | void 112 | sigint_state::await_timer() 113 | { 114 | timer_.expires_after(5s); 115 | timer_.async_wait([this](error_code const &ec) { handle_timer(ec); }); 116 | } 117 | 118 | void 119 | sigint_state::emit_signals() 120 | { 121 | BOOST_ASSERT(state_ == exit_state); 122 | auto sigs = std::move(signals_); 123 | for (auto &sig : sigs) 124 | sig(); 125 | } 126 | 127 | void 128 | sigint_state::add_slot(std::function< void() > slot) 129 | { 130 | net::dispatch(get_executor(), [this, slot = std::move(slot)]() mutable { 131 | switch (state_) 132 | { 133 | case exit_state: 134 | slot(); 135 | break; 136 | 137 | case inactive: 138 | case confirming: 139 | case waiting: 140 | signals_.push_back(std::move(slot)); 141 | break; 142 | } 143 | }); 144 | } 145 | 146 | } // namespace project -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: true 7 | AlignConsecutiveDeclarations: true 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: None 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: All 19 | AlwaysBreakBeforeMultilineStrings: true 20 | AlwaysBreakTemplateDeclarations: Yes 21 | BinPackArguments: false 22 | ##BinPackArguments: true 23 | BinPackParameters: false 24 | ##BinPackParameters: false 25 | BraceWrapping: 26 | AfterClass: true 27 | AfterControlStatement: true 28 | AfterEnum: true 29 | AfterFunction: true 30 | AfterNamespace: false 31 | AfterObjCDeclaration: true 32 | AfterStruct: true 33 | AfterUnion: true 34 | AfterExternBlock: true 35 | BeforeCatch: true 36 | BeforeElse: true 37 | IndentBraces: false 38 | SplitEmptyFunction: true 39 | SplitEmptyRecord: true 40 | SplitEmptyNamespace: true 41 | BreakBeforeBinaryOperators: None 42 | BreakBeforeBraces: Custom 43 | BreakBeforeInheritanceComma: true 44 | BreakInheritanceList: BeforeComma 45 | BreakBeforeTernaryOperators: true 46 | BreakConstructorInitializersBeforeComma: true 47 | BreakConstructorInitializers: BeforeColon 48 | BreakAfterJavaFieldAnnotations: false 49 | BreakStringLiterals: true 50 | ColumnLimit: 80 51 | CommentPragmas: '^ IWYU pragma:' 52 | CompactNamespaces: true 53 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 54 | ConstructorInitializerIndentWidth: 0 55 | ContinuationIndentWidth: 4 56 | #Cpp11BracedListStyle: true 57 | Cpp11BracedListStyle: false 58 | DerivePointerAlignment: false 59 | DisableFormat: false 60 | ExperimentalAutoDetectBinPacking: false 61 | FixNamespaceComments: true 62 | ForEachMacros: 63 | - foreach 64 | - Q_FOREACH 65 | - BOOST_FOREACH 66 | IncludeBlocks: Regroup 67 | IncludeCategories: 68 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 69 | Priority: 2 70 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 71 | Priority: 3 72 | - Regex: '<[[:alnum:].]+>' 73 | Priority: 4 74 | - Regex: '.*' 75 | Priority: 1 76 | IncludeIsMainRegex: '(Test)?$' 77 | IndentCaseLabels: false 78 | IndentPPDirectives: AfterHash 79 | IndentWidth: 4 80 | IndentWrappedFunctionNames: false 81 | JavaScriptQuotes: Leave 82 | JavaScriptWrapImports: true 83 | KeepEmptyLinesAtTheStartOfBlocks: false 84 | MacroBlockBegin: 'reenter\s(.*)' 85 | MacroBlockEnd: '' 86 | MaxEmptyLinesToKeep: 1 87 | NamespaceIndentation: Inner 88 | ObjCBinPackProtocolList: Auto 89 | ObjCBlockIndentWidth: 4 90 | ObjCSpaceAfterProperty: false 91 | ObjCSpaceBeforeProtocolList: true 92 | PenaltyBreakAssignment: 2 93 | PenaltyBreakBeforeFirstCallParameter: 19 94 | PenaltyBreakComment: 300 95 | PenaltyBreakFirstLessLess: 120 96 | PenaltyBreakString: 1000 97 | PenaltyBreakTemplateDeclaration: 10 98 | PenaltyExcessCharacter: 1000000 99 | PenaltyReturnTypeOnItsOwnLine: 60 100 | PointerAlignment: Right 101 | ReflowComments: true 102 | SortIncludes: true 103 | SortUsingDeclarations: true 104 | SpaceAfterCStyleCast: false 105 | SpaceAfterTemplateKeyword: true 106 | SpaceBeforeAssignmentOperators: true 107 | SpaceBeforeCpp11BracedList: true 108 | SpaceBeforeCtorInitializerColon: true 109 | SpaceBeforeInheritanceColon: true 110 | SpaceBeforeParens: ControlStatements 111 | SpaceBeforeRangeBasedForLoopColon: true 112 | SpaceInEmptyParentheses: false 113 | SpacesBeforeTrailingComments: 3 114 | SpacesInAngles: true 115 | SpacesInContainerLiterals: true 116 | SpacesInCStyleCastParentheses: false 117 | SpacesInParentheses: false 118 | SpacesInSquareBrackets: false 119 | Standard: Cpp11 120 | TabWidth: 8 121 | UseTab: Never 122 | ... 123 | 124 | -------------------------------------------------------------------------------- /cmake/BuildCMakeContent.cmake: -------------------------------------------------------------------------------- 1 | if(NOT BUILD_CMAKE_CONTENT_CMAKE) 2 | set(BUILD_CMAKE_CONTENT_CMAKE 1) 3 | else() 4 | return() 5 | endif() 6 | 7 | function(BuildCMakeContent bcc_NAME bcc_PACKAGE) 8 | if (NOT deps_prefix) 9 | set(deps_prefix ${CMAKE_BINARY_DIR}/dependencies/install) 10 | endif() 11 | cmake_parse_arguments(bcc 12 | "" # options 13 | "" # 14 | "CMAKE_ARGS" # multi_value keywords 15 | ${ARGN}) #...) 16 | if (bcc_NAME STREQUAL "") 17 | message(FATAL_ERROR "BuildDependency: requires name") 18 | endif() 19 | if (bcc_PACKAGE STREQUAL "") 20 | message(FATAL_ERROR "BuildDependency: requires package") 21 | endif() 22 | 23 | string(TOUPPER "${bcc_NAME}" bcc_NAME_UPPER) 24 | set("FETCHCONTENT_UPDATES_DISCONNECTED_${bcc_NAME_UPPER}" ON CACHE BOOL "Whether to check for updates" FORCE) 25 | 26 | 27 | message(STATUS "[dependencies] fetching properties for ${bcc_NAME}") 28 | FetchContent_GetProperties("${bcc_NAME}") 29 | if (NOT "${${bcc_NAME}_POPULATED}") 30 | message(STATUS "[dependencies] Populating ${bcc_NAME} in ${${bcc_NAME}_SOURCE_DIR}") 31 | FetchContent_Populate(${bcc_NAME}) 32 | FetchContent_GetProperties("${bcc_NAME}") 33 | message(STATUS "[dependencies] Populating ${bcc_NAME} in ${${bcc_NAME}_SOURCE_DIR}") 34 | endif() 35 | 36 | # 37 | # configure step 38 | # 39 | 40 | set(bcc_configure_options) 41 | if (CMAKE_TOOLCHAIN_FILE) 42 | list(APPEND bcc_configure_options "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}") 43 | endif() 44 | list(APPEND bcc_configure_options 45 | "-DCMAKE_INSTALL_PREFIX=${deps_prefix}" 46 | "-DCMAKE_PREFIX_PATH=${deps_prefix}" 47 | ${bcc_CMAKE_ARGS} 48 | "-H${${bcc_NAME}_SOURCE_DIR}" 49 | "-B${${bcc_NAME}_BINARY_DIR}") 50 | if (NOT "${${bcc_NAME}_CONFIGURED}" STREQUAL "${bcc_configure_options}") 51 | message(STATUS "[dependencies] ${bcc_NAME} previously configured with ${${bcc_NAME}_CONFIGURED}") 52 | message(STATUS "[dependencies] Configuring ${bcc_NAME} with args ${bcc_configure_options}") 53 | set(${bcc_NAME}_BUILT "" CACHE INTERNAL "") 54 | set(${bcc_NAME}_INSTALLED "" CACHE INTERNAL "") 55 | execute_process(COMMAND "${CMAKE_COMMAND}" ${bcc_configure_options}) 56 | if (NOT bcc_RESULT) 57 | set("${bcc_NAME}_CONFIGURED" "${bcc_configure_options}" CACHE INTERNAL "${bcc_NAME} has been configured") 58 | else() 59 | message(FATAL_ERROR "[dependency] failed to configure ${bcc_NAME}") 60 | endif() 61 | endif() 62 | 63 | # 64 | # build step 65 | # 66 | 67 | set(bcc_build_options "--build" "${${bcc_NAME}_BINARY_DIR}" "--parallel" "${processors}") 68 | if (NOT "${${bcc_NAME}_BUILT}" STREQUAL "${bcc_build_options}") 69 | message(STATUS "[dependencies] ${bcc_NAME} previously built ${${bcc_NAME}_BUILT}") 70 | message(STATUS "[dependencies] Building ${bcc_NAME}") 71 | set(${bcc_NAME}_INSTALLED "" CACHE INTERNAL "") 72 | execute_process(COMMAND "${CMAKE_COMMAND}" ${bcc_build_options} 73 | RESULT_VARIABLE bcc_RESULT 74 | ERROR_VARIABLE bcc_ERROR) 75 | if (bcc_RESULT) 76 | message(FATAL_ERROR "build failed:\n${bcc_ERROR}") 77 | else() 78 | set("${bcc_NAME}_BUILT" "${bcc_build_options}" CACHE INTERNAL "${bcc_NAME} has been built") 79 | endif() 80 | endif() 81 | 82 | # 83 | # install step 84 | # 85 | 86 | set(bcc_install_options "--install" "${${bcc_NAME}_BINARY_DIR}") 87 | if (NOT "${${bcc_NAME}_INSTALLED}" STREQUAL "${bcc_install_options}") 88 | message(STATUS "[dependencies] ${bcc_NAME} previously installed ${${bcc_NAME}_INSTALLED}") 89 | message(STATUS "[dependencies] Installing ${bcc_NAME}") 90 | execute_process(COMMAND "${CMAKE_COMMAND}" ${bcc_install_options} RESULT_VARIABLE bcc_result) 91 | if (bcc_RESULT) 92 | message(FATAL_ERROR "build failed") 93 | else() 94 | set("${bcc_NAME}_INSTALLED" "${bcc_install_options}" CACHE INTERNAL "${bcc_NAME} has been installed") 95 | endif() 96 | endif() 97 | 98 | message(STATUS "[dependencies] ${bcc_PACKAGE}_ROOT=${deps_prefix}") 99 | endfunction() 100 | -------------------------------------------------------------------------------- /lib/util/async_queue.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "detail/async_queue_impl.hpp" 3 | #include "net.hpp" 4 | 5 | namespace beast_fun_times::util 6 | { 7 | template < class T, class Executor > 8 | struct basic_async_queue 9 | { 10 | using executor_type = Executor; 11 | 12 | template < class OtherExec > 13 | struct rebind_executor 14 | { 15 | using other = basic_async_queue< T, OtherExec >; 16 | }; 17 | 18 | using value_type = T; 19 | 20 | basic_async_queue(executor_type exec); 21 | basic_async_queue(basic_async_queue &&other); 22 | basic_async_queue & 23 | operator=(basic_async_queue &&other); 24 | ~basic_async_queue(); 25 | 26 | /// Initiate an asynchronous wait on the queue. 27 | /// 28 | /// The function will return immediately. The WaitHandler will be 29 | /// invoked, as if by post on its associated executor when an in the 30 | /// queue is ready for delivery 31 | /// 32 | /// @tparam WaitHandler A completion token 33 | /// or handler whose signature matches void(error_code, T) 34 | /// @param handler A completion token or handler whose signature matches 35 | /// void(error_code, T) 36 | /// @return DEDUCED 37 | template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, value_type)) 38 | WaitHandler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE( 39 | executor_type) > 40 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(WaitHandler, 41 | void(error_code, value_type)) 42 | async_pop(WaitHandler &&handler 43 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)); 44 | 45 | /// Push an item onto the queue. 46 | /// This function will return quickly, and delivery of the payload is 47 | /// not guaranteed to have heppened before the function returns. \param 48 | /// arg the value to push onto the queue. 49 | void 50 | push(T arg); 51 | 52 | /// Put the queue into an error state, clear data from the queue and 53 | /// cause all subsequent async_wait operations to fail 54 | void 55 | stop(); 56 | 57 | private: 58 | using impl_class = detail::async_queue_impl< T, Executor >; 59 | using implementation_type = typename impl_class::ptr; 60 | 61 | private: 62 | implementation_type impl_; 63 | }; 64 | 65 | template < class T > 66 | using async_queue = basic_async_queue< T, net::any_io_executor >; 67 | 68 | } // namespace beast_fun_times::util 69 | 70 | namespace beast_fun_times::util 71 | { 72 | template < class T, class Executor > 73 | basic_async_queue< T, Executor >::basic_async_queue(Executor exec) 74 | : impl_(impl_class::construct(exec)) 75 | { 76 | } 77 | 78 | template < class T, class Executor > 79 | basic_async_queue< T, Executor >::basic_async_queue( 80 | basic_async_queue &&other) 81 | : impl_(std::exchange(other.impl_, nullptr)) 82 | { 83 | } 84 | 85 | template < class T, class Executor > 86 | auto 87 | basic_async_queue< T, Executor >::operator=(basic_async_queue &&other) 88 | -> basic_async_queue & 89 | { 90 | auto tmp = std::move(other); 91 | std::swap(impl_, tmp.impl_); 92 | return *this; 93 | } 94 | 95 | template < class T, class Executor > 96 | basic_async_queue< T, Executor >::~basic_async_queue() 97 | { 98 | if (impl_) 99 | impl_->stop(); 100 | } 101 | 102 | template < class T, class Executor > 103 | template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, value_type)) 104 | WaitHandler > 105 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(WaitHandler, 106 | void(error_code, value_type)) 107 | basic_async_queue< T, Executor >::async_pop(WaitHandler &&handler) 108 | { 109 | return impl_->async_pop(std::forward< WaitHandler >(handler)); 110 | } 111 | 112 | template < class T, class Executor > 113 | void 114 | basic_async_queue< T, Executor >::push(value_type v) 115 | { 116 | return impl_->push(std::move(v)); 117 | } 118 | 119 | template < class T, class Executor > 120 | void 121 | basic_async_queue< T, Executor >::stop() 122 | { 123 | return impl_->stop(); 124 | } 125 | 126 | } // namespace beast_fun_times::util -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/fmex_connection.cpp: -------------------------------------------------------------------------------- 1 | #include "fmex_connection.hpp" 2 | 3 | #include "json.hpp" 4 | 5 | #include 6 | 7 | namespace project 8 | { 9 | using namespace std::literals; 10 | 11 | auto 12 | timestamp() -> std::uint64_t 13 | { 14 | return std::chrono::duration_cast< std::chrono::milliseconds >( 15 | std::chrono::system_clock::now().time_since_epoch()) 16 | .count(); 17 | } 18 | 19 | fmex_connection::fmex_connection(net::io_context::executor_type exec, 20 | ssl::context & ssl_ctx) 21 | : wss_transport(exec, ssl_ctx) 22 | , ping_timer_(get_executor()) 23 | { 24 | } 25 | 26 | void 27 | fmex_connection::on_start() 28 | { 29 | fmt::print(stdout, "fmex: initiating connection\n"); 30 | initiate_connect("api.fmex.com", "443", "/v2/ws"); 31 | } 32 | void 33 | fmex_connection::on_transport_up() 34 | { 35 | fmt::print(stdout, "fmex: transport up\n"); 36 | ping_enter_state(); 37 | } 38 | 39 | void 40 | fmex_connection::on_transport_error(std::exception_ptr ep) 41 | { 42 | try 43 | { 44 | std::rethrow_exception(ep); 45 | } 46 | catch (system_error &se) 47 | { 48 | fmt::print(stderr, 49 | "fmex: transport error : {} : {} : {}\n", 50 | se.code().category().name(), 51 | se.code().value(), 52 | se.code().message()); 53 | } 54 | catch (std::exception &e) 55 | { 56 | fmt::print(stderr, "fmex: transport exception : {}\n", e.what()); 57 | } 58 | } 59 | 60 | void 61 | fmex_connection::on_text_frame(std::string_view frame) 62 | try 63 | { 64 | auto jframe = 65 | json::parse(json::string_view(frame.data(), frame.size())); 66 | 67 | // dispatch on frame type 68 | 69 | auto &type = jframe.as_object().at("type"); 70 | if (type == "hello") 71 | { 72 | on_hello(); 73 | } 74 | else if (type == "ping") 75 | { 76 | ping_event_pong(jframe); 77 | } 78 | else if (type.as_string().starts_with("ticker.")) 79 | { 80 | fmt::print(stdout, 81 | "fmex: tick {} : {}\n", 82 | type.as_string().subview(7), 83 | jframe.as_object().at("ticker")); 84 | } 85 | } 86 | catch (...) 87 | { 88 | fmt::print(stderr, "text frame is not json : {}\n", frame); 89 | throw; 90 | } 91 | 92 | void 93 | fmex_connection::on_close() 94 | { 95 | fmt::print(stdout, "fmex: closed\n"); 96 | } 97 | 98 | void 99 | fmex_connection::ping_enter_state() 100 | { 101 | BOOST_ASSERT(ping_state_ == ping_not_started); 102 | ping_enter_wait(); 103 | } 104 | 105 | void 106 | fmex_connection::ping_enter_wait() 107 | { 108 | ping_state_ = ping_wait; 109 | 110 | ping_timer_.expires_after(5s); 111 | 112 | ping_timer_.async_wait([this](error_code const &ec) { 113 | if (!ec) 114 | ping_event_timeout(); 115 | }); 116 | } 117 | 118 | void 119 | fmex_connection::ping_event_timeout() 120 | { 121 | ping_state_ = ping_waiting_pong; 122 | 123 | auto frame = json::value(); 124 | auto &o = frame.emplace_object(); 125 | o["cmd"] = "ping"; 126 | o["id"] = "my_ping_ident"; 127 | o["args"].emplace_array().push_back(timestamp()); 128 | send_text_frame(json::serialize(frame)); 129 | } 130 | 131 | void 132 | fmex_connection::ping_event_pong(json::value const &frame) 133 | { 134 | ping_enter_wait(); 135 | } 136 | 137 | void 138 | fmex_connection::on_hello() 139 | { 140 | fmt::print(stdout, "fmex: hello\n"); 141 | request_ticker("btcusd_p"); 142 | } 143 | 144 | void 145 | fmex_connection::request_ticker(std::string_view ticker) 146 | { 147 | auto frame = json::value(); 148 | auto &o = frame.emplace_object(); 149 | o["cmd"] = "sub"; 150 | o["id"] = "some_id"; 151 | o["args"].emplace_array().push_back([&] { 152 | auto result = json::string("ticker."); 153 | result.append(ticker.begin(), ticker.end()); 154 | return result; 155 | }()); 156 | send_text_frame(json::serialize(frame)); 157 | } 158 | } // namespace project -------------------------------------------------------------------------------- /pre-cxx20/fmex_client/fmex_connection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "connection_base.hpp" 3 | 4 | namespace project 5 | { 6 | struct ExchangeConnection : ConnectionBase 7 | { 8 | ExchangeConnection(net::io_context::executor_type const &exec, 9 | ssl::context & ssl_context) 10 | : ConnectionBase(exec, ssl_context, "Fmex") 11 | , ping_timer_(get_executor()) 12 | { 13 | } 14 | 15 | private: 16 | void 17 | handle_connect_command() override 18 | { 19 | notify_connect("api.fmex.com", "443", "/v2/ws"); 20 | } 21 | 22 | void 23 | on_error(error_code const &ec) override 24 | { 25 | fmt::print(stderr, "{} reports error: {}\n", name, ec); 26 | ping_stop(); 27 | } 28 | 29 | void 30 | on_transport_up() override 31 | { 32 | ping_start(); 33 | } 34 | 35 | void 36 | on_text_frame(std::string_view frame) override 37 | { 38 | auto j = json::parse(frame, nullptr, true); 39 | 40 | const std::string j_type = j["type"]; 41 | if (j_type == "hello") 42 | { 43 | json j_out = { { "cmd", "sub" }, 44 | { "args", { "ticker.btcusd_p" } }, 45 | { "id", "random_id.me.hk" } }; 46 | notify_send(j_out.dump()); 47 | } 48 | else 49 | { 50 | if (j_type == "ticker.btcusd_p") 51 | { 52 | int64_t now = 53 | std::chrono::duration_cast< std::chrono::milliseconds >( 54 | std::chrono::system_clock::now().time_since_epoch()) 55 | .count(); 56 | int64_t server_ts = j["ts"]; 57 | fmt::print("now: {}, server_ts: {}. lag: {}\n", 58 | now, 59 | server_ts, 60 | now - server_ts); 61 | } 62 | } 63 | } 64 | 65 | private: 66 | // 67 | // JSON ping is an orthogonal region, active while there is a connection 68 | // 69 | 70 | boost::asio::high_resolution_timer ping_timer_; 71 | 72 | enum ping_state 73 | { 74 | ping_not_active, 75 | ping_waiting_timer, 76 | ping_exited 77 | } ping_state_ = ping_not_active; 78 | 79 | // enter the ping state 80 | void 81 | ping_start() 82 | { 83 | assert(ping_state_ == ping_not_active); 84 | ping_enter_waiting_state(); 85 | } 86 | 87 | // exit the ping state 88 | void 89 | ping_stop() 90 | { 91 | switch (ping_state_) 92 | { 93 | case ping_not_active: 94 | case ping_exited: 95 | break; 96 | case ping_waiting_timer: 97 | ping_timer_.cancel(); 98 | } 99 | ping_state_ = ping_exited; 100 | } 101 | 102 | void 103 | ping_enter_waiting_state() 104 | { 105 | using namespace std::literals; 106 | 107 | ping_state_ = ping_waiting_timer; 108 | 109 | ping_timer_.expires_after(5s); 110 | ping_timer_.async_wait([this](boost::system::error_code const &ec) { 111 | ping_on_timer(ec); 112 | }); 113 | } 114 | 115 | void 116 | ping_on_timer(boost::system::error_code const &ec) 117 | { 118 | // If the ping state has exited, ec will be operation_aborted so we 119 | // can use this rather than checking for a state transition during 120 | // the wait period 121 | if (ec) 122 | { 123 | ping_state_ = ping_exited; 124 | return fail(name, ec, "ping_on_timer"); 125 | } 126 | 127 | json j_out = { 128 | { "cmd", "ping" }, 129 | { "args", 130 | { std::chrono::duration_cast< std::chrono::milliseconds >( 131 | std::chrono::system_clock::now().time_since_epoch()) 132 | .count() } }, 133 | { "id", "random_id.me.hk" } 134 | }; 135 | 136 | // send the "send" event into the "send" orthogonal region 137 | notify_send(j_out.dump()); 138 | 139 | // and re-enter the waiting state 140 | ping_enter_waiting_state(); 141 | } 142 | }; 143 | 144 | } -------------------------------------------------------------------------------- /pre-cxx20/memory-test/client.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 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/boostorg/beast 8 | // 9 | 10 | //---- 11 | //-------------------------------------------------------------------------------------------------------- 12 | // 13 | // Example: WebSocket client, synchronous 14 | // 15 | //---- 16 | //-------------------------------------------------------------------------------------------------------- 17 | 18 | //[example_websocket_client 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace beast = boost::beast; // from 30 | namespace http = beast::http; // from 31 | namespace websocket = beast::websocket; // from 32 | namespace net = boost::asio; // from 33 | using tcp = boost::asio::ip::tcp; // from 34 | 35 | // Sends a WebSocket message and prints the response 36 | int 37 | main(int argc, char **argv) 38 | { 39 | std::atomic_bool finished { false }; 40 | 41 | for (int i = 0; i < 30000; i++) 42 | { 43 | if ((i % 1000) == 0) 44 | printf("Thread spawn %i\n", i); 45 | 46 | std::thread([&] { 47 | try 48 | { 49 | std::string host = "192.168.0.55"; 50 | std::string port = "6761"; 51 | std::string text = 52 | "a8aunpw39rvu0a9p8weufvpoaidsjfkav8w39unfvapwo9ep8UPAOISUND" 53 | "PVIOUPN(*&" 54 | "Np98unp938upoiuoivasdfliJNASDOIVp028upn9v8upsoinjun:" 55 | "OIASJDVN"; 56 | 57 | // The io_context is required for all I/O 58 | net::io_context ioc; 59 | 60 | // These objects perform our I/O 61 | tcp::resolver resolver { ioc }; 62 | websocket::stream< tcp::socket > ws { ioc }; 63 | 64 | boost::beast::websocket::permessage_deflate opt; 65 | opt.client_enable = true; 66 | 67 | ws.set_option(opt); 68 | 69 | // Look up the domain name 70 | auto const results = resolver.resolve(host, port); 71 | 72 | // Make the connection on the IP address we get from a lookup 73 | auto ep = net::connect(ws.next_layer(), results); 74 | 75 | // Update the host_ string. This will provide the value of the 76 | // Host HTTP header during the WebSocket handshake. 77 | // See https://tools.ietf.org/html/rfc7230#section-5.4 78 | host += ':' + std::to_string(ep.port()); 79 | 80 | // Set a decorator to change the User-Agent of the handshake 81 | ws.set_option(websocket::stream_base::decorator( 82 | [](websocket::request_type &req) { 83 | req.set(http::field::user_agent, 84 | std::string(BOOST_BEAST_VERSION_STRING) + 85 | " websocket-client-coro"); 86 | })); 87 | 88 | // Perform the websocket handshake 89 | ws.handshake(host, "/"); 90 | 91 | // Send the message 92 | ws.write(net::buffer(std::string(text))); 93 | 94 | /// only leaks if this is defined 95 | #define LARGE_SIMULTANEOUS_CONNECTIONS 96 | #ifdef LARGE_SIMULTANEOUS_CONNECTIONS 97 | while (!finished) 98 | { 99 | std::this_thread::sleep_for( 100 | std::chrono::milliseconds(1024)); 101 | } 102 | #endif // LARGE_SIMULTANEOUS_CONNECTIONS 103 | 104 | /// this does nothing to change the nature of the leak 105 | #ifdef GRACEFUL_SHUTDOWN 106 | // Close the WebSocket connection 107 | ws.close(websocket::close_code::normal); 108 | #endif // GRACEFUL_SHUTDOWN 109 | 110 | return EXIT_FAILURE; 111 | 112 | // If we get here then the connection is closed gracefully 113 | 114 | // The make_printable() function helps print a 115 | // ConstBufferSequence 116 | } 117 | catch (std::exception const &e) 118 | { 119 | std::cerr << "Error: " << e.what() << std::endl; 120 | return EXIT_FAILURE; 121 | } 122 | }).detach(); 123 | 124 | // std::this_thread::sleep_for(std::chrono::milliseconds(1)); 125 | } 126 | 127 | printf("Finished\n"); 128 | finished = true; 129 | 130 | while (1) 131 | { 132 | std::this_thread::sleep_for(std::chrono::milliseconds(1024)); 133 | } 134 | 135 | return EXIT_SUCCESS; 136 | } 137 | 138 | //] -------------------------------------------------------------------------------- /lib/util/detail/async_queue_impl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "util/net.hpp" 3 | #include "util/poly_handler.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace beast_fun_times::util::detail { 11 | template < class T, class Executor > 12 | struct async_queue_impl 13 | : boost::intrusive_ref_counter< async_queue_impl< T, Executor > > 14 | { 15 | using value_type = T; 16 | using executor_type = Executor; 17 | using ptr = boost::intrusive_ptr< async_queue_impl >; 18 | 19 | enum waiting_state 20 | { 21 | not_waiting, 22 | waiting 23 | }; 24 | 25 | async_queue_impl(executor_type exec) 26 | : state_(not_waiting) 27 | , handler_() 28 | , handler_executor_() 29 | , values_() 30 | , default_executor_(exec) 31 | { 32 | } 33 | 34 | template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, value_type)) 35 | WaitHandler > 36 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(WaitHandler, 37 | void(error_code, value_type)) 38 | async_pop(WaitHandler &&handler); 39 | 40 | static ptr 41 | construct(executor_type exec); 42 | 43 | void 44 | push(value_type v); 45 | 46 | void 47 | stop(); 48 | 49 | private: 50 | void 51 | maybe_complete(); 52 | 53 | private: 54 | std::atomic< waiting_state > state_ = not_waiting; 55 | poly_handler< void(error_code, value_type) > handler_; 56 | net::any_io_executor handler_executor_; 57 | 58 | using queue_impl = std::queue< T, std::deque< T > >; 59 | queue_impl values_; 60 | error_code ec_; // error state of the queue 61 | executor_type default_executor_; 62 | }; 63 | } // namespace beast_fun_times::util::detail 64 | 65 | namespace beast_fun_times::util::detail { 66 | template < class T, class Executor > 67 | template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, value_type)) 68 | WaitHandler > 69 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(WaitHandler, void(error_code, value_type)) 70 | async_queue_impl< T, Executor >::async_pop(WaitHandler &&handler) 71 | { 72 | assert(this->state_ == not_waiting); 73 | 74 | auto initiate = [this](auto &&deduced_handler) { 75 | auto hexec = net::get_associated_executor(deduced_handler, 76 | this->default_executor_); 77 | this->handler_executor_ = 78 | net::prefer(hexec, net::execution::outstanding_work.tracked); 79 | this->handler_ = 80 | [wg = handler_executor_, 81 | dh = std::move(deduced_handler)](auto &&...args) mutable -> void { 82 | dh(std::forward< decltype(args) >(args)...); 83 | }; 84 | 85 | this->state_ = waiting; 86 | 87 | net::dispatch(net::bind_executor( 88 | this->default_executor_, 89 | [self = boost::intrusive_ptr(this)]() { self->maybe_complete(); })); 90 | }; 91 | 92 | return net::async_initiate< WaitHandler, void(error_code, value_type) >( 93 | initiate, handler); 94 | } 95 | 96 | template < class T, class Executor > 97 | auto 98 | async_queue_impl< T, Executor >::construct(executor_type exec) -> ptr 99 | { 100 | return ptr(new async_queue_impl(exec)); 101 | } 102 | 103 | template < class T, class Executor > 104 | void 105 | async_queue_impl< T, Executor >::push(value_type v) 106 | { 107 | net::dispatch(net::bind_executor( 108 | this->default_executor_, 109 | [self = boost::intrusive_ptr(this), v = std::move(v)]() mutable { 110 | self->values_.push(std::move(v)); 111 | self->maybe_complete(); 112 | })); 113 | } 114 | 115 | template < class T, class Executor > 116 | void 117 | async_queue_impl< T, Executor >::maybe_complete() 118 | { 119 | // running in default executor... 120 | if (values_.empty() and not ec_) 121 | return; 122 | if (state_.exchange(not_waiting) != waiting) 123 | return; 124 | 125 | if (ec_) 126 | { 127 | net::post( 128 | net::bind_executor(this->handler_executor_, 129 | [h = std::move(this->handler_), 130 | ec = ec_]() mutable { h(ec, value_type()); })); 131 | ec_.clear(); 132 | } 133 | else 134 | { 135 | net::post(net::bind_executor(this->handler_executor_, 136 | [v = std::move(this->values_.front()), 137 | h = std::move(this->handler_)]() mutable { 138 | h(error_code(), std::move(v)); 139 | })); 140 | values_.pop(); 141 | } 142 | } 143 | 144 | template < class T, class Executor > 145 | void 146 | async_queue_impl< T, Executor >::stop() 147 | { 148 | net::dispatch(net::bind_executor( 149 | this->default_executor_, [self = boost::intrusive_ptr(this)]() mutable { 150 | self->ec_ = net::error::operation_aborted; 151 | while (not self->values_.empty()) 152 | self->values_.pop(); 153 | self->maybe_complete(); 154 | })); 155 | } 156 | 157 | } // namespace beast_fun_times::util::detail -------------------------------------------------------------------------------- /pre-cxx20/fmex_client/connect_transport_op.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "config.hpp" 3 | 4 | #include 5 | 6 | namespace project 7 | { 8 | struct connect_transport_op : boost::asio::coroutine 9 | { 10 | using layer_0 = beast::tcp_stream; 11 | using layer_1 = beast::ssl_stream< layer_0 >; 12 | using websock = websocket::stream< layer_1 >; 13 | 14 | using executor_type = websock::executor_type; 15 | 16 | using protocol = net::ip::tcp; 17 | 18 | using resolver = net::ip::basic_resolver< protocol, executor_type >; 19 | using resolver_results = typename resolver::results_type; 20 | 21 | struct state_data 22 | { 23 | state_data(websock & ws, 24 | stop_register &sr, 25 | std::string && host, 26 | std::string && port, 27 | std::string && path) 28 | : exec_(ws.get_executor()) 29 | , stop_register_(sr) 30 | , resolver_(exec_) 31 | , websock_(ws) 32 | , host_(std::move(host)) 33 | , port_(std::move(port)) 34 | , path_(std::move(path)) 35 | { 36 | } 37 | 38 | executor_type exec_; 39 | 40 | stop_register &stop_register_; 41 | stop_token stop_token_; 42 | 43 | resolver resolver_; 44 | resolver_results resolve_results_; 45 | 46 | websock &websock_; 47 | 48 | std::string host_; 49 | std::string port_; 50 | std::string path_; 51 | }; 52 | std::unique_ptr< state_data > state_; 53 | 54 | connect_transport_op(websock & ws, 55 | stop_register &sr, 56 | std::string host, 57 | std::string port, 58 | std::string path) 59 | : state_(std::make_unique< state_data >(ws, 60 | sr, 61 | std::move(host), 62 | std::move(port), 63 | std::move(path))) 64 | { 65 | } 66 | 67 | auto 68 | get_executor() const -> executor_type 69 | { 70 | return state_->exec_; 71 | } 72 | 73 | template < class Self > 74 | void 75 | operator()(Self &self, error_code const &ec, resolver_results results) 76 | { 77 | state_->resolve_results_ = std::move(results); 78 | (*this)(self, ec); 79 | } 80 | 81 | template < class Self > 82 | void 83 | operator()(Self & self, 84 | error_code const &ec, 85 | layer_0::endpoint_type const &) 86 | { 87 | (*this)(self, ec); 88 | } 89 | 90 | template < class Self > 91 | void operator()(Self &self, error_code const &ec = {}, std::size_t = 0) 92 | { 93 | if (ec) 94 | return self.complete(ec); 95 | 96 | auto &state = *state_; 97 | 98 | BOOST_ASIO_CORO_REENTER(*this) 99 | { 100 | state.stop_token_ = state.stop_register_.add([&state]{ 101 | state.resolver_.cancel(); 102 | }); 103 | BOOST_ASIO_CORO_YIELD 104 | state.resolver_.async_resolve( 105 | state.host_, state.port_, std::move(self)); 106 | 107 | state.stop_token_ = state.stop_register_.add([&state]{ 108 | state.websock_.next_layer().next_layer().cancel(); 109 | }); 110 | 111 | BOOST_ASIO_CORO_YIELD 112 | state.websock_.next_layer().next_layer().async_connect( 113 | state.resolve_results_, std::move(self)); 114 | 115 | if (!SSL_set_tlsext_host_name( 116 | state.websock_.next_layer().native_handle(), 117 | state.host_.c_str())) 118 | return self.complete( 119 | error_code(static_cast< int >(::ERR_get_error()), 120 | net::error::get_ssl_category())); 121 | 122 | BOOST_ASIO_CORO_YIELD 123 | state.websock_.next_layer().async_handshake(layer_1::client, 124 | std::move(self)); 125 | 126 | state.websock_.set_option( 127 | websocket::stream_base::timeout::suggested( 128 | beast::role_type::client)); 129 | 130 | state.websock_.set_option(websocket::stream_base::decorator( 131 | [](websocket::request_type &req) { 132 | req.set(http::field::user_agent, 133 | "custom-websocket-client-async"); 134 | })); 135 | 136 | BOOST_ASIO_CORO_YIELD 137 | state.websock_.async_handshake( 138 | state.host_, state.path_, std::move(self)); 139 | 140 | state.stop_token_.reset(); 141 | 142 | return self.complete(ec); 143 | } 144 | } 145 | }; 146 | 147 | } // namespace project -------------------------------------------------------------------------------- /pre-cxx20/chatterbox/connection.cpp: -------------------------------------------------------------------------------- 1 | #include "connection.hpp" 2 | 3 | #include 4 | 5 | namespace project 6 | { 7 | namespace 8 | { 9 | const auto server_endpoint = net::ip::tcp::endpoint(net::ip::address_v4::loopback(), 4321); 10 | } 11 | 12 | connection_impl::connection_impl(net::any_io_executor exec) 13 | : stream_(net::ip::tcp::socket(exec)) 14 | , delay_timer_(exec) 15 | { 16 | auto ep = net::ip::tcp::endpoint(net::ip::address_v4::any(), 0); 17 | stream_.next_layer().open(ep.protocol()); 18 | 19 | // this is so the socket will actually have a local endpoint 20 | stream_.next_layer().bind(ep); 21 | } 22 | 23 | auto connection_impl::get_executor() -> net::any_io_executor { return stream_.get_executor(); } 24 | 25 | auto connection_impl::local_endpoint() -> net::ip::tcp::endpoint { return stream_.next_layer().local_endpoint(); } 26 | 27 | void connection_impl::run() 28 | { 29 | net::dispatch(stream_.get_executor(), [self = this->shared_from_this()] { self->handle_run(); }); 30 | } 31 | 32 | void connection_impl::handle_run() 33 | { 34 | stream_.next_layer().async_connect( 35 | server_endpoint, 36 | net::bind_executor(get_executor(), [self = shared_from_this()](error_code ec) { 37 | self->handle_connect(ec); 38 | })); 39 | } 40 | 41 | void connection_impl::handle_connect(error_code ec) 42 | { 43 | if (!ec) 44 | { 45 | initiate_handshake(); 46 | } 47 | } 48 | 49 | void connection_impl::initiate_handshake() 50 | { 51 | stream_.async_handshake( 52 | "localhost", "/", net::bind_executor(stream_.get_executor(), [self = shared_from_this()](error_code ec) { 53 | self->handle_handshake(ec); 54 | })); 55 | } 56 | 57 | void connection_impl::handle_handshake(error_code ec) 58 | { 59 | if (ec_) 60 | { 61 | // we've been stopped 62 | } 63 | else if (ec) 64 | { 65 | // connection error 66 | } 67 | else 68 | { 69 | // happy days 70 | state_ = chatting; 71 | initiate_rx(); 72 | maybe_send_next(); 73 | } 74 | } 75 | 76 | void connection_impl::stop() 77 | { 78 | net::dispatch(net::bind_executor(stream_.get_executor(), [self = shared_from_this()] { self->handle_stop(); })); 79 | } 80 | 81 | void connection_impl::handle_stop() 82 | { 83 | // we set an error code in order to handle the crossing case where the accept has completed but its handler 84 | // has not yet been sceduled for invoacation 85 | ec_ = net::error::operation_aborted; 86 | 87 | if (state_ == chatting) 88 | stream_.async_close(websocket::close_code::going_away, [self = shared_from_this()](error_code ec) { 89 | // very important that we captured self here! 90 | // the websocket stream must stay alive while there is an outstanding async op 91 | std::cout << "result of close: " << ec.message() << std::endl; 92 | }); 93 | else 94 | stream_.next_layer().cancel(); 95 | } 96 | void connection_impl::initiate_rx() 97 | { 98 | assert(state_ == chatting); 99 | assert(!ec_); 100 | stream_.async_read(rxbuffer_, [self = this->shared_from_this()](error_code ec, std::size_t bytes_transferred) { 101 | self->handle_rx(ec, bytes_transferred); 102 | }); 103 | } 104 | void connection_impl::handle_rx(error_code ec, std::size_t bytes_transferred) 105 | { 106 | if (ec) 107 | { 108 | std::cout << "rx error: " << ec.message() << std::endl; 109 | } 110 | else 111 | { 112 | // handle the read here 113 | auto message = beast::buffers_to_string(rxbuffer_.data()); 114 | std::cout << local_endpoint() << " received: " << message << "\n"; 115 | rxbuffer_.consume(message.size()); 116 | 117 | // keep reading until error 118 | initiate_rx(); 119 | 120 | // in this case we are merely going to echo the message back. 121 | // but we'll use the public interface in order to demonstrate it 122 | send(std::move(message)); 123 | } 124 | } 125 | void connection_impl::send(std::string msg) 126 | { 127 | net::dispatch( 128 | net::bind_executor(get_executor(), [self = shared_from_this(), msg = std::move(msg)]() mutable { 129 | self->tx_queue_.push(std::move(msg)); 130 | self->maybe_send_next(); 131 | })); 132 | } 133 | void connection_impl::maybe_send_next() 134 | { 135 | if (ec_ || state_ != chatting || sending_state_ == sending || tx_queue_.empty()) 136 | return; 137 | 138 | initiate_tx(); 139 | } 140 | void connection_impl::initiate_tx() 141 | { 142 | assert(sending_state_ == send_idle); 143 | assert(!ec_); 144 | assert(!tx_queue_.empty()); 145 | 146 | sending_state_ = sending; 147 | stream_.async_write(net::buffer(tx_queue_.front()), [self = shared_from_this()](error_code ec, std::size_t) { 148 | // we don't care about bytes_transferred 149 | self->handle_tx(ec); 150 | }); 151 | } 152 | void connection_impl::handle_tx(error_code ec) 153 | { 154 | if (ec) 155 | { 156 | std::cout << "failed to send message: " << tx_queue_.front() << " because " << ec.message() << std::endl; 157 | } 158 | else 159 | { 160 | tx_queue_.pop(); 161 | sending_state_ = send_idle; 162 | maybe_send_next(); 163 | } 164 | } 165 | } // namespace project 166 | -------------------------------------------------------------------------------- /pre-cxx20/echo_server/connection.cpp: -------------------------------------------------------------------------------- 1 | #include "connection.hpp" 2 | 3 | #include 4 | 5 | namespace project { 6 | 7 | using namespace std::literals; 8 | 9 | connection_impl::connection_impl(net::ip::tcp::socket sock) 10 | : stream_(std::move(sock)) 11 | , session_timer_(stream_.get_executor()) 12 | , time_remaining_(30s) 13 | { 14 | } 15 | 16 | void 17 | connection_impl::run() 18 | { 19 | net::dispatch(stream_.get_executor(), 20 | [self = this->shared_from_this()] { self->handle_run(); }); 21 | } 22 | 23 | void 24 | connection_impl::handle_run() 25 | { 26 | stream_.async_accept([self = this->shared_from_this()](error_code ec) { 27 | self->handle_accept(ec); 28 | }); 29 | 30 | initiate_timer(); 31 | } 32 | void 33 | connection_impl::handle_accept(error_code ec) 34 | { 35 | if (ec_) 36 | { 37 | // we've been stopped 38 | } 39 | else if (ec) 40 | { 41 | // connection error 42 | } 43 | else 44 | { 45 | // happy days 46 | state_ = chatting; 47 | initiate_rx(); 48 | maybe_send_next(); 49 | } 50 | } 51 | void 52 | connection_impl::stop() 53 | { 54 | net::dispatch( 55 | net::bind_executor(stream_.get_executor(), [self = shared_from_this()] { 56 | self->handle_stop(); 57 | })); 58 | } 59 | 60 | void 61 | connection_impl::handle_stop(websocket::close_reason reason) 62 | { 63 | // we set an error code in order to handle the crossing case where the 64 | // accept has completed but its handler has not yet been sceduled for 65 | // invoacation 66 | ec_ = net::error::operation_aborted; 67 | 68 | session_timer_.cancel(); 69 | 70 | if (state_ == handshaking) 71 | { 72 | error_code ec; 73 | stream_.next_layer().close(ec); 74 | } 75 | else if (state_ == chatting) 76 | { 77 | stream_.async_close(reason, [self = shared_from_this()](error_code ec) { 78 | // very important that we captured self here! 79 | // the websocket stream must stay alive while 80 | // there is an outstanding async op 81 | std::cout << "result of close: " << ec.message() << std::endl; 82 | }); 83 | } 84 | else if (state_ == closing) 85 | { 86 | } 87 | state_ = closing; 88 | } 89 | void 90 | connection_impl::initiate_rx() 91 | { 92 | assert(state_ == chatting); 93 | assert(!ec_); 94 | stream_.async_read(rxbuffer_, 95 | [self = this->shared_from_this()]( 96 | error_code ec, std::size_t bytes_transferred) { 97 | self->handle_rx(ec, bytes_transferred); 98 | }); 99 | } 100 | void 101 | connection_impl::handle_rx(error_code ec, std::size_t bytes_transferred) 102 | { 103 | if (ec) 104 | { 105 | std::cout << "rx error: " << ec.message() << std::endl; 106 | } 107 | else 108 | { 109 | // handle the read here 110 | auto message = beast::buffers_to_string(rxbuffer_.data()); 111 | std::cout << " received: " << message << "\n"; 112 | rxbuffer_.consume(message.size()); 113 | 114 | // keep reading until error 115 | initiate_rx(); 116 | 117 | // in this case we are merely going to echo the message back. 118 | // but we'll use the public interface in order to demonstrate it 119 | send(std::move(message)); 120 | } 121 | } 122 | void 123 | connection_impl::send(std::string msg) 124 | { 125 | net::dispatch(net::bind_executor( 126 | stream_.get_executor(), 127 | [self = shared_from_this(), msg = std::move(msg)]() mutable { 128 | self->handle_send(std::move(msg)); 129 | })); 130 | } 131 | 132 | void 133 | connection_impl::handle_send(std::string msg) 134 | { 135 | tx_queue_.push(std::move(msg)); 136 | maybe_send_next(); 137 | } 138 | 139 | void 140 | connection_impl::maybe_send_next() 141 | { 142 | if (ec_ || state_ != chatting || sending_state_ == sending || 143 | tx_queue_.empty()) 144 | return; 145 | 146 | initiate_tx(); 147 | } 148 | void 149 | connection_impl::initiate_tx() 150 | { 151 | assert(sending_state_ == send_idle); 152 | assert(!ec_); 153 | assert(!tx_queue_.empty()); 154 | 155 | sending_state_ = sending; 156 | stream_.async_write( 157 | net::buffer(tx_queue_.front()), 158 | [self = shared_from_this()](error_code ec, std::size_t) { 159 | // we don't care about bytes_transferred 160 | self->handle_tx(ec); 161 | }); 162 | } 163 | void 164 | connection_impl::handle_tx(error_code ec) 165 | { 166 | if (ec) 167 | { 168 | std::cout << "failed to send message: " << tx_queue_.front() 169 | << " because " << ec.message() << std::endl; 170 | } 171 | else 172 | { 173 | tx_queue_.pop(); 174 | sending_state_ = send_idle; 175 | maybe_send_next(); 176 | } 177 | } 178 | 179 | void 180 | connection_impl::initiate_timer() 181 | { 182 | assert(time_remaining_.count()); 183 | auto delta = std::min(time_remaining_, 5s); 184 | time_remaining_ -= delta; 185 | session_timer_.expires_after(delta); 186 | session_timer_.async_wait( 187 | [self = shared_from_this()](error_code const &ec) { 188 | if (!ec) 189 | self->handle_timer(); 190 | }); 191 | } 192 | 193 | void 194 | connection_impl::handle_timer() 195 | { 196 | if (ec_) 197 | return; 198 | 199 | if (time_remaining_.count()) 200 | { 201 | std::ostringstream ss; 202 | ss << time_remaining_.count() << " seconds remaining"; 203 | handle_send(ss.str()); 204 | initiate_timer(); 205 | } 206 | else 207 | handle_stop(websocket::close_reason(websocket::close_code::going_away, 208 | "session timed out")); 209 | } 210 | 211 | } // namespace project 212 | -------------------------------------------------------------------------------- /cxx20/states.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "config.hpp" 3 | #include "util/async_queue.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace project 11 | { 12 | /// The read state. 13 | /// 14 | /// Responsibilites: 15 | /// - Read messages and call on_msg (a function call) when a message has 16 | /// been received 17 | /// @exception will throw a system_error if the websocket closes or there is 18 | /// a transport error 19 | template < class NextLayer, class OnMessage > 20 | net::awaitable< void > 21 | websocket_rx_state(websocket::stream< NextLayer > &s, OnMessage &&on_msg) 22 | try 23 | { 24 | beast::flat_buffer rxbuffer; 25 | for (;;) 26 | { 27 | auto bytes = co_await s.async_read(rxbuffer); 28 | auto message = beast::buffers_to_string(rxbuffer.data()); 29 | std::cout << " received: " << message << "\n"; 30 | rxbuffer.consume(message.size()); 31 | on_msg(std::move(message)); 32 | } 33 | } 34 | catch (system_error &) 35 | { 36 | if (auto r = s.reason(); r) 37 | std::cout << "connection closed with : " << r.reason << " : " 38 | << r.code << std::endl; 39 | } 40 | 41 | /// Run the transmit state until the tx queue is stopped 42 | /// 43 | template < class QueueExecutor, class Transport > 44 | net::awaitable< void > 45 | dequeue_send( 46 | beast_fun_times::util::basic_async_queue< std::string, QueueExecutor > 47 | & txqueue, 48 | websocket::stream< Transport > &stream) 49 | { 50 | for (;;) 51 | { 52 | co_await stream.async_write( 53 | net::buffer(co_await txqueue.async_pop())); 54 | } 55 | } 56 | 57 | /// Chat state data that does not depend on transport type 58 | struct chat_state_base 59 | { 60 | error_code ec; 61 | enum 62 | { 63 | initial_state, 64 | handshaking, 65 | chatting, 66 | exit_state, 67 | } state = initial_state; 68 | }; 69 | 70 | /// Chat state data dependent on underlying transport (and therefore 71 | /// executor type) 72 | template < class Transport > 73 | struct chat_state : chat_state_base 74 | { 75 | using executor_type = typename Transport::executor_type; 76 | using transport_template = Transport; 77 | using awaitable_transport = typename net::use_awaitable_t< 78 | executor_type >::template as_default_on_t< transport_template >; 79 | using stream_type = websocket::stream< awaitable_transport >; 80 | 81 | chat_state(Transport t) 82 | : stream(std::move(t)) 83 | , txqueue(get_executor()) 84 | { 85 | } 86 | 87 | auto 88 | get_executor() 89 | { 90 | return stream.get_executor(); 91 | } 92 | 93 | /// Coroutine to notify this state and any substates of a server-side 94 | /// error. net::error::operation_aborted is the correct code to use for 95 | /// a SIGNINT response 96 | net::awaitable< void > 97 | notify_error(error_code nec) 98 | { 99 | assert(nec); 100 | if (!ec) 101 | { 102 | ec = nec; 103 | switch (state) 104 | { 105 | case chat_state_base::initial_state: 106 | break; 107 | case chat_state_base::handshaking: 108 | stream.next_layer().cancel(); 109 | txqueue.stop(); 110 | break; 111 | case chat_state_base::chatting: 112 | txqueue.stop(); 113 | co_await stream.async_close( 114 | websocket::close_reason("shutting down"), 115 | net::use_awaitable); 116 | break; 117 | case chat_state_base::exit_state: 118 | break; 119 | } 120 | } 121 | } 122 | 123 | stream_type stream; 124 | 125 | // substates 126 | 127 | using queue_template = 128 | beast_fun_times::util::basic_async_queue< std::string, 129 | executor_type >; 130 | using txqueue_t = typename net::use_awaitable_t< 131 | executor_type >::template as_default_on_t< queue_template >; 132 | txqueue_t txqueue; 133 | }; 134 | 135 | /// Coroutine which runs the chat state 136 | /// \tparam Transport 137 | /// \tparam OnConnected 138 | /// \tparam OnMessage 139 | /// \param state reference to state data 140 | /// \param on_connected callback to be called if handshake is successful 141 | /// \param on_message callback to be called on every message received 142 | /// \return net::awaitable 143 | template < class Transport, class OnConnected, class OnMessage > 144 | net::awaitable< void > 145 | run_state(chat_state< Transport > &state, 146 | OnConnected && on_connected, 147 | OnMessage && on_message) 148 | try 149 | { 150 | assert(state.state == chat_state_base::initial_state); 151 | state.state = chat_state_base::handshaking; 152 | co_await state.stream.async_accept(); 153 | if (state.ec) 154 | throw system_error(state.ec); 155 | 156 | state.state = chat_state_base::chatting; 157 | on_connected(); 158 | co_await websocket_rx_state(state.stream, 159 | std::forward< OnMessage >(on_message)); 160 | 161 | state.state = chat_state_base::exit_state; 162 | co_return; 163 | } 164 | catch (...) 165 | { 166 | state.state = chat_state_base::exit_state; 167 | throw; 168 | } 169 | } // namespace project -------------------------------------------------------------------------------- /pre-cxx20/chatterbox/console.hpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace project 7 | { 8 | struct console; 9 | 10 | enum class console_event 11 | { 12 | quit 13 | }; 14 | 15 | struct run_op_base 16 | { 17 | virtual void 18 | cancel() = 0; 19 | }; 20 | 21 | template < class Executor, class Handler > 22 | struct run_op 23 | : asio::coroutine 24 | , run_op_base 25 | , std::enable_shared_from_this< run_op< Executor, Handler > > 26 | { 27 | // net composed operation interface components 28 | using executor_type = Executor; 29 | executor_type 30 | get_executor() const 31 | { 32 | return exec_; 33 | } 34 | 35 | auto 36 | myself() 37 | { 38 | return net::bind_executor( 39 | get_executor(), 40 | [self = this->shared_from_this()](auto &&... args) { 41 | self->operator()(std::forward< decltype(args) >(args)...); 42 | }); 43 | } 44 | run_op(console *pcon, Executor exec, Handler h); 45 | 46 | void operator()(error_code ec = {}, 47 | std::size_t bytes_transferred = 0, 48 | bool cont = true); 49 | void 50 | cancel() override; 51 | 52 | private: 53 | console * pconsole_; 54 | Executor exec_; 55 | net::any_io_executor_work_guard< Executor > wg_; 56 | Handler handler_; 57 | std::string rxstore_; 58 | error_code ec_; 59 | console_event last_event_ = console_event::quit; 60 | }; 61 | 62 | struct console 63 | { 64 | console(net::any_io_executor exec) 65 | : fdin_(exec, ::dup(STDIN_FILENO)) 66 | { 67 | } 68 | 69 | using executor_type = net::any_io_executor; 70 | executor_type 71 | get_executor() 72 | { 73 | return fdin_.get_executor(); 74 | } 75 | 76 | template < class CompletionToken > 77 | auto 78 | async_run(CompletionToken &&token); 79 | 80 | void 81 | cancel() 82 | { 83 | auto op = std::atomic_load(&run_op_); 84 | if (op) 85 | op->cancel(); 86 | } 87 | 88 | private: 89 | asio::posix::stream_descriptor fdin_; 90 | std::shared_ptr< run_op_base > run_op_; 91 | 92 | template < class Executor, class Handler > 93 | friend struct run_op; 94 | }; 95 | } // namespace project 96 | 97 | #include 98 | #include 99 | 100 | namespace project 101 | { 102 | template < class CompletionToken > 103 | auto 104 | console::async_run(CompletionToken &&token) 105 | { 106 | return asio::async_initiate< CompletionToken, 107 | void(error_code, console_event) >( 108 | [&](auto &&handler) { 109 | assert(!this->run_op_); 110 | using handler_type = std::decay_t< decltype(handler) >; 111 | auto exec = 112 | net::get_associated_executor(handler, this->get_executor()); 113 | using exec_type = decltype(exec); 114 | auto p = std::make_shared< run_op< exec_type, handler_type > >( 115 | this, exec, std::move(handler)); 116 | this->run_op_ = p; 117 | (*p)(error_code(), 0, false); 118 | }, 119 | token); 120 | } 121 | 122 | template < class Executor, class Handler > 123 | run_op< Executor, Handler >::run_op(console *pcon, Executor exec, Handler h) 124 | : pconsole_(pcon) 125 | , exec_(exec) 126 | , wg_(exec) 127 | , handler_(std::move(h)) 128 | { 129 | } 130 | 131 | template < class Executor, class Handler > 132 | void 133 | run_op< Executor, Handler >::cancel() 134 | { 135 | net::dispatch(net::bind_executor( 136 | this->get_executor(), [self = this->shared_from_this()]() { 137 | self->ec_ = net::error::operation_aborted; 138 | self->pconsole_->fdin_.cancel(); 139 | })); 140 | } 141 | 142 | #include 143 | template < class Executor, class Handler > 144 | void 145 | run_op< Executor, Handler >::operator()(error_code ec, 146 | std::size_t bytes_transferred, 147 | bool cont) 148 | { 149 | if (ec && !ec_) 150 | ec_ = ec; 151 | static std::regex reg_quit("^(quit|exit|done)$", 152 | std::regex_constants::icase); 153 | reenter(this) 154 | { 155 | while (!ec_) 156 | { 157 | yield net::async_read_until(pconsole_->fdin_, 158 | net::dynamic_buffer(this->rxstore_), 159 | "\n", 160 | myself()); 161 | { 162 | auto line = rxstore_.substr(0, bytes_transferred); 163 | rxstore_.erase(0, bytes_transferred); 164 | boost::trim(line); 165 | if (std::regex_match(line, reg_quit)) 166 | { 167 | last_event_ = console_event::quit; 168 | goto done; 169 | } 170 | std::cout << "unrecognised command\n"; 171 | } 172 | } 173 | done: 174 | if (!cont) 175 | { 176 | yield 177 | { 178 | post(bind_executor( 179 | get_executor(), 180 | [self = this->shared_from_this()] { (*self); })); 181 | } 182 | } 183 | auto h = std::move(handler_); 184 | std::atomic_exchange(&pconsole_->run_op_, 185 | std::shared_ptr< run_op_base >()); 186 | h(ec_, last_event_); 187 | } 188 | } 189 | #include 190 | 191 | } // namespace project 192 | -------------------------------------------------------------------------------- /pre-cxx20/fmex_client/connection_base.cpp: -------------------------------------------------------------------------------- 1 | #include "connection_base.hpp" 2 | 3 | #include "connect_transport_op.hpp" 4 | 5 | #define FMT_HEADER_ONLY 6 | #include 7 | #include 8 | 9 | namespace project 10 | { 11 | void 12 | ConnectionBase::notify_connect(std::string host, 13 | std::string port, 14 | std::string target) 15 | { 16 | auto op = connect_transport_op(ws, 17 | stop_register_, 18 | std::move(host), 19 | std::move(port), 20 | std::move(target)); 21 | 22 | auto handler = [this](error_code const &ec) { 23 | if (ec) 24 | this->fail(name, ec, "transport"); 25 | else 26 | { 27 | this->on_transport_up(); 28 | my_stop_token_ = stop_register_.add([this]{ 29 | initiate_close(); 30 | }); 31 | this->enter_read_state(); 32 | } 33 | }; 34 | 35 | net::async_compose< decltype(handler), void(error_code) >( 36 | std::move(op), handler, *this); 37 | } 38 | 39 | void 40 | ConnectionBase::fail(const std::string &exchange, 41 | beast::error_code ec, 42 | const char * what) 43 | { 44 | fmt::print(stderr, 45 | fg(fmt::color::crimson), 46 | "{}: {}: {}\n", 47 | exchange, 48 | what, 49 | ec.message()); 50 | on_error(ec); 51 | } 52 | void 53 | ConnectionBase::fail(const std::string &exchange, const char *what) 54 | { 55 | fmt::print(stderr, fg(fmt::color::crimson), "{}: {}\n", exchange, what); 56 | on_error(net::error::fault); 57 | } 58 | void 59 | ConnectionBase::succeed(const std::string &exchange, const char *what) 60 | { 61 | fmt::print(fg(fmt::color::steel_blue), "{}: {}\n", exchange, what); 62 | } 63 | void 64 | ConnectionBase::enter_read_state() 65 | { 66 | ws.async_read( 67 | buffer, beast::bind_front_handler(&ConnectionBase::on_read, this)); 68 | } 69 | void 70 | ConnectionBase::on_read(beast::error_code ec, std::size_t bytes_transferred) 71 | { 72 | boost::ignore_unused(bytes_transferred); 73 | 74 | if (ec) 75 | { 76 | return fail(name, ec, "read"); 77 | } 78 | succeed(name, "read"); 79 | 80 | int64_t now = std::chrono::duration_cast< std::chrono::milliseconds >( 81 | std::chrono::system_clock::now().time_since_epoch()) 82 | .count(); 83 | fmt::print("received: {}\n", beast::buffers_to_string(buffer.data())); 84 | 85 | try 86 | { 87 | on_text_frame([&] { 88 | auto d = buffer.data(); 89 | return std::string_view( 90 | reinterpret_cast< const char * >(d.data()), d.size()); 91 | }()); 92 | buffer.consume(buffer.size()); 93 | enter_read_state(); 94 | } 95 | catch (system_error &se) 96 | { 97 | fail(name, se.code(), "on_text_frame"); 98 | on_error(se.code()); 99 | return; 100 | } 101 | catch (std::exception &e) 102 | { 103 | using namespace std::literals; 104 | fail(name, ("on_text_frame: "s + e.what()).c_str()); 105 | on_error(net::error::basic_errors::fault); 106 | return; 107 | } 108 | } 109 | 110 | void ConnectionBase::initiate_close() 111 | { 112 | ws.async_close(websocket::close_code::going_away, [this](error_code const& ec){ 113 | on_close(ec); 114 | }); 115 | } 116 | 117 | void 118 | ConnectionBase::on_close(beast::error_code ec) 119 | { 120 | if (ec) 121 | { 122 | return fail(name, ec, "close"); 123 | } 124 | succeed(name, "close"); 125 | } 126 | 127 | void 128 | ConnectionBase::notify_send(std::string frame) 129 | { 130 | tx_queue_.push_back(std::move(frame)); 131 | if (send_state_ == send_idle) 132 | { 133 | initiate_send(); 134 | } 135 | } 136 | void 137 | ConnectionBase::initiate_send() 138 | { 139 | assert(send_state_ == send_idle); 140 | assert(!tx_queue_.empty()); 141 | send_state_ = send_sending; 142 | 143 | // write the data at the front of the queue 144 | ws.async_write( 145 | boost::asio::buffer(tx_queue_.front()), 146 | beast::bind_front_handler(&ConnectionBase::on_write, this)); 147 | } 148 | void 149 | ConnectionBase::on_write(beast::error_code ec, 150 | std::size_t bytes_transferred) 151 | { 152 | boost::ignore_unused(bytes_transferred); 153 | 154 | // whether there was an error or not, set the state to idle 155 | // and remove the previous frame from the send queue 156 | send_state_ = send_idle; 157 | assert(!tx_queue_.empty()); 158 | tx_queue_.pop_front(); 159 | 160 | // error check 161 | if (ec) 162 | { 163 | return fail(name, ec, "write"); 164 | } 165 | succeed(name, "write"); 166 | 167 | // guard condition: if there is more to send, re-enter sending 168 | // state, otherwise allow to go idle 169 | if (!tx_queue_.empty()) 170 | initiate_send(); 171 | } 172 | 173 | void 174 | ConnectionBase::start() 175 | { 176 | // in a multithreaded environment, be sure we are on the correct thread 177 | net::dispatch(exec_, [this] { handle_connect_command(); }); 178 | } 179 | void 180 | ConnectionBase::stop() 181 | { 182 | // in a multithreaded environment, be sure we are on the correct thread 183 | net::dispatch(exec_, [this] { stop_register_.notify_all(); }); 184 | } 185 | 186 | ConnectionBase::ConnectionBase( 187 | const boost::asio::io_context::executor_type &exec, 188 | boost::asio::ssl::context & ctx, 189 | std::string name) 190 | : exec_(net::make_strand(exec)) 191 | , ws(exec_, ctx) 192 | , name(std::move(name)) 193 | { 194 | } 195 | 196 | } // namespace project 197 | -------------------------------------------------------------------------------- /pre-cxx20/memory-test/server.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 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/boostorg/beast 8 | // 9 | 10 | //---- 11 | //-------------------------------------------------------------------------------------------------------- 12 | // 13 | // Example: WebSocket server, asynchronous 14 | // 15 | //---- 16 | //-------------------------------------------------------------------------------------------------------- 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | namespace beast = boost::beast; // from 31 | namespace http = beast::http; // from 32 | namespace websocket = beast::websocket; // from 33 | namespace net = boost::asio; // from 34 | using tcp = boost::asio::ip::tcp; // from 35 | 36 | //---- 37 | //-------------------------------------------------------------------------------------------------------- 38 | 39 | // Report a failure 40 | void 41 | fail(beast::error_code ec, char const *what) 42 | { 43 | std::cerr << what << ": " << ec.message() << "\n"; 44 | } 45 | 46 | // Echoes back all received WebSocket messages 47 | class session : public std::enable_shared_from_this< session > 48 | { 49 | websocket::stream< beast::tcp_stream > ws_; 50 | beast::flat_buffer buffer_; 51 | 52 | public: 53 | // Take ownership of the socket 54 | explicit session(tcp::socket &&socket) 55 | : ws_(std::move(socket)) 56 | { 57 | } 58 | 59 | // Start the asynchronous operation 60 | void 61 | run() 62 | { 63 | // Set suggested timeout settings for the websocket 64 | ws_.set_option(websocket::stream_base::timeout::suggested( 65 | beast::role_type::server)); 66 | 67 | // Set a decorator to change the Server of the handshake 68 | ws_.set_option(websocket::stream_base::decorator( 69 | [](websocket::response_type &res) { 70 | res.set(http::field::server, 71 | std::string(BOOST_BEAST_VERSION_STRING) + 72 | " websocket-server-async"); 73 | })); 74 | 75 | boost::beast::websocket::permessage_deflate opt; 76 | opt.server_enable = true; 77 | 78 | ws_.set_option(opt); 79 | 80 | // Accept the websocket handshake 81 | ws_.async_accept( 82 | beast::bind_front_handler(&session::on_accept, shared_from_this())); 83 | } 84 | 85 | void 86 | on_accept(beast::error_code ec) 87 | { 88 | if (ec) 89 | return fail(ec, "accept"); 90 | 91 | // Read a message 92 | do_read(); 93 | } 94 | 95 | void 96 | do_read() 97 | { 98 | // Read a message into our buffer 99 | ws_.async_read( 100 | buffer_, 101 | beast::bind_front_handler(&session::on_read, shared_from_this())); 102 | } 103 | 104 | void 105 | on_read(beast::error_code ec, std::size_t bytes_transferred) 106 | { 107 | boost::ignore_unused(bytes_transferred); 108 | 109 | // This indicates that the session was closed 110 | if (ec == websocket::error::closed) 111 | return; 112 | 113 | if (ec) 114 | fail(ec, "read"); 115 | 116 | // Echo the message 117 | ws_.text(ws_.got_text()); 118 | ws_.async_write( 119 | buffer_.data(), 120 | beast::bind_front_handler(&session::on_write, shared_from_this())); 121 | } 122 | 123 | void 124 | on_write(beast::error_code ec, std::size_t bytes_transferred) 125 | { 126 | boost::ignore_unused(bytes_transferred); 127 | 128 | if (ec) 129 | return fail(ec, "write"); 130 | 131 | // Clear the buffer 132 | buffer_.consume(buffer_.size()); 133 | 134 | // Do another read 135 | do_read(); 136 | } 137 | }; 138 | 139 | //---- 140 | //-------------------------------------------------------------------------------------------------------- 141 | 142 | // Accepts incoming connections and launches the sessions 143 | class listener : public std::enable_shared_from_this< listener > 144 | { 145 | net::io_context &ioc_; 146 | tcp::acceptor acceptor_; 147 | 148 | public: 149 | listener(net::io_context &ioc, tcp::endpoint endpoint) 150 | : ioc_(ioc) 151 | , acceptor_(ioc) 152 | { 153 | beast::error_code ec; 154 | 155 | // Open the acceptor 156 | acceptor_.open(endpoint.protocol(), ec); 157 | if (ec) 158 | { 159 | fail(ec, "open"); 160 | return; 161 | } 162 | 163 | // Allow address reuse 164 | acceptor_.set_option(net::socket_base::reuse_address(true), ec); 165 | if (ec) 166 | { 167 | fail(ec, "set_option"); 168 | return; 169 | } 170 | 171 | // Bind to the server address 172 | acceptor_.bind(endpoint, ec); 173 | if (ec) 174 | { 175 | fail(ec, "bind"); 176 | return; 177 | } 178 | 179 | // Start listening for connections 180 | acceptor_.listen(1000000, ec); 181 | if (ec) 182 | { 183 | fail(ec, "listen"); 184 | return; 185 | } 186 | } 187 | 188 | // Start accepting incoming connections 189 | void 190 | run() 191 | { 192 | do_accept(); 193 | } 194 | 195 | private: 196 | void 197 | do_accept() 198 | { 199 | // The new connection gets its own strand 200 | acceptor_.async_accept(net::make_strand(ioc_), 201 | beast::bind_front_handler(&listener::on_accept, 202 | shared_from_this())); 203 | } 204 | 205 | void 206 | on_accept(beast::error_code ec, tcp::socket socket) 207 | { 208 | if (ec) 209 | { 210 | fail(ec, "accept"); 211 | } 212 | else 213 | { 214 | // Create the session and run it 215 | std::make_shared< session >(std::move(socket))->run(); 216 | } 217 | 218 | // Accept another connection 219 | do_accept(); 220 | } 221 | }; 222 | 223 | //---- 224 | //-------------------------------------------------------------------------------------------------------- 225 | 226 | int 227 | main(int argc, char *argv[]) 228 | { 229 | auto const address = net::ip::make_address("0.0.0.0"); 230 | auto const port = static_cast< unsigned short >(6761); 231 | int threads = 1; 232 | 233 | // The io_context is required for all I/O 234 | net::io_context ioc { threads }; 235 | 236 | // Create and launch a listening port 237 | std::make_shared< listener >(ioc, tcp::endpoint { address, port })->run(); 238 | 239 | // Run the I/O service on the requested number of threads 240 | std::vector< std::thread > v; 241 | v.reserve(threads - 1); 242 | for (auto i = threads - 1; i > 0; --i) 243 | v.emplace_back([&ioc] { ioc.run(); }); 244 | ioc.run(); 245 | 246 | return EXIT_SUCCESS; 247 | } 248 | -------------------------------------------------------------------------------- /cmake/RequireBoost.cmake: -------------------------------------------------------------------------------- 1 | if(NOT REQUIRE_BOOST_CMAKE) 2 | set(REQUIRE_BOOST_CMAKE 1) 3 | else() 4 | return() 5 | endif() 6 | 7 | # 8 | # file level configuation 9 | # 10 | 11 | set(boost_PATCHES_DIR boost-patch) 12 | 13 | function(ListToString list outstr) 14 | set(result) 15 | foreach(item IN LISTS ${list}) 16 | set(result "${result} \"${item}\"") 17 | endforeach() 18 | set(${outstr} "${result}" PARENT_SCOPE) 19 | endfunction() 20 | 21 | function(BoostDeduceToolSet out) 22 | set(result) 23 | if (CMAKE_CXX_COMPILER_ID MATCHES "[Cl]ang") 24 | set(result "clang") 25 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 26 | set(result "gcc") 27 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 28 | set(result "msvc") 29 | else() 30 | message(FATAL_ERROR "compiler: ${CMAKE_CXX_COMPILER} : please update the script with a boost toolset") 31 | endif() 32 | message(STATUS "[dependencies]: Boost toolset deduced: ${result}") 33 | set("${out}" "${result}" PARENT_SCOPE) 34 | endfunction() 35 | 36 | function(BoostDeduceCXXVersion boost_version out) 37 | set(result) 38 | if (CMAKE_CXX_STANDARD LESS_EQUAL 17) 39 | set(result "${CMAKE_CXX_STANDARD}") 40 | elseif(CMAKE_CXX_STANDARD EQUAL 20) 41 | if (boost_version VERSION_LESS 1.74) 42 | set(result 2a) 43 | else() 44 | set(result 20) 45 | endif() 46 | else() 47 | message(FATAL_ERROR "c++ standard ${CMAKE_CXX_STANDARD} not supported yet") 48 | endif() 49 | message(STATUS "[dependencies]: Boost cxxstd deduced: ${result}") 50 | set("${out}" "${result}" PARENT_SCOPE) 51 | endfunction() 52 | 53 | function(RequireBoost ) 54 | cmake_parse_arguments(boost 55 | "" # options 56 | "VERSION;PREFIX" # 57 | "COMPONENTS" # 58 | ${ARGN}) #...) 59 | if (NOT boost_VERSION) 60 | message(FATAL_ERROR "RequireBoost: requires VERSION argument") 61 | endif() 62 | if (NOT boost_PREFIX) 63 | message(FATAL_ERROR "RequireBoost: requires PREFIX argument") 64 | endif() 65 | if (NOT boost_COMPONENTS) 66 | set(boost_COMPONENTS headers) 67 | endif() 68 | 69 | include(FetchContent) 70 | set(boost_git_repo "git@github.com:boostorg/boost.git") 71 | set(boost_git_branch "boost-${boost_VERSION}") 72 | FetchContent_Declare(boost 73 | GIT_REPOSITORY "${boost_git_repo}" 74 | GIT_TAG "${boost_git_branch}") 75 | FetchContent_GetProperties(boost) 76 | if (NOT boost_POPULATED) 77 | message(STATUS "[dependencies] Boost: downloading [${boost_git_repo}] [${boost_git_branch}]") 78 | FetchContent_Populate(boost) 79 | endif () 80 | 81 | # 82 | # patch step 83 | # 84 | 85 | file(GLOB patches CONFIGURE_DEPENDS "${boost_PATCHES_DIR}/version-${boost_VERSION}-*.patch") 86 | if (NOT boost_PATCHES_APPLIED STREQUAL "${patches}") 87 | message(STATUS "[dependencies] applying boost patches ${patches}") 88 | 89 | set(boost_BOOTSTRAPPED "" CACHE INTERNAL "") 90 | set(boost_BUILT "" CACHE INTERNAL "") 91 | 92 | foreach(patch IN LISTS patches) 93 | string(REGEX MATCH "^(.*)/version-(.*)-(.*)\\.patch$" matched "${patch}") 94 | if (NOT matched) 95 | message(FATAL_ERROR "[dependencies] incorrect patch file format: ${patch} ${matched}") 96 | endif() 97 | set(component ${CMAKE_MATCH_3}) 98 | set(proc_args "patch" "-p2" "--backup" "-i" "${patch}") 99 | message(STATUS "[dependencies] patching boost component ${component} with ${proc_args}") 100 | message(STATUS "[dependencies] patching in directory ${boost_SOURCE_DIR}/boost/${component}") 101 | execute_process(COMMAND ${proc_args} WORKING_DIRECTORY "${boost_SOURCE_DIR}/boost/${component}" RESULT_VARIABLE res) 102 | if (res) 103 | message(WARNING "[dependencies] failed to patch boost component ${component} with ${proc_args} in directory ${boost_SOURCE_DIR}/boost/${component}") 104 | endif() 105 | endforeach() 106 | set(boost_PATCHES_APPLIED "${patches}" CACHE INTERNAL "patches applied to boost") 107 | endif() 108 | 109 | # 110 | # bootstrap 111 | # 112 | 113 | set(bootstrap_COMMAND "./bootstrap.sh") 114 | if (NOT boost_BOOTSTRAPPED STREQUAL "${bootstrap_COMMAND}") 115 | message("[boost] bootstrapping at ${boost_SOURCE_DIR} with ${bootstrap_COMMAND}") 116 | 117 | set(boost_BUILT "" CACHE INTERNAL "") 118 | 119 | execute_process( 120 | COMMAND ${bootstrap_COMMAND} 121 | WORKING_DIRECTORY ${boost_SOURCE_DIR} 122 | RESULT_VARIABLE boost_BOOTSTRAP_ERROR) 123 | if (NOT boost_BOOTSTRAP_ERROR) 124 | set(boost_BOOTSTRAPPED "${bootstrap_COMMAND}" CACHE INTERNAL "boost has been bootstrapped") 125 | else() 126 | message(FATAL_ERROR "cannot bootstrap boost, error code: ${boost_BOOTSTRAP_ERROR}") 127 | endif() 128 | endif () 129 | 130 | # 131 | # build step 132 | # 133 | 134 | include(ProcessorCount) 135 | ProcessorCount(processors) 136 | set(b2_args 137 | "variant=release" 138 | "link=static" 139 | "threading=multi") 140 | BoostDeduceToolSet(toolset) 141 | list(APPEND b2_args "toolset=${toolset}") 142 | BoostDeduceCXXVersion(${boost_VERSION} cxxstd) 143 | list(APPEND b2_args "cxxstd=${cxxstd}") 144 | if (CMAKE_CXX_FLAGS) 145 | string(REGEX REPLACE "-std=[^ \t\r\n$]*" "" flags "${CMAKE_CXX_FLAGS}") 146 | string(STRIP "${flags}" flags) 147 | if (NOT flags STREQUAL "") 148 | list(APPEND b2_args "cxxflags=${flags}") 149 | endif() 150 | endif() 151 | if (CMAKE_C_FLAGS) 152 | list(APPEND b2_args "cxxflags=${CMAKE_C_FLAGS}") 153 | endif() 154 | if (CMAKE_EXE_LINKER_FLAGS) 155 | list(APPEND b2_args "linkflags=${CMAKE_EXE_LINKER_FLAGS}") 156 | endif() 157 | list(APPEND b2_args 158 | "--build-dir=${boost_BINARY_DIR}/build" 159 | "--stage-dir=${boost_BINARY_DIR}/stage" 160 | "--prefix=${boost_PREFIX}" 161 | "-j${processors}") 162 | foreach(comp IN LISTS boost_COMPONENTS) 163 | list(APPEND b2_args "--with-${comp}") 164 | endforeach() 165 | list(APPEND b2_args 166 | "stage" 167 | "install") 168 | 169 | if(NOT boost_BUILT STREQUAL "${b2_args}") 170 | ListToString(b2_args args_str) 171 | message(STATUS "[dependencies] building boost with ${args_str}") 172 | 173 | execute_process( 174 | COMMAND "./b2" "--reconfigure" ${b2_args} 175 | WORKING_DIRECTORY ${boost_SOURCE_DIR} 176 | OUTPUT_VARIABLE boost_OUT 177 | ERROR_VARIABLE boost_ERROR 178 | RESULT_VARIABLE boost_BUILD_ERROR) 179 | if (NOT boost_BUILD_ERROR) 180 | set(boost_BUILT "${b2_args}" CACHE INTERNAL "components built for boost") 181 | message(STATUS "[dependencies] Boost build success: ${boost_OUT}") 182 | else() 183 | message(STATUS "[dependencies] Boost build output:\n ${boost_OUT}") 184 | message(STATUS "[dependencies] Boost build error:\n ${boost_ERROR}") 185 | message(FATAL_ERROR "[dependencies] boost build failed") 186 | endif() 187 | else() 188 | message(STATUS "[dependencies] Using cached boost: ${b2_args}") 189 | endif() 190 | 191 | set(BOOST_ROOT "${boost_PREFIX}") 192 | set(BOOST_ROOT "${BOOST_ROOT}" PARENT_SCOPE) 193 | message(STATUS "[dependencies] BOOST_ROOT = ${BOOST_ROOT}") 194 | 195 | endfunction() 196 | -------------------------------------------------------------------------------- /lib/util/poly_handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace beast_fun_times::util 5 | { 6 | namespace detail 7 | { 8 | union sbo_storage 9 | { 10 | sbo_storage() noexcept 11 | { 12 | } 13 | 14 | ~sbo_storage() 15 | { 16 | } 17 | 18 | void * short_[6]; 19 | std::unique_ptr< unsigned char[] > long_; 20 | }; 21 | 22 | template < class Ret, class... Args > 23 | struct poly_handler_vtable 24 | { 25 | // move-construct from value - may throw 26 | virtual void 27 | move_construct(sbo_storage &storage, void *source) const = 0; 28 | 29 | // move construct from other SBO. May not throw 30 | virtual void 31 | move_construct(sbo_storage &storage, 32 | sbo_storage &source) const noexcept = 0; 33 | 34 | virtual Ret 35 | invoke(sbo_storage &storage, Args... args) const = 0; 36 | 37 | virtual void 38 | destroy(sbo_storage &storage) const noexcept = 0; 39 | }; 40 | 41 | template < class Actual, class Ret, class... Args > 42 | static auto 43 | make_small_poly_handler_vtable() 44 | { 45 | static_assert(sizeof(Actual) <= sizeof(sbo_storage)); 46 | 47 | static const struct : poly_handler_vtable< Ret, Args... > 48 | { 49 | static Actual & 50 | realise(sbo_storage &storage) noexcept 51 | { 52 | return *reinterpret_cast< Actual * >(&storage.short_[0]); 53 | } 54 | 55 | void 56 | move_construct(sbo_storage &storage, 57 | void * source) const override 58 | { 59 | new (&realise(storage)) Actual( 60 | std::move(*reinterpret_cast< Actual * >(source))); 61 | } 62 | 63 | void 64 | move_construct(sbo_storage &storage, 65 | sbo_storage &source) const noexcept override 66 | { 67 | new (&realise(storage)) Actual(std::move(realise(source))); 68 | } 69 | 70 | Ret 71 | invoke(sbo_storage &storage, Args... args) const override 72 | { 73 | auto act = std::move(realise(storage)); 74 | destroy(storage); 75 | return act(std::move(args)...); 76 | } 77 | 78 | void 79 | destroy(sbo_storage &storage) const noexcept override 80 | { 81 | realise(storage).~Actual(); 82 | } 83 | } x; 84 | return &x; 85 | } 86 | 87 | template < class Actual, class Ret, class... Args > 88 | auto 89 | make_big_poly_handler_vtable() 90 | { 91 | static_assert(sizeof(Actual) > sizeof(sbo_storage)); 92 | 93 | static const struct : poly_handler_vtable< Ret, Args... > 94 | { 95 | void 96 | move_construct(sbo_storage &storage, 97 | void * source) const override 98 | { 99 | constexpr auto size = sizeof(Actual); 100 | new (&storage.long_) std::unique_ptr< unsigned char[] >(); 101 | storage.long_.reset(new unsigned char[size]); 102 | new (storage.long_.get()) Actual( 103 | std::move(*reinterpret_cast< Actual * >(source))); 104 | } 105 | 106 | void 107 | move_construct(sbo_storage &storage, 108 | sbo_storage &source) const noexcept override 109 | { 110 | new (&storage.long_) std::unique_ptr(std::move(source.long_)); 111 | } 112 | 113 | Ret 114 | invoke(sbo_storage &storage, Args... args) const override 115 | { 116 | auto act = std::move(*reinterpret_cast(storage.long_.get())); 117 | storage.long_.reset(); 118 | destroy(storage); 119 | return act(std::move(args)...); 120 | } 121 | 122 | void 123 | destroy(sbo_storage &storage) const noexcept override 124 | { 125 | if (storage.long_) 126 | reinterpret_cast(storage.long_.get())->~Actual(); 127 | storage.long_.~unique_ptr< unsigned char[] >(); 128 | } 129 | } x; 130 | return &x; 131 | } 132 | 133 | } // namespace detail 134 | /// A polymorphic completion handler 135 | /// \tparam Sig 136 | template < class Sig > 137 | struct poly_handler; 138 | 139 | template < class Ret, class... Args > 140 | class poly_handler< Ret(Args...) > 141 | { 142 | detail::sbo_storage storage_; 143 | detail::poly_handler_vtable< Ret, Args... > const *kind_; 144 | 145 | public: 146 | poly_handler() 147 | : storage_ {} 148 | , kind_(nullptr) 149 | { 150 | } 151 | 152 | template < 153 | class Actual, 154 | std::enable_if_t< !std::is_same_v< Actual, poly_handler > && 155 | std::is_invocable_r_v< Ret, Actual, Args... > > 156 | * = nullptr > 157 | poly_handler(Actual actual) 158 | { 159 | constexpr auto size = sizeof(Actual); 160 | if constexpr (size > sizeof(storage_)) 161 | { 162 | // non-sbo 163 | auto kind = detail:: 164 | make_big_poly_handler_vtable< Actual, Ret, Args... >(); 165 | kind->move_construct(storage_, std::addressof(actual)); 166 | kind_ = kind; 167 | } 168 | else 169 | { 170 | // sbo 171 | auto kind = detail:: 172 | make_small_poly_handler_vtable< Actual, Ret, Args... >(); 173 | kind->move_construct(storage_, std::addressof(actual)); 174 | kind_ = kind; 175 | } 176 | } 177 | 178 | poly_handler(poly_handler &&other) noexcept 179 | : storage_ {} 180 | , kind_(std::exchange(other.kind_, nullptr)) 181 | { 182 | if(kind_) 183 | kind_->move_construct(storage_, other.storage_); 184 | } 185 | 186 | poly_handler & 187 | operator=(poly_handler &&other) noexcept 188 | { 189 | this->~poly_handler(); 190 | new (this) poly_handler(std::move(other)); 191 | return *this; 192 | } 193 | 194 | ~poly_handler() 195 | { 196 | if (kind_) 197 | kind_->destroy(storage_); 198 | } 199 | 200 | Ret 201 | operator()(Args... args) 202 | { 203 | auto k = std::exchange(kind_, nullptr); 204 | if (k) 205 | return k->invoke(storage_, std::move(args)...); 206 | else 207 | throw std::bad_function_call(); 208 | } 209 | 210 | bool 211 | has_value() const 212 | { 213 | return kind_ != nullptr; 214 | } 215 | operator bool() const 216 | { 217 | return has_value(); 218 | } 219 | }; 220 | } // namespace beast_fun_times::util -------------------------------------------------------------------------------- /pre-cxx20/mime_reader/main.cpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace program { 10 | 11 | /// @brief Simple implementation to extract boundary. It's a little permissive 12 | /// but it'll do 13 | /// @param content_type 14 | /// @return 15 | boost::optional< std::string > 16 | deduce_boundary(beast::string_view content_type) 17 | { 18 | boost::optional< std::string > result; 19 | 20 | static std::regex r1 { 21 | R"regex(^multipart/x-mixed-replace\s*;\s*boundary="([^"]+)"$)regex", 22 | std::regex_constants::icase 23 | }; 24 | static std::regex r2 { 25 | R"regex(^multipart/x-mixed-replace\s*;\s*boundary=([^"]+)$)regex", 26 | std::regex_constants::icase 27 | }; 28 | 29 | auto match = std::cmatch(); 30 | 31 | if (std::regex_match(content_type.begin(), content_type.end(), match, r1) || 32 | std::regex_match(content_type.begin(), content_type.end(), match, r2)) 33 | { 34 | result = match[1].str(); 35 | } 36 | 37 | return result; 38 | } 39 | 40 | std::string 41 | mimeify(std::string boundary, std::vector< std::string > docs) 42 | { 43 | std::ostringstream oss; 44 | for (auto const &doc : docs) 45 | { 46 | oss << "--" << boundary << "\r\n"; 47 | oss << "Content-Type: image/jpeg\r\n"; 48 | oss << "Content-Length: " << doc.size() << "\r\n"; 49 | oss << "\r\n"; 50 | oss << doc << "\r\n"; 51 | } 52 | oss << "--" << boundary << "--\r\n"; 53 | return oss.str(); 54 | } 55 | 56 | std::string 57 | chunkify(int chunks, std::string input) 58 | { 59 | std::ostringstream oss; 60 | 61 | auto chunk_len = (input.size() + (chunks - 1)) / chunks; 62 | auto pos = std::size_t(0); 63 | while (pos < input.size()) 64 | { 65 | auto chunk = input.substr(pos, chunk_len); 66 | oss << std::hex << chunk.size() << "\r\n"; 67 | oss << chunk << "\r\n"; 68 | pos += chunk.size(); 69 | } 70 | oss << "0\r\n"; 71 | return oss.str(); 72 | } 73 | 74 | error_code 75 | test(beast::string_view sample) 76 | { 77 | try 78 | { 79 | net::io_context ioc; 80 | auto exec = ioc.get_executor(); 81 | 82 | auto spin = [&ioc](std::string const &message) { 83 | spdlog::debug("spin: {}", message); 84 | ioc.restart(); 85 | std::size_t count = 0; 86 | while (auto c = ioc.poll()) 87 | count += c; 88 | return count; 89 | }; 90 | 91 | auto local = 92 | beast::test::stream(ioc); // simulate the local socket/tls stream 93 | auto remote = connect( 94 | local); // simulate the remote socket so we can push in test data 95 | 96 | remote.write_some(net::buffer(sample.data(), sample.size())); 97 | 98 | error_code ec; 99 | std::size_t bytes_transferred = 0; 100 | auto handler = [&](error_code ec_, std::size_t bytes_transferred_) { 101 | ec = ec_; 102 | bytes_transferred = bytes_transferred_; 103 | }; 104 | auto quit_check = [&] { 105 | if (ec) 106 | { 107 | spdlog::error("read error {}", ec); 108 | throw system_error(ec); 109 | } 110 | }; 111 | 112 | http::response_parser< http::buffer_body > parser; 113 | beast::multi_buffer rxbuf; 114 | 115 | // 116 | // This is where you read the header 117 | // 118 | 119 | parser.eager(false); 120 | std::string work_store; 121 | auto outbuf = asio::dynamic_buffer(work_store); 122 | 123 | { 124 | outbuf.grow(256); 125 | auto prev = outbuf.size(); 126 | auto dat = outbuf.data(prev, 256); 127 | parser.get().body().data = dat.data(); 128 | parser.get().body().size = dat.size(); 129 | parser.get().body().more = true; 130 | http::async_read_header(local, rxbuf, parser, handler); 131 | spin("reading header"); 132 | outbuf.shrink(256 - bytes_transferred); 133 | quit_check(); 134 | } 135 | 136 | auto boundary = 137 | deduce_boundary(parser.get()[http::field::content_type]); 138 | if (not boundary) 139 | { 140 | spdlog::error("Not mime"); 141 | return error_code(boost::system::errc::protocol_error, 142 | boost::system::generic_category()); 143 | } 144 | 145 | spdlog::info("detected mime boundary: {}, chunked: {}", 146 | *boundary, 147 | parser.chunked()); 148 | 149 | // 150 | // now we want to scan the input looking for the boundary 151 | // 152 | 153 | bool first_part = true; 154 | 155 | auto boundary_pos = std::string::size_type(0); 156 | std::string boundary_line; 157 | 158 | next_boundary: 159 | for (;;) 160 | { 161 | boundary_line = "--" + *boundary + "\r\n"; 162 | boundary_pos = work_store.find(boundary_line); 163 | if (boundary_pos >= work_store.size()) 164 | { 165 | boundary_line = "--" + *boundary + "--\r\n"; 166 | boundary_pos = work_store.find(boundary_line); 167 | } 168 | 169 | // found boundary 170 | if (boundary_pos < work_store.size()) 171 | break; 172 | 173 | { 174 | outbuf.grow(512); 175 | auto dat = outbuf.data(outbuf.size() - 512, outbuf.size()); 176 | parser.get().body().data = dat.data(); 177 | parser.get().body().size = dat.size(); 178 | parser.get().body().more = false; 179 | http::async_read_some(local, rxbuf, parser, handler); 180 | spin("seeking boundary"); 181 | outbuf.shrink(512 - parser.get().body().size); 182 | } 183 | quit_check(); 184 | } 185 | if (!first_part) 186 | { 187 | // process the previous part 188 | std::cout << "***HERE IS A PART: " 189 | << work_store.substr(0, boundary_pos - 2) << std::endl; 190 | } 191 | 192 | if (boundary_line == "--" + *boundary + "--\r\n") 193 | { 194 | spdlog::info("End of Stream"); 195 | return error_code(); 196 | } 197 | 198 | work_store.erase(0, boundary_pos + boundary_line.size()); 199 | 200 | // now pick up headers 201 | next_header: 202 | auto hdr_pos = work_store.find("\r\n"); 203 | if (hdr_pos == std::string::npos) 204 | { 205 | { 206 | outbuf.grow(512); 207 | auto dat = outbuf.data(outbuf.size() - 512, outbuf.size()); 208 | parser.get().body().data = dat.data(); 209 | parser.get().body().size = dat.size(); 210 | parser.get().body().more = false; 211 | http::async_read_some(local, rxbuf, parser, handler); 212 | spin("seeking boundary header"); 213 | outbuf.shrink(512 - parser.get().body().size); 214 | } 215 | quit_check(); 216 | goto next_header; 217 | } 218 | if (hdr_pos == 0) 219 | { 220 | // end of headers 221 | } 222 | else 223 | { 224 | spdlog::info("part header: {}", work_store.substr(0, hdr_pos)); 225 | work_store.erase(0, hdr_pos + 2); 226 | goto next_header; 227 | } 228 | 229 | first_part = false; 230 | goto next_boundary; 231 | } 232 | catch (system_error &ec) 233 | { 234 | return ec.code(); 235 | } 236 | } 237 | 238 | } // namespace program 239 | 240 | int 241 | main() 242 | { 243 | using namespace program; 244 | 245 | // now simulate our host's read 246 | 247 | auto docs = std::vector< std::string > { "abc", "def" }; 248 | auto payload1 = mimeify("FrameSeparator", docs); 249 | auto payload2 = chunkify(4, payload1); 250 | 251 | auto unchunked = 252 | std::string( 253 | "HTTP/1.1 200 OK\r\n" 254 | "Date: Thu, 22 Oct 2020 20:26:01 GMT\r\n" 255 | "Content-Type: " 256 | "multipart/x-mixed-replace;boundary=\"FrameSeparator\"\r\n" 257 | "\r\n") + 258 | payload1; 259 | 260 | auto chunked = 261 | std::string( 262 | "HTTP/1.1 200 OK\r\n" 263 | "Date: Thu, 22 Oct 2020 20:26:01 GMT\r\n" 264 | "Content-Type: " 265 | "multipart/x-mixed-replace;boundary=\"FrameSeparator\"\r\n" 266 | "Transfer-Encoding: chunked\r\n" 267 | "\r\n") + 268 | payload2; 269 | 270 | std::cout << "Test CHUNKED\n" 271 | "============\n\n"; 272 | test(chunked); 273 | 274 | std::cout << "\n" 275 | "Test NOT CHUNKED\n" 276 | "================\n\n"; 277 | 278 | test(unchunked); 279 | } -------------------------------------------------------------------------------- /pre-cxx20/blog-2020-09/wss_transport.cpp: -------------------------------------------------------------------------------- 1 | #include "wss_transport.hpp" 2 | #include 3 | 4 | namespace project 5 | { 6 | using namespace std::literals; 7 | 8 | wss_transport::wss_transport(net::io_context::executor_type exec, 9 | ssl::context & ssl_ctx) 10 | : exec_(exec) 11 | , websock_(get_executor(), ssl_ctx) 12 | { 13 | } 14 | 15 | void 16 | wss_transport::start() 17 | { 18 | net::dispatch(get_executor(), [this] { 19 | switch (state_) 20 | { 21 | case not_started: 22 | on_start(); 23 | break; 24 | case connecting: 25 | case connected: 26 | case closing: 27 | case finished: 28 | BOOST_ASSERT(false); 29 | break; 30 | } 31 | }); 32 | } 33 | void 34 | wss_transport::stop() 35 | { 36 | // to be continued 37 | } 38 | 39 | struct wss_transport::connect_op : asio::coroutine 40 | { 41 | using executor_type = wss_transport::executor_type; 42 | using websock = wss_transport::websock; 43 | 44 | struct impl_data 45 | { 46 | impl_data(websock & ws, 47 | std::string host, 48 | std::string port, 49 | std::string target) 50 | : ws(ws) 51 | , resolver(ws.get_executor()) 52 | , host(host) 53 | , port(port) 54 | , target(target) 55 | { 56 | } 57 | 58 | layer_0 & 59 | tcp_layer() const 60 | { 61 | return ws.next_layer().next_layer(); 62 | } 63 | 64 | layer_1 & 65 | ssl_layer() const 66 | { 67 | return ws.next_layer(); 68 | } 69 | 70 | websock & ws; 71 | net::ip::tcp::resolver resolver; 72 | net::ip::tcp::resolver::results_type endpoints; 73 | std::string host, port, target; 74 | }; 75 | 76 | connect_op(websock & ws, 77 | std::string host, 78 | std::string port, 79 | std::string target) 80 | : impl_(std::make_unique< impl_data >(ws, host, port, target)) 81 | { 82 | } 83 | 84 | template < class Self > 85 | void 86 | operator()(Self & self, 87 | error_code ec, 88 | net::ip::tcp::resolver::results_type results) 89 | { 90 | impl_->endpoints = results; 91 | (*this)(self, ec); 92 | } 93 | 94 | template < class Self > 95 | void 96 | operator()(Self &self, error_code ec, net::ip::tcp::endpoint const &) 97 | { 98 | (*this)(self, ec); 99 | } 100 | 101 | template < class Self > 102 | void operator()(Self &self, error_code ec = {}, std::size_t = 0) 103 | { 104 | if (ec) 105 | self.complete(ec); 106 | 107 | auto &impl = *impl_; 108 | 109 | #include 110 | reenter(*this) 111 | { 112 | yield impl.resolver.async_resolve( 113 | impl.host, impl.port, std::move(self)); 114 | 115 | impl.tcp_layer().expires_after(15s); 116 | yield impl.tcp_layer().async_connect(impl.endpoints, 117 | std::move(self)); 118 | 119 | if (!SSL_set_tlsext_host_name(impl.ssl_layer().native_handle(), 120 | impl.host.c_str())) 121 | return self.complete( 122 | error_code(static_cast< int >(::ERR_get_error()), 123 | net::error::get_ssl_category())); 124 | 125 | impl.tcp_layer().expires_after(15s); 126 | yield impl.ssl_layer().async_handshake(ssl::stream_base::client, 127 | std::move(self)); 128 | 129 | impl.tcp_layer().expires_after(15s); 130 | yield impl.ws.async_handshake( 131 | impl.host, impl.target, std::move(self)); 132 | 133 | impl.tcp_layer().expires_never(); 134 | yield self.complete(ec); 135 | } 136 | #include 137 | } 138 | 139 | std::unique_ptr< impl_data > impl_; 140 | }; 141 | 142 | void 143 | wss_transport::initiate_connect(std::string host, 144 | std::string port, 145 | std::string target) 146 | { 147 | switch (state_) 148 | { 149 | case state_type::not_started: 150 | break; 151 | 152 | case connecting: 153 | case connected: 154 | case closing: 155 | case finished: 156 | return; 157 | } 158 | 159 | state_ = connecting; 160 | 161 | auto handler = [this](error_code const &ec) { 162 | if (ec) 163 | event_transport_error(ec); 164 | else 165 | event_transport_up(); 166 | }; 167 | 168 | net::async_compose< decltype(handler), void(error_code) >( 169 | connect_op( 170 | websock_, std::move(host), std::move(port), std::move(target)), 171 | handler, 172 | get_executor()); 173 | } 174 | 175 | void 176 | wss_transport::send_text_frame(std::string frame) 177 | { 178 | if (state_ != connected) 179 | return; 180 | 181 | send_queue_.push_back(std::move(frame)); 182 | start_sending(); 183 | } 184 | 185 | void 186 | wss_transport::initiate_close() 187 | { 188 | } 189 | 190 | void 191 | wss_transport::on_start() 192 | { 193 | } 194 | 195 | void 196 | wss_transport::on_transport_up() 197 | { 198 | } 199 | 200 | void 201 | wss_transport::on_transport_error(std::exception_ptr ep) 202 | { 203 | } 204 | 205 | void 206 | wss_transport::on_text_frame(std::string_view frame) 207 | { 208 | } 209 | 210 | void 211 | wss_transport::on_close() 212 | { 213 | } 214 | 215 | auto 216 | wss_transport::get_executor() const -> const executor_type & 217 | { 218 | return exec_; 219 | } 220 | 221 | void 222 | wss_transport::event_transport_up() 223 | { 224 | state_ = connected; 225 | on_transport_up(); 226 | start_sending(); 227 | start_reading(); 228 | } 229 | 230 | void 231 | wss_transport::event_transport_error(const error_code &ec) 232 | { 233 | event_transport_error(std::make_exception_ptr(system_error(ec))); 234 | } 235 | 236 | void 237 | wss_transport::event_transport_error(std::exception_ptr ep) 238 | { 239 | switch (state_) 240 | { 241 | case connecting: 242 | case connected: 243 | case closing: 244 | state_ = state_type::finished; 245 | on_transport_error(std::move(ep)); 246 | break; 247 | 248 | default: 249 | break; 250 | } 251 | } 252 | 253 | void 254 | wss_transport::start_sending() 255 | { 256 | if (state_ == connected && send_state_ == not_sending && 257 | !send_queue_.empty()) 258 | { 259 | send_state_ = sending; 260 | websock_.async_write(net::buffer(send_queue_.front()), 261 | [this](error_code const &ec, std::size_t bt) { 262 | handle_send(ec, bt); 263 | }); 264 | } 265 | } 266 | 267 | void 268 | wss_transport::handle_send(const error_code &ec, std::size_t) 269 | { 270 | send_state_ = not_sending; 271 | 272 | send_queue_.pop_front(); 273 | 274 | if (ec) 275 | event_transport_error(ec); 276 | else 277 | start_sending(); 278 | } 279 | 280 | void 281 | wss_transport::start_reading() 282 | { 283 | if (state_ != connected) 284 | return; 285 | 286 | websock_.async_read(rx_buffer_, 287 | [this](error_code const &ec, std::size_t bt) { 288 | handle_read(ec, bt); 289 | }); 290 | } 291 | 292 | void 293 | wss_transport::handle_read(error_code const &ec, 294 | std::size_t bytes_transferred) 295 | { 296 | if (ec) 297 | event_transport_error(ec); 298 | else 299 | try 300 | { 301 | auto bytes = rx_buffer_.data(); 302 | if (websock_.text()) 303 | { 304 | auto frame = std::string_view( 305 | reinterpret_cast< const char * >(bytes.data()), 306 | bytes_transferred); 307 | on_text_frame(frame); 308 | } 309 | else 310 | { 311 | // @todo: add support for binary frames 312 | } 313 | rx_buffer_.consume(bytes_transferred); 314 | start_reading(); 315 | } 316 | catch (...) 317 | { 318 | event_transport_error(std::current_exception()); 319 | } 320 | } 321 | 322 | } // namespace project -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Richard Hodges (hodges.r@gmail.com) 2 | 3 | Disributed under the Boost Software Licence, Version 1.0. (See accompanying file 4 | [LICENCE_1_0.txt](LICENCE_1_0.txt) or a copy at 5 | [http://www.boost.org/LICENSE_1_0.txt]( http://www.boost.org/LICENSE_1_0.txt)) 6 | 7 | This repo contains a number of examples that I have written in response to being asked 8 | questions about Boost.Beast and Boost.Asio. 9 | 10 | This repository makes NO CLAIMS WHATSOEVER to represent the views of the Beast team, 11 | or the views of Beast's author. In particular, the coding style used here is my own, 12 | totally arbitrary and is not endorsed by the Beast team or used in the Beast library itself. 13 | 14 | I use a `.clang-format` file and take what I am given - because I can't be bothered to 15 | have code style arguments with other people or myself. My personal view is that the ends 16 | justify the means. Correct code is code that works, delivers required functionality, 17 | does not leak resources, shuts down cleanly and never segfaults. If you want to fork 18 | the repo, prettify the code and tinker with it for performance gains, please fill your 19 | boots. 20 | 21 | I may even use the occasional `goto`. If that's going to make you spit your 22 | dummy[¹](#1) out, you can leave the room now. 23 | 24 | The examples are provided in response to questions that do not already have a clear 25 | answer in the official documentation, or perhaps the documentation does cover it but 26 | people want to see an example because their level of pre-existing expertise is not 27 | sufficient to understand what it being presented to them in the documentation 28 | (this is common). 29 | 30 | **The Beast documentation clearly states that users of Beast should already be proficient 31 | with Asio.** 32 | 33 | This is reasonable as it keeps the human cost of support in the Beast maintenance team 34 | low. 35 | 36 | However in reality, there is a chicken and egg scenario: Beast provides people a reason 37 | to use Asio. But it demands that people first learn Asio. Asio is hard to learn, even harder 38 | without a motivating use case. In today's HTTP/Websocket-centric world, Beast is often the 39 | motivating factor. 40 | 41 | Asio is hard to learn because its documentation expects a certain level of knowledge 42 | in computer science, asynchronous messaging and underlying network IO. I've been using 43 | Asio for some time in production projects. My first attempts were wrong, because I 44 | didn't understand (and didn't bother to read up properly) on the meanings of 45 | _Execution Context_, _Implicit Strand_, _Completion Handler_ and _Completion Token_. 46 | I just wanted to dive in and make things happen. 47 | 48 | I expect since you're here, you do too. 49 | 50 | This is why this repository exists. To provide annotated examples which hopefully 51 | explain why things are done the way they are (at least by me). 52 | 53 | # Common Misconceptions 54 | 55 | ## Completion Handler 56 | 57 | Many people think that _Completion Handler_ means callback. 58 | 59 | *This is totally false.* 60 | 61 | OK, it's not _totally_ false. A _Completion Handler_ is a kind of callback, in the same way 62 | that an elephant is a kind of animal. All elephants are indeed animals, but not all animals 63 | have elongated trunks, long memories and are being hunted to extinction by psychopathic 64 | hairless apes for their solidified whiskers. 65 | 66 | It is better to think of a _Completion Handler_, in the context of the last argument provided 67 | to an _Asynchronous Initiating Function_ as a form of _Future_[²](#2). 68 | It is a guarantee that some code will execute in the future by being _Invoked_ by an 69 | _Executor_. Once. This means something different to merely _Callback_. 70 | 71 | The code will be invoked on its _Associated Executor_, which is either: 72 | 73 | * The default executor associated with the IO object that has promised to invoke your 74 | completion handler, or 75 | * The executor you bound to your completion handler function with 76 | `asio::bind_executor`, or 77 | * The executor associated with the current coroutine (the one you specified in the 78 | call to `asio::co_spawn` if you supplied the `asio::use_awaitable` 79 | _Completion Token_, or 80 | * Totally irrelevant if you supplied the _Completion Token_ `asio::use_future` 81 | 82 | ## Completion Token 83 | 84 | You might be tempted to think that a _Completion Token_ means the same thing as a 85 | _Completion Handler_. 86 | 87 | **You would be wrong.** 88 | 89 | And this time, no qualification required, wrong. 90 | 91 | A _Completion Token_ is different from a _Completion Handler_ in the same way that a high 92 | performance nickel-based superalloy jet turbine blade is different from a pet rat. 93 | 94 | A _Completion Handler_ is a function object that will be _Invoked_ on the _Associated 95 | Executor_ in response to the completion of an _Asynchronous Operation_. 96 | 97 | A _Completion Token_ describes to Asio how to rewrite the _Initiating Function_ in order 98 | to: 99 | * Ensure that the Asynchronous Operation is initiated correctly. 100 | * Ensure that the correct _Completion Handler_ is manufactured and passed to the asynchronous 101 | operation for later execution. 102 | * Ensure that the correct _Result Type_ is returned to the caller, so that the caller can 103 | await the _Completion_ of the _Asynchronous Operation_ in the appropriate way. 104 | 105 | For example, the _Completion Handler_ produced by the _Completion Token_ `asio::use_awaitable`, 106 | actually invokes code to resume the current coroutine upon operation completion. 107 | 108 | The _Completion Handler_ produced by the _Completion Token_ `asio::use_future` 109 | actually invokes code to supply a value or an error to the `std::promise` backing 110 | the `std::future` you received as a result of calling the _Initiating Function_, e.g.: 111 | ```c++ 112 | auto f = my_timer.async_wait(asio::use_future); 113 | ``` 114 | 115 | "But I've always just supplied a lambda"... 116 | 117 | Right. Because if you supply an _Completion Handler_ where a _Completion Token_ is specified 118 | in the documents, a minimal transformation on your handler is made, to ensure that: 119 | * The initiating function returns `void` 120 | * All steps of the _Asynchronous Operation_ make progress by being invoked by the IO object's 121 | _Default Executor_. 122 | * the supplied lambda will be _Invoked_ through the io object's _Default excecutor_ - once. 123 | 124 | Examples: 125 | 126 | ```c++ 127 | using namespace asio; 128 | 129 | // This is an execution context, not an executor. The only thing you should ever do 130 | // with this is call run() on it. Do not move it, store it in a shared pointer or 131 | // any other daft thing. create it in main() and leave it there. 132 | 133 | auto ioc = io_context(); 134 | 135 | // This is an Executor. You can copy this and pass it around. It is essentially 136 | // a cheap handle to the io_context. 137 | 138 | auto e = ioc.get_executor(); 139 | 140 | // This timer's Default Executor is e 141 | auto t = system_timer(e); 142 | 143 | // as is the socket's 144 | auto s = ip::tcp::socket(e); 145 | 146 | // f is a std::future. It will throw if the timer errors out or is cancelled. 147 | auto f = t.async_wait(use_future); 148 | 149 | // Returns an awaitable, which you must co_await. When it _resumes_ your coroutine, 150 | // either `bytes` will contain the number of bytes you actually read or an exception 151 | // (of type std::system_error) will be thrown. 152 | 153 | std::vector mem(128); 154 | auto bytes = co_await s.async_read_some(buffer(mem), use_awaitable); 155 | 156 | async_wait(t, [](error_code ec){ 157 | // This is a Completion Handler. Think of it as a FUTURE. It will happen exactly once. 158 | // It will happen by being called by executor e. 159 | }); 160 | ``` 161 | 162 | ## "All steps of the Asynchronous Operation make progress by being invoked by the IO object's Default Executor" 163 | 164 | Wait, what? 165 | 166 | consider: 167 | 168 | ```c++ 169 | auto bytes = co_await beast::http::async_read(sock, buffer, parser, asio::use_awaitable); 170 | ``` 171 | 172 | In the above asynchronous operation, how many actual asynchronous reads will be performed before the 173 | macro operation of consuming an entire HTTP request _completes_? It's not knowable until the final 174 | completion handler is invoked and (in this case), the current coroutine resumes execution. 175 | 176 | So what? 177 | 178 | Well, every time the sub-operations of which this operation is composed need to acquire or parse more 179 | data, they are going to touch the socket, or the buffer, or the parser, or all three. 180 | 181 | On which thread should they do this? 182 | 183 | They will make progress within the context of the _Completion Handler_'s _Associated Executor_. In this 184 | case, the same executor on which the calling coroutine make progress. 185 | 186 | Why is that important? 187 | 188 | Because the 'reader' coroutine may not be the only one touching the socket. There may well be a 'writer' 189 | coroutine in progress too. And maybe a 'timer' coroutine which at some point might call `cancel` on 190 | our socket. Asio (and Beast) IO Objects are not thread-safe by design. That call to `cancel` or 191 | `async_write` had better happen on the _same implicit or explicit strand_ of execution. 192 | 193 | The reasons they are not thread safe are: 194 | 195 | 1. Thread safety would unduly pessimise single-threaded programs or programs that create 196 | _Implicit Strands_. (i.e. they would be slower). 197 | 198 | 2. IO Objects and in particular complex asynchronous streams and operations like the above function 199 | would be harder to maintain. 200 | 201 | 3. _Intermediate Completion Handlers_ would need to each maintain some kind of mutex before touching 202 | any data they had access to. (see 1 and 2) 203 | 204 | In simple terms, it means that we can protect the entire "connection object" or "connection state" with 205 | a single `strand` and never have to worry about mutexes ever again. 206 | 207 | Asio is able to achieve extremely high _jitterless_ throughput because of its reliance of execution 208 | of code within executors. This method of execution provides very high confidence of fair sharing of 209 | cpu time. This is particularly important in high capacity server environments, but is 210 | also useful nice to have in clients. 211 | 212 | # Contributing 213 | 214 | You are welcome to contribute a PR. If it brings new dependencies, please make sure 215 | they build properly using CMake FetchContent. User experience of this repo should be 216 | as trouble-free as possible. 217 | 218 | # Commenting and Raising Issues 219 | 220 | Please feel free to comment and raise issues or contact me in slack once the procedure 221 | below has been followed. 222 | 223 | Issues raised over code layout or style issues that do not affect program correctness 224 | or efficiency will simply be ignored and closed. 225 | 226 | # Getting Help 227 | 228 | Here is the procedure for getting help: 229 | 230 | Does the thing I'm doing resemble in any way one of the examples here? 231 | 232 | if yes, 233 | 234 |     copy the example and tinker from there. 235 | 236 |     if you get really stuck, goto [Slack](#Slack) 237 | 238 | if no, 239 | 240 |     submit a PR with a link to compilable code on 241 | [Goldbolt](https://godbolt.org/) demonstrating what you have already tried. 242 | 243 | End 244 | 245 | [Link to Slack Invitation](https://cppalliance.org/slack/) Join 246 | the `#beast` channel and politely as a question there. Expect to be flamed if the answer 247 | is in the docs. 248 | 249 | # Social Responsibility Policy 250 | 251 | I'm not a socially reponsible person. Why I'm allowed out by myself it is a mystery to 252 | me. If you feel insulted or triggered by anything I say to you, you should probably 253 | grow up and stop being a baby. Even better, think of a more offensive retort. If you 254 | make me laugh, I may even respect you. 255 | 256 | In code, there is correct (program functions) and there is wrong (program does not 257 | function, or functions by luck). Your feelings are irrelevant. 258 | 259 | # Environmental Responsibility Policy 260 | 261 | If you are cruel to animals or engaged in activities that reduce the natural beauty 262 | of the World, then we probably won't get on. I'd keep that quiet if I were you, or 263 | better yet, change your ways and become a good person. 264 | 265 | # Footnotes 266 | ¹ Americans might use the word 'comforter' here...[⏎](#a1)
267 | 268 | ² Anyone out there who can find an official text on computer science 269 | that disproves my claim that a completion handler is a form of future, please step forward. 270 | I enjoy a good argument, particularly one that is well presented and supported by factual 271 | documentation.[⏎](#a2)
272 | --------------------------------------------------------------------------------