├── .editorconfig ├── src ├── hello.cpp ├── qt_client_async │ ├── CMakeLists.txt │ ├── my_window.h │ ├── daytime_client.h │ ├── my_window.cpp │ ├── main.cpp │ └── daytime_client.cpp ├── ssl │ ├── dh2048.pem │ ├── ca.pem │ └── server.pem ├── utility.h ├── timer4_multi.cpp ├── timer1_sync.cpp ├── timer2_async.cpp ├── utility.cpp ├── timer5_threaded.cpp ├── CMakeLists.txt ├── timer3_lambda.cpp ├── timer7_memfunc.cpp ├── timer6_args.cpp ├── echo_server_sync.cpp ├── context_and_services.cpp ├── strand2.cpp ├── strand.cpp ├── echo_client_sync.cpp ├── echo_server_async.cpp ├── ssl_server.cpp ├── ssl_client.cpp ├── ssl_http_client_sync.cpp ├── ssl_http_client_async.cpp ├── echo_client_async.cpp ├── ssl_http_client_async_blocking.cpp └── ssl_http_client_async_blocking_timeout.cpp ├── README.md ├── CMakeLists.txt ├── Asio Example Notes.md ├── _clang-format ├── Asio_Tips_And_Notes_zh-CN.md └── Tutorial_zh-CN.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # All files 2 | [*] 3 | guidelines = 80 4 | guidelines_style = 3px solid ff9933 5 | -------------------------------------------------------------------------------- /src/hello.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "boost/asio/io_context.hpp" 4 | 5 | // Empty loop. 6 | // Print after the loop ends. 7 | 8 | int main() { 9 | boost::asio::io_context io_context; 10 | io_context.run(); 11 | 12 | std::cout << "Hello, World!" << std::endl; 13 | 14 | return 0; 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # boost-asio-study 2 | 3 | Examples and tutorials for C++ Boost Asio library. 4 | 5 | Based on *Boost v1.66+*. 6 | Tested with *VS 2013 & 2015* and *GCC on Ubuntu 18.04*. 7 | 8 | 2020-08-28: Tested with *VS 2019* and *Boost 1.74*. 9 | 10 | 教程: 11 | - [基于 Asio 的 C++ 网络编程](Tutorial_zh-CN.md) 12 | - [Asio 注意事项](Asio_Tips_And_Notes_zh-CN.md) 13 | -------------------------------------------------------------------------------- /src/qt_client_async/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Instruct CMake to run moc automatically when needed. 2 | set(CMAKE_AUTOMOC ON) 3 | 4 | find_package(Qt5Widgets) 5 | 6 | set(SRCS 7 | daytime_client.cpp 8 | daytime_client.h 9 | main.cpp 10 | my_window.cpp 11 | my_window.h) 12 | 13 | add_executable(qt_client_async WIN32 MACOSX_BUNDLE ${SRCS}) 14 | 15 | target_link_libraries(qt_client_async Qt5::Widgets) 16 | -------------------------------------------------------------------------------- /src/ssl/dh2048.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIIBCAKCAQEAyNnxZSYc6J89mDNnqOH8bnwBiAJxcaUS3PkIEcwW8D9o2BlNq6EO 3 | XKMIbdfwPFZi80GMpNu3YP2A2B42sAHmb7w7ZA92QDv3JjqzR0QuS/CkMv4CEjha 4 | QBFwBDDWnnHBSj4w/t54ii0SH34mWcjBItI2eMtnM9J6fnvNiWqJxdt4iA4mZjZD 5 | qZTjIRyjgKAevzkqAlBqQRoVUUgu+9Cf29wXjVl3bE+0VU5CdFeyT+Y9yunz88mq 6 | rGyx1uPt+zbIfxuNLH+coY67y1ht7iZEL5WLd3wGCycRT+lYy2AL/rxGBPxStFIT 7 | 2bOkQao6sAfb4UdGEUlwHUXZrAV51oM30wIBAg== 8 | -----END DH PARAMETERS----- 9 | -------------------------------------------------------------------------------- /src/qt_client_async/my_window.h: -------------------------------------------------------------------------------- 1 | #ifndef MY_WINDOW_H_ 2 | #define MY_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include "boost/asio/io_context.hpp" 7 | 8 | QT_FORWARD_DECLARE_CLASS(QPushButton) 9 | 10 | class MyWindow : public QMainWindow { 11 | Q_OBJECT 12 | 13 | public: 14 | MyWindow(boost::asio::io_context& io_context); 15 | 16 | private slots: 17 | void GetDaytime(); 18 | 19 | private: 20 | boost::asio::io_context& io_context_; 21 | 22 | QPushButton* button_; 23 | }; 24 | 25 | #endif // MY_WINDOW_H_ 26 | -------------------------------------------------------------------------------- /src/utility.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILITY_H_ 2 | #define UTILITY_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "boost/asio/ip/tcp.hpp" 8 | 9 | namespace utility { 10 | 11 | void PrintEndpoint(std::ostream& ostream, 12 | const boost::asio::ip::tcp::endpoint& endpoint); 13 | 14 | void PrintEndpoints( 15 | std::ostream& ostream, 16 | const boost::asio::ip::tcp::resolver::results_type& endpoints); 17 | 18 | std::string EndpointToString(const boost::asio::ip::tcp::endpoint& endpoint); 19 | 20 | } // namespace utility 21 | 22 | #endif // UTILITY_H_ 23 | -------------------------------------------------------------------------------- /src/timer4_multi.cpp: -------------------------------------------------------------------------------- 1 | // Wait multiple timers asynchronously. 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "boost/asio/io_context.hpp" 8 | #include "boost/asio/steady_timer.hpp" 9 | 10 | void Print(boost::system::error_code ec) { 11 | std::cout << "Hello, World!"; 12 | std::cout << " (" << std::this_thread::get_id() << ")" << std::endl; 13 | } 14 | 15 | int main() { 16 | std::cout << std::this_thread::get_id() << std::endl; 17 | 18 | boost::asio::io_context io_context; 19 | 20 | boost::asio::steady_timer timer1{ io_context, std::chrono::seconds(3) }; 21 | boost::asio::steady_timer timer2{ io_context, std::chrono::seconds(3) }; 22 | 23 | timer1.async_wait(&Print); 24 | timer2.async_wait(&Print); 25 | 26 | std::size_t size = io_context.run(); 27 | std::cout << "Number of handlers executed: " << size << std::endl; // 2 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /src/qt_client_async/daytime_client.h: -------------------------------------------------------------------------------- 1 | #ifndef DAYTIME_CLIENT_H_ 2 | #define DAYTIME_CLIENT_H_ 3 | 4 | // Asynchronous daytime client. 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #define BOOST_ASIO_NO_DEPRECATED 11 | #include "boost/asio/io_context.hpp" 12 | #include "boost/asio/ip/tcp.hpp" 13 | 14 | class DaytimeClient : public std::enable_shared_from_this { 15 | public: 16 | DaytimeClient(boost::asio::io_context& io_context, 17 | const std::string& host); 18 | 19 | void Start(); 20 | 21 | private: 22 | void OnConnect(boost::system::error_code ec, 23 | boost::asio::ip::tcp::endpoint endpoint); 24 | 25 | void DoRead(); 26 | void OnRead(boost::system::error_code ec, std::size_t length); 27 | 28 | boost::asio::ip::tcp::socket socket_; 29 | std::string host_; 30 | 31 | std::array buf_; 32 | }; 33 | 34 | #endif // DAYTIME_CLIENT_H_ 35 | -------------------------------------------------------------------------------- /src/timer1_sync.cpp: -------------------------------------------------------------------------------- 1 | // Wait a timer synchronously. 2 | 3 | #include 4 | #include 5 | 6 | // Including "boost/asio.hpp" directly might simplify a lot but also introduce 7 | // many unnecessary header files. You should never include "boost/asio.hpp" in 8 | // your own header files. Try to include as less as possible. 9 | #include "boost/asio/io_context.hpp" 10 | #include "boost/asio/steady_timer.hpp" 11 | 12 | int main() { 13 | boost::asio::io_context io_context; 14 | 15 | boost::asio::steady_timer timer{ io_context, std::chrono::seconds(3) }; 16 | 17 | // All asynchronous APIs in Asio start with a prefix "async_". 18 | // Here we just call a synchronous API which will block until the timer 19 | // expires. 20 | timer.wait(); 21 | 22 | // 3 seconds later, print hello world. 23 | std::cout << "Hello, World!" << std::endl; 24 | 25 | // No need to call io_context.run() since we don't have any asynchronous 26 | // operations to execute. 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /src/timer2_async.cpp: -------------------------------------------------------------------------------- 1 | // Wait a timer asynchronously. 2 | 3 | #include 4 | #include 5 | 6 | #include "boost/asio/io_context.hpp" 7 | #include "boost/asio/steady_timer.hpp" 8 | 9 | // Handler to be called when the asynchronous wait finishes. 10 | // The |error_code| parameter for all handlers in Asio is passed by value 11 | // now instead of const reference. In some old examples, you may still notice 12 | // the usage of const reference. 13 | void Print(boost::system::error_code ec) { 14 | std::cout << "Hello, World!" << std::endl; 15 | } 16 | 17 | int main() { 18 | boost::asio::io_context io_context; 19 | 20 | boost::asio::steady_timer timer{ io_context, std::chrono::seconds(3) }; 21 | 22 | timer.async_wait(&Print); 23 | 24 | // NOTE: 25 | // Removing '&' from "&Print" also works (but not for member functions): 26 | // timer.async_wait(Print); 27 | 28 | std::size_t size = io_context.run(); 29 | std::cout << "Number of handlers executed: " << size << std::endl; // 1 30 | 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /src/utility.cpp: -------------------------------------------------------------------------------- 1 | #include "utility.h" 2 | 3 | #include 4 | #include 5 | 6 | using tcp = boost::asio::ip::tcp; 7 | 8 | namespace utility { 9 | 10 | void PrintEndpoint(std::ostream& ostream, 11 | const boost::asio::ip::tcp::endpoint& endpoint) { 12 | ostream << endpoint; 13 | if (endpoint.protocol() == tcp::v4()) { 14 | ostream << ", v4"; 15 | } else if (endpoint.protocol() == tcp::v6()) { 16 | ostream << ", v6"; 17 | } 18 | } 19 | 20 | void PrintEndpoints(std::ostream& ostream, 21 | const tcp::resolver::results_type& endpoints) { 22 | ostream << "Endpoints: " << endpoints.size() << std::endl; 23 | tcp::resolver::results_type::iterator it = endpoints.begin(); 24 | for (; it != endpoints.end(); ++it) { 25 | ostream << " - "; 26 | PrintEndpoint(ostream, it->endpoint()); 27 | ostream << std::endl; 28 | } 29 | } 30 | 31 | std::string EndpointToString(const boost::asio::ip::tcp::endpoint& endpoint) { 32 | std::stringstream ss; 33 | PrintEndpoint(ss, endpoint); 34 | return ss.str(); 35 | } 36 | 37 | } // namespace utility 38 | -------------------------------------------------------------------------------- /src/timer5_threaded.cpp: -------------------------------------------------------------------------------- 1 | // Wait multiple timers asynchronously. 2 | // At the same time, run the loop in two threads. 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "boost/asio/io_context.hpp" 10 | #include "boost/asio/steady_timer.hpp" 11 | 12 | std::mutex g_io_mutex; 13 | 14 | void Print(boost::system::error_code ec) { 15 | std::lock_guard lock(g_io_mutex); 16 | 17 | std::cout << "Hello, World!"; 18 | std::cout << " (" << std::this_thread::get_id() << ")" << std::endl; 19 | } 20 | 21 | int main() { 22 | boost::asio::io_context io_context; 23 | 24 | boost::asio::steady_timer timer1{ io_context, std::chrono::seconds(3) }; 25 | boost::asio::steady_timer timer2{ io_context, std::chrono::seconds(1) }; 26 | 27 | timer1.async_wait(&Print); 28 | timer2.async_wait(&Print); 29 | 30 | // Run the loop in 2 threads. 31 | std::thread t1{ &boost::asio::io_context::run, &io_context }; 32 | std::thread t2{ &boost::asio::io_context::run, &io_context }; 33 | 34 | // Wait for the 2 loops to end. 35 | t1.join(); 36 | t2.join(); 37 | 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(UTILITY_SRCS utility.cpp utility.h) 2 | 3 | set(LIBS ${Boost_LIBRARIES} "${CMAKE_THREAD_LIBS_INIT}") 4 | 5 | if(ENABLE_SSL) 6 | set(LIBS ${LIBS} ${OPENSSL_LIBRARIES}) 7 | if(WIN32) 8 | set(LIBS ${LIBS} crypt32) 9 | endif() 10 | endif() 11 | 12 | if(UNIX) 13 | # Add `-ldl` for Linux to avoid "undefined reference to `dlopen'". 14 | set(LIBS ${LIBS} ${CMAKE_DL_LIBS}) 15 | endif() 16 | 17 | set(TARGETS 18 | hello 19 | timer1_sync 20 | timer2_async 21 | timer3_lambda 22 | timer4_multi 23 | timer5_threaded 24 | timer6_args 25 | timer7_memfunc 26 | strand 27 | strand2 28 | echo_server_sync 29 | echo_server_async 30 | echo_client_sync 31 | echo_client_async 32 | 33 | context_and_services 34 | ) 35 | 36 | if(ENABLE_SSL) 37 | set(TARGETS ${TARGETS} 38 | ssl_http_client_sync 39 | ssl_http_client_async 40 | ssl_http_client_async_blocking 41 | ssl_http_client_async_blocking_timeout 42 | ) 43 | endif() 44 | 45 | foreach(name ${TARGETS}) 46 | add_executable(${name} ${name}.cpp) 47 | target_link_libraries(${name} ${LIBS}) 48 | endforeach() 49 | 50 | if(ENABLE_QT) 51 | add_subdirectory(qt_client_async) 52 | endif() 53 | -------------------------------------------------------------------------------- /src/timer3_lambda.cpp: -------------------------------------------------------------------------------- 1 | // Wait a timer asynchronously. 2 | // Use a lambda as the wait handler. 3 | 4 | #include 5 | #include 6 | 7 | #include "boost/asio/io_context.hpp" 8 | #include "boost/asio/steady_timer.hpp" 9 | #include "boost/core/ignore_unused.hpp" 10 | 11 | // Call run_one() instead of run() of io_context. 12 | #define CALL_RUN_ONE 0 13 | 14 | int main() { 15 | boost::asio::io_context io_context; 16 | 17 | boost::asio::steady_timer timer{ io_context, std::chrono::seconds(3) }; 18 | 19 | #if CALL_RUN_ONE 20 | 21 | boost::system::error_code ec = boost::asio::error::would_block; 22 | 23 | timer.async_wait([&ec](boost::system::error_code inner_ec) { 24 | ec = inner_ec; 25 | std::cout << "Hello, World!" << std::endl; 26 | }); 27 | 28 | // Block until the asynchronous operation has completed. 29 | // The do...while loop is optional for this case because we have triggered 30 | // only one async operation. 31 | // TODO: Trigger another asynchronous operation from within the handler. 32 | do { 33 | io_context.run_one(); 34 | } while (ec == boost::asio::error::would_block); 35 | 36 | #else 37 | 38 | timer.async_wait([](boost::system::error_code ec) { 39 | boost::ignore_unused(ec); 40 | std::cout << "Hello, World!" << std::endl; 41 | }); 42 | 43 | io_context.run(); 44 | 45 | #endif // CALL_RUN_ONE 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /src/timer7_memfunc.cpp: -------------------------------------------------------------------------------- 1 | // Wait a timer asynchronously. 2 | // Use a member function as handler. 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "boost/asio/steady_timer.hpp" 9 | #include "boost/asio/io_context.hpp" 10 | 11 | class Printer { 12 | public: 13 | explicit Printer(boost::asio::io_context& io_context) 14 | : timer_(io_context, std::chrono::seconds(1)), count_(0) { 15 | // For member function handlers, always bind |this| as the first argument. 16 | // Unlike global functions, '&' is mandatory for referring to member 17 | // function pointers. 18 | timer_.async_wait(std::bind(&Printer::Print, this, std::placeholders::_1)); 19 | } 20 | 21 | ~Printer() { 22 | std::cout << "Final count is " << count_ << std::endl; 23 | } 24 | 25 | private: 26 | void Print(boost::system::error_code ec) { 27 | if (count_ < 3) { 28 | std::cout << count_ << std::endl; 29 | ++count_; 30 | 31 | timer_.expires_after(std::chrono::seconds(1)); 32 | 33 | timer_.async_wait(std::bind(&Printer::Print, this, 34 | std::placeholders::_1)); 35 | } 36 | } 37 | 38 | boost::asio::steady_timer timer_; 39 | int count_; 40 | }; 41 | 42 | int main() { 43 | boost::asio::io_context io_context; 44 | 45 | Printer printer{ io_context }; 46 | 47 | io_context.run(); 48 | 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /src/timer6_args.cpp: -------------------------------------------------------------------------------- 1 | // Wait a timer asynchronously. 2 | // Bind extra arguments to a function so that it matches the signature of 3 | // the expected handler. 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "boost/asio/steady_timer.hpp" 10 | #include "boost/asio/io_context.hpp" 11 | #include "boost/date_time/posix_time/posix_time.hpp" 12 | 13 | void Print(boost::system::error_code ec, 14 | boost::asio::steady_timer& timer, 15 | int& count) { 16 | if (count < 3) { 17 | std::cout << count << std::endl; 18 | ++count; 19 | 20 | // Change the timer's expiry time. 21 | timer.expires_after(std::chrono::seconds(1)); 22 | 23 | // Start a new asynchronous wait. 24 | timer.async_wait(std::bind(&Print, std::placeholders::_1, std::ref(timer), 25 | std::ref(count))); 26 | } 27 | } 28 | 29 | int main() { 30 | boost::asio::io_context io_context; 31 | 32 | boost::asio::steady_timer timer{ io_context, std::chrono::seconds(1) }; 33 | 34 | int count = 0; 35 | 36 | // async_wait() expects a handler function (or function object) with the 37 | // signature |void(boost::system::error_code)|. 38 | // Binding the additional parameters converts your Print function into a 39 | // function object that matches the signature correctly. 40 | timer.async_wait(std::bind(&Print, std::placeholders::_1, std::ref(timer), 41 | std::ref(count))); 42 | 43 | io_context.run(); 44 | 45 | std::cout << "Final count is " << count << std::endl; 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /src/qt_client_async/my_window.cpp: -------------------------------------------------------------------------------- 1 | #include "my_window.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "daytime_client.h" 12 | 13 | static const std::string kHost = "time.nist.gov"; 14 | 15 | MyWindow::MyWindow(boost::asio::io_context& io_context) 16 | : io_context_(io_context) { 17 | setWindowTitle(tr("Qt Asio Example")); 18 | 19 | QWidget* central_widget = new QWidget(); 20 | setCentralWidget(central_widget); 21 | 22 | button_ = new QPushButton(tr("Get Daytime")); 23 | button_->setToolTip(QStringLiteral("Use Asio to get current daytime")); 24 | 25 | QVBoxLayout* vlayout = new QVBoxLayout(); 26 | 27 | vlayout->addWidget(button_, 0, Qt::AlignCenter); 28 | 29 | central_widget->setLayout(vlayout); 30 | 31 | resize(400, 300); 32 | 33 | QObject::connect(button_, &QPushButton::clicked, 34 | this, &MyWindow::GetDaytime); 35 | } 36 | 37 | void MyWindow::GetDaytime() { 38 | // Use std::shared_ptr to ensure the life of DaytimeClient. 39 | auto client = std::make_shared(io_context_, kHost); 40 | 41 | // Don't execute Start() from the GUI thread because it consists of 42 | // async operations which should be run in the same thread as io_context. 43 | // Use post() instead. 44 | // Note that the lambda function posted doesn't rely on MyWindow at all. 45 | // The |client| variable is copied so the reference count is increased. 46 | boost::asio::post(io_context_, [client]() { 47 | client->Start(); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /src/echo_server_sync.cpp: -------------------------------------------------------------------------------- 1 | // Synchronous echo server. 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "boost/asio.hpp" 8 | 9 | using boost::asio::ip::tcp; 10 | 11 | enum { BUF_SIZE = 1024 }; 12 | 13 | void Session(tcp::socket socket) { 14 | try { 15 | while (true) { 16 | std::array data; 17 | 18 | boost::system::error_code ec; 19 | std::size_t length = socket.read_some(boost::asio::buffer(data), ec); 20 | 21 | if (ec == boost::asio::error::eof) { 22 | std::cout << "Connection closed cleanly by peer." << std::endl; 23 | break; 24 | } else if (ec) { 25 | // Some other error 26 | throw boost::system::system_error(ec); 27 | } 28 | 29 | boost::asio::write(socket, boost::asio::buffer(data, length)); 30 | } 31 | } catch (const std::exception& e) { 32 | std::cerr << "Exception: " << e.what() << std::endl; 33 | } 34 | } 35 | 36 | int main(int argc, char* argv[]) { 37 | if (argc != 2) { 38 | std::cerr << "Usage: " << argv[0] << " " << std::endl; 39 | return 1; 40 | } 41 | 42 | unsigned short port = std::atoi(argv[1]); 43 | 44 | boost::asio::io_context io_context; 45 | 46 | // Create an acceptor to listen for new connections. 47 | tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), port)); 48 | 49 | try { 50 | // Handle one connection at a time. 51 | while (true) { 52 | // The socket object returned from accept will be moved to Session's 53 | // parameter without any copy cost. 54 | Session(acceptor.accept()); 55 | } 56 | } catch (const std::exception& e) { 57 | std::cerr << e.what() << std::endl; 58 | } 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /src/qt_client_async/main.cpp: -------------------------------------------------------------------------------- 1 | // This example demostrates how to use ASIO in a GUI application. 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define BOOST_ASIO_NO_DEPRECATED 8 | #include "boost/asio/io_context.hpp" 9 | #include "boost/asio/executor_work_guard.hpp" 10 | 11 | #include 12 | #include 13 | 14 | #include "my_window.h" 15 | 16 | int main(int argc, char* argv[]) { 17 | QApplication app(argc, argv); 18 | 19 | int code = 0; 20 | 21 | // Exception handling is for Asio. 22 | try { 23 | // Run the io_context off in its own thread so that it operates completely 24 | // asynchronously with respect to the rest of the program. 25 | 26 | boost::asio::io_context io_context; 27 | 28 | // Make sure the io_context won't end without work to do. 29 | // The variable |work| is not used but necessary for the guard to work. 30 | auto work = boost::asio::make_work_guard(io_context); 31 | 32 | // You can also use bind instead of lambda as below: 33 | // std::bind(&boost::asio::io_context::run, &io_context) 34 | std::thread asio_thread([&io_context]() { io_context.run(); }); 35 | 36 | // Pass a reference of io_context to MyWindow so that async operations 37 | // could be created from inside. 38 | MyWindow my_window(io_context); 39 | my_window.show(); 40 | 41 | // The GUI blocks here. 42 | code = app.exec(); 43 | 44 | // The GUI ends, stop the Asio thread. 45 | // Pending async operations will be canceled. 46 | io_context.stop(); 47 | // Wait for the thread to exit. 48 | asio_thread.join(); 49 | 50 | } catch (const std::exception& e) { 51 | qDebug() << "Exception:" << e.what(); 52 | code = 1; 53 | } 54 | 55 | return code; 56 | } -------------------------------------------------------------------------------- /src/context_and_services.cpp: -------------------------------------------------------------------------------- 1 | // context_and_services.cpp 2 | // A study on the execution context and the services. 3 | 4 | #include 5 | 6 | #include "boost/asio/deadline_timer.hpp" 7 | #include "boost/asio/detail/deadline_timer_service.hpp" 8 | #include "boost/asio/detail/resolver_service.hpp" 9 | #include "boost/asio/detail/strand_service.hpp" 10 | #include "boost/asio/io_context.hpp" 11 | #include "boost/asio/ip/tcp.hpp" 12 | #include "boost/asio/strand.hpp" 13 | 14 | namespace asio = boost::asio; 15 | 16 | int main() { 17 | asio::io_context io_context; 18 | 19 | bool has_service = false; 20 | std::cout << std::boolalpha; 21 | 22 | // NOTE: asio::deadline_timer::traits_type -> 23 | // asio::time_traits 24 | using DeadlineTimerService = 25 | asio::detail::deadline_timer_service; 26 | 27 | std::cout << asio::has_service(io_context) << std::endl; 28 | // false 29 | 30 | // Create a deadline timer then the service will be available. 31 | asio::deadline_timer timer{ io_context }; 32 | std::cout << asio::has_service(io_context) << std::endl; 33 | // true 34 | 35 | // Calling `asio::use_service` instead also makes the service available: 36 | // asio::use_service(io_context) 37 | 38 | using ResolverService = asio::detail::resolver_service; 39 | asio::ip::tcp::resolver resolver{ io_context }; 40 | std::cout << asio::has_service(io_context) << std::endl; 41 | // true 42 | 43 | std::cout << asio::has_service(io_context) 44 | << std::endl; 45 | // false 46 | 47 | asio::io_context::strand strand{ io_context }; 48 | std::cout << asio::has_service(io_context) 49 | << std::endl; 50 | // true 51 | 52 | // ... 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /src/strand2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "boost/asio/bind_executor.hpp" 6 | #include "boost/asio/io_context.hpp" 7 | #include "boost/asio/steady_timer.hpp" 8 | #include "boost/asio/strand.hpp" 9 | 10 | // Synchronise callback handlers in a multi-threaded program. 11 | // Class strand provides the ability to post and dispatch handlers with the 12 | // guarantee that none of those handlers will execute concurrently. 13 | 14 | class Printer { 15 | public: 16 | explicit Printer(boost::asio::io_context& io_context) 17 | : strand_(boost::asio::make_strand(io_context)), 18 | timer1_(strand_, std::chrono::seconds(1)), 19 | timer2_(strand_, std::chrono::seconds(1)) { 20 | timer1_.async_wait(std::bind(&Printer::Print1, this)); 21 | timer2_.async_wait(std::bind(&Printer::Print2, this)); 22 | } 23 | 24 | ~Printer() { 25 | std::cout << "Final count is " << count_ << std::endl; 26 | } 27 | 28 | void Print1() { 29 | if (count_ < 6) { 30 | std::cout << "Timer 1: " << count_ << std::endl; 31 | ++count_; 32 | 33 | timer1_.expires_after(std::chrono::seconds(1)); 34 | timer1_.async_wait(std::bind(&Printer::Print1, this)); 35 | } 36 | } 37 | 38 | void Print2() { 39 | if (count_ < 6) { 40 | std::cout << "Timer 2: " << count_ << std::endl; 41 | ++count_; 42 | 43 | timer2_.expires_after(std::chrono::seconds(1)); 44 | timer2_.async_wait(std::bind(&Printer::Print2, this)); 45 | } 46 | } 47 | 48 | private: 49 | // NOTE: `asio::strand` is a different class from `io_context::strand`. 50 | boost::asio::strand strand_; 51 | boost::asio::steady_timer timer1_; 52 | boost::asio::steady_timer timer2_; 53 | int count_ = 0; 54 | }; 55 | 56 | int main() { 57 | boost::asio::io_context io_context; 58 | 59 | Printer printer{ io_context }; 60 | 61 | // The new thread runs a loop. 62 | std::thread t{ std::bind(&boost::asio::io_context::run, &io_context) }; 63 | 64 | // The main thread runs another loop. 65 | io_context.run(); 66 | 67 | // Wait for the thread to finish. 68 | t.join(); 69 | 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /src/qt_client_async/daytime_client.cpp: -------------------------------------------------------------------------------- 1 | #include "daytime_client.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "boost/asio/connect.hpp" 7 | #include "boost/asio/read.hpp" 8 | #include "boost/asio/write.hpp" 9 | 10 | using boost::asio::ip::tcp; 11 | 12 | DaytimeClient::DaytimeClient(boost::asio::io_context& io_context, 13 | const std::string& host) 14 | : socket_(io_context), host_(host) { 15 | } 16 | 17 | void DaytimeClient::Start() { 18 | tcp::resolver resolver(socket_.get_executor().context()); 19 | auto endpoints = resolver.resolve(host_, "daytime"); 20 | 21 | // ConnectHandler: void(boost::system::error_code, tcp::endpoint) 22 | boost::asio::async_connect(socket_, endpoints, 23 | std::bind(&DaytimeClient::OnConnect, 24 | shared_from_this(), 25 | std::placeholders::_1, 26 | std::placeholders::_2)); 27 | } 28 | 29 | void DaytimeClient::OnConnect(boost::system::error_code ec, 30 | tcp::endpoint endpoint) { 31 | if (ec) { 32 | std::cout << "Connect failed: " << ec.message() << std::endl; 33 | socket_.close(); 34 | } else { 35 | DoRead(); 36 | } 37 | } 38 | 39 | void DaytimeClient::DoRead() { 40 | socket_.async_read_some(boost::asio::buffer(buf_), 41 | std::bind(&DaytimeClient::OnRead, 42 | shared_from_this(), 43 | std::placeholders::_1, 44 | std::placeholders::_2)); 45 | } 46 | 47 | void DaytimeClient::OnRead(boost::system::error_code ec, std::size_t length) { 48 | if (!ec) { 49 | // This will not print anything except when launched from Linux termimal. 50 | // If you want to display something in the GUI to indicate the status (a 51 | // progress bar, maybe), remember this is executed in a separate thread, 52 | // so you have to "notify" the GUI instead of call it directly. To notify 53 | // the GUI, Qt's signal machanism should work. 54 | std::cout.write(buf_.data(), length); 55 | std::cout << std::endl; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/strand.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "boost/asio/bind_executor.hpp" 6 | #include "boost/asio/io_context.hpp" 7 | #include "boost/asio/steady_timer.hpp" 8 | #include "boost/asio/strand.hpp" 9 | 10 | // Synchronise callback handlers in a multi-threaded program. 11 | // Class strand provides the ability to post and dispatch handlers with the 12 | // guarantee that none of those handlers will execute concurrently. 13 | 14 | class Printer { 15 | public: 16 | explicit Printer(boost::asio::io_context& io_context) 17 | : strand_(io_context), 18 | timer1_(io_context, std::chrono::seconds(1)), 19 | timer2_(io_context, std::chrono::seconds(1)) { 20 | timer1_.async_wait( 21 | boost::asio::bind_executor(strand_, std::bind(&Printer::Print1, this))); 22 | timer2_.async_wait( 23 | boost::asio::bind_executor(strand_, std::bind(&Printer::Print2, this))); 24 | } 25 | 26 | ~Printer() { 27 | std::cout << "Final count is " << count_ << std::endl; 28 | } 29 | 30 | void Print1() { 31 | if (count_ < 6) { 32 | std::cout << "Timer 1: " << count_ << std::endl; 33 | ++count_; 34 | 35 | timer1_.expires_after(std::chrono::seconds(1)); 36 | timer1_.async_wait(boost::asio::bind_executor( 37 | strand_, std::bind(&Printer::Print1, this))); 38 | } 39 | } 40 | 41 | void Print2() { 42 | if (count_ < 6) { 43 | std::cout << "Timer 2: " << count_ << std::endl; 44 | ++count_; 45 | 46 | timer2_.expires_after(std::chrono::seconds(1)); 47 | timer2_.async_wait(boost::asio::bind_executor( 48 | strand_, std::bind(&Printer::Print2, this))); 49 | } 50 | } 51 | 52 | private: 53 | boost::asio::io_context::strand strand_; 54 | boost::asio::steady_timer timer1_; 55 | boost::asio::steady_timer timer2_; 56 | int count_ = 0; 57 | }; 58 | 59 | int main() { 60 | boost::asio::io_context io_context; 61 | 62 | Printer printer{ io_context }; 63 | 64 | // The new thread runs a loop. 65 | std::thread t{ std::bind(&boost::asio::io_context::run, &io_context) }; 66 | 67 | // The main thread runs another loop. 68 | io_context.run(); 69 | 70 | // Wait for the thread to finish. 71 | t.join(); 72 | 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1.0) 2 | 3 | if(POLICY CMP0074) 4 | # find_package() uses _ROOT variables. 5 | # This policy was introduced in CMake version 3.12. 6 | cmake_policy(SET CMP0074 NEW) 7 | endif() 8 | 9 | project(boost_asio_study) 10 | 11 | option(ENABLE_SSL "Enable SSL/HTTPS examples (need OpenSSL)?" ON) 12 | option(ENABLE_QT "Enable Qt examples?" OFF) 13 | 14 | # Output directories 15 | set(BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}) 16 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${BUILD_DIR}/bin) 17 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${BUILD_DIR}/bin) 18 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BUILD_DIR}/bin) 19 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${BUILD_DIR}/bin/debug) 20 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${BUILD_DIR}/bin/debug) 21 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${BUILD_DIR}/bin/debug) 22 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${BUILD_DIR}/bin/release) 23 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${BUILD_DIR}/bin/release) 24 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${BUILD_DIR}/bin/release) 25 | 26 | # C++ standard requirements. 27 | set(CMAKE_CXX_STANDARD 11) 28 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 29 | set(CMAKE_CXX_EXTENSIONS OFF) 30 | 31 | if(MSVC) 32 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 33 | endif() 34 | 35 | # Don't use any deprecated definitions (e.g., io_service). 36 | add_definitions(-DBOOST_ASIO_NO_DEPRECATED) 37 | 38 | if(WIN32) 39 | # Asio needs this! 40 | # 0x0601 means Win7. So our application targets Win7 and above. 41 | add_definitions(-D_WIN32_WINNT=0x0601) 42 | endif() 43 | 44 | # CMake 3.1.0+ required. 45 | # See: https://stackoverflow.com/a/29871891 46 | set(THREADS_PREFER_PTHREAD_FLAG ON) 47 | find_package(Threads REQUIRED) 48 | 49 | # Boost 1.66+ required. 50 | set(Boost_USE_STATIC_LIBS ON) 51 | set(Boost_USE_MULTITHREADED ON) 52 | find_package(Boost REQUIRED COMPONENTS system date_time) 53 | if(Boost_FOUND) 54 | include_directories(${Boost_INCLUDE_DIRS}) 55 | link_directories(${Boost_LIBRARY_DIRS}) 56 | endif() 57 | 58 | if(ENABLE_SSL) 59 | # Commented on 20190605. 60 | # The static libs have linkage issues with VS2015 on Win10. 61 | # set(OPENSSL_USE_STATIC_LIBS ON) 62 | # set(OPENSSL_MSVC_STATIC_RT ON) 63 | find_package(OpenSSL) 64 | if(OPENSSL_FOUND) 65 | include_directories(${OPENSSL_INCLUDE_DIR}) 66 | message(STATUS "OpenSSL libs: " ${OPENSSL_LIBRARIES}) 67 | endif() 68 | endif() 69 | 70 | include_directories(${PROJECT_SOURCE_DIR}/src) 71 | 72 | add_subdirectory(src) 73 | -------------------------------------------------------------------------------- /Asio Example Notes.md: -------------------------------------------------------------------------------- 1 | # Asio Example Notes 2 | 3 | ## timer2_async 4 | 5 | Use `const reference` for error code: 6 | 7 | ```cpp 8 | timer.async_wait([](const boost::system::error_code& ec) { 9 | std::cout << "Hello, World!" << std::endl; 10 | }); 11 | ``` 12 | 13 | ## timer3_lambda 14 | 15 | Use lambda expression: 16 | 17 | ```cpp 18 | timer.async_wait([](boost::system::error_code ec) { 19 | std::cout << "Hello, World!" << std::endl; 20 | }); 21 | ``` 22 | 23 | ## timer4_multi 24 | 25 | Print thread ID to explain the handlers are executed in the same thread as `io_context.run`. 26 | 27 | ```cpp 28 | std::cout << std::this_thread::get_id() << std::endl; 29 | ``` 30 | 31 | ## timer5_threaded 32 | 33 | The handlers are guaranteed to be called from the same thread as `io_context.run`. 34 | 35 | Explain why `g_io_mutex` is needed by trying to comment the lock. 36 | 37 | ## timer6_args 38 | 39 | Explain why it's difficult to use lambda expression. 40 | 41 | ```cpp 42 | timer.async_wait([&timer, &count](boost::system::error_code ec) { 43 | if (count < 3) { 44 | std::cout << count++ << std::endl; 45 | 46 | timer.expires_after(std::chrono::seconds(1)); 47 | 48 | timer.async_wait(???); 49 | } 50 | }); 51 | ``` 52 | 53 | Introduce how to know the signature of a handler. E.g., 54 | 55 | ```cpp 56 | template 57 | BOOST_ASIO_INITFN_RESULT_TYPE(WaitHandler, 58 | void (boost::system::error_code)) 59 | async_wait(BOOST_ASIO_MOVE_ARG(WaitHandler) handler) 60 | { 61 | return async_initiate( 62 | initiate_async_wait(), handler, this); 63 | } 64 | 65 | ``` 66 | 67 | About `std::bind`: 68 | 69 | ```cpp 70 | void f(int a, int b, int c) { 71 | std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl; 72 | } 73 | 74 | auto fx = std::bind(&f, 1, std::placeholders::_1, std::placeholders::_2); 75 | 76 | fx(2, 3); 77 | ``` 78 | 79 | ## timer7_memfunc 80 | 81 | Explain the usage of `std::bind` for member functions. 82 | 83 | ```cpp 84 | timer_.async_wait(std::bind(&Printer::Print, this, std::placeholders::_1)); 85 | ``` 86 | 87 | Explain why async waiting `Print` from inside `Print` is not a recursive call. Create the following logger at the beginning of `Print`. 88 | 89 | ```cpp 90 | struct Logger { 91 | Logger() { 92 | std::cout << "enter." << std::endl; 93 | } 94 | 95 | ~Logger() { 96 | std::cout << "leave." << std::endl; 97 | } 98 | }; 99 | ``` 100 | 101 | ## echo_client_sync 102 | 103 | Introduce `boost::asio::buffer`: `char[]`, `std::array`, `std::vector`. 104 | 105 | ## echo_client_async 106 | 107 | Don't exit after read the reply. Go to next round instead. 108 | 109 | ## Others 110 | 111 | Avoid including `boost/asio.hpp` (in your header). 112 | -------------------------------------------------------------------------------- /src/echo_client_sync.cpp: -------------------------------------------------------------------------------- 1 | // Synchronous echo client. 2 | 3 | #include 4 | #include // for std::strlen 5 | #include 6 | 7 | #include "boost/asio/connect.hpp" 8 | #include "boost/asio/io_context.hpp" 9 | #include "boost/asio/ip/tcp.hpp" 10 | #include "boost/asio/read.hpp" 11 | #include "boost/asio/write.hpp" 12 | 13 | using boost::asio::ip::tcp; 14 | 15 | #define USE_GLOBAL_READ 1 16 | 17 | enum { BUF_SIZE = 1024 }; 18 | 19 | int main(int argc, char* argv[]) { 20 | if (argc != 3) { 21 | std::cerr << "Usage: " << argv[0] << " " << std::endl; 22 | return 1; 23 | } 24 | 25 | const char* host = argv[1]; 26 | const char* port = argv[2]; 27 | 28 | boost::asio::io_context io_context; 29 | 30 | // NOTE: 31 | // Don't use output parameter |error_code| in this example. 32 | // Using exception handling could largely simplify the source code. 33 | try { 34 | tcp::resolver resolver{ io_context }; 35 | 36 | // Return type: tcp::resolver::results_type 37 | auto endpoints = resolver.resolve(tcp::v4(), host, port); 38 | 39 | // Don't use socket.connect() directly. 40 | // Global function connect() calls socket.connect() internally. 41 | tcp::socket socket{ io_context }; 42 | boost::asio::connect(socket, endpoints); 43 | 44 | // Get user input. 45 | 46 | char request[BUF_SIZE]; 47 | std::size_t request_length = 0; 48 | do { 49 | std::cout << "Enter message: "; 50 | std::cin.getline(request, BUF_SIZE); 51 | request_length = std::strlen(request); 52 | } while (request_length == 0); 53 | 54 | // Write to the socket. 55 | boost::asio::write(socket, boost::asio::buffer(request, request_length)); 56 | 57 | // Read the response. 58 | // Use global read() or not (please note the difference). 59 | 60 | std::cout << "Reply is: "; 61 | 62 | #if USE_GLOBAL_READ 63 | // Receive reply with global read(). 64 | 65 | char reply[BUF_SIZE]; 66 | 67 | // Global read() returns once the specified size of buffer has been 68 | // fully filled. 69 | std::size_t reply_length = boost::asio::read( 70 | socket, boost::asio::buffer(reply, request_length)); 71 | 72 | std::cout.write(reply, reply_length); 73 | 74 | #else 75 | // Receive reply with socket.read_some(). 76 | 77 | std::size_t total_reply_length = 0; 78 | while (true) { 79 | std::array reply; 80 | std::size_t reply_length = socket.read_some(boost::asio::buffer(reply)); 81 | 82 | std::cout.write(reply.data(), reply_length); 83 | 84 | total_reply_length += reply_length; 85 | if (total_reply_length >= request_length) { 86 | break; 87 | } 88 | } 89 | 90 | #endif // USE_GLOBAL_READ 91 | 92 | std::cout << std::endl; 93 | 94 | } catch (const std::exception& e) { 95 | std::cerr << e.what() << std::endl; 96 | } 97 | 98 | return 0; 99 | } 100 | -------------------------------------------------------------------------------- /src/ssl/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDlzCCAn+gAwIBAgIJAMJYU3U6A0IRMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNV 3 | BAYTAkFVMQwwCgYDVQQIEwNOU1cxDzANBgNVBAcTBlN5ZG5leTENMAsGA1UEChME 4 | YXNpbzAeFw0xNTExMTgyMjMzNDhaFw0yMDExMTYyMjMzNDhaMDsxCzAJBgNVBAYT 5 | AkFVMQwwCgYDVQQIEwNOU1cxDzANBgNVBAcTBlN5ZG5leTENMAsGA1UEChMEYXNp 6 | bzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcRJocHdVMdLUJ/pypY 7 | QVSTC0t3IIgjwjazrK3kAaoIMvzPmDFxEXWcDx+nyz8kQ/E38Ir/ef2BCNGci5hu 8 | wkfMSuMoW9l2N4hx3QCcF46tTDEZztFxWAH7QbE2wYMlMgKZSxWimNfq0YjxEEXb 9 | QM0lGPLFh7Xoko29H0F3LKaaQV9u/vop3Hs0h12HeWlY4PiLp7QQTNGqbWcXycA0 10 | NZ/fyismireyEvPAgo6L8iXuAi7g0TVKVNlrticGGjMcMq6IMvxzEpSMkuMQ5rWj 11 | pZjWOoBjSYBuXdblcBRvXhOr2Ws8jJLMZfehKq9q1reQfoGV6xMnbwmumSXbWRWT 12 | 0vkCAwEAAaOBnTCBmjAdBgNVHQ4EFgQUK/Zv/AVtfIeucJw8VEtux1dhI1YwawYD 13 | VR0jBGQwYoAUK/Zv/AVtfIeucJw8VEtux1dhI1ahP6Q9MDsxCzAJBgNVBAYTAkFV 14 | MQwwCgYDVQQIEwNOU1cxDzANBgNVBAcTBlN5ZG5leTENMAsGA1UEChMEYXNpb4IJ 15 | AMJYU3U6A0IRMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBABLYXimq 16 | v/HLyIJi7Xn8AJUsICj8LKF/J24nwwiF+ibf7UkoChJURs4nN78bod/lpDVPTEVl 17 | gTBdV/vBJs416sCEFfsGjqB9OBYj4gb0VaJDsQd0+NMvXp0faKv2y9wgScxG9/cg 18 | aM7eRmyfMn1qjb6tpNxVOPpe/nFi8Vx/1orejBRaZr4zF5TkoPepfwLWQeXDUIdE 19 | +QHZ60jZAkR5RXTVU4u3kOKcJs839pmJYyxM4H2VxpR18vy4/YdIVWkREIUM2OgT 20 | 5iznIQIIgR56QRGP85uef+I6n0BHzrBk6du69bkQFxrFjLVGlal4bIQqSg4KGWgx 21 | dEdymMWzmMxpO9s= 22 | -----END CERTIFICATE----- 23 | -----BEGIN RSA PRIVATE KEY----- 24 | MIIEpgIBAAKCAQEAxxEmhwd1Ux0tQn+nKlhBVJMLS3cgiCPCNrOsreQBqggy/M+Y 25 | MXERdZwPH6fLPyRD8Tfwiv95/YEI0ZyLmG7CR8xK4yhb2XY3iHHdAJwXjq1MMRnO 26 | 0XFYAftBsTbBgyUyAplLFaKY1+rRiPEQRdtAzSUY8sWHteiSjb0fQXcspppBX27+ 27 | +incezSHXYd5aVjg+IuntBBM0aptZxfJwDQ1n9/KKyaKt7IS88CCjovyJe4CLuDR 28 | NUpU2Wu2JwYaMxwyrogy/HMSlIyS4xDmtaOlmNY6gGNJgG5d1uVwFG9eE6vZazyM 29 | ksxl96Eqr2rWt5B+gZXrEydvCa6ZJdtZFZPS+QIDAQABAoIBAQCOma+SvPoDzvvU 30 | DiPOxqgOEMPfjHfGbm86xl0luBalGfiEd6WbjVanfGKtF4MWOUFec+chez+FJMEP 31 | fufVC0qrKiJfNVMOpYvEd2SMgkSx1VymM8me6WXVDYsSipn2+1cm228ZEYAR9Emj 32 | oqQ4loaGLlP/3RaJbhBF7ruMJvXaZZQ4fZy74Z4tyRaaE1B659ua7Rjne7eNhQE8 33 | cR7cQDkxsNNN3LTbfLRwEc/gcDXWgLe5JlR/K4ZrdKc3lyivm+Uew3ubKs+fgkyY 34 | kHmuI3RJGIjpnsZW0/So+pHm3b/fo6lmlhTXtNNd+tkkKn2K9ttbXT3Sc13Pc+4w 35 | c4MLyUpdAoGBAOxTtGDpeF6U4s+GPuOCzHCwKQyzfOyCL/UTZv1UJX7Kn1FYycJH 36 | eOjtBRtS661cGkGd1MPfjdX2VV84AmBGDUmRqJ2KfTI1NjLAEJ115ANTpmSTm3lF 37 | UYncgbzl6aflLpjE1mgY+JTJykYeN5jhhO0r2bsdY7S+zaMCSI5NLuznAoGBANej 38 | aMtqLg2qKoq+fUkNBHHLXelR5dBXFnKgSrTj++H4yeW9pYbl8bK3gTF3I5+dSjHW 39 | DdC4+X09iPqY7p8vm8Gq/vgO8Bu+EnKNVr80PJSj7AzFGd6mk/CVrAzoY2XJWbAp 40 | YFwpo1WfHjS5wBfQzBlXY7kWVB7fj32kk14PYmUfAoGBAJXfd7NGHPoOfdCSGGv8 41 | VV7ZuQ6+/WiYH4XS6iuaI7VHFsZmAn3dCcbeGbD8Y04r7NLUH0yhB7g7YmTihk87 42 | 3c1cPIy8eS1QJbEFsQPK8fFSKWH7YkwEM/O0DesX+5hodaaYnkiiHXNujYLuQuAH 43 | lV87wfcyajsEDjFkj1L/i9TdAoGBAKYfRUQv8HqmdU+doHb+iEYCHb75UMpHzQtR 44 | YTwpxoo3V5Kdnz9lNeYwaF7rIY59ZgMunEYHumw5U6V625nW228/hF0lZOR6cUu+ 45 | hu2WGHWKMvdDgMJ+IcpeA8WN4cUwcN+9gHZ/vUzg4CxOTSYLvLBpGnIkOXnvUGPC 46 | vaTgxTSRAoGBAOHcuZ9hcUrPuVI1HVkjQQLu5mLZ3tz6linEbe/RCdJMK8JrRX4w 47 | ubB7gFclMYGbLlDNAJVYkydJaCy/2NAI3rfsOda+VmDqGx6z4BbSGceHhomyU1Oo 48 | 1H7YaXsuzDkzl23HRsyp0pKJpTdghZdbVsGF8vAB8ygK3ehM233neSln 49 | -----END RSA PRIVATE KEY----- 50 | -------------------------------------------------------------------------------- /src/ssl/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDAzCCAesCCQD9QcRiWk0y9TANBgkqhkiG9w0BAQUFADA7MQswCQYDVQQGEwJB 3 | VTEMMAoGA1UECBMDTlNXMQ8wDQYDVQQHEwZTeWRuZXkxDTALBgNVBAoTBGFzaW8w 4 | HhcNMTUxMTE4MjIzNzMxWhcNMjAxMTE2MjIzNzMxWjBMMQswCQYDVQQGEwJBVTEM 5 | MAoGA1UECBMDTlNXMQ8wDQYDVQQHEwZTeWRuZXkxDTALBgNVBAoTBGFzaW8xDzAN 6 | BgNVBAsTBnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALr0 7 | +NXSklsGJR7HYHP/H4V5+KpYrmFKva/K7iiqi+XyWEjGnj+/iImJW26phhg9GouN 8 | JJxdrP7/0LwpMsEC/9v09dMNAEewtYhPgD4kiUH/E/79wVmayMZZZGrpF9Rw+wWv 9 | q58y3L1wKge3qilX6slVDdNhqU3vBiMKEJfsjE4PKcEVjPCjVJG2562eHK9FxyjQ 10 | DykyH61lQKBQOiElilPQKzAO7U36yTvs+chWuUfK47B8EC+PJ5KcLEppli4ljlwE 11 | w01HnGxwvjDLobKm2jL6CWi3aYGWudyTsNAd7YC5C7psktBypQLBcfp7uUrrR5Bb 12 | PEjFHJUWIlyoYvm2OjMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAtceVW6tixFsB 13 | ZRhjL5aRCcbx2iMwEXd54lcP6BWe1qOcDPHoSYI1zvvGzohbEvBfqUv78S9MtzaT 14 | gMe5rIU9M1ZM09PyaM6ZutGpKHE8L4qcOslTt41GQFsSqPFdcbgSV20MvBzjGayR 15 | AI/WV0avW3oasdetJPZCR7bRbCbMbWTgclUfv5F25ENcR+BhNuilfL15owL0s4sS 16 | Wb4jOOHhXV9iXeS2dH0snFqv4BmQ9ZoA7zbM9lG3EU5DuxHESYkCnzJyEqqY3vWv 17 | PFRViCxLp5LQLmkTQ3dglVQA4x6ZaonaewdPtdhjkLUuIqDvQx5+kIaOELbSws+c 18 | bREYlnGrFw== 19 | -----END CERTIFICATE----- 20 | -----BEGIN RSA PRIVATE KEY----- 21 | Proc-Type: 4,ENCRYPTED 22 | DEK-Info: AES-256-CBC,D459676347D389E9135496D8AAFA7953 23 | 24 | wbrjxr9NHur8kgxDsgXOY9qFGKpONIQLxkuahUrDD/H+s/l7ugsLWOPsOXbjNL/7 25 | QYUBAx85HKm9D8BQ5g78Y82qfArap3/3IIuysDfQDh4fQodhVtmGTFiCOvudlGEp 26 | lq1niQRLThlxeRoFphH8KKiOTO9a/d8tdL7zRmiFwnVnhK4014mgVmgcSefA1AF5 27 | RbJAeMclUKddG6ltQK00ptg84CDXiMWQXFBGGmQ1av2lyFzC+xLP+qDqZAYTM9lZ 28 | NFRo2oEZP1ozfOVNSbXTanJgZ0DSSmhGE1PcVrHSeE/v+k1kPh3oVKi9GV51kIDC 29 | Zd9f/XltuDOzy1Ybn6gRy4nzNpzcwjSCIHEdSD5nxU5JfHfQ3OtnsEab7qf989iP 30 | s2LbCSp5uGTMvfesMIkixIZAQp2FeahZTAgU2Vx+wi5Kks68rOqeywEfzACL/Um5 31 | 7XZu8gDs4MgRRWnxK1BbJDPifICLvSJZvgB9FKX/hk4FHFF+MtcrkalehCuLooDV 32 | 3rfHNvRSbg7J97XQ3QC+k9ZDaumpy6n+LhaVv7BIJRBnBBtZ5Eg3DmPg6flqaHAU 33 | Y/8d82wb/pCmbvR3B1/Ebgs84DPJ+uZnY9M5Iwx19oqlVSR2ts/Tx619LGAm+BiQ 34 | 7YDoC4CFmpAA8Uw0xnUbNgx94NdNmlnLeLtS50b0XlWpHKbVzmVbNYEjY6NHMlLt 35 | aqxWHTYTa7g/c1bg2/nxF1Lbfu5VSTROGBUuer1c3yzVuyBrjcX92Jp4BJH78qOp 36 | N6lY6MnH4HYRXHjzlt/S0ZzO0faPPe18Q8SWvnDVuE3fYzzL772B56d2t8eodc+/ 37 | t6M3qJ60eXdsmgYOaPRLRUovN2xT2UUr0+biuguHyqfaVfcEU/adw+b9oUVE+5Nw 38 | nZHI5qhPnhLxChyZqbBl68zMUyKlfff4OyLvRGpfcHwBw6DTGjduB+DDsqqkcIB9 39 | 2VL6nps7ZVCwMPI18siUd6cttEOf6ZXrVqHg9wfDvJOlh2NNKNLxSAFubHc90Jlj 40 | KejrWenXo2w6YkSUeTV4t4cWu7U8rXIkTJXDl1S6NO8DWqNDo5KjgJ2SK5NlSOJ7 41 | jgECn390ooneJOxxytPVQO2xppXQZZS65RHrvhB+ss5xUknly9q+ICyt6xTR9nqA 42 | PKkeSE6qVY0J4JgFXpkgQxgwMnjSED3LKr3jlz28pr5cC6tsc5SSlekHjT2fcSrX 43 | uccaVahaJRigf+q+4XzmJtdwbZU+YWGZRVMlQLA5yzPHQHDYkPpOeYU4WReND8S4 44 | TZRkPHaxOZ2lKQwJB93V8Vbt2MvwRy392452a33S4TcQLaWzoOljXjmZjrp2rvRz 45 | prBaNe8LnO4V8Oliv+H+E0UWiWFDuI+HBy4X4O9plsbw/gk64Phl9qLiBwaX/AIR 46 | 66FXvC/czABo9oSt2jekcMtJofYr8Gr2bsJlt5ZX+GEOxz4jMv7xvz5/L3W7jVav 47 | pHGIv4xfN9FrXzL47O7UuUF9xZg4Rp/fxwpgEDNZmX/3DnP0ewZQUcgUX0pdqNGQ 48 | YVqJXcRF7KqG2NSQFuwPESZQnxU0WzSgRyUae7xg1WKfSuN8NVAzKhOgeqlD2IAo 49 | -----END RSA PRIVATE KEY----- 50 | -----BEGIN CERTIFICATE----- 51 | MIIDlzCCAn+gAwIBAgIJAMJYU3U6A0IRMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNV 52 | BAYTAkFVMQwwCgYDVQQIEwNOU1cxDzANBgNVBAcTBlN5ZG5leTENMAsGA1UEChME 53 | YXNpbzAeFw0xNTExMTgyMjMzNDhaFw0yMDExMTYyMjMzNDhaMDsxCzAJBgNVBAYT 54 | AkFVMQwwCgYDVQQIEwNOU1cxDzANBgNVBAcTBlN5ZG5leTENMAsGA1UEChMEYXNp 55 | bzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcRJocHdVMdLUJ/pypY 56 | QVSTC0t3IIgjwjazrK3kAaoIMvzPmDFxEXWcDx+nyz8kQ/E38Ir/ef2BCNGci5hu 57 | wkfMSuMoW9l2N4hx3QCcF46tTDEZztFxWAH7QbE2wYMlMgKZSxWimNfq0YjxEEXb 58 | QM0lGPLFh7Xoko29H0F3LKaaQV9u/vop3Hs0h12HeWlY4PiLp7QQTNGqbWcXycA0 59 | NZ/fyismireyEvPAgo6L8iXuAi7g0TVKVNlrticGGjMcMq6IMvxzEpSMkuMQ5rWj 60 | pZjWOoBjSYBuXdblcBRvXhOr2Ws8jJLMZfehKq9q1reQfoGV6xMnbwmumSXbWRWT 61 | 0vkCAwEAAaOBnTCBmjAdBgNVHQ4EFgQUK/Zv/AVtfIeucJw8VEtux1dhI1YwawYD 62 | VR0jBGQwYoAUK/Zv/AVtfIeucJw8VEtux1dhI1ahP6Q9MDsxCzAJBgNVBAYTAkFV 63 | MQwwCgYDVQQIEwNOU1cxDzANBgNVBAcTBlN5ZG5leTENMAsGA1UEChMEYXNpb4IJ 64 | AMJYU3U6A0IRMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBABLYXimq 65 | v/HLyIJi7Xn8AJUsICj8LKF/J24nwwiF+ibf7UkoChJURs4nN78bod/lpDVPTEVl 66 | gTBdV/vBJs416sCEFfsGjqB9OBYj4gb0VaJDsQd0+NMvXp0faKv2y9wgScxG9/cg 67 | aM7eRmyfMn1qjb6tpNxVOPpe/nFi8Vx/1orejBRaZr4zF5TkoPepfwLWQeXDUIdE 68 | +QHZ60jZAkR5RXTVU4u3kOKcJs839pmJYyxM4H2VxpR18vy4/YdIVWkREIUM2OgT 69 | 5iznIQIIgR56QRGP85uef+I6n0BHzrBk6du69bkQFxrFjLVGlal4bIQqSg4KGWgx 70 | dEdymMWzmMxpO9s= 71 | -----END CERTIFICATE----- 72 | -------------------------------------------------------------------------------- /src/echo_server_async.cpp: -------------------------------------------------------------------------------- 1 | // Asynchronous echo server. 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "boost/asio.hpp" 10 | #include "boost/core/ignore_unused.hpp" 11 | 12 | using boost::asio::ip::tcp; 13 | 14 | // ----------------------------------------------------------------------------- 15 | 16 | #define USE_BIND 1 // Use std::bind or lambda 17 | 18 | enum { BUF_SIZE = 1024 }; 19 | 20 | // ----------------------------------------------------------------------------- 21 | 22 | class Session : public std::enable_shared_from_this { 23 | public: 24 | Session(tcp::socket socket) : socket_(std::move(socket)) { 25 | } 26 | 27 | void Start() { 28 | DoRead(); 29 | } 30 | 31 | private: 32 | void DoRead() { 33 | #if USE_BIND 34 | socket_.async_read_some(boost::asio::buffer(buffer_), 35 | std::bind(&Session::OnRead, shared_from_this(), 36 | std::placeholders::_1, 37 | std::placeholders::_2)); 38 | #else 39 | auto self(shared_from_this()); 40 | 41 | socket_.async_read_some( 42 | boost::asio::buffer(buffer_), 43 | [this, self](boost::system::error_code ec, std::size_t length) { 44 | if (!ec) { 45 | AsyncWrite(length); 46 | } 47 | }); 48 | #endif // USE_BIND 49 | } 50 | 51 | void DoWrite(std::size_t length) { 52 | #if USE_BIND 53 | boost::asio::async_write(socket_, 54 | boost::asio::buffer(buffer_, length), 55 | std::bind(&Session::OnWrite, shared_from_this(), 56 | std::placeholders::_1, 57 | std::placeholders::_2)); 58 | #else 59 | auto self(shared_from_this()); 60 | 61 | boost::asio::async_write( 62 | socket_, 63 | boost::asio::buffer(buffer_, length), 64 | [this, self](boost::system::error_code ec, std::size_t /*length*/) { 65 | if (!ec) { 66 | AsyncRead(); 67 | } 68 | }); 69 | #endif // USE_BIND 70 | } 71 | 72 | #if USE_BIND 73 | void OnRead(boost::system::error_code ec, std::size_t length) { 74 | if (!ec) { 75 | DoWrite(length); 76 | } else { 77 | if (ec == boost::asio::error::eof) { 78 | std::cerr << "Socket read EOF: " << ec.message() << std::endl; 79 | } else if (ec == boost::asio::error::operation_aborted) { 80 | // The socket of this connection has been closed. 81 | // This happens, e.g., when the server was stopped by a signal (Ctrl-C). 82 | std::cerr << "Socket operation aborted: " << ec.message() << std::endl; 83 | } else { 84 | std::cerr << "Socket read error: " << ec.message() << std::endl; 85 | } 86 | } 87 | } 88 | 89 | void OnWrite(boost::system::error_code ec, std::size_t length) { 90 | boost::ignore_unused(length); 91 | 92 | if (!ec) { 93 | DoRead(); 94 | } 95 | } 96 | #endif // USE_BIND 97 | 98 | tcp::socket socket_; 99 | std::array buffer_; 100 | }; 101 | 102 | // ----------------------------------------------------------------------------- 103 | 104 | class Server { 105 | public: 106 | Server(boost::asio::io_context& io_context, std::uint16_t port) 107 | : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) { 108 | DoAccept(); 109 | } 110 | 111 | private: 112 | void DoAccept() { 113 | acceptor_.async_accept( 114 | [this](boost::system::error_code ec, tcp::socket socket) { 115 | if (!ec) { 116 | std::make_shared(std::move(socket))->Start(); 117 | } 118 | DoAccept(); 119 | }); 120 | } 121 | 122 | tcp::acceptor acceptor_; 123 | }; 124 | 125 | // ----------------------------------------------------------------------------- 126 | 127 | int main(int argc, char* argv[]) { 128 | if (argc != 2) { 129 | std::cerr << "Usage: " << argv[0] << " " << std::endl; 130 | return 1; 131 | } 132 | 133 | std::uint16_t port = std::atoi(argv[1]); 134 | 135 | boost::asio::io_context io_context; 136 | 137 | Server server{ io_context, port }; 138 | 139 | io_context.run(); 140 | 141 | return 0; 142 | } 143 | -------------------------------------------------------------------------------- /_clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Google 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: None 15 | AllowShortIfStatementsOnASingleLine: true 16 | AllowShortLoopsOnASingleLine: true 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: true 20 | AlwaysBreakTemplateDeclarations: Yes 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Attach 41 | BreakBeforeInheritanceComma: false 42 | BreakInheritanceList: BeforeColon 43 | BreakBeforeTernaryOperators: true 44 | BreakConstructorInitializersBeforeComma: false 45 | BreakConstructorInitializers: BeforeColon 46 | BreakAfterJavaFieldAnnotations: false 47 | BreakStringLiterals: true 48 | ColumnLimit: 80 49 | CommentPragmas: '^ IWYU pragma:' 50 | CompactNamespaces: false 51 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 52 | ConstructorInitializerIndentWidth: 4 53 | ContinuationIndentWidth: 4 54 | # Cpp11BracedListStyle: true 55 | Cpp11BracedListStyle: false 56 | DerivePointerAlignment: false 57 | DisableFormat: false 58 | ExperimentalAutoDetectBinPacking: false 59 | FixNamespaceComments: true 60 | ForEachMacros: 61 | - foreach 62 | - Q_FOREACH 63 | - BOOST_FOREACH 64 | IncludeBlocks: Preserve 65 | IncludeCategories: 66 | - Regex: '^' 67 | Priority: 2 68 | - Regex: '^<.*\.h>' 69 | Priority: 1 70 | - Regex: '^<.*' 71 | Priority: 2 72 | - Regex: '.*' 73 | Priority: 3 74 | IncludeIsMainRegex: '([-_](test|unittest))?$' 75 | IndentCaseLabels: true 76 | IndentPPDirectives: None 77 | IndentWidth: 2 78 | IndentWrappedFunctionNames: false 79 | JavaScriptQuotes: Leave 80 | JavaScriptWrapImports: true 81 | KeepEmptyLinesAtTheStartOfBlocks: false 82 | MacroBlockBegin: '' 83 | MacroBlockEnd: '' 84 | MaxEmptyLinesToKeep: 1 85 | NamespaceIndentation: None 86 | ObjCBinPackProtocolList: Never 87 | ObjCBlockIndentWidth: 2 88 | ObjCSpaceAfterProperty: false 89 | ObjCSpaceBeforeProtocolList: true 90 | PenaltyBreakAssignment: 2 91 | PenaltyBreakBeforeFirstCallParameter: 1 92 | PenaltyBreakComment: 300 93 | PenaltyBreakFirstLessLess: 120 94 | PenaltyBreakString: 1000 95 | PenaltyBreakTemplateDeclaration: 10 96 | PenaltyExcessCharacter: 1000000 97 | PenaltyReturnTypeOnItsOwnLine: 200 98 | PointerAlignment: Left 99 | RawStringFormats: 100 | - Language: Cpp 101 | Delimiters: 102 | - cc 103 | - CC 104 | - cpp 105 | - Cpp 106 | - CPP 107 | - 'c++' 108 | - 'C++' 109 | CanonicalDelimiter: '' 110 | BasedOnStyle: google 111 | - Language: TextProto 112 | Delimiters: 113 | - pb 114 | - PB 115 | - proto 116 | - PROTO 117 | EnclosingFunctions: 118 | - EqualsProto 119 | - EquivToProto 120 | - PARSE_PARTIAL_TEXT_PROTO 121 | - PARSE_TEST_PROTO 122 | - PARSE_TEXT_PROTO 123 | - ParseTextOrDie 124 | - ParseTextProtoOrDie 125 | CanonicalDelimiter: '' 126 | BasedOnStyle: google 127 | ReflowComments: true 128 | SortIncludes: true 129 | SortUsingDeclarations: true 130 | SpaceAfterCStyleCast: false 131 | SpaceAfterTemplateKeyword: true 132 | SpaceBeforeAssignmentOperators: true 133 | SpaceBeforeCpp11BracedList: false 134 | SpaceBeforeCtorInitializerColon: true 135 | SpaceBeforeInheritanceColon: true 136 | SpaceBeforeParens: ControlStatements 137 | SpaceBeforeRangeBasedForLoopColon: true 138 | SpaceInEmptyParentheses: false 139 | SpacesBeforeTrailingComments: 2 140 | SpacesInAngles: false 141 | SpacesInContainerLiterals: true 142 | SpacesInCStyleCastParentheses: false 143 | SpacesInParentheses: false 144 | SpacesInSquareBrackets: false 145 | Standard: Auto 146 | TabWidth: 8 147 | UseTab: Never 148 | ... 149 | 150 | -------------------------------------------------------------------------------- /src/ssl_server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "boost/asio.hpp" 5 | #include "boost/asio/ssl.hpp" 6 | 7 | using boost::asio::ip::tcp; 8 | namespace ssl = boost::asio::ssl; 9 | 10 | typedef ssl::stream ssl_socket; 11 | 12 | // ----------------------------------------------------------------------------- 13 | 14 | class Session { 15 | public: 16 | Session(boost::asio::io_context& io_context, ssl::context& ssl_context) 17 | : socket_(io_context, ssl_context) { 18 | } 19 | 20 | ssl_socket::lowest_layer_type& socket() { 21 | return socket_.lowest_layer(); 22 | } 23 | 24 | void Start() { 25 | socket_.async_handshake(ssl::stream_base::server, 26 | std::bind(&Session::HandleHandshake, this, 27 | std::placeholders::_1)); 28 | } 29 | 30 | void HandleHandshake(boost::system::error_code error) { 31 | if (!error) { 32 | socket_.async_read_some(boost::asio::buffer(data_, kMaxLength), 33 | std::bind(&Session::HandleRead, this, 34 | std::placeholders::_1, 35 | std::placeholders::_2)); 36 | } else { 37 | delete this; 38 | } 39 | } 40 | 41 | void HandleRead(boost::system::error_code ec, std::size_t length) { 42 | if (!ec) { 43 | boost::asio::async_write(socket_, 44 | boost::asio::buffer(data_, length), 45 | std::bind(&Session::HandleWrite, this, 46 | std::placeholders::_1)); 47 | } else { 48 | delete this; 49 | } 50 | } 51 | 52 | void HandleWrite(boost::system::error_code ec) { 53 | if (!ec) { 54 | socket_.async_read_some(boost::asio::buffer(data_, kMaxLength), 55 | std::bind(&Session::HandleRead, this, 56 | std::placeholders::_1, 57 | std::placeholders::_2)); 58 | } else { 59 | delete this; 60 | } 61 | } 62 | 63 | private: 64 | ssl_socket socket_; 65 | 66 | enum { kMaxLength = 1024 }; 67 | char data_[kMaxLength]; 68 | }; 69 | 70 | // ----------------------------------------------------------------------------- 71 | 72 | class Server { 73 | public: 74 | Server(boost::asio::io_context& io_context, unsigned short port) 75 | : io_context_(io_context), 76 | acceptor_(io_context, tcp::endpoint(tcp::v4(), port)), 77 | ssl_context_(ssl::context::sslv23) { 78 | ssl_context_.set_options(ssl::context::default_workarounds | 79 | ssl::context::no_sslv2 | 80 | ssl::context::single_dh_use); 81 | 82 | // See: 83 | // - http://blog.think-async.com/2006/09/ssl-password-callbacks.html 84 | // - https://stackoverflow.com/a/13986949/6825348 85 | ssl_context_.set_password_callback(std::bind(&Server::GetPassword, this)); 86 | 87 | // TODO 88 | std::string key = "D:\\github\\boost-asio-study\\src\\ssl\\server.pem"; 89 | std::string dh_file = "D:\\github\\boost-asio-study\\src\\ssl\\dh2048.pem"; 90 | 91 | ssl_context_.use_certificate_chain_file(key); 92 | ssl_context_.use_private_key_file(key, ssl::context::pem); 93 | ssl_context_.use_tmp_dh_file(dh_file); 94 | 95 | StartAccept(); 96 | } 97 | 98 | std::string GetPassword() const { 99 | return "test"; 100 | } 101 | 102 | void StartAccept() { 103 | Session* new_session = new Session(io_context_, ssl_context_); 104 | acceptor_.async_accept(new_session->socket(), 105 | std::bind(&Server::HandleAccept, this, new_session, 106 | std::placeholders::_1)); 107 | } 108 | 109 | void HandleAccept(Session* new_session, boost::system::error_code ec) { 110 | if (!ec) { 111 | new_session->Start(); 112 | } else { 113 | delete new_session; 114 | } 115 | 116 | StartAccept(); 117 | } 118 | 119 | private: 120 | boost::asio::io_context& io_context_; 121 | tcp::acceptor acceptor_; 122 | ssl::context ssl_context_; 123 | }; 124 | 125 | // ----------------------------------------------------------------------------- 126 | 127 | int main(int argc, char* argv[]) { 128 | if (argc != 2) { 129 | std::cout << "Usage: " << argv[0] << " " << std::endl; 130 | return 1; 131 | } 132 | 133 | try { 134 | boost::asio::io_context io_context; 135 | 136 | Server server(io_context, std::atoi(argv[1])); 137 | 138 | io_context.run(); 139 | 140 | } catch (const std::exception& e) { 141 | std::cerr << "Exception: " << e.what() << "\n"; 142 | } 143 | 144 | return 0; 145 | } 146 | -------------------------------------------------------------------------------- /src/ssl_client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define BOOST_ASIO_NO_DEPRECATED 5 | #include "boost/asio.hpp" 6 | #include "boost/asio/ssl.hpp" 7 | 8 | using boost::asio::ip::tcp; 9 | namespace ssl = boost::asio::ssl; 10 | 11 | // ----------------------------------------------------------------------------- 12 | 13 | class Client { 14 | public: 15 | Client(boost::asio::io_context& io_context, 16 | ssl::context& ssl_context, 17 | tcp::resolver::results_type endpoints) 18 | : socket_(io_context, ssl_context) { 19 | socket_.set_verify_mode(ssl::verify_peer); 20 | 21 | socket_.set_verify_callback(std::bind(&Client::VerifyCertificate, this, 22 | std::placeholders::_1, 23 | std::placeholders::_2)); 24 | 25 | boost::asio::async_connect(socket_.lowest_layer(), endpoints, 26 | std::bind(&Client::HandleConnect, this, 27 | std::placeholders::_1)); 28 | } 29 | 30 | bool VerifyCertificate(bool preverified, ssl::verify_context& ctx) { 31 | // The verify callback can be used to check whether the certificate that is 32 | // being presented is valid for the peer. For example, RFC 2818 describes 33 | // the steps involved in doing this for HTTPS. Consult the OpenSSL 34 | // documentation for more details. Note that the callback is called once 35 | // for each certificate in the certificate chain, starting from the root 36 | // certificate authority. 37 | 38 | // In this example we will simply print the certificate's subject name. 39 | char subject_name[256]; 40 | X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle()); 41 | X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256); 42 | std::cout << "Verifying " << subject_name << "\n"; 43 | 44 | return preverified; 45 | } 46 | 47 | void HandleConnect(boost::system::error_code ec) { 48 | if (!ec) { 49 | socket_.async_handshake(ssl::stream_base::client, 50 | std::bind(&Client::HandleHandshake, this, 51 | std::placeholders::_1)); 52 | } else { 53 | std::cout << "Connect failed: " << ec.message() << "\n"; 54 | } 55 | } 56 | 57 | void HandleHandshake(boost::system::error_code ec) { 58 | if (!ec) { 59 | std::cout << "Enter message: "; 60 | std::cin.getline(request_, kMaxLength); 61 | size_t request_length = strlen(request_); 62 | 63 | boost::asio::async_write(socket_, 64 | boost::asio::buffer(request_, request_length), 65 | std::bind(&Client::HandleWrite, this, 66 | std::placeholders::_1, 67 | std::placeholders::_2)); 68 | } else { 69 | std::cout << "Handshake failed: " << ec.message() << "\n"; 70 | } 71 | } 72 | 73 | void HandleWrite(boost::system::error_code ec, size_t length) { 74 | if (!ec) { 75 | boost::asio::async_read(socket_, 76 | boost::asio::buffer(reply_, length), 77 | std::bind(&Client::HandleRead, this, 78 | std::placeholders::_1, 79 | std::placeholders::_2)); 80 | } else { 81 | std::cout << "Write failed: " << ec.message() << "\n"; 82 | } 83 | } 84 | 85 | void HandleRead(boost::system::error_code ec, size_t length) { 86 | if (!ec) { 87 | std::cout << "Reply: "; 88 | std::cout.write(reply_, length); 89 | std::cout << "\n"; 90 | } else { 91 | std::cout << "Read failed: " << ec.message() << "\n"; 92 | } 93 | } 94 | 95 | private: 96 | ssl::stream socket_; 97 | 98 | enum { kMaxLength = 1024 }; 99 | char request_[kMaxLength]; 100 | char reply_[kMaxLength]; 101 | }; 102 | 103 | // ----------------------------------------------------------------------------- 104 | 105 | int main(int argc, char* argv[]) { 106 | if (argc != 3) { 107 | std::cout << "Usage: " << argv[0] << " " << std::endl; 108 | return 1; 109 | } 110 | 111 | try { 112 | boost::asio::io_context io_context; 113 | 114 | tcp::resolver resolver(io_context); 115 | auto endpoints = resolver.resolve(argv[1], argv[2]); 116 | 117 | ssl::context ssl_context(ssl::context::sslv23); 118 | 119 | // Use the default paths for finding CA certificates. 120 | //ssl_context.set_default_verify_paths(); 121 | // TODO 122 | ssl_context.load_verify_file("D:\\github\\boost-asio-study\\src\\ssl\\ca.pem"); 123 | 124 | Client client(io_context, ssl_context, endpoints); 125 | 126 | io_context.run(); 127 | 128 | } catch (const std::exception& e) { 129 | std::cerr << "Exception: " << e.what() << "\n"; 130 | } 131 | 132 | return 0; 133 | } 134 | -------------------------------------------------------------------------------- /src/ssl_http_client_sync.cpp: -------------------------------------------------------------------------------- 1 | // HTTPs client sending a GET request. 2 | // Based on Asio synchronous APIs. 3 | // Adapted from: https://stackoverflow.com/q/28264313/6825348 4 | 5 | #include 6 | #include 7 | 8 | #include "boost/asio.hpp" 9 | #include "boost/asio/ssl.hpp" 10 | 11 | using boost::asio::ip::tcp; 12 | namespace ssl = boost::asio::ssl; 13 | 14 | // Verify the certificate of the peer (remote host). 15 | #define SSL_VERIFY 0 16 | 17 | #define VERBOSE_VERIFICATION 1 18 | 19 | // ----------------------------------------------------------------------------- 20 | 21 | #if VERBOSE_VERIFICATION 22 | 23 | // Helper class that prints the current certificate's subject name and the 24 | // verification results. 25 | template 26 | class VerboseVerification { 27 | public: 28 | VerboseVerification(Verifier verifier) : verifier_(verifier) { 29 | } 30 | 31 | bool operator()(bool preverified, ssl::verify_context& ctx) { 32 | char subject_name[256]; 33 | X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle()); 34 | X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256); 35 | 36 | bool verified = verifier_(preverified, ctx); 37 | std::cout << "Verifying: " << subject_name << std::endl; 38 | std::cout << "Verified: " << verified << std::endl; 39 | return verified; 40 | } 41 | 42 | private: 43 | Verifier verifier_; 44 | }; 45 | 46 | template 47 | VerboseVerification MakeVerboseVerification(Verifier verifier) { 48 | return VerboseVerification(verifier); 49 | } 50 | 51 | #endif // VERBOSE_VERIFICATION 52 | 53 | // ----------------------------------------------------------------------------- 54 | 55 | void Help(const char* argv0) { 56 | std::cout << "Usage: " << argv0 << " " << std::endl; 57 | std::cout << " E.g.," << std::endl; 58 | std::cout << " " << argv0 << " www.boost.org /LICENSE_1_0.txt" << std::endl; 59 | std::cout << " " << argv0 << " www.google.com /" << std::endl; 60 | } 61 | 62 | int main(int argc, char* argv[]) { 63 | if (argc != 3) { 64 | Help(argv[0]); 65 | return 1; 66 | } 67 | 68 | std::string host = argv[1]; 69 | std::string path = argv[2]; 70 | 71 | try { 72 | boost::asio::io_context io_context; 73 | 74 | ssl::context ssl_context(ssl::context::sslv23); 75 | 76 | // Use the default paths for finding CA certificates. 77 | // Trusted certificates are often installed or updated via the OS, browsers, 78 | // or individual packages. For instance, in the *nix world, the certificates 79 | // are often available through the ca-certificates package, and the 80 | // certificates are installed to locations that set_default_verify_paths() 81 | // will find. 82 | // Run the following command on Linux for the package information: 83 | // $ aptitude search ca-certifi* 84 | // See also: https://serverfault.com/questions/62496/ssl-certificate-location-on-unix-linux 85 | // On Windows, you have to set environment variable SSL_CERT_FILE properly. 86 | ssl_context.set_default_verify_paths(); 87 | 88 | // Get a list of endpoints corresponding to the server name. 89 | tcp::resolver resolver(io_context); 90 | auto endpoints = resolver.resolve(host, "https"); // TODO: "https" 91 | 92 | // Try each endpoint until we successfully establish a connection. 93 | ssl::stream ssl_socket(io_context, ssl_context); 94 | boost::asio::connect(ssl_socket.lowest_layer(), endpoints); 95 | 96 | #if SSL_VERIFY 97 | // Perform SSL handshake and verify the remote host's certificate. 98 | ssl_socket.set_verify_mode(ssl::verify_peer); 99 | 100 | #else 101 | ssl_socket.set_verify_mode(ssl::verify_none); 102 | #endif // SSL_VERIFY 103 | 104 | // ssl::host_name_verification has been added since Boost 1.73 to replace 105 | // ssl::rfc2818_verification. 106 | #if BOOST_VERSION < 107300 107 | 108 | #if VERBOSE_VERIFICATION 109 | ssl_socket.set_verify_callback( 110 | MakeVerboseVerification(ssl::rfc2818_verification(host))); 111 | #else 112 | ssl_socket.set_verify_callback(ssl::rfc2818_verification(host)); 113 | #endif // VERBOSE_VERIFICATION 114 | 115 | #else 116 | 117 | #if VERBOSE_VERIFICATION 118 | ssl_socket.set_verify_callback( 119 | MakeVerboseVerification(ssl::host_name_verification(host))); 120 | #else 121 | ssl_socket.set_verify_callback(ssl::host_name_verification(host)); 122 | #endif // VERBOSE_VERIFICATION 123 | 124 | #endif // BOOST_VERSION < 107300 125 | 126 | ssl_socket.handshake(ssl::stream_base::client); 127 | 128 | boost::asio::streambuf request; 129 | std::ostream request_stream(&request); 130 | request_stream << "GET " << path << " HTTP/1.1\r\n"; 131 | request_stream << "Host: " << host << "\r\n\r\n"; 132 | 133 | // Send the request. 134 | boost::asio::write(ssl_socket, request); 135 | 136 | // Read response. 137 | 138 | // Read the status line. 139 | boost::asio::streambuf response; 140 | boost::asio::read_until(ssl_socket, response, "\r\n"); 141 | 142 | std::istream response_stream(&response); 143 | std::string status_line; 144 | std::getline(response_stream, status_line); 145 | std::cout << status_line << std::endl; 146 | 147 | // TODO: Continue to read until the end. 148 | 149 | } catch (const std::exception& e) { 150 | std::cout << "Exception: " << e.what() << std::endl; 151 | } 152 | 153 | return 0; 154 | } 155 | -------------------------------------------------------------------------------- /src/ssl_http_client_async.cpp: -------------------------------------------------------------------------------- 1 | // HTTPs client sending a GET request. 2 | // Based on Asio asynchronous APIs. 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "boost/asio.hpp" 9 | #include "boost/asio/ssl.hpp" 10 | 11 | // ----------------------------------------------------------------------------- 12 | 13 | using boost::asio::ip::tcp; 14 | namespace ssl = boost::asio::ssl; 15 | 16 | typedef ssl::stream ssl_socket; 17 | 18 | // Verify the certificate of the peer (remote host). 19 | #define SSL_VERIFY 0 20 | 21 | // ----------------------------------------------------------------------------- 22 | 23 | class Client { 24 | public: 25 | Client(boost::asio::io_context& io_context, 26 | const std::string& host, const std::string& path); 27 | 28 | private: 29 | void ConnectHandler(boost::system::error_code ec, tcp::endpoint); 30 | 31 | void HandshakeHandler(boost::system::error_code ec); 32 | 33 | void AsyncWrite(); 34 | void WriteHandler(boost::system::error_code ec, std::size_t length); 35 | 36 | void AsyncReadSome(); 37 | void ReadHandler(boost::system::error_code ec, std::size_t length); 38 | 39 | boost::asio::io_context& io_context_; 40 | 41 | std::string host_; 42 | std::string path_; 43 | 44 | ssl::context ssl_context_; 45 | ssl::stream ssl_socket_; 46 | 47 | boost::asio::streambuf request_; 48 | std::vector buffer_; 49 | }; 50 | 51 | // ----------------------------------------------------------------------------- 52 | 53 | Client::Client(boost::asio::io_context& io_context, 54 | const std::string& host, const std::string& path) 55 | : io_context_(io_context), 56 | host_(host), path_(path), 57 | ssl_context_(ssl::context::sslv23), 58 | ssl_socket_(io_context_, ssl_context_), 59 | buffer_(1024) { 60 | 61 | // Use the default paths for finding CA certificates. 62 | ssl_context_.set_default_verify_paths(); 63 | 64 | boost::system::error_code ec; 65 | 66 | // Get a list of endpoints corresponding to the server name. 67 | tcp::resolver resolver(io_context_); 68 | auto endpoints = resolver.resolve(host_, "https", ec); 69 | 70 | if (ec) { 71 | std::cerr << "Resolve failed: " << ec.message() << std::endl; 72 | return; 73 | } 74 | 75 | // ConnectHandler: void (boost::system::error_code, tcp::endpoint) 76 | boost::asio::async_connect(ssl_socket_.lowest_layer(), endpoints, 77 | std::bind(&Client::ConnectHandler, this, 78 | std::placeholders::_1, 79 | std::placeholders::_2)); 80 | } 81 | 82 | void Client::ConnectHandler(boost::system::error_code ec, tcp::endpoint) { 83 | if (ec) { 84 | std::cout << "Connect failed: " << ec.message() << std::endl; 85 | } else { 86 | #if SSL_VERIFY 87 | ssl_socket_.set_verify_mode(ssl::verify_peer); 88 | #else 89 | ssl_socket_.set_verify_mode(ssl::verify_none); 90 | #endif // SSL_VERIFY 91 | 92 | // ssl::host_name_verification has been added since Boost 1.73 to replace 93 | // ssl::rfc2818_verification. 94 | #if BOOST_VERSION < 107300 95 | ssl_socket_.set_verify_callback(ssl::rfc2818_verification(host_)); 96 | #else 97 | ssl_socket_.set_verify_callback(ssl::host_name_verification(host_)); 98 | #endif // BOOST_VERSION < 107300 99 | 100 | // HandshakeHandler: void (boost::system::error_code) 101 | ssl_socket_.async_handshake(ssl::stream_base::client, 102 | std::bind(&Client::HandshakeHandler, 103 | this, 104 | std::placeholders::_1)); 105 | 106 | } 107 | } 108 | 109 | void Client::HandshakeHandler(boost::system::error_code ec) { 110 | if (ec) { 111 | std::cerr << "Handshake failed: " << ec.message() << std::endl; 112 | } else { 113 | AsyncWrite(); 114 | } 115 | } 116 | 117 | void Client::AsyncWrite() { 118 | std::ostream request_stream(&request_); 119 | request_stream << "GET " << path_ << " HTTP/1.1\r\n"; 120 | request_stream << "Host: " << host_ << "\r\n\r\n"; 121 | 122 | // WriteHandler: void (boost::system::error_code, std::size_t) 123 | boost::asio::async_write(ssl_socket_, request_, 124 | std::bind(&Client::WriteHandler, this, 125 | std::placeholders::_1, 126 | std::placeholders::_2)); 127 | } 128 | 129 | void Client::WriteHandler(boost::system::error_code ec, std::size_t length) { 130 | if (ec) { 131 | std::cerr << "Write failed: " << ec.message() << std::endl; 132 | } else { 133 | AsyncReadSome(); 134 | } 135 | } 136 | 137 | void Client::AsyncReadSome() { 138 | ssl_socket_.async_read_some(boost::asio::buffer(buffer_), 139 | std::bind(&Client::ReadHandler, this, 140 | std::placeholders::_1, 141 | std::placeholders::_2)); 142 | } 143 | 144 | 145 | void Client::ReadHandler(boost::system::error_code ec, std::size_t length) { 146 | if (ec) { 147 | std::cout << ec.message() << std::endl; 148 | } else { 149 | std::cout.write(buffer_.data(), length); 150 | 151 | // TODO: Call AsyncReadSome() to read until the end. 152 | } 153 | } 154 | 155 | // ----------------------------------------------------------------------------- 156 | 157 | void Help(const char* argv0) { 158 | std::cout << "Usage: " << argv0 << " " << std::endl; 159 | std::cout << " E.g.," << std::endl; 160 | std::cout << " " << argv0 << " www.boost.org /LICENSE_1_0.txt" << std::endl; 161 | std::cout << " " << argv0 << " www.google.com /" << std::endl; 162 | } 163 | 164 | int main(int argc, char* argv[]) { 165 | if (argc != 3) { 166 | Help(argv[0]); 167 | return 1; 168 | } 169 | 170 | std::string host = argv[1]; 171 | std::string path = argv[2]; 172 | 173 | try { 174 | boost::asio::io_context io_context; 175 | 176 | Client client(io_context, host, path); 177 | 178 | io_context.run(); 179 | 180 | } catch (const std::exception& e) { 181 | std::cout << "Exception: " << e.what() << std::endl; 182 | } 183 | 184 | return 0; 185 | } 186 | -------------------------------------------------------------------------------- /src/echo_client_async.cpp: -------------------------------------------------------------------------------- 1 | // Asynchronous echo client. 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "boost/asio.hpp" 10 | #include "boost/core/ignore_unused.hpp" 11 | 12 | #include "utility.h" // for printing endpoints 13 | 14 | using boost::asio::ip::tcp; 15 | 16 | // Use async_resolve() or not. 17 | #define RESOLVE_ASYNC 1 18 | 19 | // Only resolve IPv4. 20 | #define RESOLVE_IPV4_ONLY 1 21 | 22 | // ----------------------------------------------------------------------------- 23 | 24 | class Client { 25 | public: 26 | Client(boost::asio::io_context& io_context, 27 | const std::string& host, const std::string& port); 28 | 29 | private: 30 | 31 | #if RESOLVE_ASYNC 32 | void OnResolve(boost::system::error_code ec, 33 | tcp::resolver::results_type endpoints); 34 | #endif // RESOLVE_ASYNC 35 | 36 | void OnConnect(boost::system::error_code ec, tcp::endpoint endpoint); 37 | 38 | void DoWrite(); 39 | void OnWrite(boost::system::error_code ec, std::size_t length); 40 | 41 | void OnRead(boost::system::error_code ec, std::size_t length); 42 | 43 | tcp::socket socket_; 44 | 45 | #if RESOLVE_ASYNC 46 | tcp::resolver resolver_; 47 | #endif 48 | 49 | enum { BUF_SIZE = 1024 }; 50 | 51 | char cin_buf_[BUF_SIZE]; 52 | 53 | // NOTE: std::vector is better than std::array in practice. 54 | std::array buf_; 55 | }; 56 | 57 | // ----------------------------------------------------------------------------- 58 | 59 | Client::Client(boost::asio::io_context& io_context, 60 | const std::string& host, const std::string& port) 61 | #if RESOLVE_ASYNC 62 | : socket_(io_context), resolver_(io_context) { 63 | #else 64 | : socket_(io_context) { 65 | #endif 66 | 67 | #if RESOLVE_ASYNC 68 | 69 | resolver_.async_resolve(tcp::v4(), host, port, 70 | std::bind(&Client::OnResolve, this, 71 | std::placeholders::_1, 72 | std::placeholders::_2)); 73 | 74 | #else 75 | 76 | // If you don't specify tcp::v4() as the first parameter (protocol) of 77 | // resolve(), the result will have two endpoints, one for v6, one for 78 | // v4. The first v6 endpoint will fail to connect. 79 | 80 | tcp::resolver resolver(io_context); 81 | 82 | #if RESOLVE_IPV4_ONLY 83 | 84 | auto endpoints = resolver.resolve(tcp::v4(), host, port); 85 | // 127.0.0.1:2017, v4 86 | 87 | #else 88 | 89 | auto endpoints = resolver.resolve(host, port); 90 | // [::1]:2017, v6 91 | // 127.0.0.1:2017, v4 92 | 93 | #endif // RESOLVE_IPV4_ONLY 94 | 95 | utility::PrintEndpoints(std::cout, endpoints); 96 | 97 | // ConnectHandler: void(boost::system::error_code, tcp::endpoint) 98 | boost::asio::async_connect(socket_, endpoints, 99 | std::bind(&Client::OnConnect, this, 100 | std::placeholders::_1, 101 | std::placeholders::_2)); 102 | 103 | #endif // RESOLVE_ASYNC 104 | } 105 | 106 | #if RESOLVE_ASYNC 107 | 108 | void Client::OnResolve(boost::system::error_code ec, 109 | tcp::resolver::results_type endpoints) { 110 | if (ec) { 111 | std::cerr << "Resolve: " << ec.message() << std::endl; 112 | } else { 113 | // ConnectHandler: void(boost::system::error_code, tcp::endpoint) 114 | boost::asio::async_connect(socket_, endpoints, 115 | std::bind(&Client::OnConnect, this, 116 | std::placeholders::_1, 117 | std::placeholders::_2)); 118 | } 119 | } 120 | 121 | #endif // RESOLVE_ASYNC 122 | 123 | void Client::OnConnect(boost::system::error_code ec, tcp::endpoint endpoint) { 124 | if (ec) { 125 | std::cout << "Connect failed: " << ec.message() << std::endl; 126 | socket_.close(); 127 | } else { 128 | DoWrite(); 129 | } 130 | } 131 | 132 | void Client::DoWrite() { 133 | std::size_t len = 0; 134 | do { 135 | std::cout << "Enter message: "; 136 | std::cin.getline(cin_buf_, BUF_SIZE); 137 | len = strlen(cin_buf_); 138 | } while (len == 0); 139 | 140 | // TODO: Second parameter 141 | // WriteHandler: void (boost::system::error_code, std::size_t) 142 | boost::asio::async_write(socket_, 143 | boost::asio::buffer(cin_buf_, len), 144 | std::bind(&Client::OnWrite, this, 145 | std::placeholders::_1, 146 | std::placeholders::_2)); 147 | } 148 | 149 | void Client::OnWrite(boost::system::error_code ec, std::size_t length) { 150 | boost::ignore_unused(length); 151 | 152 | if (!ec) { 153 | std::cout << "Reply is: "; 154 | 155 | socket_.async_read_some(boost::asio::buffer(buf_), 156 | std::bind(&Client::OnRead, this, 157 | std::placeholders::_1, 158 | std::placeholders::_2)); 159 | } 160 | } 161 | 162 | void Client::OnRead(boost::system::error_code ec, std::size_t length) { 163 | if (!ec) { 164 | std::cout.write(buf_.data(), length); 165 | std::cout << std::endl; 166 | } 167 | 168 | // Pause, please press any key to continue. 169 | getchar(); 170 | 171 | std::cout << "Shutdown socket..." << std::endl; 172 | 173 | // Initiate graceful connection closure. 174 | // Socket close VS. shutdown: 175 | // https://stackoverflow.com/questions/4160347/close-vs-shutdown-socket 176 | boost::system::error_code ec2; 177 | socket_.shutdown(tcp::socket::shutdown_both, ec2); 178 | 179 | // shutdown() with `shutdown_both` or `shutdown_send` signals EOF to the peer. 180 | // It means that the peer will read an EOF. 181 | 182 | if (ec2) { 183 | std::cerr << "Socket shutdown error: " << ec2.message() << std::endl; 184 | ec2.clear(); 185 | // Don't return, try to close the socket anywhere. 186 | } 187 | 188 | // Pause, please press any key to continue. 189 | getchar(); 190 | 191 | std::cout << "Close socket..." << std::endl; 192 | 193 | socket_.close(ec2); 194 | 195 | if (ec2) { 196 | std::cerr << "Socket close error: " << ec2.message() << std::endl; 197 | } 198 | 199 | // Optionally, continue to write. 200 | // DoWrite(); 201 | } 202 | 203 | // ----------------------------------------------------------------------------- 204 | 205 | int main(int argc, char* argv[]) { 206 | if (argc != 3) { 207 | std::cout << "Usage: " << argv[0] << " " << std::endl; 208 | return 1; 209 | } 210 | 211 | std::string host = argv[1]; 212 | std::string port = argv[2]; 213 | 214 | boost::asio::io_context io_context; 215 | 216 | Client client{ io_context, host, port }; 217 | 218 | io_context.run(); 219 | 220 | return 0; 221 | } 222 | -------------------------------------------------------------------------------- /src/ssl_http_client_async_blocking.cpp: -------------------------------------------------------------------------------- 1 | // HTTPs client sending a GET request. 2 | // Based on Asio asynchronous APIs but run in blocking mode. 3 | // Why not just use synchronous APIs? 4 | // Because you might want to add a deadline timer for timeout control. 5 | // See |ssl_http_client_async_blocking_timeout| for the details. 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "boost/asio.hpp" 12 | #include "boost/asio/ssl.hpp" 13 | #include "boost/lambda/bind.hpp" 14 | #include "boost/lambda/lambda.hpp" 15 | 16 | // ----------------------------------------------------------------------------- 17 | 18 | using boost::asio::ip::tcp; 19 | namespace ssl = boost::asio::ssl; 20 | 21 | typedef ssl::stream ssl_socket; 22 | 23 | // Verify the certificate of the peer (remote host). 24 | #define SSL_VERIFY 0 25 | 26 | // ----------------------------------------------------------------------------- 27 | 28 | class Client { 29 | public: 30 | Client(const std::string& host, const std::string& path); 31 | 32 | bool Request(); 33 | 34 | private: 35 | bool Connect(); 36 | 37 | bool Handshake(); 38 | 39 | bool SendRequest(); 40 | 41 | bool ReadResponse(); 42 | 43 | boost::asio::io_context io_context_; 44 | 45 | std::string host_; 46 | std::string path_; 47 | 48 | ssl::context ssl_context_; 49 | ssl::stream ssl_socket_; 50 | 51 | boost::asio::streambuf request_; 52 | 53 | std::vector buffer_; 54 | }; 55 | 56 | // ----------------------------------------------------------------------------- 57 | 58 | Client::Client(const std::string& host, const std::string& path) 59 | : host_(host), path_(path), 60 | ssl_context_(ssl::context::sslv23), 61 | ssl_socket_(io_context_, ssl_context_), 62 | buffer_(1024) { 63 | 64 | // Use the default paths for finding CA certificates. 65 | ssl_context_.set_default_verify_paths(); 66 | } 67 | 68 | bool Client::Request() { 69 | if (!Connect()) { 70 | return false; 71 | } 72 | 73 | if (!Handshake()) { 74 | return false; 75 | } 76 | 77 | if (!SendRequest()) { 78 | return false; 79 | } 80 | 81 | return ReadResponse(); 82 | } 83 | 84 | bool Client::Connect() { 85 | boost::system::error_code ec; 86 | 87 | // Get a list of endpoints corresponding to the server name. 88 | tcp::resolver resolver(io_context_); 89 | auto endpoints = resolver.resolve(host_, "https", ec); 90 | 91 | if (ec) { 92 | std::cerr << "Resolve failed: " << ec.message() << std::endl; 93 | return false; 94 | } 95 | 96 | ec = boost::asio::error::would_block; 97 | 98 | // ConnectHandler: void (boost::system::error_code, tcp::endpoint) 99 | // Using |boost::lambda::var()| is identical to: 100 | // boost::asio::async_connect( 101 | // ssl_socket_.lowest_layer(), endpoints, 102 | // [this, &ec](boost::system::error_code inner_ec, tcp::endpoint) { 103 | // ec = inner_ec; 104 | // }); 105 | boost::asio::async_connect(ssl_socket_.lowest_layer(), endpoints, 106 | boost::lambda::var(ec) = boost::lambda::_1); 107 | 108 | // Block until the asynchronous operation has completed. 109 | do { 110 | io_context_.run_one(); 111 | } while (ec == boost::asio::error::would_block); 112 | 113 | if (ec) { 114 | std::cerr << "Connect failed: " << ec.message() << std::endl; 115 | return false; 116 | } 117 | 118 | return true; 119 | } 120 | 121 | bool Client::Handshake() { 122 | boost::system::error_code ec = boost::asio::error::would_block; 123 | 124 | #if SSL_VERIFY 125 | ssl_socket_.set_verify_mode(ssl::verify_peer); 126 | #else 127 | ssl_socket_.set_verify_mode(ssl::verify_none); 128 | #endif // SSL_VERIFY 129 | 130 | // ssl::host_name_verification has been added since Boost 1.73 to replace 131 | // ssl::rfc2818_verification. 132 | #if BOOST_VERSION < 107300 133 | ssl_socket_.set_verify_callback(ssl::rfc2818_verification(host_)); 134 | #else 135 | ssl_socket_.set_verify_callback(ssl::host_name_verification(host_)); 136 | #endif // BOOST_VERSION < 107300 137 | 138 | // HandshakeHandler: void (boost::system::error_code) 139 | ssl_socket_.async_handshake(ssl::stream_base::client, 140 | boost::lambda::var(ec) = boost::lambda::_1); 141 | 142 | // Block until the asynchronous operation has completed. 143 | do { 144 | io_context_.run_one(); 145 | } while (ec == boost::asio::error::would_block); 146 | 147 | if (ec) { 148 | std::cerr << "Handshake failed: " << ec.message() << std::endl; 149 | return false; 150 | } 151 | 152 | return true; 153 | } 154 | 155 | bool Client::SendRequest() { 156 | std::ostream request_stream(&request_); 157 | request_stream << "GET " << path_ << " HTTP/1.1\r\n"; 158 | request_stream << "Host: " << host_ << "\r\n\r\n"; 159 | 160 | boost::system::error_code ec = boost::asio::error::would_block; 161 | 162 | // WriteHandler: void (boost::system::error_code, std::size_t) 163 | boost::asio::async_write(ssl_socket_, request_, 164 | boost::lambda::var(ec) = boost::lambda::_1); 165 | 166 | // Block until the asynchronous operation has completed. 167 | do { 168 | io_context_.run_one(); 169 | } while (ec == boost::asio::error::would_block); 170 | 171 | if (ec) { 172 | std::cerr << "Write failed: " << ec.message() << std::endl; 173 | return false; 174 | } 175 | 176 | return true; 177 | } 178 | 179 | bool Client::ReadResponse() { 180 | boost::system::error_code ec = boost::asio::error::would_block; 181 | 182 | ssl_socket_.async_read_some( 183 | boost::asio::buffer(buffer_), 184 | [this, &ec](boost::system::error_code inner_ec, std::size_t length) { 185 | ec = inner_ec; 186 | 187 | if (inner_ec || length == 0) { 188 | std::cout << "Socket read error." << std::endl; 189 | return; 190 | } 191 | 192 | std::cout.write(buffer_.data(), length); 193 | 194 | // TODO: Call ReadResponse() to read until the end. 195 | }); 196 | 197 | // Block until the asynchronous operation has completed. 198 | do { 199 | io_context_.run_one(); 200 | } while (ec == boost::asio::error::would_block); 201 | 202 | if (ec) { 203 | std::cout << "Read failed: " << ec.message() << std::endl; 204 | return false; 205 | } 206 | 207 | return true; 208 | } 209 | 210 | // ----------------------------------------------------------------------------- 211 | 212 | void Help(const char* argv0) { 213 | std::cout << "Usage: " << argv0 << " " << std::endl; 214 | std::cout << " E.g.," << std::endl; 215 | std::cout << " " << argv0 << " www.boost.org /LICENSE_1_0.txt" << std::endl; 216 | std::cout << " " << argv0 << " www.google.com /" << std::endl; 217 | } 218 | 219 | int main(int argc, char* argv[]) { 220 | if (argc != 3) { 221 | Help(argv[0]); 222 | return 1; 223 | } 224 | 225 | std::string host = argv[1]; 226 | std::string path = argv[2]; 227 | 228 | try { 229 | Client client(host, path); 230 | 231 | client.Request(); 232 | 233 | } catch (const std::exception& e) { 234 | std::cout << "Exception: " << e.what() << std::endl; 235 | } 236 | 237 | return 0; 238 | } 239 | -------------------------------------------------------------------------------- /Asio_Tips_And_Notes_zh-CN.md: -------------------------------------------------------------------------------- 1 | # Asio Tips And Notes 2 | 3 | 本文列举 Asio 各种值得注意的细节。 4 | 5 | ## No Deprecated 6 | 7 | 在包含 Asio 头文件之前,定义宏 `BOOST_ASIO_NO_DEPRECATED`,这样在编译时,Asio 就会剔除那些已经过时的接口。 8 | 9 | 比如在最新的 Boost 1.66 中,`io_service` 已经改名为 `io_context`,如果没有 `BOOST_ASIO_NO_DEPRECATED`,还是可以用 `io_service` 的,虽然那只是 `io_context` 的一个 `typedef`。 10 | 11 | `BOOST_ASIO_NO_DEPRECATED` 可以保证你用的是最新修订的 API。长期来看,有便于代码的维护。何况,这些修订正是 Asio 进入标准库的前奏。 12 | 13 | ```cpp 14 | #define BOOST_ASIO_NO_DEPRECATED 15 | #include "boost/asio/io_context.hpp" 16 | #include "boost/asio/steady_timer.hpp" 17 | ... 18 | ``` 19 | 20 | ## \_WIN32_WINNT Warning 21 | 22 | 在 Windows 平台,编译时会遇到关于 `_WIN32_WINNT` 的警告。 23 | 24 | ~~可以说,这是 Asio 自身的问题。它应该在某个地方包含 `SDKDDKVer.h`。不应该让用户自己去定义平台的版本。~~ 25 | 26 | ~~如果你用 CMake,可以借助下面这个宏自动检测 `_WIN32_WINNT`: 27 | (详见:https://stackoverflow.com/a/40217291/6825348)~~ 28 | 29 | ```cmake 30 | if (WIN32) 31 | macro(get_WIN32_WINNT version) 32 | if (CMAKE_SYSTEM_VERSION) 33 | set(ver ${CMAKE_SYSTEM_VERSION}) 34 | string(REGEX MATCH "^([0-9]+).([0-9])" ver ${ver}) 35 | string(REGEX MATCH "^([0-9]+)" verMajor ${ver}) 36 | # Check for Windows 10, b/c we'll need to convert to hex 'A'. 37 | if ("${verMajor}" MATCHES "10") 38 | set(verMajor "A") 39 | string(REGEX REPLACE "^([0-9]+)" ${verMajor} ver ${ver}) 40 | endif ("${verMajor}" MATCHES "10") 41 | # Remove all remaining '.' characters. 42 | string(REPLACE "." "" ver ${ver}) 43 | # Prepend each digit with a zero. 44 | string(REGEX REPLACE "([0-9A-Z])" "0\\1" ver ${ver}) 45 | set(${version} "0x${ver}") 46 | endif(CMAKE_SYSTEM_VERSION) 47 | endmacro(get_WIN32_WINNT) 48 | 49 | get_WIN32_WINNT(ver) 50 | add_definitions(-D_WIN32_WINNT=${ver}) 51 | endif(WIN32) 52 | ``` 53 | 2020-08-28:上面的描述是错的!Asio 需要你定义 `_WIN32_WINNT`,是指你想支持的最低 Windows 版本。所以只需在 CMake 里这样写就可以了: 54 | 55 | ```cmake 56 | if(WIN32) 57 | # Asio needs this! 58 | # 0x0601 means Win7. So our application targets Win7 and above. 59 | add_definitions(-D_WIN32_WINNT=0x0601) 60 | endif() 61 | ``` 62 | 63 | ## 尽量少包含头文件 64 | 65 | 尽量不要直接包含大而全的 `boost/asio.hpp`。 66 | 这样做,是为了帮助自己记忆哪个类源于哪个具体的头文件,以及避免包含那些不必要的头文件。 67 | 68 | 在实际项目中,在你自己的某个「头文件」里简单粗暴的包含 `boost/asio.hpp` 是很不妥的;当然,在你的「源文件」里包含 `boost/asio.hpp` 是可以接受的,毕竟实际项目依赖的东西比较多,很难搞清楚每一个定义源自哪里。 69 | 70 | ## Handler 签名问题 71 | 72 | 虽然关于 Handler 的签名,文档里都有说明,但是直接定位到源码,更方便,也更精确。 73 | 74 | 以 `steady_timer.async_wait()` 为例,在 IDE 里定位到 `async_wait()` 的定义,代码(片段)如下: 75 | 76 | ```cpp 77 | template 78 | BOOST_ASIO_INITFN_RESULT_TYPE(WaitHandler, 79 | void (boost::system::error_code)) 80 | async_wait(BOOST_ASIO_MOVE_ARG(WaitHandler) handler) 81 | { 82 | ``` 83 | 84 | 通过宏 `BOOST_ASIO_INITFN_RESULT_TYPE`,`WaitHandler` 的签名一目了然。 85 | 86 | 87 | ## Handler 的 error_code 参数到底是不是引用? 88 | 89 | 其实,早期的版本应该是 `const boost::system::error_code&`,现在文档和代码注释里还有这么写的,估计是没来得及更新。 90 | 前面在说 Handler 签名时,已经看到 `BOOST_ASIO_INITFN_RESULT_TYPE` 这个宏的提示作用,翻一翻 Asio 源码,`error_code` 其实都已经传值了。 91 | 92 | 奇怪的是,即使你的 Handler 传 `error_code` 为引用,编译运行也都没有问题。 93 | 94 | ```cpp 95 | void Print(const boost::system::error_code& ec) { 96 | std::cout << "Hello, world!" << std::endl; 97 | } 98 | 99 | int main() { 100 | boost::asio::io_context ioc; 101 | boost::asio::steady_timer timer(ioc, std::chrono::seconds(3)); 102 | 103 | timer.async_wait(&Print); 104 | 105 | ioc.run(); 106 | return 0; 107 | } 108 | ``` 109 | 110 | 而我发现,当 Handler 是成员函数时,就不行了。下面这个 timer 的例子,如果把 `Print` 的 `error_code` 改成引用,就不能编译了。 111 | ```cpp 112 | class Printer { 113 | public: 114 | ... 115 | 116 | void Start() { 117 | timer_.async_wait(std::bind(&Printer::Print, this, std::placeholders::_1)); 118 | } 119 | 120 | private: 121 | // 不能用 const boost::system::error_code& 122 | void Print(boost::system::error_code ec) { 123 | ... 124 | } 125 | 126 | private: 127 | boost::asio::steady_timer timer_; 128 | int count_; 129 | }; 130 | ``` 131 | 这个问题在习惯了引用的情况下,害苦了我,真是百思不得其解!也算是 Boost 比较坑的一个地方吧。 132 | 2019/08/30: 实测 1.70 没有这个问题,可以用 const reference。也许是 1.66 的 bug 吧。 133 | 134 | ## Bind 占位符 135 | 136 | 调用 `bind` 时,使用了占位符(placeholder),其实下面四种写法都可以: 137 | 138 | ```cpp 139 | boost::bind(Print, boost::asio::placeholders::error, &timer, &count) 140 | boost::bind(Print, boost::placeholders::_1, &timer, &count); 141 | boost::bind(Print, _1, &timer, &count); 142 | std::bind(Print, std::placeholders::_1, &timer, &count); 143 | ``` 144 | 145 | 第一种,占位符是 Boost Asio 定义的。 146 | 第二种,占位符是 Boost Bind 定义的。 147 | 第三种,同第二种,之所以可行,是因为 `boost/bind.hpp` 里有一句 `using namespace boost::placeholders;`。 148 | 149 | ```cpp 150 | // boost/bind.hpp 151 | #include 152 | 153 | #ifndef BOOST_BIND_NO_PLACEHOLDERS 154 | 155 | using namespace boost::placeholders; 156 | ... 157 | ``` 158 | 159 | 第四种,STL Bind,类似于 Boost Bind,只是没有声明 `using namespace std::placeholders;`。 160 | 161 | 四种写法,推荐使用二或四。至于是用 Boost Bind 还是 STL Bind,没那么重要。 162 | 163 | 此外,数字占位符共有 9 个,`_1` - `_9`。 164 | 165 | ## Endpoint 是一个单词 166 | 167 | 不要写成 "end point"。 168 | 169 | 170 | ## Server 也可以用 Resolver 171 | 172 | TCP Server 的 acceptor 一般是这样构造的: 173 | 174 | ```cpp 175 | tcp::acceptor(io_context, tcp::endpoint(tcp::v4(), port)) 176 | ``` 177 | 178 | 也就是说,指定 protocol (`tcp::v4()`) 和 port 就行了。 179 | 180 | 但是,Asio 的 http 这个例子,确实用了 resolver,根据 IP 地址 resolve 出 endpoint: 181 | 182 | ```cpp 183 | tcp::resolver resolver(io_context_); 184 | 185 | tcp::resolver::results_type endpoints = resolver.resolve(address, port); 186 | 187 | tcp::endpoint endpoint = *endpoints.begin(); 188 | 189 | acceptor_.open(endpoint.protocol()); 190 | acceptor_.set_option(tcp::acceptor::reuse_address(true)); 191 | acceptor_.bind(endpoint); 192 | acceptor_.listen(); 193 | 194 | acceptor_.async_accept(...); 195 | ``` 196 | 197 | http 这个例子之所以这么写,主要是初始化 `acceptor_` 时,还拿不到 endpoint,否则可以直接用下面这个构造函数: 198 | 199 | ```cpp 200 | basic_socket_acceptor(boost::asio::io_context& io_context, 201 | const endpoint_type& endpoint, bool reuse_addr = true) 202 | ``` 203 | 204 | 这个构造函数注释说它等价于下面这段代码: 205 | 206 | ```cpp 207 | basic_socket_acceptor acceptor(io_context); 208 | acceptor.open(endpoint.protocol()); 209 | if (reuse_addr) 210 | acceptor.set_option(socket_base::reuse_address(true)); 211 | acceptor.bind(endpoint); 212 | acceptor.listen(listen_backlog); 213 | ``` 214 | 215 | 下面是不同的 `address` 对应的 endpoints 结果(假定 port 都是 `8080`): 216 | 217 | - "localhost": [::1]:8080, v6; [127.0.0.1]:8080, v4 218 | - "0.0.0.0": 0.0.0.0:8080, v4 219 | - "0::0": [::]:8080, v6 220 | - 本机实际 IP 地址 (e.g., IPv4 "10.123.164.142"): 10.123.164.142:8080, v4。这时候,本机 client 无法通过 "localhost" 连接到这个 server,通过具体的 IP 地址则可以。 221 | - 一个具体的非本机地址 (e.g., IPv4 "10.123.164.145"): exception: bind: The requested address is not valid in its context 222 | 223 | 224 | ## Move Acceptable Handler 225 | 226 | 使用 `acceptor.async_accept` 时,发现了 Move Acceptable Handler。 227 | 228 | 简单来说,`async_accept` 接受两种 AcceptHandler,直接看源码: 229 | 230 | ```cpp 231 | template 232 | BOOST_ASIO_INITFN_RESULT_TYPE(MoveAcceptHandler, 233 | void (boost::system::error_code, typename Protocol::socket)) 234 | async_accept(BOOST_ASIO_MOVE_ARG(MoveAcceptHandler) handler) 235 | ``` 236 | 237 | ```cpp 238 | template 239 | BOOST_ASIO_INITFN_RESULT_TYPE(AcceptHandler, 240 | void (boost::system::error_code)) 241 | async_accept(basic_socket& peer, 242 | BOOST_ASIO_MOVE_ARG(AcceptHandler) handler, 243 | typename enable_if::value>::type* = 0) 244 | ``` 245 | 246 | 第一种是 Move Acceptable Handler,它的第二个参数是新 accept 的 socket。 247 | 第二种是普通的 Handler,它的第一个参数是预先构造的 socket。 248 | 249 | 对于 Move Acceptable Handler,用 bind 行不通。比如给定: 250 | ```cpp 251 | void Server::HandleAccept(boost::system::error_code ec, 252 | boost::asio::ip::tcp::socket socket) { 253 | } 254 | ``` 255 | 256 | 在 VS 2015 下(支持 C++14),`std::bind` 可以编译,`boost::bind` 则不行。 257 | ```cpp 258 | // std::bind 可以,boost::bind 不可以。 259 | acceptor_.async_accept(std::bind(&Server::HandleAccept, 260 | this, 261 | std::placeholders::_1, 262 | std::placeholders::_2)); 263 | ``` 264 | 在 VS 2013 下,`std::bind` 和 `boost::bind` 都不行。 265 | 266 | 结论是,对于 Move Acceptable Handler,不要用 bind,直接用 lambda 表达式: 267 | ```cpp 268 | void DoAccept() { 269 | acceptor_.async_accept( 270 | [this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) { 271 | // Check whether the server was stopped by a signal before this 272 | // completion handler had a chance to run. 273 | if (!acceptor_.is_open()) { 274 | return; 275 | } 276 | 277 | if (!ec) { 278 | connection_manager_.Start( 279 | std::make_shared(std::move(socket), 280 | connection_manager_, 281 | request_handler_)); 282 | } 283 | 284 | DoAccept(); 285 | }); 286 | } 287 | -------------------------------------------------------------------------------- /src/ssl_http_client_async_blocking_timeout.cpp: -------------------------------------------------------------------------------- 1 | // HTTPs client sending a GET request. 2 | // Based on Asio asynchronous APIs but run in blocking mode. 3 | // A deadline timer is added for timeout control. 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "boost/asio.hpp" 11 | #include "boost/asio/steady_timer.hpp" 12 | #include "boost/asio/ssl.hpp" 13 | #include "boost/lambda/bind.hpp" 14 | #include "boost/lambda/lambda.hpp" 15 | 16 | // ----------------------------------------------------------------------------- 17 | 18 | using boost::asio::ip::tcp; 19 | namespace ssl = boost::asio::ssl; 20 | 21 | typedef ssl::stream ssl_socket; 22 | 23 | // Verify the certificate of the peer (remote host). 24 | #define SSL_VERIFY 0 25 | 26 | // Timeout seconds. 27 | const int kMaxConnectSeconds = 10; 28 | const int kMaxHandshakeSeconds = 10; 29 | const int kMaxSendSeconds = 30; 30 | const int kMaxReceiveSeconds = 5; 31 | 32 | // ----------------------------------------------------------------------------- 33 | 34 | class Client { 35 | public: 36 | Client(const std::string& host, const std::string& path); 37 | 38 | bool Request(); 39 | 40 | void set_timeout_seconds(int timeout_seconds) { 41 | assert(timeout_seconds > 0); 42 | timeout_seconds_ = timeout_seconds; 43 | } 44 | 45 | bool timed_out() const { return timed_out_; } 46 | 47 | void Stop(); 48 | 49 | private: 50 | bool Connect(); 51 | 52 | bool Handshake(); 53 | 54 | bool SendRequest(); 55 | 56 | bool ReadResponse(); 57 | 58 | void CheckDeadline(); 59 | 60 | boost::asio::io_context io_context_; 61 | 62 | std::string host_; 63 | std::string path_; 64 | 65 | ssl::context ssl_context_; 66 | ssl::stream ssl_socket_; 67 | 68 | boost::asio::streambuf request_; 69 | 70 | std::vector buffer_; 71 | 72 | boost::asio::steady_timer deadline_; 73 | 74 | // Maximum seconds to wait before the client cancels the operation. 75 | // Only for receiving response from server. 76 | int timeout_seconds_; 77 | 78 | bool stopped_; 79 | 80 | // If the error was caused by timeout or not. 81 | bool timed_out_; 82 | }; 83 | 84 | // ----------------------------------------------------------------------------- 85 | 86 | Client::Client(const std::string& host, const std::string& path) 87 | : host_(host), path_(path), 88 | ssl_context_(ssl::context::sslv23), 89 | ssl_socket_(io_context_, ssl_context_), 90 | buffer_(1024), 91 | deadline_(io_context_), 92 | timeout_seconds_(30), 93 | stopped_(false), 94 | timed_out_(false) { 95 | // Use the default paths for finding CA certificates. 96 | ssl_context_.set_default_verify_paths(); 97 | } 98 | 99 | bool Client::Request() { 100 | stopped_ = false; 101 | timed_out_ = false; 102 | 103 | // Start the persistent actor that checks for deadline expiry. 104 | deadline_.expires_at(boost::asio::steady_timer::time_point::max()); 105 | CheckDeadline(); 106 | 107 | if (!Connect()) { 108 | return false; 109 | } 110 | 111 | if (!Handshake()) { 112 | return false; 113 | } 114 | 115 | if (!SendRequest()) { 116 | return false; 117 | } 118 | 119 | return ReadResponse(); 120 | } 121 | 122 | void Client::Stop() { 123 | stopped_ = true; 124 | 125 | boost::system::error_code ignored_ec; 126 | ssl_socket_.lowest_layer().close(ignored_ec); 127 | 128 | deadline_.cancel(); 129 | } 130 | 131 | bool Client::Connect() { 132 | boost::system::error_code ec; 133 | 134 | // Get a list of endpoints corresponding to the server name. 135 | tcp::resolver resolver(io_context_); 136 | auto endpoints = resolver.resolve(host_, "https", ec); 137 | 138 | if (ec) { 139 | std::cerr << "Resolve failed: " << ec.message() << std::endl; 140 | return false; 141 | } 142 | 143 | deadline_.expires_after(std::chrono::seconds(kMaxConnectSeconds)); 144 | 145 | ec = boost::asio::error::would_block; 146 | 147 | // ConnectHandler: void (boost::system::error_code, tcp::endpoint) 148 | // Using |boost::lambda::var()| is identical to: 149 | // boost::asio::async_connect( 150 | // ssl_socket_.lowest_layer(), endpoints, 151 | // [this, &ec](boost::system::error_code inner_ec, tcp::endpoint) { 152 | // ec = inner_ec; 153 | // }); 154 | boost::asio::async_connect(ssl_socket_.lowest_layer(), endpoints, 155 | boost::lambda::var(ec) = boost::lambda::_1); 156 | 157 | // Block until the asynchronous operation has completed. 158 | do { 159 | io_context_.run_one(); 160 | } while (ec == boost::asio::error::would_block); 161 | 162 | // Determine whether a connection was successfully established. The 163 | // deadline actor may have had a chance to run and close our socket, even 164 | // though the connect operation notionally succeeded. Therefore we must 165 | // check whether the socket is still open before deciding if we succeeded 166 | // or failed. 167 | if (ec) { 168 | std::cerr << "Connect failed: " << ec.message() << std::endl; 169 | return false; 170 | } 171 | 172 | // The deadline actor may have had a chance to run and close our socket, even 173 | // though the connect operation notionally succeeded. 174 | if (stopped_) { 175 | // |timed_out_| should be true in this case. 176 | std::cerr << "Connect timed out." << std::endl; 177 | return false; 178 | } 179 | 180 | return true; 181 | } 182 | 183 | bool Client::Handshake() { 184 | deadline_.expires_after(std::chrono::seconds(kMaxHandshakeSeconds)); 185 | 186 | boost::system::error_code ec = boost::asio::error::would_block; 187 | 188 | #if SSL_VERIFY 189 | ssl_socket_.set_verify_mode(ssl::verify_peer); 190 | #else 191 | ssl_socket_.set_verify_mode(ssl::verify_none); 192 | #endif // SSL_VERIFY 193 | 194 | // ssl::host_name_verification has been added since Boost 1.73 to replace 195 | // ssl::rfc2818_verification. 196 | #if BOOST_VERSION < 107300 197 | ssl_socket_.set_verify_callback(ssl::rfc2818_verification(host_)); 198 | #else 199 | ssl_socket_.set_verify_callback(ssl::host_name_verification(host_)); 200 | #endif // BOOST_VERSION < 107300 201 | 202 | // HandshakeHandler: void (boost::system::error_code) 203 | ssl_socket_.async_handshake(ssl::stream_base::client, 204 | boost::lambda::var(ec) = boost::lambda::_1); 205 | 206 | // Block until the asynchronous operation has completed. 207 | do { 208 | io_context_.run_one(); 209 | } while (ec == boost::asio::error::would_block); 210 | 211 | if (ec) { 212 | Stop(); 213 | std::cerr << "Handshake failed: " << ec.message() << std::endl; 214 | return false; 215 | } 216 | 217 | if (stopped_) { 218 | // |timed_out_| should be true in this case. 219 | std::cerr << "Handshake timed out." << std::endl; 220 | return false; 221 | } 222 | 223 | return true; 224 | } 225 | 226 | bool Client::SendRequest() { 227 | std::ostream request_stream(&request_); 228 | request_stream << "GET " << path_ << " HTTP/1.1\r\n"; 229 | request_stream << "Host: " << host_ << "\r\n\r\n"; 230 | 231 | deadline_.expires_after(std::chrono::seconds(kMaxSendSeconds)); 232 | 233 | boost::system::error_code ec = boost::asio::error::would_block; 234 | 235 | // WriteHandler: void (boost::system::error_code, std::size_t) 236 | boost::asio::async_write(ssl_socket_, request_, 237 | boost::lambda::var(ec) = boost::lambda::_1); 238 | 239 | // Block until the asynchronous operation has completed. 240 | do { 241 | io_context_.run_one(); 242 | } while (ec == boost::asio::error::would_block); 243 | 244 | if (ec) { 245 | Stop(); 246 | std::cerr << "Write failed: " << ec.message() << std::endl; 247 | return false; 248 | } 249 | 250 | return true; 251 | } 252 | 253 | bool Client::ReadResponse() { 254 | deadline_.expires_after(std::chrono::seconds(kMaxReceiveSeconds)); 255 | 256 | boost::system::error_code ec = boost::asio::error::would_block; 257 | 258 | ssl_socket_.async_read_some( 259 | boost::asio::buffer(buffer_), 260 | [this, &ec](boost::system::error_code inner_ec, std::size_t length) { 261 | ec = inner_ec; 262 | 263 | if (inner_ec || length == 0) { 264 | std::cout << "Socket read error." << std::endl; 265 | return; 266 | } 267 | 268 | std::cout.write(buffer_.data(), length); 269 | 270 | // TODO: Call ReadResponse() to read until the end. 271 | ReadResponse(); 272 | }); 273 | 274 | // Block until the asynchronous operation has completed. 275 | do { 276 | io_context_.run_one(); 277 | } while (ec == boost::asio::error::would_block); 278 | 279 | if (ec) { 280 | std::cout << "Read failed: " << ec.message() << std::endl; 281 | return false; 282 | } 283 | 284 | return true; 285 | } 286 | 287 | void Client::CheckDeadline() { 288 | if (stopped_) { 289 | return; 290 | } 291 | 292 | if (deadline_.expiry() <= boost::asio::steady_timer::clock_type::now()) { 293 | // The deadline has passed. 294 | // The socket is closed so that any outstanding asynchronous operations 295 | // are canceled. 296 | std::cout << "HTTP client timed out." << std::endl; 297 | Stop(); 298 | timed_out_ = true; 299 | } 300 | 301 | // Put the actor back to sleep. 302 | deadline_.async_wait(std::bind(&Client::CheckDeadline, this)); 303 | } 304 | 305 | // ----------------------------------------------------------------------------- 306 | 307 | void Help(const char* argv0) { 308 | std::cout << "Usage: " << argv0 << " " << std::endl; 309 | std::cout << " E.g.," << std::endl; 310 | std::cout << " " << argv0 << " www.boost.org /LICENSE_1_0.txt" << std::endl; 311 | std::cout << " " << argv0 << " www.google.com /" << std::endl; 312 | } 313 | 314 | int main(int argc, char* argv[]) { 315 | if (argc != 3) { 316 | Help(argv[0]); 317 | return 1; 318 | } 319 | 320 | std::string host = argv[1]; 321 | std::string path = argv[2]; 322 | 323 | try { 324 | Client client(host, path); 325 | 326 | client.Request(); 327 | 328 | } catch (const std::exception& e) { 329 | std::cout << "Exception: " << e.what() << std::endl; 330 | } 331 | 332 | return 0; 333 | } 334 | -------------------------------------------------------------------------------- /Tutorial_zh-CN.md: -------------------------------------------------------------------------------- 1 | # 基于 Boost Asio 的 C++ 网络编程 2 | 3 | **环境:** Boost v1.66, VS 2013 & 2015 4 | 5 | **说明:** 6 | 这篇教程形成于 Boost v1.62 时代,最近(2018/01)针对 v1.66 做了一次大的更新。 7 | 此外,在代码风格上,C++11 用得更多了。 8 | 9 | ---- 10 | 11 | ## 概述 12 | 13 | 近期学习 Boost Asio,依葫芦画瓢,写了不少例子,对这个「轻量级」的网络库算是有了一定理解。但是秉着理论与实践结合的态度,决定写一篇教程,把脑子里一知半解的东西,试图说清楚。 14 | 15 | Asio,即「异步 IO」(Asynchronous Input/Output),本是一个 [独立的 C++ 网络程序库](http://think-async.com/Asio),似乎并不为人所知,后来因为被 Boost 相中,才声名鹊起。 16 | 17 | 从设计上来看,Asio 相似且重度依赖于 Boost,与 thread、bind、smart pointers 等结合时,体验顺滑。从使用上来看,依然是重组合而轻继承,一贯的 C++ 标准库风格。 18 | 19 | 什么是「异步 IO」? 20 | 21 | 简单来说,就是你发起一个 IO 操作,却不用等它结束,你可以继续做其他事情,当它结束时,你会得到通知。 22 | 23 | 当然这种表述是不精确的,操作系统并没有直接提供这样的机制。以 Unix 为例,有五种 IO 模型可用: 24 | 25 | - 阻塞 I/O 26 | - 非阻塞 I/O 27 | - I/O 多路复用(multiplexing)(`select` 和 `poll`) 28 | - 信号驱动 I/O(`SIGIO`) 29 | - 异步 I/O(POSIX `aio_` 系列函数) 30 | 31 | 这五种模型的定义和比较,详见「Unix Network Programming, Volume 1: The Sockets Networking API」一书 6.2 节,或者可参考 [这篇笔记](https://segmentfault.com/n/1330000004444307)。 32 | 33 | Asio 封装的正是「I/O 多路复用」。具体一点,`epoll` 之于 Linux,`kqueue` 之于 Mac 和 BSD。`epoll` 和 `kqueue` 比 `select` 和 `poll` 更高效。当然在 Windows 上封装的则是 IOCP(完成端口)。 34 | 35 | Asio 的「I/O 操作」,主要还是指「网络 IO」,比如 socket 读写。由于网络传输的特性,「网络 IO」相对比较费时,设计良好的服务器,不可能同步等待一个 IO 操作的结束,这太浪费 CPU 了。 36 | 37 | 对于普通的「文件 IO」,操作系统并没有提供“异步”读写机制,libuv 的做法是用线程模拟异步,为网络和文件提供了一致的接口。Asio 并没有这样做,它专注于网络。提供机制而不是策略,这很符合 C++ 哲学。 38 | 39 | 下面以示例,由浅到深,由简单到复杂,逐一介绍 Asio 的用法。 40 | 简单起见,头文件一律省略。 41 | 42 | ## I/O Context 43 | 44 | 每个 Asio 程序都至少有一个 `io_context` 对象,它代表了操作系统的 I/O 服务(`io_context` 在 Boost 1.66 之前一直叫 `io_service`),把你的程序和这些服务链接起来。 45 | 46 | 下面这个程序空有 `io_context` 对象,却没有任何异步操作,所以它其实什么也没做,也没有任何输出。 47 | ```cpp 48 | int main() { 49 | boost::asio::io_context ioc; 50 | ioc.run(); 51 | return 0; 52 | } 53 | ``` 54 | 55 | `io_context.run` 是一个阻塞(blocking)调用,姑且把它想象成一个 loop(事件循环),直到所有异步操作完成后,loop 才结束,`run` 才返回。但是这个程序没有任何异步操作,所以 loop 直接就结束了。 56 | 57 | ## Timer 58 | 59 | 有了 `io_context` 还不足以完成 I/O 操作,用户一般也不跟 `io_context` 直接交互。 60 | 61 | 根据 I/O 操作的不同,Asio 提供了不同的 I/O 对象,比如 timer(定时器),socket,等等。 62 | Timer 是最简单的一种 I/O 对象,可以用来实现异步调用的超时机制,下面是最简单的用法: 63 | 64 | ```cpp 65 | void Print(boost::system::error_code ec) { 66 | std::cout << "Hello, world!" << std::endl; 67 | } 68 | 69 | int main() { 70 | boost::asio::io_context ioc; 71 | boost::asio::steady_timer timer(ioc, std::chrono::seconds(3)); 72 | timer.async_wait(&Print); 73 | ioc.run(); 74 | return 0; 75 | } 76 | ``` 77 | 78 | 先创建一个 `steady_timer`,指定时间 3 秒,然后异步等待这个 timer,3 秒后,timer 超时结束,`Print` 被调用。 79 | 80 | 以下几点需要注意: 81 | - 所有 I/O 对象都依赖 `io_context`,一般在构造时指定。 82 | - `async_wait` 初始化了一个异步操作,但是这个异步操作的执行,要等到 `io_context.run` 时才开始。 83 | - Timer 除了异步等待(`async_wait`),还可以同步等待(`wait`)。同步等待是阻塞的,直到 timer 超时结束。基本上所有 I/O 对象的操作都有同步和异步两个版本,也许是出于设计上的完整性。 84 | - `async_wait` 的参数是一个函数对象,异步操作完成时它会被调用,所以也叫 completion handler,简称 handler,可以理解成回调函数。 85 | - 所有 I/O 对象的 `async_xyz` 函数都有 handler 参数,对于 handler 的签名,不同的异步操作有不同的要求,除了官方文档里的说明,也可以直接查看 Boost 源码。 86 | 87 | `async_wait` 的 handler 签名为 `void (boost::system::error_code)`,如果要传递额外的参数,就得用 `bind`。不妨修改一下 `Print`,让它每隔一秒打印一次计数,从 `0` 递增到 `3`。 88 | 89 | ```cpp 90 | void Print(boost::system::error_code ec, 91 | boost::asio::steady_timer* timer, 92 | int* count) { 93 | if (*count < 3) { 94 | std::cout << *count << std::endl; 95 | ++(*count); 96 | 97 | timer->expires_after(std::chrono::seconds(1)); 98 | 99 | timer->async_wait(std::bind(&Print, std::placeholders::_1, timer, count)); 100 | } 101 | } 102 | ``` 103 | 与前版相比,`Print` 多了两个参数,以便访问当前计数及重启 timer。 104 | ```cpp 105 | int main() { 106 | boost::asio::io_context ioc; 107 | boost::asio::steady_timer timer(ioc, std::chrono::seconds(1)); 108 | int count = 0; 109 | timer.async_wait(std::bind(&Print, std::placeholders::_1, &timer, &count)); 110 | 111 | ioc.run(); 112 | return 0; 113 | } 114 | ``` 115 | 调用 `bind` 时,使用了占位符(placeholder)`std::placeholders::_1`。数字占位符共有 9 个,`_1` - `_9`。占位符也有很多种写法,这里就不详述了。 116 | 117 | ## Echo Server 118 | 119 | Socket 也是一种 I/O 对象,这一点前面已经提及。相比于 timer,socket 更为常用,毕竟 Asio 是一个网络程序库。 120 | 121 | 下面以经典的 Echo 程序为例,实现一个 TCP Server。所谓 Echo,就是 Server 把 Client 发来的内容原封不动发回给 Client。 122 | 123 | 先从同步方式开始,异步太复杂,慢慢来。 124 | 125 | ### 同步方式 126 | 127 | `Session` 代表会话,负责管理一个 client 的连接。参数 `socket` 传的是值,但是会用到 move 语义来避免拷贝。 128 | ```cpp 129 | void Session(tcp::socket socket) { 130 | try { 131 | while (true) { 132 | boost::array data; 133 | 134 | boost::system::error_code ec; 135 | std::size_t length = socket.read_some(boost::asio::buffer(data), ec); 136 | 137 | if (ec == boost::asio::error::eof) { 138 | std::cout << "连接被 client 妥善的关闭了" << std::endl; 139 | break; 140 | } else if (ec) { 141 | // 其他错误 142 | throw boost::system::system_error(ec); 143 | } 144 | 145 | boost::asio::write(socket, boost::asio::buffer(data, length)); 146 | } 147 | } catch (const std::exception& e) { 148 | std::cerr << "Exception: " << e.what() << std::endl; 149 | } 150 | } 151 | ``` 152 | 其中,`tcp` 即 `boost::asio::ip::tcp`;`BUF_SIZE` 定义为 `enum { BUF_SIZE = 1024 };`。这些都是细节,后面的例子不再赘述。 153 | ```cpp 154 | int main(int argc, char* argv[]) { 155 | if (argc != 2) { 156 | std::cerr << "Usage: " << argv[0] << " " << std::endl; 157 | return 1; 158 | } 159 | 160 | unsigned short port = std::atoi(argv[1]); 161 | 162 | boost::asio::io_context ioc; 163 | 164 | // 创建 Acceptor 侦听新的连接 165 | tcp::acceptor acceptor(ioc, tcp::endpoint(tcp::v4(), port)); 166 | 167 | try { 168 | // 一次处理一个连接 169 | while (true) { 170 | Session(acceptor.accept()); 171 | } 172 | } catch (const std::exception& e) { 173 | std::cerr << "Exception: " << e.what() << std::endl; 174 | } 175 | 176 | return 0; 177 | } 178 | ``` 179 | 启动时,通过命令行参数指定端口号,比如: 180 | ``` 181 | $ echo_server_sync 8080 182 | ``` 183 | 因为 Client 部分还未实现,先用 `netcat` 测试一下: 184 | ``` 185 | $ nc localhost 8080 186 | hello 187 | hello 188 | ``` 189 | 190 | 以下几点需要注意: 191 | - `tcp::acceptor` 也是一种 I/O 对象,用来接收 TCP 连接,连接端口由 `tcp::endpoint` 指定。 192 | - 数据 buffer 以 `boost::array` 表示,也可以用 `char data[BUF_SIZE]`,或 `std::vector data(BUF_SIZE)`。事实上,用 `std::vector` 是最推荐的,因为它不但可以动态调整大小,还支持 [Buffer Debugging](http://blog.think-async.com/2006/11/buffer-debugging.html)。 193 | - 同步方式下,没有调用 `io_context.run`,因为 `accept`、`read_some` 和 `write` 都是阻塞的。这也意味着一次只能处理一个 Client 连接,但是可以连续 echo,除非 Client 断开连接。 194 | - 写回数据时,没有直接调用 `socket.write_some`,因为它不能保证一次写完所有数据,但是 `boost::asio::write` 可以。我觉得这是 Asio 接口设计不周,应该提供 `socket.write`。 195 | - `acceptor.accept` 返回一个新的 socket 对象,利用 move 语义,直接就转移给了 `Session` 的参数,期间并没有拷贝开销。 196 | 197 | ### 异步方式 198 | 199 | 异步方式下,困难在于对象的生命周期,可以用 `shared_ptr` 解决。 200 | 201 | 为了同时处理多个 Client 连接,需要保留每个连接的 socket 对象,于是抽象出一个表示连接会话的类,叫 `Session`: 202 | ```cpp 203 | class Session : public std::enable_shared_from_this { 204 | public: 205 | Session(tcp::socket socket) : socket_(std::move(socket)) { 206 | } 207 | 208 | void Start() { 209 | DoRead(); 210 | } 211 | 212 | void DoRead() { 213 | auto self(shared_from_this()); 214 | socket_.async_read_some( 215 | boost::asio::buffer(buffer_), 216 | [this, self](boost::system::error_code ec, std::size_t length) { 217 | if (!ec) { 218 | DoWrite(length); 219 | } 220 | }); 221 | } 222 | 223 | void DoWrite(std::size_t length) { 224 | auto self(shared_from_this()); 225 | boost::asio::async_write( 226 | socket_, 227 | boost::asio::buffer(buffer_, length), 228 | [this, self](boost::system::error_code ec, std::size_t length) { 229 | if (!ec) { 230 | DoRead(); 231 | } 232 | }); 233 | } 234 | 235 | private: 236 | tcp::socket socket_; 237 | std::array buffer_; 238 | }; 239 | ``` 240 | 就代码风格来说,有以下几点需要注意: 241 | - 优先使用 STL,比如 `std::enable_shared_from_this`,`std::bind`,`std::array`,等等。 242 | - 定义 handler 时,尽量使用匿名函数(lambda 表达式)。 243 | - 以 C++ `std::size_t` 替 C `size_t`。 244 | 刚开始,你可能会不习惯,我也是这样,过了好久才慢慢拥抱 C++11 乃至 C++14。 245 | 246 | `Session` 有两个成员变量,`socket_` 与 Client 通信,`buffer_` 是接收 Client 数据的缓存。只要 `Session` 对象在,socket 就在,连接就不断。Socket 对象是构造时传进来的,而且是通过 move 语义转移进来的。 247 | 248 | 虽然还没看到 `Session` 对象是如何创建的,但可以肯定的是,它必须用 `std::shared_ptr` 进行封装,这样才能保证异步模式下对象的生命周期。 249 | 250 | 此外,在 `Session::DoRead` 和 `Session::DoWrite` 中,因为读写都是异步的,同样为了防止当前 `Session` 不被销毁(因为超出作用域),所以要增加它的引用计数,即 `auto self(shared_from_this());` 这一句的作用。 251 | 252 | 至于读写的逻辑,基本上就是把 `read_some` 换成 `async_read_some`,把 `write` 换成 `async_write`,然后以匿名函数作为 completion handler。 253 | 254 | 接收 Client 连接的代码,提取出来,抽象成一个类 `Server`: 255 | ```cpp 256 | class Server { 257 | public: 258 | Server(boost::asio::io_context& ioc, std::uint16_t port) 259 | : acceptor_(ioc, tcp::endpoint(tcp::v4(), port)) { 260 | DoAccept(); 261 | } 262 | 263 | private: 264 | void DoAccept() { 265 | acceptor_.async_accept( 266 | [this](boost::system::error_code ec, tcp::socket socket) { 267 | if (!ec) { 268 | std::make_shared(std::move(socket))->Start(); 269 | } 270 | DoAccept(); 271 | }); 272 | } 273 | 274 | private: 275 | tcp::acceptor acceptor_; 276 | }; 277 | ``` 278 | 279 | 同样,`async_accept` 替换了 `accept`。`async_accept` 不再阻塞,`DoAccept` 即刻就会返回。 280 | 为了保证 `Session` 对象继续存在,使用 `std::shared_ptr` 代替普通的栈对象,同时把新接收的 socket 对象转移过去。 281 | 282 | 最后是 `main()`: 283 | 284 | ```cpp 285 | int main(int argc, char* argv[]) { 286 | if (argc != 2) { 287 | std::cerr << "Usage: " << argv[0] << " " << std::endl; 288 | return 1; 289 | } 290 | 291 | std::uint16_t port = std::atoi(argv[1]); 292 | 293 | boost::asio::io_context ioc; 294 | Server server(ioc, port); 295 | 296 | ioc.run(); 297 | return 0; 298 | } 299 | ``` 300 | 301 | ## Echo Client 302 | 303 | 虽然用 `netcat` 测试 Echo Server 非常方便,但是自己动手写一个 Echo Client 仍然十分必要。 304 | 还是先考虑同步方式。 305 | 306 | ### 同步方式 307 | 308 | 首先通过 `host` 和 `port` 解析出 endpoints(对,是复数!): 309 | ```cpp 310 | tcp::resolver resolver(ioc); 311 | auto endpoints = resolver.resolve(tcp::v4(), host, port); 312 | ``` 313 | `resolve` 返回的 endpoints 类型为 `tcp::resolver::results_type`,代之以 `auto` 可以简化代码。类型推导应适当使用,至于连 `int` 都用 `auto` 就没有必要了。 314 | `host` 和 `port` 通过命令行参数指定,比如 `localhost` 和 `8080`。 315 | 316 | 接着创建 socket,建立连接: 317 | ```cpp 318 | tcp::socket socket(ioc); 319 | boost::asio::connect(socket, endpoints); 320 | ``` 321 | 322 | 这里没有直接调用 `socket.connect`,因为 `endpoints` 可能会有多个,`boost::asio::connect` 会挨个尝试,逐一调用 `socket.connect` 直到连接成功。 323 | 324 | 其实这样说不太严谨,根据我的测试,`resolve` 在没有指定 protocol 时,确实会返回多个 endpoints,一个是 IPv6,一个是 IPv4。但是我们已经指定了 protocol 为 `tcp::v4()`: 325 | ```cpp 326 | resolver.resolve(tcp::v4(), host, port) 327 | ``` 328 | 所以,应该只有一个 endpoint。 329 | 330 | 接下来,从标准输入(`std::cin`)读一行数据,然后通过 `boost::asio::write` 发送给 Server: 331 | ```cpp 332 | char request[BUF_SIZE]; 333 | std::size_t request_length = 0; 334 | do { 335 | std::cout << "Enter message: "; 336 | std::cin.getline(request, BUF_SIZE); 337 | request_length = std::strlen(request); 338 | } while (request_length == 0); 339 | 340 | boost::asio::write(socket, boost::asio::buffer(request, request_length)); 341 | ``` 342 | 343 | `do...while` 是为了防止用户直接 Enter 导致输入为空。`boost::asio::write` 是阻塞调用,发送完才返回。 344 | 345 | 从 Server 同步接收数据有两种方式: 346 | - 使用 `boost::asio::read`(对应于 `boost::asio::write`); 347 | - 使用 `socket.read_some`。 348 | 349 | 两者的差别是,`boost::asio::read` 读到指定长度时,就会返回,你需要知道你想读多少;而 `socket.read_some` 一旦读到一些数据就会返回,所以必须放在循环里,然后手动判断是否已经读到想要的长度,否则无法退出循环。 350 | 351 | 下面分别是两种实现的代码。 352 | 353 | 使用 `boost::asio::read`: 354 | ```cpp 355 | char reply[BUF_SIZE]; 356 | std::size_t reply_length = boost::asio::read( 357 | socket, 358 | boost::asio::buffer(reply, request_length)); 359 | 360 | std::cout.write(reply, reply_length); 361 | ``` 362 | 363 | 使用 `socket.read_some`: 364 | ```cpp 365 | std::size_t total_reply_length = 0; 366 | while (true) { 367 | std::array reply; 368 | std::size_t reply_length = socket.read_some(boost::asio::buffer(reply)); 369 | 370 | std::cout.write(reply.data(), reply_length); 371 | 372 | total_reply_length += reply_length; 373 | if (total_reply_length >= request_length) { 374 | break; 375 | } 376 | } 377 | ``` 378 | 不难看出,`socket.read_some` 用起来更为复杂。 379 | Echo 程序的特殊之处就是,你可以假定 Server 会原封不动的把请求发回来,所以你知道 Client 要读多少。 380 | 但是很多时候,我们不知道要读多少数据。 381 | 所以,`socket.read_some` 反倒更为实用。 382 | 383 | 此外,在这个例子中,我们没有为各函数指定输出参数 `boost::system::error_code`,而是使用了异常,把整个代码块放在 `try...catch` 中。 384 | ```cpp 385 | try { 386 | // ... 387 | } catch (const std::exception& e) { 388 | std::cerr << e.what() << std::endl; 389 | } 390 | ``` 391 | 392 | Asio 的 API 基本都通过重载(overload),提供了 `error_code` 和 `exception` 两种错误处理方式。使用异常更易于错误处理,也可以简化代码,但是 `try...catch` 该包含多少代码,并不是那么明显,新手很容易误用,什么都往 `try...catch` 里放。 393 | 394 | **一般来说,异步方式下,使用 `error_code` 更方便一些。所以 complete handler 的参数都有 `error_code`。** 395 | 396 | ### 异步方式 397 | 398 | 就 Client 来说,异步也许并非必要,除非想同时连接多个 Server。 399 | 400 | 异步读写前面已经涉及,我们就先看 `async_resolve` 和 `async_connect`。 401 | 402 | 首先,抽取出一个类 `Client`: 403 | ```cpp 404 | class Client { 405 | public: 406 | Client(boost::asio::io_context& ioc, 407 | const std::string& host, const std::string& port) 408 | : socket_(ioc), resolver_(ioc) { 409 | } 410 | 411 | private: 412 | tcp::socket socket_; 413 | tcp::resolver resolver_; 414 | 415 | char cin_buf_[BUF_SIZE]; 416 | std::array buf_; 417 | }; 418 | ``` 419 | 420 | `resolver_` 是为了 `async_resolve`,作为成员变量,生命周期便得到了保证,不会因为函数结束而失效。 421 | 422 | 下面来看 `async_resolve` 实现(代码在构造函数中): 423 | ```cpp 424 | Client(...) { 425 | resolver_.async_resolve(tcp::v4(), host, port, 426 | std::bind(&Client::OnResolve, this, 427 | std::placeholders::_1, 428 | std::placeholders::_2)); 429 | } 430 | ``` 431 | 432 | `async_resolve` 的 handler: 433 | ```cpp 434 | void OnResolve(boost::system::error_code ec, 435 | tcp::resolver::results_type endpoints) { 436 | if (ec) { 437 | std::cerr << "Resolve: " << ec.message() << std::endl; 438 | } else { 439 | boost::asio::async_connect(socket_, endpoints, 440 | std::bind(&Client::OnConnect, this, 441 | std::placeholders::_1, 442 | std::placeholders::_2)); 443 | } 444 | } 445 | ``` 446 | `async_connect` 的 handler: 447 | ```cpp 448 | void OnConnect(boost::system::error_code ec, tcp::endpoint endpoint) { 449 | if (ec) { 450 | std::cout << "Connect failed: " << ec.message() << std::endl; 451 | socket_.close(); 452 | } else { 453 | DoWrite(); 454 | } 455 | } 456 | ``` 457 | 连接成功后,调用 `DoWrite`,从标准输入读取一行数据,然后异步发送给 Server。 458 | 下面是异步读写相关的函数,一并给出: 459 | ```cpp 460 | void DoWrite() { 461 | std::size_t len = 0; 462 | do { 463 | std::cout << "Enter message: "; 464 | std::cin.getline(cin_buf_, BUF_SIZE); 465 | len = strlen(cin_buf_); 466 | } while (len == 0); 467 | 468 | boost::asio::async_write(socket_, 469 | boost::asio::buffer(cin_buf_, len), 470 | std::bind(&Client::OnWrite, this, 471 | std::placeholders::_1)); 472 | } 473 | 474 | void OnWrite(boost::system::error_code ec) { 475 | if (!ec) { 476 | std::cout << "Reply is: "; 477 | 478 | socket_.async_read_some(boost::asio::buffer(buf_), 479 | std::bind(&Client::OnRead, this, 480 | std::placeholders::_1, 481 | std::placeholders::_2)); 482 | } 483 | } 484 | 485 | void OnRead(boost::system::error_code ec, std::size_t length) { 486 | if (!ec) { 487 | std::cout.write(buf_.data(), length); 488 | std::cout << std::endl; 489 | // 如果想继续下一轮,可以在这里调用 DoWrite()。 490 | } 491 | } 492 | ``` 493 | 异步读写在异步 Server 那一节已经介绍过,这里就不再赘述了。 494 | 495 | 最后是 `main()`: 496 | ```cpp 497 | int main(int argc, char* argv[]) { 498 | if (argc != 3) { 499 | std::cerr << "Usage: " << argv[0] << " " << std::endl; 500 | return 1; 501 | } 502 | 503 | const char* host = argv[1]; 504 | const char* port = argv[2]; 505 | 506 | boost::asio::io_context ioc; 507 | Client client(ioc, host, port); 508 | 509 | ioc.run(); 510 | return 0; 511 | } 512 | ``` 513 | 514 | 至此,异步方式的 Echo Client 就算实现了。 515 | 516 | 为了避免文章太长,Asio 的介绍暂时先告一段落。若有补遗,会另行记录。 517 | 518 | 完整及更加丰富的示例代码,请移步 [GitHub](https://github.com/sprinfall/boost-asio-study)。 519 | --------------------------------------------------------------------------------