├── .clang-format ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── include ├── asio_net.hpp └── asio_net │ ├── config.hpp │ ├── dds_client.hpp │ ├── dds_server.hpp │ ├── detail │ ├── dds_client_t.hpp │ ├── dds_inner_cmd.hpp │ ├── dds_server_t.hpp │ ├── dds_type.hpp │ ├── log.h │ ├── message.hpp │ ├── noncopyable.hpp │ ├── rpc_client_t.hpp │ ├── rpc_server_t.hpp │ ├── rpc_session_t.hpp │ ├── socket_type.hpp │ ├── tcp_channel_t.hpp │ ├── tcp_client_t.hpp │ ├── tcp_server_t.hpp │ ├── udp_client_t.hpp │ └── udp_server_t.hpp │ ├── rpc_client.hpp │ ├── rpc_server.hpp │ ├── rpc_session.hpp │ ├── serial_port.hpp │ ├── server_discovery.hpp │ ├── tcp_client.hpp │ ├── tcp_server.hpp │ ├── udp_client.hpp │ ├── udp_server.hpp │ └── version.hpp ├── library.json └── test ├── assert_def.h ├── compile_check ├── lib.cpp └── main.cpp ├── dds.cpp ├── dds_c.cpp ├── dds_s.cpp ├── domain_rpc.cpp ├── domain_tcp.cpp ├── domain_udp.cpp ├── log.h ├── rpc.cpp ├── rpc_c.cpp ├── rpc_c_check_destroy.cpp ├── rpc_c_coroutine.cpp ├── rpc_c_coroutine.hpp ├── rpc_c_open_close.cpp ├── rpc_c_ping.cpp ├── rpc_config.cpp ├── rpc_reconnect.cpp ├── rpc_s.cpp ├── rpc_s_coroutine.cpp ├── rpc_ssl.cpp ├── serial_port.cpp ├── server_discovery.cpp ├── ssl ├── ca.pem ├── dh4096.pem └── server.pem ├── tcp.cpp ├── tcp_bigdata.cpp ├── tcp_c.cpp ├── tcp_reconnect.cpp ├── tcp_s.cpp ├── tcp_ssl_c.cpp ├── tcp_ssl_s.cpp ├── udp.cpp ├── udp_c.cpp ├── udp_s.cpp └── version.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | BasedOnStyle: Google 3 | ColumnLimit: 150 4 | AllowShortFunctionsOnASingleLine: Empty 5 | AllowShortLambdasOnASingleLine: Empty 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | pull_request: 8 | paths-ignore: 9 | - '**.md' 10 | 11 | jobs: 12 | build-and-test: 13 | 14 | name: ${{ matrix.toolchain }}-${{ matrix.configuration }} 15 | runs-on: ${{ matrix.os }} 16 | 17 | strategy: 18 | fail-fast: false 19 | 20 | matrix: 21 | toolchain: 22 | - linux-gcc 23 | - macos-clang 24 | - windows-msvc 25 | - windows-mingw 26 | 27 | configuration: 28 | - Debug 29 | - Release 30 | 31 | include: 32 | - toolchain: linux-gcc 33 | os: ubuntu-latest 34 | compiler: gcc 35 | env: 36 | CMAKE_OPTIONS: "-DASIO_NET_ENABLE_SSL=ON" 37 | 38 | - toolchain: macos-clang 39 | os: macos-latest 40 | compiler: clang 41 | env: 42 | CMAKE_OPTIONS: "-DASIO_NET_ENABLE_SSL=ON" 43 | 44 | - toolchain: windows-msvc 45 | os: windows-latest 46 | compiler: msvc 47 | env: 48 | BIN_SUFFIX: ".exe" 49 | 50 | - toolchain: windows-mingw 51 | os: windows-latest 52 | compiler: mingw 53 | env: 54 | BIN_SUFFIX: ".exe" 55 | CMAKE_OPTIONS: "-G \"MinGW Makefiles\"" 56 | 57 | steps: 58 | - uses: actions/checkout@v4 59 | with: 60 | submodules: true 61 | 62 | - name: Init ASIO 63 | run: git clone https://github.com/chriskohlhoff/asio.git -b asio-1-32-0 --depth=1 64 | 65 | - name: Configure (${{ matrix.configuration }}) 66 | env: 67 | ASIO_PATH: asio/asio/include 68 | run: cmake -S . -B build -DCMAKE_BUILD_TYPE=${{ matrix.configuration }} ${{ matrix.env.CMAKE_OPTIONS }} -DASIO_NET_DISABLE_ON_DATA_PRINT=ON 69 | 70 | - name: Build with ${{ matrix.compiler }} 71 | run: cmake --build build --config ${{ matrix.configuration }} -j 72 | 73 | - name: Windows-MSVC Compatible 74 | if: matrix.os == 'windows-latest' && matrix.compiler == 'msvc' 75 | working-directory: build 76 | run: Move-Item -Path .\${{ matrix.configuration }}\* -Destination .\ 77 | 78 | - name: Test TCP 79 | working-directory: build 80 | run: ./asio_net_test_tcp${{ matrix.env.BIN_SUFFIX }} 81 | 82 | - name: Test TCP (big data) 83 | working-directory: build 84 | run: ./asio_net_test_tcp_bigdata${{ matrix.env.BIN_SUFFIX }} 85 | 86 | - name: Test TCP (reconnect) 87 | working-directory: build 88 | run: ./asio_net_test_tcp_reconnect${{ matrix.env.BIN_SUFFIX }} 89 | 90 | - name: Test UDP 91 | working-directory: build 92 | run: ./asio_net_test_udp${{ matrix.env.BIN_SUFFIX }} 93 | 94 | - name: Test RPC 95 | working-directory: build 96 | run: ./asio_net_test_rpc${{ matrix.env.BIN_SUFFIX }} 97 | 98 | - name: Test RPC (rpc_config) 99 | working-directory: build 100 | run: ./asio_net_test_rpc_config${{ matrix.env.BIN_SUFFIX }} 101 | 102 | - name: Test RPC (reconnect) 103 | working-directory: build 104 | run: ./asio_net_test_rpc_reconnect${{ matrix.env.BIN_SUFFIX }} 105 | 106 | - name: Test RPC (ssl) 107 | if: matrix.os == 'macos-latest' 108 | working-directory: build 109 | run: ./asio_net_test_rpc_ssl${{ matrix.env.BIN_SUFFIX }} 110 | 111 | - name: Test Domain TCP 112 | if: matrix.os != 'windows-latest' 113 | working-directory: build 114 | run: ./asio_net_test_domain_tcp${{ matrix.env.BIN_SUFFIX }} 115 | 116 | - name: Test Domain UDP 117 | if: matrix.os != 'windows-latest' 118 | working-directory: build 119 | run: ./asio_net_test_domain_udp${{ matrix.env.BIN_SUFFIX }} 120 | 121 | - name: Test Domain RPC 122 | if: matrix.os != 'windows-latest' 123 | working-directory: build 124 | run: ./asio_net_test_domain_rpc${{ matrix.env.BIN_SUFFIX }} 125 | 126 | - name: Test DDS 127 | working-directory: build 128 | run: ./asio_net_test_dds${{ matrix.env.BIN_SUFFIX }} 1 129 | 130 | - name: Test DDS (ssl) 131 | if: matrix.os != 'windows-latest' 132 | working-directory: build 133 | run: ./asio_net_test_dds_ssl${{ matrix.env.BIN_SUFFIX }} 1 134 | 135 | - name: Test DDS (domain) 136 | if: matrix.os != 'windows-latest' 137 | working-directory: build 138 | run: ./asio_net_test_domain_dds${{ matrix.env.BIN_SUFFIX }} 1 139 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /.vscode/ 3 | /build/ 4 | /cmake-build-*/ 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "include/asio_net/rpc_core"] 2 | path = include/asio_net/rpc_core 3 | url = git@github.com:shuai132/rpc_core.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(asio_net CXX) 4 | 5 | option(ASIO_NET_ENABLE_SSL "" OFF) 6 | option(ASIO_NET_BUILD_TEST "" OFF) 7 | option(ASIO_NET_DISABLE_ON_DATA_PRINT "" OFF) 8 | 9 | if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 10 | set(ASIO_NET_BUILD_TEST ON) 11 | endif () 12 | 13 | if (MSVC) 14 | set(CMAKE_CXX_STANDARD 20) 15 | add_compile_options(/Zc:preprocessor) 16 | add_compile_options(/utf-8) 17 | add_compile_options(-DNOMINMAX) 18 | add_compile_options(-D_WIN32_WINNT=0x0601) 19 | else () 20 | set(CMAKE_CXX_STANDARD 14) 21 | add_compile_options(-Wall -Wunused-parameter) 22 | endif () 23 | 24 | if (MINGW) 25 | add_compile_options(-D_WIN32_WINNT=0x0601) 26 | link_libraries(ws2_32 wsock32) 27 | endif () 28 | 29 | add_library(${PROJECT_NAME} INTERFACE) 30 | target_include_directories(${PROJECT_NAME} INTERFACE 31 | include 32 | include/asio_net/rpc_core/include 33 | ) 34 | 35 | if (ASIO_NET_ENABLE_SSL) 36 | target_compile_definitions(${PROJECT_NAME} INTERFACE -DASIO_NET_ENABLE_SSL) 37 | endif () 38 | 39 | if (ASIO_NET_ENABLE_SSL) 40 | find_package(OpenSSL 1.1.0 REQUIRED) 41 | target_link_libraries(${PROJECT_NAME} INTERFACE OpenSSL::SSL) 42 | endif () 43 | 44 | if (ASIO_NET_BUILD_TEST) 45 | message(STATUS "ASIO_PATH: $ENV{ASIO_PATH}") 46 | add_definitions(-DASIO_NO_DEPRECATED) 47 | include_directories($ENV{ASIO_PATH}) 48 | link_libraries(${PROJECT_NAME}) 49 | if (NOT MSVC) 50 | link_libraries(pthread) 51 | endif () 52 | add_definitions(-DOPENSSL_PEM_PATH=\"${CMAKE_CURRENT_LIST_DIR}/test/ssl/\") 53 | 54 | # for github actions/ci 55 | if (ASIO_NET_DISABLE_ON_DATA_PRINT) 56 | add_definitions(-DASIO_NET_DISABLE_ON_DATA_PRINT) 57 | endif () 58 | 59 | # for android standalone e.g. termux 60 | add_definitions(-DANDROID_STANDALONE) 61 | 62 | add_executable(${PROJECT_NAME}_test_version test/version.cpp) 63 | add_executable(${PROJECT_NAME}_test_compile_check test/compile_check/main.cpp test/compile_check/lib.cpp) 64 | add_executable(${PROJECT_NAME}_test_tcp test/tcp.cpp) 65 | add_executable(${PROJECT_NAME}_test_tcp_s test/tcp_s.cpp) 66 | add_executable(${PROJECT_NAME}_test_tcp_c test/tcp_c.cpp) 67 | add_executable(${PROJECT_NAME}_test_tcp_bigdata test/tcp_bigdata.cpp) 68 | add_executable(${PROJECT_NAME}_test_tcp_reconnect test/tcp_reconnect.cpp) 69 | add_executable(${PROJECT_NAME}_test_udp test/udp.cpp) 70 | add_executable(${PROJECT_NAME}_test_udp_s test/udp_s.cpp) 71 | add_executable(${PROJECT_NAME}_test_udp_c test/udp_c.cpp) 72 | add_executable(${PROJECT_NAME}_test_server_discovery test/server_discovery.cpp) 73 | add_executable(${PROJECT_NAME}_test_domain_tcp test/domain_tcp.cpp) 74 | add_executable(${PROJECT_NAME}_test_domain_udp test/domain_udp.cpp) 75 | if (ASIO_NET_ENABLE_SSL) 76 | add_executable(${PROJECT_NAME}_test_tcp_ssl_c test/tcp_ssl_c.cpp) 77 | add_executable(${PROJECT_NAME}_test_tcp_ssl_s test/tcp_ssl_s.cpp) 78 | endif () 79 | 80 | add_compile_definitions(RPC_CORE_LOG_SHOW_DEBUG) 81 | add_compile_definitions(ASIO_NET_LOG_SHOW_DEBUG) 82 | add_executable(${PROJECT_NAME}_test_rpc test/rpc.cpp) 83 | add_executable(${PROJECT_NAME}_test_rpc_config test/rpc_config.cpp) 84 | add_executable(${PROJECT_NAME}_test_rpc_reconnect test/rpc_reconnect.cpp) 85 | add_executable(${PROJECT_NAME}_test_rpc_c_check_destroy test/rpc_c_check_destroy.cpp) 86 | add_executable(${PROJECT_NAME}_test_rpc_c_open_close test/rpc_c_open_close.cpp) 87 | add_executable(${PROJECT_NAME}_test_rpc_c_ping test/rpc_c_ping.cpp) 88 | add_executable(${PROJECT_NAME}_test_rpc_s test/rpc_s.cpp) 89 | add_executable(${PROJECT_NAME}_test_rpc_c test/rpc_c.cpp) 90 | add_executable(${PROJECT_NAME}_test_domain_rpc test/domain_rpc.cpp) 91 | add_executable(${PROJECT_NAME}_test_serial_port test/serial_port.cpp) 92 | add_executable(${PROJECT_NAME}_test_dds_s test/dds_s.cpp) 93 | add_executable(${PROJECT_NAME}_test_dds_c test/dds_c.cpp) 94 | add_executable(${PROJECT_NAME}_test_dds test/dds.cpp) 95 | target_compile_definitions(${PROJECT_NAME}_test_dds PRIVATE TEST_DDS_NORMAL) 96 | add_executable(${PROJECT_NAME}_test_domain_dds test/dds.cpp) 97 | target_compile_definitions(${PROJECT_NAME}_test_domain_dds PRIVATE TEST_DDS_DOMAIN) 98 | if (ASIO_NET_ENABLE_SSL) 99 | add_executable(${PROJECT_NAME}_test_dds_ssl test/dds.cpp) 100 | target_compile_definitions(${PROJECT_NAME}_test_dds_ssl PRIVATE TEST_DDS_SSL) 101 | endif () 102 | 103 | if (ASIO_NET_ENABLE_SSL) 104 | add_executable(${PROJECT_NAME}_test_rpc_ssl test/rpc_ssl.cpp) 105 | endif () 106 | 107 | add_executable(${PROJECT_NAME}_test_rpc_s_coroutine test/rpc_s_coroutine.cpp) 108 | target_compile_features(${PROJECT_NAME}_test_rpc_s_coroutine PRIVATE cxx_std_20) 109 | target_compile_definitions(${PROJECT_NAME}_test_rpc_s_coroutine PRIVATE -DRPC_CORE_FEATURE_CO_ASIO) 110 | 111 | add_executable(${PROJECT_NAME}_test_rpc_c_coroutine test/rpc_c_coroutine.cpp) 112 | target_compile_features(${PROJECT_NAME}_test_rpc_c_coroutine PRIVATE cxx_std_20) 113 | target_compile_definitions(${PROJECT_NAME}_test_rpc_c_coroutine PRIVATE -DRPC_CORE_FEATURE_CO_ASIO) 114 | endif () 115 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-2025 liushuai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # asio_net 2 | 3 | [![Build Status](https://github.com/shuai132/asio_net/workflows/CI/badge.svg)](https://github.com/shuai132/asio_net/actions?workflow=CI) 4 | [![Release](https://img.shields.io/github/release/shuai132/asio_net.svg)](https://github.com/shuai132/asio_net/releases) 5 | [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) 6 | 7 | a tiny C++14 Async TCP/UDP/RPC/DDS library based on [asio](http://think-async.com/Asio/) 8 | and [rpc_core](https://github.com/shuai132/rpc_core) 9 | 10 | ## Features 11 | 12 | * Header-Only 13 | * TCP/UDP: support auto_pack option for tcp, will ensure packets are complete, just like websocket 14 | * RPC: via socket(with SSL/TLS), domain socket, support c++20 coroutine for asynchronous operations 15 | * DDS: via socket(with SSL/TLS), domain socket 16 | * Service Discovery: based on UDP multicast 17 | * IPv4 and IPv6 18 | * SSL/TLS: depend OpenSSL 19 | * Serial Port 20 | * Automatic reconnection 21 | * Comprehensive unittests 22 | 23 | ## Requirements 24 | 25 | * [asio](http://think-async.com/Asio/) 26 | * C++14 27 | * Optional: C++20 (for rpc coroutine api, co_await co_call) 28 | 29 | ## Usage 30 | 31 | * clone 32 | 33 | ```shell 34 | git clone --recurse-submodules git@github.com:shuai132/asio_net.git 35 | ``` 36 | 37 | or 38 | 39 | ```shell 40 | git clone git@github.com:shuai132/asio_net.git && cd asio_net 41 | git submodule update --init --recursive 42 | ``` 43 | 44 | The following are examples of using each module. For complete unit tests, 45 | please refer to the source code: [test](test) 46 | 47 | ### RPC 48 | 49 | rpc based on tcp and [rpc_core](https://github.com/shuai132/rpc_core), and also support ipv6 and ssl. 50 | inspect the code for more details [rpc.cpp](test/rpc.cpp) 51 | 52 | ```c++ 53 | // server 54 | asio::io_context context; 55 | rpc_server server(context, PORT/*, rpc_config*/); 56 | server.on_session = [](const std::weak_ptr& rs) { 57 | auto session = rs.lock(); 58 | session->on_close = [] {}; 59 | session->rpc->subscribe("cmd", [](const std::string& data) -> std::string { 60 | return "world"; 61 | }); 62 | }; 63 | server.start(true); 64 | ``` 65 | 66 | ```c++ 67 | // client 68 | asio::io_context context; 69 | rpc_client client(context/*, rpc_config*/); 70 | client.on_open = [](const std::shared_ptr& rpc) { 71 | rpc->cmd("cmd") 72 | ->msg(std::string("hello")) 73 | ->rsp([](const std::string& data) { 74 | assert(data == "world"); 75 | }) 76 | ->call(); 77 | }; 78 | client.on_close = [] {}; 79 | client.open("localhost", PORT); 80 | client.run(); 81 | ``` 82 | 83 | and, you can create rpc first, for more details: [rpc_config.cpp](test/rpc_config.cpp) 84 | 85 | ```c++ 86 | // server 87 | auto rpc = rpc_core::rpc::create(); 88 | rpc->subscribe("cmd", [](const std::string& data) -> std::string { 89 | assert(data == "hello"); 90 | return "world"; 91 | }); 92 | 93 | asio::io_context context; 94 | rpc_server server(context, PORT, rpc_config{.rpc = rpc}); 95 | server.start(true); 96 | ``` 97 | 98 | ```c++ 99 | // client 100 | auto rpc = rpc_core::rpc::create(); 101 | asio::io_context context; 102 | rpc_client client(context, rpc_config{.rpc = rpc}); 103 | client.open("localhost", PORT); 104 | client.run(); 105 | 106 | rpc->cmd("cmd")->msg(std::string("hello"))->call(); 107 | // or: rpc->call("cmd", std::string("hello")); 108 | ``` 109 | 110 | and you can use C++20 coroutine: 111 | 112 | ```c++ 113 | // server 114 | rpc->subscribe("cmd", [&](request_response rr) -> asio::awaitable { 115 | assert(rr->req == "hello"); 116 | asio::steady_timer timer(context); 117 | timer.expires_after(std::chrono::seconds(1)); 118 | co_await timer.async_wait(); 119 | rr->rsp("world"); 120 | }, scheduler_asio_coroutine); 121 | 122 | // client 123 | // use C++20 co_await with asio, or you can use custom async implementation, and co_await it! 124 | auto rsp = co_await rpc->cmd("cmd")->msg(std::string("hello"))->co_call(); 125 | // or: auto rsp = co_await rpc->co_call("cmd", std::string("hello")); 126 | assert(rsp.data == "world"); 127 | ``` 128 | 129 | inspect the code for more 130 | details: [rpc_s_coroutine.cpp](test/rpc_s_coroutine.cpp) 131 | and [rpc_c_coroutine.cpp](test/rpc_c_coroutine.cpp) 132 | 133 | ### DDS 134 | 135 | ```c++ 136 | // run a server as daemon 137 | asio::io_context context; 138 | dds_server server(context, PORT); 139 | server.start(true); 140 | ``` 141 | 142 | ```c++ 143 | // client 144 | asio::io_context context; 145 | dds_client client(context); 146 | client.open("localhost", PORT); 147 | client.subscribe("topic", [](const std::string& data) { 148 | }); 149 | client.publish("topic", "string/binary"); 150 | client.run(); 151 | ``` 152 | 153 | ### Server Discovery 154 | 155 | ```c++ 156 | // receiver 157 | asio::io_context context; 158 | server_discovery::receiver receiver(context, [](const std::string& name, const std::string& message) { 159 | printf("receive: name: %s, message: %s\n", name.c_str(), message.c_str()); 160 | }); 161 | context.run(); 162 | ``` 163 | 164 | ```c++ 165 | // sender 166 | asio::io_context context; 167 | server_discovery::sender sender_ip(context, "ip", "message"); 168 | context.run(); 169 | ``` 170 | 171 | ### TCP 172 | 173 | You can enable automatic handling of packet fragmentation using `tcp_config`. 174 | Subsequent send and receive will be complete data packets. 175 | 176 | By default, this feature is disabled. 177 | 178 | ```c++ 179 | // echo server 180 | asio::io_context context; 181 | tcp_server server(context, PORT/*, tcp_config*/); 182 | server.on_session = [](const std::weak_ptr& ws) { 183 | auto session = ws.lock(); 184 | session->on_close = [] { 185 | }; 186 | session->on_data = [ws](std::string data) { 187 | ws.lock()->send(std::move(data)); 188 | }; 189 | }; 190 | server.start(true); 191 | ``` 192 | 193 | ```c++ 194 | // echo client 195 | asio::io_context context; 196 | tcp_client client(context/*, tcp_config*/); 197 | client.on_data = [](const std::string& data) { 198 | }; 199 | client.on_close = [] { 200 | }; 201 | client.open("localhost", PORT); 202 | client.run(); 203 | ``` 204 | 205 | ### UDP 206 | 207 | ```c++ 208 | // server 209 | asio::io_context context; 210 | udp_server server(context, PORT); 211 | server.on_data = [](uint8_t* data, size_t size, const udp::endpoint& from) { 212 | }; 213 | server.start(); 214 | ``` 215 | 216 | ```c++ 217 | // client 218 | asio::io_context context; 219 | udp_client client(context); 220 | auto endpoint = udp::endpoint(asio::ip::address_v4::from_string("127.0.0.1"), PORT); 221 | client.send_to("hello", endpoint); 222 | context.run(); 223 | ``` 224 | 225 | ### Serial Port 226 | 227 | ```c++ 228 | asio::io_context context; 229 | serial_port serial(context); 230 | serial.on_open = [&] { 231 | /// set_option 232 | serial.set_option(asio::serial_port::baud_rate(115200)); 233 | serial.set_option(asio::serial_port::flow_control(asio::serial_port::flow_control::none)); 234 | serial.set_option(asio::serial_port::parity(asio::serial_port::parity::none)); 235 | serial.set_option(asio::serial_port::stop_bits(asio::serial_port::stop_bits::one)); 236 | serial.set_option(asio::serial_port::character_size(asio::serial_port::character_size(8))); 237 | 238 | /// test 239 | serial.send("hello world"); 240 | }; 241 | serial.on_data = [](const std::string& data) { 242 | }; 243 | serial.on_open_failed = [](std::error_code ec) { 244 | }; 245 | serial.on_close = [] { 246 | }; 247 | serial.open("/dev/tty.usbserial-xx"); 248 | serial.run(); 249 | ``` 250 | 251 | # Links 252 | 253 | * RPC library for MCU 254 | 255 | most MCU not support asio, there is a library can be ported easily: [esp_rpc](https://github.com/shuai132/esp_rpc) 256 | -------------------------------------------------------------------------------- /include/asio_net.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // version 4 | #include "asio_net/version.hpp" 5 | 6 | // first include 7 | #include "rpc_core/serialize.hpp" 8 | 9 | // server discovery 10 | #include "asio_net/server_discovery.hpp" 11 | 12 | // tcp 13 | #include "asio_net/tcp_client.hpp" 14 | #include "asio_net/tcp_server.hpp" 15 | 16 | // udp 17 | #include "asio_net/udp_client.hpp" 18 | #include "asio_net/udp_server.hpp" 19 | 20 | // rpc 21 | #include "asio_net/rpc_client.hpp" 22 | #include "asio_net/rpc_server.hpp" 23 | 24 | // serial port 25 | #include "asio_net/serial_port.hpp" 26 | 27 | // dds 28 | #include "asio_net/dds_client.hpp" 29 | #include "asio_net/dds_server.hpp" 30 | -------------------------------------------------------------------------------- /include/asio_net/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "rpc_core/rpc.hpp" 6 | 7 | namespace asio_net { 8 | 9 | struct tcp_config { 10 | bool auto_pack = false; 11 | bool enable_ipv6 = false; 12 | uint32_t max_body_size = UINT32_MAX; 13 | uint32_t max_send_buffer_size = UINT32_MAX; 14 | 15 | // socket option 16 | uint32_t socket_send_buffer_size = UINT32_MAX; 17 | uint32_t socket_recv_buffer_size = UINT32_MAX; 18 | 19 | void init() { 20 | // when auto_pack disable, max_body_size means buffer size, default is 1024 bytes 21 | if ((!auto_pack) && (max_body_size == UINT32_MAX)) { 22 | max_body_size = 1024; 23 | } 24 | } 25 | }; 26 | 27 | struct rpc_config { 28 | // rpc config 29 | std::shared_ptr rpc; // NOTICE: set on server means that a single connection is desired 30 | uint32_t ping_interval_ms = 0; // 0: disable auto ping, >0: enable auto ping 31 | uint32_t pong_timeout_ms = 5000; // if pong timeout, client/session will be close 32 | 33 | // socket config 34 | bool enable_ipv6 = false; 35 | uint32_t max_body_size = UINT32_MAX; 36 | uint32_t max_send_buffer_size = UINT32_MAX; 37 | 38 | // socket option 39 | uint32_t socket_send_buffer_size = UINT32_MAX; 40 | uint32_t socket_recv_buffer_size = UINT32_MAX; 41 | 42 | tcp_config to_tcp_config() { 43 | return {.auto_pack = true, 44 | .enable_ipv6 = enable_ipv6, 45 | .max_body_size = max_body_size, 46 | .max_send_buffer_size = max_send_buffer_size, 47 | .socket_send_buffer_size = socket_send_buffer_size, 48 | .socket_recv_buffer_size = socket_recv_buffer_size}; 49 | } 50 | }; 51 | 52 | struct serial_config { 53 | // device 54 | std::string device; 55 | 56 | // buffer 57 | uint32_t max_send_buffer_size = UINT32_MAX; 58 | uint32_t max_recv_buffer_size = 1024; 59 | }; 60 | 61 | } // namespace asio_net 62 | -------------------------------------------------------------------------------- /include/asio_net/dds_client.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "detail/dds_client_t.hpp" 4 | 5 | namespace asio_net { 6 | 7 | using dds_client = detail::dds_client_t; 8 | using dds_client_ssl = detail::dds_client_t; 9 | using domain_dds_client = detail::dds_client_t; 10 | 11 | } // namespace asio_net 12 | -------------------------------------------------------------------------------- /include/asio_net/dds_server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "detail/dds_server_t.hpp" 4 | 5 | namespace asio_net { 6 | 7 | using dds_server = detail::dds_server_t; 8 | using dds_server_ssl = detail::dds_server_t; 9 | using domain_dds_server = detail::dds_server_t; 10 | 11 | } // namespace asio_net 12 | -------------------------------------------------------------------------------- /include/asio_net/detail/dds_client_t.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "dds_inner_cmd.hpp" 7 | #include "dds_type.hpp" 8 | #include "rpc_client_t.hpp" 9 | 10 | namespace asio_net { 11 | namespace detail { 12 | 13 | template 14 | class dds_client_t { 15 | public: 16 | explicit dds_client_t(asio::io_context& io_context) : io_context_(io_context), client_(io_context, rpc_config{.rpc = rpc_}) { 17 | init(); 18 | } 19 | 20 | #ifdef ASIO_NET_ENABLE_SSL 21 | explicit dds_client_t(asio::io_context& io_context, asio::ssl::context& ssl_context) 22 | : io_context_(io_context), client_(io_context, ssl_context, rpc_config{.rpc = rpc_}) { 23 | init(); 24 | } 25 | #endif 26 | 27 | template 28 | void publish(std::string topic, D data = {}) { 29 | auto msg = dds::Msg{.topic = std::move(topic), .data = rpc_core::serialize(std::move(data))}; 30 | dispatch_publish(msg); 31 | rpc_->cmd(cmd_publish)->msg(std::move(msg))->retry(-1)->call(); 32 | } 33 | 34 | template ::argc == 0, int>::type = 0> 35 | uintptr_t subscribe(const std::string& topic, F handle) { 36 | auto handle_sp = std::make_shared([handle = std::move(handle)](const std::string&) mutable { 37 | handle(); 38 | }); 39 | return subscribe_raw(topic, std::move(handle_sp)); 40 | } 41 | 42 | template ::argc == 1, int>::type = 0> 43 | uintptr_t subscribe(const std::string& topic, F handle) { 44 | auto handle_sp = std::make_shared([handle = std::move(handle)](std::string msg) mutable { 45 | using F_Param = rpc_core::detail::remove_cvref_t::template argument_type<0>>; 46 | F_Param p; 47 | rpc_core::deserialize(std::move(msg), p); 48 | handle(std::move(p)); 49 | }); 50 | return subscribe_raw(topic, std::move(handle_sp)); 51 | } 52 | 53 | uintptr_t subscribe_raw(const std::string& topic, dds::handle_s handle_sp) { 54 | auto handle_id = (uintptr_t)handle_sp.get(); 55 | auto it = topic_handles_map_.find(topic); 56 | if (it == topic_handles_map_.cend()) { 57 | topic_handles_map_[topic].push_back(std::move(handle_sp)); 58 | update_topic_list(); 59 | } else { 60 | it->second.push_back(std::move(handle_sp)); 61 | } 62 | return handle_id; 63 | } 64 | 65 | bool unsubscribe(const std::string& topic) { 66 | auto it = topic_handles_map_.find(topic); 67 | if (it != topic_handles_map_.cend()) { 68 | topic_handles_map_.erase(it); 69 | update_topic_list(); 70 | return true; 71 | } else { 72 | return false; 73 | } 74 | } 75 | 76 | bool unsubscribe(uintptr_t handle_id) { 77 | auto it = std::find_if(topic_handles_map_.begin(), topic_handles_map_.end(), [id = handle_id](auto& p) { 78 | auto& vec = p.second; 79 | auto len_before = vec.size(); 80 | vec.erase(std::remove_if(vec.begin(), vec.end(), 81 | [id](auto& sp) { 82 | return (uintptr_t)sp.get() == id; 83 | }), 84 | vec.end()); 85 | auto len_after = vec.size(); 86 | return len_before != len_after; 87 | }); 88 | if (it != topic_handles_map_.end()) { 89 | ASIO_NET_LOGD("unsubscribe: id: %zu", handle_id); 90 | if (it->second.empty()) { 91 | topic_handles_map_.erase(it); 92 | update_topic_list(); 93 | } 94 | return true; 95 | } else { 96 | ASIO_NET_LOGD("unsubscribe: no such id: %zu", handle_id); 97 | return false; 98 | } 99 | } 100 | 101 | void open(std::string ip, uint16_t port) { 102 | static_assert(T == detail::socket_type::normal || T == detail::socket_type::ssl, ""); 103 | client_.open(std::move(ip), port); 104 | } 105 | 106 | void open(std::string endpoint) { 107 | static_assert(T == detail::socket_type::domain, ""); 108 | client_.open(std::move(endpoint)); 109 | } 110 | 111 | void close() { 112 | client_.close(); 113 | } 114 | 115 | void run() { 116 | client_.run(); 117 | } 118 | 119 | void stop() { 120 | client_.stop(); 121 | } 122 | 123 | void set_reconnect(uint32_t ms) { 124 | client_.set_reconnect(ms); 125 | } 126 | 127 | void cancel_reconnect() { 128 | client_.cancel_reconnect(); 129 | } 130 | 131 | void wait_open() { 132 | while (!is_open) { 133 | io_context_.run_one(); 134 | } 135 | } 136 | 137 | private: 138 | void init() { 139 | client_.set_reconnect(1000); 140 | client_.on_open = [this](const dds::rpc_s&) { 141 | ASIO_NET_LOGD("dds_client_t<%d>: on_open", (int)T); 142 | rpc_->subscribe(cmd_publish, [this](const dds::Msg& msg) { 143 | dispatch_publish(msg); 144 | }); 145 | update_topic_list(); 146 | if (on_open) on_open(); 147 | is_open = true; 148 | }; 149 | client_.on_close = [this] { 150 | if (on_close) on_close(); 151 | is_open = false; 152 | }; 153 | } 154 | 155 | void dispatch_publish(const dds::Msg& msg) { 156 | auto it = topic_handles_map_.find(msg.topic); 157 | if (it != topic_handles_map_.cend()) { 158 | auto& handles = it->second; 159 | for (const auto& handle : handles) { 160 | (*handle)(msg.data); 161 | } 162 | } 163 | } 164 | 165 | void update_topic_list() { 166 | std::vector topic_list; 167 | topic_list.reserve(topic_handles_map_.size()); 168 | for (const auto& kv : topic_handles_map_) { 169 | topic_list.push_back(kv.first); 170 | } 171 | rpc_->cmd(cmd_update_topic_list)->msg(topic_list)->retry(-1)->call(); 172 | } 173 | 174 | public: 175 | std::function on_open; 176 | std::function on_close; 177 | bool is_open = false; 178 | 179 | private: 180 | asio::io_context& io_context_; 181 | dds::rpc_s rpc_ = rpc_core::rpc::create(); 182 | detail::rpc_client_t client_; 183 | std::unordered_map> topic_handles_map_; 184 | }; 185 | 186 | } // namespace detail 187 | } // namespace asio_net 188 | -------------------------------------------------------------------------------- /include/asio_net/detail/dds_inner_cmd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace asio_net { 4 | namespace detail { 5 | 6 | const char* const cmd_update_topic_list = "u"; 7 | const char* const cmd_publish = "p"; 8 | 9 | } // namespace detail 10 | } // namespace asio_net 11 | -------------------------------------------------------------------------------- /include/asio_net/detail/dds_server_t.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "dds_inner_cmd.hpp" 7 | #include "dds_type.hpp" 8 | #include "rpc_server_t.hpp" 9 | 10 | namespace asio_net { 11 | namespace detail { 12 | 13 | template 14 | class dds_server_t { 15 | public: 16 | dds_server_t(asio::io_context& io_context, uint16_t port) : server_(io_context, port) { 17 | static_assert(T == detail::socket_type::normal, ""); 18 | init(); 19 | } 20 | 21 | #ifdef ASIO_NET_ENABLE_SSL 22 | dds_server_t(asio::io_context& io_context, uint16_t port, asio::ssl::context& ssl_context) : server_(io_context, port, ssl_context) { 23 | static_assert(T == detail::socket_type::ssl, ""); 24 | init(); 25 | } 26 | #endif 27 | 28 | dds_server_t(asio::io_context& io_context, const std::string& endpoint) : server_(io_context, endpoint) { 29 | static_assert(T == detail::socket_type::domain, ""); 30 | init(); 31 | } 32 | 33 | void start(bool loop) { 34 | server_.start(loop); 35 | } 36 | 37 | private: 38 | void init() { 39 | server_.on_session = [this](const std::weak_ptr>& rs) { 40 | ASIO_NET_LOGD("dds_server_t<%d>: on_session", (int)T); 41 | auto session = rs.lock(); 42 | session->on_close = [this, sp = session.get()] { 43 | remove_rpc(sp->rpc); 44 | }; 45 | 46 | auto rpc = session->rpc; 47 | rpc->subscribe(cmd_update_topic_list, [this, rpc_wp = dds::rpc_w(rpc)](const std::vector& topic_list) { 48 | update_topic_list(rpc_wp.lock(), topic_list); 49 | }); 50 | rpc->subscribe(cmd_publish, [this, rpc_wp = dds::rpc_w(rpc)](const dds::Msg& msg) { 51 | publish(msg, rpc_wp); 52 | }); 53 | }; 54 | } 55 | 56 | void publish(const dds::Msg& msg, const dds::rpc_w& from_rpc) { 57 | auto it = topic_rpc_map_.find(msg.topic); 58 | if (it != topic_rpc_map_.cend()) { 59 | auto from_rpc_sp = from_rpc.lock(); 60 | for (const auto& rpc : it->second) { 61 | if (rpc == from_rpc_sp) continue; 62 | rpc->cmd(cmd_publish)->msg(msg)->retry(-1)->call(); 63 | } 64 | } 65 | } 66 | 67 | void remove_rpc(const dds::rpc_s& rpc) { 68 | std::vector empty_topic; 69 | for (auto& kv : topic_rpc_map_) { 70 | const auto& topic = kv.first; 71 | auto& rpc_set = kv.second; 72 | auto it = rpc_set.find(rpc); 73 | if (it != rpc_set.cend()) { 74 | rpc_set.erase(it); 75 | if (rpc_set.empty()) { 76 | empty_topic.push_back(topic); 77 | } 78 | } 79 | } 80 | for (const auto& item : empty_topic) { 81 | topic_rpc_map_.erase(item); 82 | } 83 | } 84 | 85 | void update_topic_list(const dds::rpc_s& rpc, const std::vector& topic_list) { 86 | for (auto& topic : topic_list) { 87 | topic_rpc_map_[topic].insert(rpc); 88 | } 89 | } 90 | 91 | private: 92 | detail::rpc_server_t server_; 93 | std::unordered_map> topic_rpc_map_; 94 | }; 95 | 96 | } // namespace detail 97 | } // namespace asio_net 98 | -------------------------------------------------------------------------------- /include/asio_net/detail/dds_type.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "rpc_core.hpp" 4 | 5 | namespace asio_net { 6 | namespace detail { 7 | namespace dds { 8 | 9 | using rpc_s = std::shared_ptr; 10 | using rpc_w = std::weak_ptr; 11 | using handle_t = std::function; 12 | using handle_s = std::shared_ptr; 13 | 14 | struct Msg { 15 | std::string topic; 16 | std::string data; 17 | RPC_CORE_DEFINE_TYPE_INNER(topic, data); 18 | }; 19 | 20 | } // namespace dds 21 | } // namespace detail 22 | } // namespace asio_net 23 | -------------------------------------------------------------------------------- /include/asio_net/detail/log.h: -------------------------------------------------------------------------------- 1 | // 1. global control 2 | // L_O_G_NDEBUG disable debug log(auto by NDEBUG) 3 | // L_O_G_SHOW_DEBUG force enable debug log 4 | // L_O_G_DISABLE_ALL force disable all log 5 | // L_O_G_DISABLE_COLOR disable color 6 | // L_O_G_LINE_END_CRLF 7 | // L_O_G_SHOW_FULL_PATH 8 | // L_O_G_FOR_MCU 9 | // L_O_G_FREERTOS 10 | // L_O_G_NOT_EXIT_ON_FATAL 11 | // 12 | // C++11 enable default: 13 | // L_O_G_ENABLE_THREAD_SAFE thread safety 14 | // L_O_G_ENABLE_THREAD_ID show thread id 15 | // L_O_G_ENABLE_DATE_TIME show data time 16 | // can disable by define: 17 | // L_O_G_DISABLE_THREAD_SAFE 18 | // L_O_G_DISABLE_THREAD_ID 19 | // L_O_G_DISABLE_DATE_TIME 20 | // 21 | // 2. custom implements 22 | // L_O_G_PRINTF_CUSTOM int L_O_G_PRINTF_CUSTOM(const char *fmt, ...) 23 | // L_O_G_GET_TID_CUSTOM uint32_t L_O_G_GET_TID_CUSTOM() 24 | // 25 | // 3. use in library 26 | // 3.1. rename `ASIO_NET_LOG` to library name 27 | // 3.2. define `ASIO_NET_LOG_IN_LIB` 28 | // 3.3. configuration options 29 | // ASIO_NET_LOG_SHOW_DEBUG 30 | // ASIO_NET_LOG_SHOW_VERBOSE 31 | // ASIO_NET_LOG_DISABLE_ALL 32 | 33 | #pragma once 34 | 35 | // clang-format off 36 | 37 | #define ASIO_NET_LOG_IN_LIB 38 | 39 | #if defined(ASIO_NET_LOG_DISABLE_ALL) || defined(L_O_G_DISABLE_ALL) 40 | 41 | #define ASIO_NET_LOG(fmt, ...) ((void)0) 42 | #define ASIO_NET_LOGT(tag, fmt, ...) ((void)0) 43 | #define ASIO_NET_LOGI(fmt, ...) ((void)0) 44 | #define ASIO_NET_LOGW(fmt, ...) ((void)0) 45 | #define ASIO_NET_LOGE(fmt, ...) ((void)0) 46 | #define ASIO_NET_LOGF(fmt, ...) ((void)0) 47 | #define ASIO_NET_LOGD(fmt, ...) ((void)0) 48 | #define ASIO_NET_LOGV(fmt, ...) ((void)0) 49 | 50 | #else 51 | 52 | #ifdef __cplusplus 53 | #include 54 | #include 55 | #if __cplusplus >= 201103L || defined(_MSC_VER) 56 | 57 | #if !defined(L_O_G_DISABLE_THREAD_SAFE) && !defined(L_O_G_ENABLE_THREAD_SAFE) 58 | #define L_O_G_ENABLE_THREAD_SAFE 59 | #endif 60 | 61 | #if !defined(L_O_G_DISABLE_THREAD_ID) && !defined(L_O_G_ENABLE_THREAD_ID) 62 | #define L_O_G_ENABLE_THREAD_ID 63 | #endif 64 | 65 | #if !defined(L_O_G_DISABLE_DATE_TIME) && !defined(L_O_G_ENABLE_DATE_TIME) 66 | #define L_O_G_ENABLE_DATE_TIME 67 | #endif 68 | 69 | #endif 70 | #else 71 | #include 72 | #include 73 | #endif 74 | 75 | #ifdef L_O_G_LINE_END_CRLF 76 | #define ASIO_NET_LOG_LINE_END "\r\n" 77 | #else 78 | #define ASIO_NET_LOG_LINE_END "\n" 79 | #endif 80 | 81 | #ifdef L_O_G_NOT_EXIT_ON_FATAL 82 | #define ASIO_NET_LOG_EXIT_PROGRAM() 83 | #else 84 | #ifdef L_O_G_FOR_MCU 85 | #define ASIO_NET_LOG_EXIT_PROGRAM() do{ for(;;); } while(0) 86 | #else 87 | #define ASIO_NET_LOG_EXIT_PROGRAM() exit(EXIT_FAILURE) 88 | #endif 89 | #endif 90 | 91 | #ifdef L_O_G_SHOW_FULL_PATH 92 | #define ASIO_NET_LOG_BASE_FILENAME (__FILE__) 93 | #else 94 | #ifdef __FILE_NAME__ 95 | #define ASIO_NET_LOG_BASE_FILENAME (__FILE_NAME__) 96 | #else 97 | #define ASIO_NET_LOG_BASE_FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) 98 | #endif 99 | #endif 100 | 101 | #define ASIO_NET_LOG_WITH_COLOR 102 | 103 | #if defined(_WIN32) || (defined(__ANDROID__) && !defined(ANDROID_STANDALONE)) || defined(L_O_G_FOR_MCU) || defined(ESP_PLATFORM) 104 | #undef ASIO_NET_LOG_WITH_COLOR 105 | #endif 106 | 107 | #ifdef L_O_G_DISABLE_COLOR 108 | #undef ASIO_NET_LOG_WITH_COLOR 109 | #endif 110 | 111 | #ifdef ASIO_NET_LOG_WITH_COLOR 112 | #define ASIO_NET_LOG_COLOR_RED "\033[31m" 113 | #define ASIO_NET_LOG_COLOR_GREEN "\033[32m" 114 | #define ASIO_NET_LOG_COLOR_YELLOW "\033[33m" 115 | #define ASIO_NET_LOG_COLOR_BLUE "\033[34m" 116 | #define ASIO_NET_LOG_COLOR_CARMINE "\033[35m" 117 | #define ASIO_NET_LOG_COLOR_CYAN "\033[36m" 118 | #define ASIO_NET_LOG_COLOR_DEFAULT 119 | #define ASIO_NET_LOG_COLOR_END "\033[m" 120 | #else 121 | #define ASIO_NET_LOG_COLOR_RED 122 | #define ASIO_NET_LOG_COLOR_GREEN 123 | #define ASIO_NET_LOG_COLOR_YELLOW 124 | #define ASIO_NET_LOG_COLOR_BLUE 125 | #define ASIO_NET_LOG_COLOR_CARMINE 126 | #define ASIO_NET_LOG_COLOR_CYAN 127 | #define ASIO_NET_LOG_COLOR_DEFAULT 128 | #define ASIO_NET_LOG_COLOR_END 129 | #endif 130 | 131 | #define ASIO_NET_LOG_END ASIO_NET_LOG_COLOR_END ASIO_NET_LOG_LINE_END 132 | 133 | #ifndef L_O_G_PRINTF 134 | #ifndef ASIO_NET_LOG_PRINTF_DEFAULT 135 | #if defined(__ANDROID__) && !defined(ANDROID_STANDALONE) 136 | #include 137 | #define ASIO_NET_LOG_PRINTF_DEFAULT(fmt, ...) __android_log_print(ANDROID_L##OG_DEBUG, "ASIO_NET_LOG", fmt, ##__VA_ARGS__) 138 | #else 139 | #define ASIO_NET_LOG_PRINTF_DEFAULT(fmt, ...) printf(fmt, ##__VA_ARGS__) 140 | #endif 141 | #endif 142 | 143 | #ifndef L_O_G_PRINTF_CUSTOM 144 | #ifdef __cplusplus 145 | #include 146 | #else 147 | #include 148 | #endif 149 | #ifdef L_O_G_ENABLE_THREAD_SAFE 150 | #ifndef L_O_G_NS_MUTEX 151 | #define L_O_G_NS_MUTEX L_O_G_NS_MUTEX 152 | #include 153 | // 1. struct instead of namespace, ensure single instance 154 | struct L_O_G_NS_MUTEX { 155 | static std::mutex& mutex() { 156 | // 2. never delete, avoid destroy before user log 157 | // 3. static memory, avoid memory fragmentation 158 | static char memory[sizeof(std::mutex)]; 159 | static std::mutex& mutex = *(new (memory) std::mutex()); 160 | return mutex; 161 | } 162 | }; 163 | #endif 164 | #define L_O_G_PRINTF(fmt, ...) { \ 165 | std::lock_guard L_O_G_NS_lock(L_O_G_NS_MUTEX::mutex()); \ 166 | ASIO_NET_LOG_PRINTF_DEFAULT(fmt, ##__VA_ARGS__); \ 167 | } 168 | #else 169 | #define L_O_G_PRINTF(fmt, ...) ASIO_NET_LOG_PRINTF_DEFAULT(fmt, ##__VA_ARGS__) 170 | #endif 171 | #else 172 | extern int L_O_G_PRINTF_CUSTOM(const char *fmt, ...); 173 | #define L_O_G_PRINTF(fmt, ...) L_O_G_PRINTF_CUSTOM(fmt, ##__VA_ARGS__) 174 | #endif 175 | #endif 176 | 177 | #ifdef L_O_G_ENABLE_THREAD_ID 178 | #ifndef L_O_G_NS_GET_TID 179 | #define L_O_G_NS_GET_TID L_O_G_NS_GET_TID 180 | #include 181 | #ifdef L_O_G_GET_TID_CUSTOM 182 | extern uint32_t L_O_G_GET_TID_CUSTOM(); 183 | #elif defined(_WIN32) 184 | #include 185 | #include 186 | struct L_O_G_NS_GET_TID { 187 | static inline uint32_t get_tid() { 188 | return GetCurrentThreadId(); 189 | } 190 | }; 191 | #elif defined(__linux__) 192 | #include 193 | #include 194 | struct L_O_G_NS_GET_TID { 195 | static inline uint32_t get_tid() { 196 | return syscall(SYS_gettid); 197 | } 198 | }; 199 | #elif defined(L_O_G_FREERTOS) || defined(FREERTOS_CONFIG_H) 200 | #include 201 | struct L_O_G_NS_GET_TID { 202 | static inline uint32_t get_tid() { 203 | return (uint32_t)xTaskGetCurrentTaskHandle(); 204 | } 205 | }; 206 | #else /* for mac, bsd.. */ 207 | #include 208 | struct L_O_G_NS_GET_TID { 209 | static inline uint32_t get_tid() { 210 | uint64_t x; 211 | pthread_threadid_np(nullptr, &x); 212 | return (uint32_t)x; 213 | } 214 | }; 215 | #endif 216 | #endif 217 | #ifdef L_O_G_GET_TID_CUSTOM 218 | #define ASIO_NET_LOG_THREAD_LABEL "%u " 219 | #define ASIO_NET_LOG_THREAD_VALUE ,L_O_G_GET_TID_CUSTOM() 220 | #else 221 | #define ASIO_NET_LOG_THREAD_LABEL "%u " 222 | #define ASIO_NET_LOG_THREAD_VALUE ,L_O_G_NS_GET_TID::get_tid() 223 | #endif 224 | #else 225 | #define ASIO_NET_LOG_THREAD_LABEL 226 | #define ASIO_NET_LOG_THREAD_VALUE 227 | #endif 228 | 229 | #ifdef L_O_G_ENABLE_DATE_TIME 230 | #include 231 | #include 232 | #include // std::put_time 233 | #ifndef L_O_G_NS_GET_TIME 234 | #define L_O_G_NS_GET_TIME L_O_G_NS_GET_TIME 235 | struct L_O_G_NS_GET_TIME { 236 | static inline std::string get_time() { 237 | auto now = std::chrono::system_clock::now(); 238 | std::time_t time = std::chrono::system_clock::to_time_t(now); 239 | auto ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; 240 | std::stringstream ss; 241 | std::tm dst; // NOLINT 242 | #ifdef _MSC_VER 243 | ::localtime_s(&dst, &time); 244 | #else 245 | dst = *std::localtime(&time); 246 | #endif 247 | ss << std::put_time(&dst, "%Y-%m-%d %H:%M:%S") << '.' << std::setw(3) << std::setfill('0') << ms.count(); 248 | return ss.str(); 249 | } 250 | }; 251 | #endif 252 | #define ASIO_NET_LOG_TIME_LABEL "%s " 253 | #define ASIO_NET_LOG_TIME_VALUE ,L_O_G_NS_GET_TIME::get_time().c_str() 254 | #else 255 | #define ASIO_NET_LOG_TIME_LABEL 256 | #define ASIO_NET_LOG_TIME_VALUE 257 | #endif 258 | 259 | #define ASIO_NET_LOG(fmt, ...) do{ L_O_G_PRINTF(ASIO_NET_LOG_COLOR_GREEN ASIO_NET_LOG_TIME_LABEL ASIO_NET_LOG_THREAD_LABEL "[*]: %s:%d " fmt ASIO_NET_LOG_END ASIO_NET_LOG_TIME_VALUE ASIO_NET_LOG_THREAD_VALUE, ASIO_NET_LOG_BASE_FILENAME, __LINE__, ##__VA_ARGS__); } while(0) 260 | #define ASIO_NET_LOGT(tag, fmt, ...) do{ L_O_G_PRINTF(ASIO_NET_LOG_COLOR_BLUE ASIO_NET_LOG_TIME_LABEL ASIO_NET_LOG_THREAD_LABEL "[" tag "]: %s:%d " fmt ASIO_NET_LOG_END ASIO_NET_LOG_TIME_VALUE ASIO_NET_LOG_THREAD_VALUE, ASIO_NET_LOG_BASE_FILENAME, __LINE__, ##__VA_ARGS__); } while(0) 261 | #define ASIO_NET_LOGI(fmt, ...) do{ L_O_G_PRINTF(ASIO_NET_LOG_COLOR_YELLOW ASIO_NET_LOG_TIME_LABEL ASIO_NET_LOG_THREAD_LABEL "[I]: %s:%d " fmt ASIO_NET_LOG_END ASIO_NET_LOG_TIME_VALUE ASIO_NET_LOG_THREAD_VALUE, ASIO_NET_LOG_BASE_FILENAME, __LINE__, ##__VA_ARGS__); } while(0) 262 | #define ASIO_NET_LOGW(fmt, ...) do{ L_O_G_PRINTF(ASIO_NET_LOG_COLOR_CARMINE ASIO_NET_LOG_TIME_LABEL ASIO_NET_LOG_THREAD_LABEL "[W]: %s:%d [%s] " fmt ASIO_NET_LOG_END ASIO_NET_LOG_TIME_VALUE ASIO_NET_LOG_THREAD_VALUE, ASIO_NET_LOG_BASE_FILENAME, __LINE__, __func__, ##__VA_ARGS__); } while(0) // NOLINT(bugprone-lambda-function-name) 263 | #define ASIO_NET_LOGE(fmt, ...) do{ L_O_G_PRINTF(ASIO_NET_LOG_COLOR_RED ASIO_NET_LOG_TIME_LABEL ASIO_NET_LOG_THREAD_LABEL "[E]: %s:%d [%s] " fmt ASIO_NET_LOG_END ASIO_NET_LOG_TIME_VALUE ASIO_NET_LOG_THREAD_VALUE, ASIO_NET_LOG_BASE_FILENAME, __LINE__, __func__, ##__VA_ARGS__); } while(0) // NOLINT(bugprone-lambda-function-name) 264 | #define ASIO_NET_LOGF(fmt, ...) do{ L_O_G_PRINTF(ASIO_NET_LOG_COLOR_CYAN ASIO_NET_LOG_TIME_LABEL ASIO_NET_LOG_THREAD_LABEL "[!]: %s:%d [%s] " fmt ASIO_NET_LOG_END ASIO_NET_LOG_TIME_VALUE ASIO_NET_LOG_THREAD_VALUE, ASIO_NET_LOG_BASE_FILENAME, __LINE__, __func__, ##__VA_ARGS__); ASIO_NET_LOG_EXIT_PROGRAM(); } while(0) // NOLINT(bugprone-lambda-function-name) 265 | 266 | #if defined(ASIO_NET_LOG_IN_LIB) && !defined(ASIO_NET_LOG_SHOW_DEBUG) && !defined(L_O_G_NDEBUG) 267 | #define ASIO_NET_LOG_NDEBUG 268 | #endif 269 | 270 | #if defined(L_O_G_NDEBUG) && !defined(ASIO_NET_LOG_NDEBUG) 271 | #define ASIO_NET_LOG_NDEBUG 272 | #endif 273 | 274 | #if (defined(NDEBUG) || defined(ASIO_NET_LOG_NDEBUG)) && !defined(L_O_G_SHOW_DEBUG) 275 | #define ASIO_NET_LOGD(fmt, ...) ((void)0) 276 | #else 277 | #define ASIO_NET_LOGD(fmt, ...) do{ L_O_G_PRINTF(ASIO_NET_LOG_COLOR_DEFAULT ASIO_NET_LOG_TIME_LABEL ASIO_NET_LOG_THREAD_LABEL "[D]: %s:%d " fmt ASIO_NET_LOG_END ASIO_NET_LOG_TIME_VALUE ASIO_NET_LOG_THREAD_VALUE, ASIO_NET_LOG_BASE_FILENAME, __LINE__, ##__VA_ARGS__); } while(0) 278 | #endif 279 | 280 | #if defined(ASIO_NET_LOG_SHOW_VERBOSE) 281 | #define ASIO_NET_LOGV(fmt, ...) do{ L_O_G_PRINTF(ASIO_NET_LOG_COLOR_DEFAULT ASIO_NET_LOG_TIME_LABEL ASIO_NET_LOG_THREAD_LABEL "[V]: %s:%d " fmt ASIO_NET_LOG_END ASIO_NET_LOG_TIME_VALUE ASIO_NET_LOG_THREAD_VALUE, ASIO_NET_LOG_BASE_FILENAME, __LINE__, ##__VA_ARGS__); } while(0) 282 | #else 283 | #define ASIO_NET_LOGV(fmt, ...) ((void)0) 284 | #endif 285 | 286 | #endif 287 | -------------------------------------------------------------------------------- /include/asio_net/detail/message.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "noncopyable.hpp" 7 | 8 | namespace asio_net { 9 | namespace detail { 10 | 11 | struct message : private noncopyable { 12 | message() = default; 13 | explicit message(std::string msg) : length((uint32_t)msg.length()), body(std::move(msg)) {} 14 | 15 | void clear() { 16 | length = 0; 17 | body.clear(); 18 | body.shrink_to_fit(); 19 | } 20 | 21 | uint32_t length{}; 22 | std::string body; 23 | }; 24 | 25 | } // namespace detail 26 | } // namespace asio_net 27 | -------------------------------------------------------------------------------- /include/asio_net/detail/noncopyable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace asio_net { 4 | namespace detail { 5 | 6 | class noncopyable { 7 | protected: 8 | noncopyable() = default; 9 | ~noncopyable() = default; 10 | 11 | public: 12 | noncopyable(const noncopyable&) = delete; 13 | const noncopyable& operator=(const noncopyable&) = delete; 14 | }; 15 | 16 | } // namespace detail 17 | } // namespace asio_net 18 | -------------------------------------------------------------------------------- /include/asio_net/detail/rpc_client_t.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "noncopyable.hpp" 6 | #include "rpc_core.hpp" 7 | #include "rpc_session_t.hpp" 8 | #include "tcp_client_t.hpp" 9 | 10 | namespace asio_net { 11 | namespace detail { 12 | 13 | template 14 | class rpc_client_t : noncopyable { 15 | public: 16 | explicit rpc_client_t(asio::io_context& io_context, rpc_config rpc_config = {}) 17 | : io_context_(io_context), rpc_config_(rpc_config), client_(std::make_shared>(io_context, rpc_config.to_tcp_config())) { 18 | init(); 19 | } 20 | 21 | #ifdef ASIO_NET_ENABLE_SSL 22 | explicit rpc_client_t(asio::io_context& io_context, asio::ssl::context& ssl_context, rpc_config rpc_config = {}) 23 | : io_context_(io_context), 24 | rpc_config_(rpc_config), 25 | client_(std::make_shared>(io_context, ssl_context, rpc_config.to_tcp_config())) { 26 | init(); 27 | } 28 | #endif 29 | 30 | void open(std::string ip, uint16_t port) { 31 | static_assert(T == socket_type::normal || T == socket_type::ssl, ""); 32 | client_->open(std::move(ip), port); 33 | } 34 | 35 | void open(std::string endpoint) { 36 | static_assert(T == socket_type::domain, ""); 37 | client_->open(std::move(endpoint)); 38 | } 39 | 40 | void close() { 41 | client_->close(); 42 | } 43 | 44 | void set_reconnect(uint32_t ms) { 45 | client_->set_reconnect(ms); 46 | } 47 | 48 | void cancel_reconnect() { 49 | client_->cancel_reconnect(); 50 | } 51 | 52 | rpc_config& config() { 53 | return rpc_config_; 54 | } 55 | 56 | void run() { 57 | client_->run(); 58 | } 59 | 60 | void stop() { 61 | client_->stop(); 62 | } 63 | 64 | private: 65 | void init() { 66 | client_->on_open = [this]() { 67 | auto session = std::make_shared>(io_context_, rpc_config_); 68 | rpc_session_ = session; 69 | session->init(client_); 70 | 71 | session->on_close = [this] { 72 | client_->on_data = nullptr; 73 | if (on_close) on_close(); 74 | }; 75 | 76 | session->start_ping(); 77 | if (on_open) on_open(session->rpc); 78 | }; 79 | 80 | client_->on_open_failed = [this](const std::error_code& ec) { 81 | if (on_open_failed) on_open_failed(ec); 82 | }; 83 | } 84 | 85 | public: 86 | std::function)> on_open; 87 | std::function on_close; 88 | std::function on_open_failed; 89 | 90 | private: 91 | asio::io_context& io_context_; 92 | rpc_config rpc_config_; 93 | std::shared_ptr> client_; 94 | std::weak_ptr> rpc_session_; 95 | }; 96 | 97 | } // namespace detail 98 | } // namespace asio_net 99 | -------------------------------------------------------------------------------- /include/asio_net/detail/rpc_server_t.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "asio.hpp" 6 | #include "rpc_session_t.hpp" 7 | #include "tcp_server_t.hpp" 8 | 9 | namespace asio_net { 10 | namespace detail { 11 | 12 | template 13 | class rpc_server_t : noncopyable { 14 | public: 15 | rpc_server_t(asio::io_context& io_context, uint16_t port, rpc_config rpc_config = {}) 16 | : io_context_(io_context), rpc_config_(rpc_config), server_(io_context, port, rpc_config.to_tcp_config()) { 17 | static_assert(T == detail::socket_type::normal, ""); 18 | server_.on_session = [this](std::weak_ptr> ws) { 19 | auto session = std::make_shared>(io_context_, rpc_config_); 20 | if (!session->init(std::move(ws))) return; 21 | if (on_session) { 22 | on_session(session); 23 | } 24 | }; 25 | } 26 | 27 | #ifdef ASIO_NET_ENABLE_SSL 28 | rpc_server_t(asio::io_context& io_context, uint16_t port, asio::ssl::context& ssl_context, rpc_config rpc_config = {}) 29 | : io_context_(io_context), rpc_config_(rpc_config), server_(io_context, port, ssl_context, rpc_config.to_tcp_config()) { 30 | static_assert(T == detail::socket_type::ssl, ""); 31 | init(); 32 | } 33 | #endif 34 | 35 | rpc_server_t(asio::io_context& io_context, const std::string& endpoint, rpc_config rpc_config = {}) 36 | : io_context_(io_context), rpc_config_(rpc_config), server_(io_context, endpoint, rpc_config.to_tcp_config()) { 37 | static_assert(T == detail::socket_type::domain, ""); 38 | init(); 39 | } 40 | 41 | public: 42 | void start(bool loop = false) { 43 | server_.start(loop); 44 | } 45 | 46 | private: 47 | void init() { 48 | server_.on_session = [this](std::weak_ptr> ws) { 49 | auto session = std::make_shared>(io_context_, rpc_config_); 50 | if (!session->init(std::move(ws))) return; 51 | if (on_session) { 52 | on_session(session); 53 | } 54 | }; 55 | #ifdef ASIO_NET_ENABLE_SSL 56 | server_.on_handshake_error = [this](std::error_code ec) { 57 | if (on_handshake_error) on_handshake_error(ec); 58 | }; 59 | #endif 60 | } 61 | 62 | public: 63 | std::function>)> on_session; 64 | 65 | #ifdef ASIO_NET_ENABLE_SSL 66 | std::function on_handshake_error; 67 | #endif 68 | 69 | private: 70 | asio::io_context& io_context_; 71 | rpc_config rpc_config_; 72 | detail::tcp_server_t server_; 73 | }; 74 | 75 | } // namespace detail 76 | } // namespace asio_net 77 | -------------------------------------------------------------------------------- /include/asio_net/detail/rpc_session_t.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "noncopyable.hpp" 7 | #include "rpc_core.hpp" 8 | #include "tcp_channel_t.hpp" 9 | 10 | namespace asio_net { 11 | namespace detail { 12 | 13 | template 14 | class rpc_session_t : noncopyable, public std::enable_shared_from_this> { 15 | public: 16 | explicit rpc_session_t(asio::io_context& io_context, rpc_config& rpc_config) : io_context_(io_context), rpc_config_(rpc_config) { 17 | ASIO_NET_LOGD("rpc_session: %p", this); 18 | } 19 | 20 | ~rpc_session_t() { 21 | ASIO_NET_LOGD("~rpc_session: %p", this); 22 | } 23 | 24 | public: 25 | bool init(std::weak_ptr> ws) { 26 | tcp_session_ = std::move(ws); 27 | auto tcp_session = tcp_session_.lock(); 28 | 29 | if (rpc_config_.rpc) { 30 | if (rpc_config_.rpc->is_ready()) { 31 | ASIO_NET_LOGD("rpc already connected"); 32 | tcp_session->close(); 33 | return false; 34 | } 35 | rpc = rpc_config_.rpc; 36 | } else { 37 | rpc = rpc_core::rpc::create(); 38 | } 39 | 40 | rpc->set_timer([this](uint32_t ms, rpc_core::rpc::timeout_cb cb) { 41 | auto timer = std::make_shared(io_context_); 42 | timer->expires_after(std::chrono::milliseconds(ms)); 43 | auto tp = timer.get(); 44 | tp->async_wait([timer = std::move(timer), cb = std::move(cb)](const std::error_code&) { 45 | cb(); 46 | }); 47 | }); 48 | 49 | rpc->get_connection()->send_package_impl = [this](std::string data) { 50 | auto tcp_session = tcp_session_.lock(); 51 | if (tcp_session) { 52 | tcp_session->send(std::move(data)); 53 | } else { 54 | ASIO_NET_LOGW("tcp_session expired"); 55 | } 56 | }; 57 | 58 | rpc->set_ready(true); 59 | 60 | // save origin on_close(may from tcp_client) 61 | auto oc = tcp_session->on_close; 62 | // bind rpc_session lifecycle to tcp_session and end with on_close 63 | tcp_session->on_close = [this, rpc_session = this->shared_from_this(), oc = std::move(oc)]() mutable { 64 | if (oc) oc(); 65 | 66 | rpc->set_ready(false); 67 | 68 | stop_ping(); 69 | 70 | if (rpc_session->on_close) { 71 | rpc_session->on_close(); 72 | } 73 | // post delay destroy rpc_session, ensure rpc.rsp() callback finish 74 | asio::post(io_context_, [rpc_session = std::move(rpc_session)] {}); 75 | // clear tcp_session->on_close, avoid called more than once by close api 76 | tcp_session_.lock()->on_close = nullptr; 77 | }; 78 | 79 | assert(tcp_session->on_data == nullptr); // ensure it's empty 80 | tcp_session->on_data = [this](std::string data) { 81 | rpc->get_connection()->on_recv_package(std::move(data)); 82 | }; 83 | 84 | start_ping(); 85 | return true; 86 | } 87 | 88 | void close() { 89 | auto ts = tcp_session_.lock(); 90 | if (ts) { 91 | ts->close(); 92 | } 93 | } 94 | 95 | void start_ping() { 96 | if (rpc_config_.ping_interval_ms == 0) return; 97 | if (!ping_timer_) { 98 | ping_timer_ = std::make_unique(io_context_); 99 | } 100 | ping_timer_->expires_after(std::chrono::milliseconds(rpc_config_.ping_interval_ms)); 101 | ping_timer_->async_wait([ws = std::weak_ptr>(rpc_session_t::shared_from_this())](std::error_code ec) { 102 | if (ec) return; 103 | auto session = ws.lock(); 104 | if (session && session->rpc->is_ready()) { 105 | ASIO_NET_LOGD("ping..."); 106 | session->rpc->ping() 107 | ->rsp([ws] { 108 | auto session = ws.lock(); 109 | if (session) { 110 | session->start_ping(); 111 | } 112 | }) 113 | ->timeout([ws]() { 114 | ASIO_NET_LOGW("ping timeout"); 115 | auto session = ws.lock(); 116 | if (session) { 117 | session->stop_ping(); 118 | session->close(); 119 | } 120 | }) 121 | ->timeout_ms(session->rpc_config_.pong_timeout_ms) 122 | ->call(); 123 | } 124 | }); 125 | } 126 | 127 | void stop_ping() { 128 | if (ping_timer_) { 129 | ping_timer_->cancel(); 130 | ping_timer_ = nullptr; 131 | } 132 | } 133 | 134 | public: 135 | std::function on_close; 136 | 137 | public: 138 | std::shared_ptr rpc; 139 | 140 | private: 141 | asio::io_context& io_context_; 142 | rpc_config& rpc_config_; 143 | std::weak_ptr> tcp_session_; 144 | std::unique_ptr ping_timer_; 145 | }; 146 | 147 | } // namespace detail 148 | } // namespace asio_net 149 | -------------------------------------------------------------------------------- /include/asio_net/detail/socket_type.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "asio.hpp" 4 | #include "log.h" 5 | #include "noncopyable.hpp" 6 | 7 | #ifdef ASIO_NET_ENABLE_SSL 8 | #include "asio/ssl.hpp" 9 | #endif 10 | 11 | namespace asio_net { 12 | namespace detail { 13 | 14 | enum class socket_type { 15 | normal, 16 | domain, 17 | ssl, 18 | }; 19 | 20 | template 21 | struct socket_impl; 22 | 23 | template <> 24 | struct socket_impl { 25 | using socket = asio::ip::tcp::socket; 26 | using endpoint = asio::ip::tcp::endpoint; 27 | using resolver = asio::ip::tcp::resolver; 28 | using acceptor = asio::ip::tcp::acceptor; 29 | }; 30 | 31 | template <> 32 | struct socket_impl { 33 | using socket = asio::local::stream_protocol::socket; 34 | using endpoint = asio::local::stream_protocol::endpoint; 35 | using acceptor = asio::local::stream_protocol::acceptor; 36 | }; 37 | 38 | #ifdef ASIO_NET_ENABLE_SSL 39 | template <> 40 | struct socket_impl { 41 | using socket = asio::ssl::stream; 42 | using endpoint = asio::ip::tcp::endpoint; 43 | using resolver = asio::ip::tcp::resolver; 44 | using acceptor = asio::ip::tcp::acceptor; 45 | }; 46 | #endif 47 | 48 | } // namespace detail 49 | } // namespace asio_net 50 | -------------------------------------------------------------------------------- /include/asio_net/detail/tcp_channel_t.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "../config.hpp" 7 | #include "asio.hpp" 8 | #include "log.h" 9 | #include "message.hpp" 10 | #include "noncopyable.hpp" 11 | #include "socket_type.hpp" 12 | 13 | namespace asio_net { 14 | namespace detail { 15 | 16 | template 17 | class tcp_channel_t : private noncopyable { 18 | public: 19 | tcp_channel_t(typename socket_impl::socket& socket, const tcp_config& config) : socket_(socket), config_(config) { 20 | ASIO_NET_LOGD("tcp_channel: %p", this); 21 | } 22 | 23 | ~tcp_channel_t() { 24 | ASIO_NET_LOGD("~tcp_channel: %p", this); 25 | } 26 | 27 | protected: 28 | void init_socket() { 29 | if (config_.socket_send_buffer_size != UINT32_MAX) { 30 | asio::socket_base::send_buffer_size option(config_.socket_send_buffer_size); 31 | get_socket().set_option(option); 32 | } 33 | if (config_.socket_recv_buffer_size != UINT32_MAX) { 34 | asio::socket_base::receive_buffer_size option(config_.socket_recv_buffer_size); 35 | get_socket().set_option(option); 36 | } 37 | } 38 | 39 | inline auto& get_socket() const { 40 | return socket_.lowest_layer(); 41 | } 42 | 43 | inline void close_socket() const { 44 | asio::error_code ec; 45 | get_socket().close(ec); 46 | } 47 | 48 | public: 49 | /** 50 | * async send message 51 | * 1. will close if error occur, e.g. msg.size() > max_body_size 52 | * 2. will block wait if send buffer > max_send_buffer_size 53 | * 3. not threadsafe, only can be used on io_context thread 54 | * 55 | * @param msg can be string or binary 56 | */ 57 | void send(std::string msg) { 58 | do_write(std::move(msg), false); 59 | } 60 | 61 | /** 62 | * close socket 63 | * will trigger @see`on_close` if opened 64 | */ 65 | void close() { 66 | do_close(); 67 | } 68 | 69 | bool is_open() const { 70 | return get_socket().is_open(); 71 | } 72 | 73 | typename socket_impl::endpoint local_endpoint() { 74 | return socket_.local_endpoint(); 75 | } 76 | 77 | typename socket_impl::endpoint remote_endpoint() { 78 | return socket_.remote_endpoint(); 79 | } 80 | 81 | protected: 82 | void do_read_start(std::shared_ptr self = nullptr) { 83 | if (config_.auto_pack) { 84 | do_read_header(std::move(self)); 85 | } else { 86 | // init read_msg_.body as buffer 87 | read_msg_.body.resize(config_.max_body_size); 88 | do_read_data(std::move(self)); 89 | } 90 | } 91 | 92 | private: 93 | void do_read_header(std::shared_ptr self) { 94 | asio::async_read( 95 | socket_, asio::buffer(&read_msg_.length, sizeof(read_msg_.length)), 96 | [this, self = std::move(self), alive = std::weak_ptr(this->is_alive_)](const std::error_code& ec, std::size_t size) mutable { 97 | if (alive.expired()) return; 98 | 99 | if (ec == asio::error::eof || ec == asio::error::connection_reset) { 100 | do_close(); 101 | return; 102 | } else if (ec || size == 0) { 103 | ASIO_NET_LOGD("do_read_header: %s, size: %zu", ec.message().c_str(), size); 104 | do_close(); 105 | return; 106 | } 107 | 108 | if (read_msg_.length <= config_.max_body_size) { 109 | do_read_body(std::move(self)); 110 | } else { 111 | ASIO_NET_LOGE("read: body size=%u > max_body_size=%u", read_msg_.length, config_.max_body_size); 112 | do_close(); 113 | } 114 | }); 115 | } 116 | 117 | void do_read_body(std::shared_ptr self) { 118 | read_msg_.body.resize(read_msg_.length); 119 | asio::async_read( 120 | socket_, asio::buffer(read_msg_.body), 121 | [this, self = std::move(self), alive = std::weak_ptr(this->is_alive_)](const std::error_code& ec, std::size_t size) mutable { 122 | if (alive.expired()) return; 123 | 124 | if (ec == asio::error::eof || ec == asio::error::connection_reset) { 125 | do_close(); 126 | return; 127 | } else if (ec || size == 0) { 128 | ASIO_NET_LOGD("do_read_body: %s, size: %zu", ec.message().c_str(), size); 129 | do_close(); 130 | return; 131 | } 132 | 133 | auto msg = std::move(read_msg_.body); 134 | read_msg_.clear(); 135 | if (on_data) on_data(std::move(msg)); 136 | do_read_header(std::move(self)); 137 | }); 138 | } 139 | 140 | void do_read_data(std::shared_ptr self) { 141 | auto& readBuffer = read_msg_.body; 142 | socket_.async_read_some(asio::buffer(readBuffer), [this, self = std::move(self), alive = std::weak_ptr(this->is_alive_)]( 143 | const std::error_code& ec, std::size_t length) mutable { 144 | if (alive.expired()) return; 145 | if (!ec) { 146 | if (on_data) on_data(std::string(read_msg_.body.data(), length)); 147 | do_read_data(std::move(self)); 148 | } else { 149 | do_close(); 150 | } 151 | }); 152 | } 153 | 154 | void do_write(std::string msg, bool from_queue) { 155 | if (config_.auto_pack && msg.size() > config_.max_body_size) { 156 | ASIO_NET_LOGE("write: body size=%zu > max_body_size=%u", msg.size(), config_.max_body_size); 157 | do_close(); 158 | } 159 | 160 | if (msg.size() > config_.max_send_buffer_size) { 161 | ASIO_NET_LOGE("write: body size=%zu > max_send_buffer_size=%u", msg.size(), config_.max_send_buffer_size); 162 | do_close(); 163 | } 164 | 165 | // block wait send_buffer idle 166 | while (msg.size() + send_buffer_now_ > config_.max_send_buffer_size) { 167 | ASIO_NET_LOGV("block wait send_buffer idle"); 168 | static_cast(&socket_.get_executor().context())->run_one(); 169 | } 170 | 171 | // queue for asio::async_write 172 | if (!from_queue && send_buffer_now_ != 0) { 173 | ASIO_NET_LOGV("queue for asio::async_write"); 174 | send_buffer_now_ += msg.size(); 175 | write_msg_queue_.emplace_back(std::move(msg)); 176 | return; 177 | } 178 | if (!from_queue) { 179 | send_buffer_now_ += msg.size(); 180 | } 181 | 182 | auto keeper = std::make_unique(std::move(msg)); 183 | std::vector buffer; 184 | if (config_.auto_pack) { 185 | buffer.emplace_back(&keeper->length, sizeof(keeper->length)); 186 | } 187 | buffer.emplace_back(asio::buffer(keeper->body)); 188 | asio::async_write( 189 | socket_, buffer, 190 | [this, keeper = std::move(keeper), alive = std::weak_ptr(this->is_alive_)](const std::error_code& ec, std::size_t /*length*/) { 191 | if (alive.expired()) return; 192 | send_buffer_now_ -= keeper->body.size(); 193 | if (ec) { 194 | do_close(); 195 | } 196 | 197 | if (!write_msg_queue_.empty()) { 198 | asio::post(socket_.get_executor(), [this, msg = std::move(write_msg_queue_.front())]() mutable { 199 | do_write(std::move(msg), true); 200 | }); 201 | write_msg_queue_.pop_front(); 202 | } else { 203 | write_msg_queue_.shrink_to_fit(); 204 | } 205 | }); 206 | } 207 | 208 | void do_close() { 209 | reset_data(); 210 | 211 | if (!is_open()) return; 212 | asio::error_code ec; 213 | get_socket().close(ec); 214 | if (ec) { 215 | ASIO_NET_LOGE("do_close: %s", ec.message().c_str()); 216 | } 217 | if (on_close) on_close(); 218 | } 219 | 220 | void reset_data() { 221 | read_msg_.clear(); 222 | send_buffer_now_ = 0; 223 | write_msg_queue_.clear(); 224 | write_msg_queue_.shrink_to_fit(); 225 | } 226 | 227 | public: 228 | std::function on_close; 229 | std::function on_data; 230 | 231 | protected: 232 | std::shared_ptr is_alive_ = std::make_shared(); 233 | 234 | private: 235 | typename socket_impl::socket& socket_; 236 | const tcp_config& config_; 237 | detail::message read_msg_; 238 | size_t send_buffer_now_ = 0; 239 | std::deque write_msg_queue_; 240 | }; 241 | 242 | } // namespace detail 243 | } // namespace asio_net 244 | -------------------------------------------------------------------------------- /include/asio_net/detail/tcp_client_t.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "noncopyable.hpp" 4 | #include "tcp_channel_t.hpp" 5 | 6 | namespace asio_net { 7 | namespace detail { 8 | 9 | template 10 | class tcp_client_t : public tcp_channel_t { 11 | public: 12 | explicit tcp_client_t(asio::io_context& io_context, tcp_config config = {}) 13 | : tcp_channel_t(socket_, config_), io_context_(io_context), socket_(io_context), config_(config) { 14 | config_.init(); 15 | } 16 | 17 | #ifdef ASIO_NET_ENABLE_SSL 18 | explicit tcp_client_t(asio::io_context& io_context, asio::ssl::context& ssl_context, tcp_config config = {}) 19 | : tcp_channel_t(socket_, config_), io_context_(io_context), socket_(io_context, ssl_context), config_(config) { 20 | config_.init(); 21 | } 22 | #endif 23 | 24 | /** 25 | * connect to server 26 | * 27 | * @param host A string identifying a location. May be a descriptive name or a numeric address string. 28 | * @param port The port to open. 29 | */ 30 | void open(std::string host, uint16_t port) { 31 | open_ = [this, host = std::move(host), port] { 32 | do_open(host, port); 33 | }; 34 | open_(); 35 | } 36 | 37 | /** 38 | * connect to domain socket 39 | * 40 | * @param endpoint e.g. /tmp/foobar 41 | */ 42 | void open(std::string endpoint) { 43 | open_ = [this, endpoint = std::move(endpoint)] { 44 | do_open(endpoint); 45 | }; 46 | open_(); 47 | } 48 | 49 | void close(bool need_reconnect = false) { 50 | if (!need_reconnect) { 51 | cancel_reconnect(); 52 | } 53 | tcp_channel_t::close(); 54 | } 55 | 56 | void set_reconnect(uint32_t ms) { 57 | reconnect_ms_ = ms; 58 | reconnect_timer_ = std::make_unique(io_context_); 59 | } 60 | 61 | void cancel_reconnect() { 62 | if (reconnect_timer_) { 63 | reconnect_timer_->cancel(); 64 | reconnect_timer_ = nullptr; 65 | } 66 | } 67 | 68 | void check_reconnect() { 69 | if (!is_open && reconnect_timer_) { 70 | reconnect_timer_->expires_after(std::chrono::milliseconds(reconnect_ms_)); 71 | reconnect_timer_->async_wait([this](const asio::error_code& ec) { 72 | if (is_open) return; 73 | if (!ec) { 74 | ASIO_NET_LOGD("reconnect..."); 75 | open_(); 76 | } 77 | }); 78 | } 79 | } 80 | 81 | void run() { 82 | auto work = asio::make_work_guard(io_context_); 83 | io_context_.run(); 84 | } 85 | 86 | void stop() { 87 | close(); 88 | io_context_.stop(); 89 | } 90 | 91 | private: 92 | void do_open(const std::string& host, uint16_t port) { 93 | static_assert(T == socket_type::normal || T == socket_type::ssl, ""); 94 | auto resolver = std::make_unique::resolver>(io_context_); 95 | auto rp = resolver.get(); 96 | rp->async_resolve(host, std::to_string(port), 97 | [this, resolver = std::move(resolver), alive = std::weak_ptr(this->is_alive_)]( 98 | const std::error_code& ec, const typename socket_impl::resolver::results_type& endpoints) mutable { 99 | if (alive.expired()) return; 100 | if (!ec) { 101 | asio::async_connect(tcp_channel_t::get_socket(), endpoints, 102 | [this, alive = std::move(alive)](const std::error_code& ec, const typename socket_impl::endpoint&) { 103 | if (alive.expired()) return; 104 | async_connect_handler(ec); 105 | }); 106 | } else { 107 | tcp_channel_t::close_socket(); // release resource 108 | if (on_open_failed) on_open_failed(ec); 109 | check_reconnect(); 110 | } 111 | }); 112 | } 113 | 114 | void do_open(const std::string& endpoint) { 115 | static_assert(T == socket_type::domain, ""); 116 | socket_.async_connect(typename socket_impl::endpoint(endpoint), [this](const std::error_code& ec) { 117 | async_connect_handler(ec); 118 | }); 119 | } 120 | 121 | template 122 | void async_connect_handler(const std::error_code& ec); 123 | 124 | public: 125 | std::function on_open; 126 | std::function on_open_failed; 127 | std::function on_close; 128 | 129 | public: 130 | bool is_open = false; 131 | 132 | private: 133 | asio::io_context& io_context_; 134 | typename socket_impl::socket socket_; 135 | tcp_config config_; 136 | std::unique_ptr reconnect_timer_; 137 | uint32_t reconnect_ms_ = 0; 138 | std::function open_; 139 | }; 140 | 141 | template <> 142 | template <> 143 | inline void tcp_client_t::async_connect_handler(const std::error_code& ec) { 144 | if (!ec) { 145 | this->init_socket(); 146 | tcp_channel_t::on_close = [this] { 147 | is_open = false; 148 | if (tcp_client_t::on_close) tcp_client_t::on_close(); 149 | check_reconnect(); 150 | }; 151 | is_open = true; 152 | if (on_open) on_open(); 153 | if (reconnect_timer_) { 154 | reconnect_timer_->cancel(); 155 | } 156 | this->do_read_start(); 157 | } else { 158 | if (on_open_failed) on_open_failed(ec); 159 | check_reconnect(); 160 | } 161 | } 162 | 163 | template <> 164 | template <> 165 | inline void tcp_client_t::async_connect_handler(const std::error_code& ec) { 166 | if (!ec) { 167 | this->init_socket(); 168 | tcp_channel_t::on_close = [this] { 169 | is_open = false; 170 | if (tcp_client_t::on_close) tcp_client_t::on_close(); 171 | check_reconnect(); 172 | }; 173 | is_open = true; 174 | if (on_open) on_open(); 175 | if (reconnect_timer_) { 176 | reconnect_timer_->cancel(); 177 | } 178 | this->do_read_start(); 179 | } else { 180 | if (on_open_failed) on_open_failed(ec); 181 | check_reconnect(); 182 | } 183 | } 184 | 185 | #ifdef ASIO_NET_ENABLE_SSL 186 | template <> 187 | template <> 188 | inline void tcp_client_t::async_connect_handler(const std::error_code& ec) { 189 | if (!ec) { 190 | this->init_socket(); 191 | socket_.async_handshake(asio::ssl::stream_base::client, [this](const std::error_code& error) { 192 | if (!error) { 193 | tcp_channel_t::on_close = [this] { 194 | is_open = false; 195 | if (tcp_client_t::on_close) tcp_client_t::on_close(); 196 | check_reconnect(); 197 | }; 198 | is_open = true; 199 | if (on_open) on_open(); 200 | if (reconnect_timer_) { 201 | reconnect_timer_->cancel(); 202 | } 203 | this->do_read_start(); 204 | } else { 205 | if (on_open_failed) on_open_failed(error); 206 | check_reconnect(); 207 | } 208 | }); 209 | } else { 210 | if (on_open_failed) on_open_failed(ec); 211 | check_reconnect(); 212 | } 213 | } 214 | #endif 215 | 216 | } // namespace detail 217 | } // namespace asio_net 218 | -------------------------------------------------------------------------------- /include/asio_net/detail/tcp_server_t.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../config.hpp" 6 | #include "asio.hpp" 7 | #include "tcp_channel_t.hpp" 8 | 9 | namespace asio_net { 10 | namespace detail { 11 | 12 | template 13 | class tcp_session_t : public tcp_channel_t, public std::enable_shared_from_this> { 14 | using socket = typename socket_impl::socket; 15 | 16 | public: 17 | explicit tcp_session_t(socket socket, const tcp_config& config) : tcp_channel_t(socket_, config), socket_(std::move(socket)) { 18 | this->init_socket(); 19 | } 20 | 21 | void start() { 22 | tcp_channel_t::do_read_start(tcp_session_t::shared_from_this()); 23 | } 24 | 25 | #ifdef ASIO_NET_ENABLE_SSL 26 | void async_handshake(std::function handle) { 27 | socket_.async_handshake(asio::ssl::stream_base::server, [this, handle = std::move(handle)](const std::error_code& error) { 28 | handle(error); 29 | if (!error) { 30 | this->start(); 31 | } 32 | }); 33 | } 34 | #endif 35 | 36 | private: 37 | socket socket_; 38 | }; 39 | 40 | template 41 | class tcp_server_t { 42 | using socket = typename socket_impl::socket; 43 | using endpoint = typename socket_impl::endpoint; 44 | 45 | public: 46 | tcp_server_t(asio::io_context& io_context, uint16_t port, tcp_config config = {}) 47 | : io_context_(io_context), 48 | acceptor_(io_context, endpoint(config.enable_ipv6 ? asio::ip::tcp::v6() : asio::ip::tcp::v4(), port)), 49 | config_(config) { 50 | config_.init(); 51 | } 52 | 53 | #ifdef ASIO_NET_ENABLE_SSL 54 | tcp_server_t(asio::io_context& io_context, uint16_t port, asio::ssl::context& ssl_context, tcp_config config = {}) 55 | : io_context_(io_context), ssl_context_(ssl_context), acceptor_(io_context, endpoint(asio::ip::tcp::v4(), port)), config_(config) { 56 | config_.init(); 57 | } 58 | #endif 59 | 60 | /** 61 | * domain socket 62 | * 63 | * @param io_context 64 | * @param endpoint e.g. /tmp/foobar 65 | * @param config 66 | */ 67 | tcp_server_t(asio::io_context& io_context, const std::string& endpoint, tcp_config config = {}) 68 | : io_context_(io_context), acceptor_(io_context, typename socket_impl::endpoint(endpoint)), config_(config) { 69 | config_.init(); 70 | } 71 | 72 | public: 73 | void start(bool loop = false) { 74 | do_accept(); 75 | if (loop) { 76 | io_context_.run(); 77 | } 78 | } 79 | 80 | public: 81 | std::function>)> on_session; 82 | 83 | #ifdef ASIO_NET_ENABLE_SSL 84 | std::function on_handshake_error; 85 | #endif 86 | 87 | private: 88 | template 89 | void do_accept(); 90 | 91 | private: 92 | asio::io_context& io_context_; 93 | #ifdef ASIO_NET_ENABLE_SSL 94 | typename std::conditional::type ssl_context_; 95 | #endif 96 | typename socket_impl::acceptor acceptor_; 97 | tcp_config config_; 98 | }; 99 | 100 | template <> 101 | template <> 102 | inline void tcp_server_t::do_accept() { 103 | acceptor_.async_accept([this](const std::error_code& ec, socket socket) { 104 | if (!ec) { 105 | auto session = std::make_shared>(std::move(socket), config_); 106 | session->start(); 107 | if (on_session) on_session(session); 108 | tcp_server_t::do_accept(); 109 | } else { 110 | ASIO_NET_LOGD("do_accept: %s", ec.message().c_str()); 111 | } 112 | }); 113 | } 114 | 115 | template <> 116 | template <> 117 | inline void tcp_server_t::do_accept() { 118 | acceptor_.async_accept([this](const std::error_code& ec, socket socket) { 119 | if (!ec) { 120 | auto session = std::make_shared>(std::move(socket), config_); 121 | session->start(); 122 | if (on_session) on_session(session); 123 | tcp_server_t::do_accept(); 124 | } else { 125 | ASIO_NET_LOGD("do_accept: %s", ec.message().c_str()); 126 | } 127 | }); 128 | } 129 | 130 | #ifdef ASIO_NET_ENABLE_SSL 131 | template <> 132 | template <> 133 | inline void tcp_server_t::do_accept() { 134 | acceptor_.async_accept([this](const std::error_code& ec, asio::ip::tcp::socket socket) { 135 | if (!ec) { 136 | using ssl_stream = typename socket_impl::socket; 137 | auto session = std::make_shared>(ssl_stream(std::move(socket), ssl_context_), config_); 138 | session->async_handshake([this, session](const std::error_code& error) { 139 | if (!error) { 140 | if (on_session) on_session(session); 141 | } else { 142 | if (on_handshake_error) on_handshake_error(error); 143 | } 144 | }); 145 | do_accept(); 146 | } else { 147 | ASIO_NET_LOGD("do_accept: %s", ec.message().c_str()); 148 | } 149 | }); 150 | } 151 | #endif 152 | 153 | } // namespace detail 154 | } // namespace asio_net 155 | -------------------------------------------------------------------------------- /include/asio_net/detail/udp_client_t.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "asio.hpp" 4 | #include "noncopyable.hpp" 5 | 6 | namespace asio_net { 7 | namespace detail { 8 | 9 | template 10 | class udp_client_t : private noncopyable { 11 | public: 12 | using socket = typename T::socket; 13 | using endpoint = typename T::endpoint; 14 | 15 | using result_cb = std::function; 16 | 17 | public: 18 | explicit udp_client_t(asio::io_context& io_context) : socket_(create_socket(io_context)) {} 19 | 20 | void send_to(std::string data, const endpoint& endpoint, result_cb cb = nullptr) { 21 | auto keeper = std::make_unique(std::move(data)); 22 | auto k_data = keeper->data(); 23 | auto k_size = keeper->size(); 24 | socket_.async_send_to(asio::buffer(k_data, k_size), endpoint, 25 | [keeper = std::move(keeper), cb = std::move(cb)](const std::error_code& ec, std::size_t size) { 26 | if (cb) cb(ec, size); 27 | }); 28 | } 29 | 30 | void send_to(void* data, size_t size, const endpoint& endpoint, result_cb cb = nullptr) { 31 | send_to(std::string((char*)data, size), endpoint, std::move(cb)); 32 | } 33 | 34 | /** 35 | * connect to server, for `@send` api 36 | * 37 | * @param endpoint 38 | */ 39 | void connect(const endpoint& endpoint) { 40 | socket_.connect(endpoint); 41 | } 42 | 43 | void send(std::string data, result_cb cb = nullptr) { 44 | auto keeper = std::make_unique(std::move(data)); 45 | auto k_data = keeper->data(); 46 | auto k_size = keeper->size(); 47 | socket_.async_send(asio::buffer(k_data, k_size), [keeper = std::move(keeper), cb = std::move(cb)](const std::error_code& ec, std::size_t size) { 48 | if (cb) cb(ec, size); 49 | }); 50 | } 51 | 52 | void send(void* data, size_t size, result_cb cb = nullptr) { 53 | send(std::string((char*)data, size), std::move(cb)); 54 | } 55 | 56 | private: 57 | template 58 | static typename std::enable_if::value, typename Type::socket>::type create_socket(asio::io_context& io_context) { 59 | return typename Type::socket(io_context, typename Type::endpoint()); 60 | } 61 | 62 | template 63 | static typename std::enable_if::value, typename Type::socket>::type create_socket( 64 | asio::io_context& io_context) { 65 | typename Type::socket s(io_context); 66 | s.open(); 67 | return s; 68 | } 69 | 70 | private: 71 | socket socket_; 72 | }; 73 | 74 | } // namespace detail 75 | } // namespace asio_net 76 | -------------------------------------------------------------------------------- /include/asio_net/detail/udp_server_t.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "asio.hpp" 4 | #include "log.h" 5 | #include "noncopyable.hpp" 6 | 7 | namespace asio_net { 8 | namespace detail { 9 | 10 | template 11 | class udp_server_t : private noncopyable { 12 | public: 13 | using socket = typename T::socket; 14 | using endpoint = typename T::endpoint; 15 | 16 | public: 17 | udp_server_t(asio::io_context& io_context, short port, uint16_t max_length = 4096) 18 | : io_context_(io_context), socket_(io_context, typename T::endpoint(T::v4(), port)), max_length_(max_length) { 19 | data_.resize(max_length); 20 | do_receive(); 21 | } 22 | 23 | /** 24 | * domain socket 25 | * 26 | * @param io_context 27 | * @param endpoint e.g. /tmp/foobar 28 | * @param max_length 29 | */ 30 | udp_server_t(asio::io_context& io_context, const std::string& endpoint, uint16_t max_length = 4096) 31 | : io_context_(io_context), socket_(io_context, typename T::endpoint(endpoint)), max_length_(max_length) { 32 | data_.resize(max_length); 33 | do_receive(); 34 | } 35 | 36 | void start() { 37 | io_context_.run(); 38 | } 39 | 40 | std::function on_data; 41 | 42 | private: 43 | void do_receive() { 44 | socket_.async_receive_from(asio::buffer((void*)data_.data(), max_length_), from_endpoint_, [this](const std::error_code& ec, size_t length) { 45 | if (!ec && length > 0) { 46 | if (on_data) on_data((uint8_t*)data_.data(), length, from_endpoint_); 47 | do_receive(); 48 | } else { 49 | ASIO_NET_LOGD("udp_server error: %s", ec.message().c_str()); 50 | } 51 | }); 52 | } 53 | 54 | private: 55 | asio::io_context& io_context_; 56 | socket socket_; 57 | endpoint from_endpoint_; 58 | uint16_t max_length_; 59 | std::string data_; 60 | }; 61 | 62 | } // namespace detail 63 | } // namespace asio_net 64 | -------------------------------------------------------------------------------- /include/asio_net/rpc_client.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "asio.hpp" 4 | #include "detail/rpc_client_t.hpp" 5 | 6 | namespace asio_net { 7 | 8 | using rpc_client = detail::rpc_client_t; 9 | using rpc_client_ssl = detail::rpc_client_t; 10 | using domain_rpc_client = detail::rpc_client_t; 11 | 12 | } // namespace asio_net 13 | -------------------------------------------------------------------------------- /include/asio_net/rpc_server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "asio.hpp" 4 | #include "detail/rpc_server_t.hpp" 5 | #include "rpc_session.hpp" 6 | 7 | namespace asio_net { 8 | 9 | using rpc_server = detail::rpc_server_t; 10 | using rpc_server_ssl = detail::rpc_server_t; 11 | using domain_rpc_server = detail::rpc_server_t; 12 | 13 | } // namespace asio_net 14 | -------------------------------------------------------------------------------- /include/asio_net/rpc_session.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "asio.hpp" 4 | #include "detail/rpc_session_t.hpp" 5 | 6 | namespace asio_net { 7 | 8 | using rpc_session = detail::rpc_session_t; 9 | using rpc_session_ssl = detail::rpc_session_t; 10 | using domain_rpc_session = detail::rpc_session_t; 11 | 12 | } // namespace asio_net 13 | -------------------------------------------------------------------------------- /include/asio_net/serial_port.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "asio.hpp" 7 | #include "asio_net/detail/log.h" 8 | #include "asio_net/detail/message.hpp" 9 | #include "config.hpp" 10 | #include "detail/noncopyable.hpp" 11 | 12 | namespace asio_net { 13 | 14 | class serial_port : detail::noncopyable { 15 | public: 16 | explicit serial_port(asio::io_context& io_context, serial_config config = {}) 17 | : io_context_(io_context), serial_(io_context), config_(std::move(config)) {} 18 | 19 | template 20 | inline void set_option(const Option& option) { 21 | serial_.set_option(option); 22 | } 23 | 24 | template 25 | inline void get_option(Option& option) { 26 | serial_.get_option(option); 27 | } 28 | 29 | void open(std::string device = {}) { 30 | if (!device.empty()) { 31 | config_.device = std::move(device); 32 | } 33 | try_open(); 34 | } 35 | 36 | bool is_open() const { 37 | return serial_.is_open(); 38 | } 39 | 40 | void send(std::string msg) { 41 | do_write(std::move(msg), false); 42 | } 43 | 44 | void close() { 45 | cancel_reconnect(); 46 | do_close(); 47 | } 48 | 49 | void set_reconnect(uint32_t ms) { 50 | reconnect_ms_ = ms; 51 | reconnect_timer_ = std::make_unique(io_context_); 52 | } 53 | 54 | void cancel_reconnect() { 55 | if (reconnect_timer_) { 56 | reconnect_timer_->cancel(); 57 | reconnect_timer_ = nullptr; 58 | } 59 | } 60 | 61 | serial_config& config() { 62 | return config_; 63 | } 64 | 65 | void run() { 66 | auto work = asio::make_work_guard(io_context_); 67 | io_context_.run(); 68 | } 69 | 70 | private: 71 | void try_open() { 72 | if (on_try_open) on_try_open(); 73 | try { 74 | serial_.open(config_.device); 75 | if (on_open) on_open(); 76 | read_msg_.resize(config_.max_recv_buffer_size); 77 | do_read_data(); 78 | } catch (const std::system_error& e) { 79 | if (on_open_failed) on_open_failed(e.code()); 80 | check_reconnect(); 81 | } 82 | } 83 | 84 | void do_write(std::string msg, bool from_queue) { 85 | if (msg.size() > config_.max_send_buffer_size) { 86 | ASIO_NET_LOGE("msg size=%zu > max_send_buffer_size=%u", msg.size(), config_.max_send_buffer_size); 87 | do_close(); 88 | } 89 | 90 | // block wait send_buffer idle 91 | while (msg.size() + send_buffer_now_ > config_.max_send_buffer_size) { 92 | ASIO_NET_LOGV("block wait send_buffer idle"); 93 | io_context_.run_one(); 94 | } 95 | 96 | // queue for asio::async_write 97 | if (!from_queue && send_buffer_now_ != 0) { 98 | ASIO_NET_LOGV("queue for asio::async_write"); 99 | send_buffer_now_ += (uint32_t)msg.size(); 100 | write_msg_queue_.emplace_back(std::move(msg)); 101 | return; 102 | } 103 | if (!from_queue) { 104 | send_buffer_now_ += (uint32_t)msg.size(); 105 | } 106 | 107 | auto keeper = std::make_unique(std::move(msg)); 108 | auto buffer = asio::buffer(keeper->body); 109 | asio::async_write(serial_, buffer, [this, keeper = std::move(keeper)](const std::error_code& ec, std::size_t /*length*/) { 110 | send_buffer_now_ -= (uint32_t)keeper->body.size(); 111 | if (ec) { 112 | do_close(); 113 | } 114 | 115 | if (!write_msg_queue_.empty()) { 116 | asio::post(io_context_, [this, msg = std::move(write_msg_queue_.front())]() mutable { 117 | do_write(std::move(msg), true); 118 | }); 119 | write_msg_queue_.pop_front(); 120 | } else { 121 | write_msg_queue_.shrink_to_fit(); 122 | } 123 | }); 124 | } 125 | 126 | void do_close() { 127 | reset_data(); 128 | check_reconnect(); 129 | 130 | if (!is_open()) return; 131 | asio::error_code ec; 132 | serial_.close(ec); 133 | if (ec) { 134 | ASIO_NET_LOGE("do_close: %s", ec.message().c_str()); 135 | } 136 | if (on_close) on_close(); 137 | } 138 | 139 | void reset_data() { 140 | read_msg_.clear(); 141 | read_msg_.shrink_to_fit(); 142 | send_buffer_now_ = 0; 143 | write_msg_queue_.clear(); 144 | write_msg_queue_.shrink_to_fit(); 145 | } 146 | 147 | void do_read_data() { 148 | serial_.async_read_some(asio::buffer(read_msg_), [this](const std::error_code& ec, std::size_t length) mutable { 149 | if (!ec) { 150 | if (on_data) on_data(std::string(read_msg_.data(), length)); 151 | do_read_data(); 152 | } else { 153 | do_close(); 154 | } 155 | }); 156 | } 157 | 158 | void check_reconnect() { 159 | if (reconnect_timer_) { 160 | reconnect_timer_->expires_after(std::chrono::milliseconds(reconnect_ms_)); 161 | reconnect_timer_->async_wait([this](std::error_code ec) { 162 | if (!ec) { 163 | try_open(); 164 | } 165 | }); 166 | } 167 | } 168 | 169 | public: 170 | std::function on_try_open; 171 | std::function on_open; 172 | std::function on_open_failed; 173 | std::function on_close; 174 | 175 | std::function on_data; 176 | 177 | private: 178 | asio::io_context& io_context_; 179 | asio::serial_port serial_; 180 | serial_config config_; 181 | 182 | std::string read_msg_; 183 | uint32_t send_buffer_now_ = 0; 184 | std::deque write_msg_queue_; 185 | 186 | std::unique_ptr reconnect_timer_; 187 | uint32_t reconnect_ms_ = 0; 188 | }; 189 | 190 | } // namespace asio_net 191 | -------------------------------------------------------------------------------- /include/asio_net/server_discovery.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "asio.hpp" 9 | #include "detail/log.h" 10 | 11 | namespace asio_net { 12 | 13 | /** 14 | * message format: 15 | * prefix + '\n' + name + '\n' + message 16 | * i.e: "discovery\nname\nmessage" 17 | */ 18 | namespace server_discovery { 19 | 20 | const char* const addr_default = "239.255.0.1"; 21 | const uint16_t port_default = 30001; 22 | 23 | class receiver { 24 | using service_found_handle_t = std::function; 25 | 26 | public: 27 | receiver(asio::io_context& io_context, service_found_handle_t handle, // NOLINT(cppcoreguidelines-pro-type-member-init) 28 | std::string addr = addr_default, uint16_t port = port_default) 29 | : socket_(io_context), service_found_handle_(std::move(handle)), addr_(std::move(addr)), port_(port) { 30 | try_init(); 31 | } 32 | 33 | private: 34 | void try_init() { 35 | try { 36 | // create the socket so that multiple may be bound to the same address. 37 | asio::ip::udp::endpoint listen_endpoint(asio::ip::make_address("0.0.0.0"), port_); 38 | socket_.open(listen_endpoint.protocol()); 39 | socket_.set_option(asio::ip::udp::socket::reuse_address(true)); 40 | socket_.set_option(asio::ip::multicast::join_group(asio::ip::make_address(addr_))); 41 | socket_.bind(listen_endpoint); 42 | do_receive(); 43 | } catch (const std::exception& e) { 44 | ASIO_NET_LOGE("receive: init err: %s", e.what()); 45 | auto timer = std::make_shared(socket_.get_executor()); 46 | timer->expires_after(std::chrono::seconds(1)); 47 | auto timer_p = timer.get(); 48 | timer_p->async_wait([this, timer = std::move(timer)](const std::error_code&) mutable { 49 | try_init(); 50 | timer = nullptr; 51 | }); 52 | } 53 | } 54 | 55 | void do_receive() { 56 | socket_.async_receive_from(asio::buffer(data_), sender_endpoint_, [this](const std::error_code& ec, std::size_t length) { 57 | if (!ec) { 58 | std::vector msgs; 59 | { 60 | msgs.reserve(3); 61 | auto begin = data_.data(); 62 | auto end = data_.data() + length; 63 | decltype(begin) it; 64 | int count = 0; 65 | while ((it = std::find(begin, end, '\n')) != end) { 66 | msgs.emplace_back(begin, it); 67 | begin = it + 1; 68 | if (++count > 2) break; 69 | } 70 | msgs.emplace_back(begin, it); 71 | } 72 | if (msgs.size() == 3 && msgs[0] == "discovery") { 73 | service_found_handle_(std::move(msgs[1]), std::move(msgs[2])); 74 | } 75 | do_receive(); 76 | } else { 77 | ASIO_NET_LOGD("server_discovery: receive: %s", ec.message().c_str()); 78 | } 79 | }); 80 | } 81 | 82 | asio::ip::udp::socket socket_; 83 | asio::ip::udp::endpoint sender_endpoint_; 84 | std::array data_; 85 | service_found_handle_t service_found_handle_; 86 | std::string addr_; 87 | uint16_t port_; 88 | }; 89 | 90 | class sender { 91 | public: 92 | sender(asio::io_context& io_context, const std::string& service_name, const std::string& message, uint32_t send_period_sec = 1, 93 | const char* addr = addr_default, uint16_t port = port_default) 94 | : endpoint_(asio::ip::make_address(addr), port), 95 | socket_(io_context, endpoint_.protocol()), 96 | timer_(io_context), 97 | send_period_sec_(send_period_sec), 98 | message_("discovery\n" + service_name + '\n' + message) { 99 | do_send(); 100 | } 101 | 102 | private: 103 | void do_send() { 104 | socket_.async_send_to(asio::buffer(message_), endpoint_, [this](const std::error_code& ec, std::size_t /*length*/) { 105 | if (ec) { 106 | ASIO_NET_LOGE("server_discovery: sender: err: %s", ec.message().c_str()); 107 | } 108 | do_send_next(); 109 | }); 110 | } 111 | 112 | void do_send_next() { 113 | timer_.expires_after(std::chrono::seconds(send_period_sec_)); 114 | timer_.async_wait([this](const std::error_code& ec) { 115 | if (!ec) do_send(); 116 | }); 117 | } 118 | 119 | private: 120 | asio::ip::udp::endpoint endpoint_; 121 | asio::ip::udp::socket socket_; 122 | asio::steady_timer timer_; 123 | uint32_t send_period_sec_; 124 | std::string message_; 125 | }; 126 | 127 | } // namespace server_discovery 128 | } // namespace asio_net 129 | -------------------------------------------------------------------------------- /include/asio_net/tcp_client.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "asio.hpp" 4 | #include "detail/tcp_client_t.hpp" 5 | 6 | namespace asio_net { 7 | 8 | using tcp_client = detail::tcp_client_t; 9 | using tcp_client_ssl = detail::tcp_client_t; 10 | using domain_tcp_client = detail::tcp_client_t; 11 | 12 | } // namespace asio_net 13 | -------------------------------------------------------------------------------- /include/asio_net/tcp_server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "asio.hpp" 4 | #include "detail/tcp_server_t.hpp" 5 | 6 | namespace asio_net { 7 | 8 | using tcp_session = detail::tcp_session_t; 9 | using tcp_session_ssl = detail::tcp_session_t; 10 | using tcp_server = detail::tcp_server_t; 11 | using tcp_server_ssl = detail::tcp_server_t; 12 | 13 | using domain_tcp_session = detail::tcp_session_t; 14 | using domain_tcp_server = detail::tcp_server_t; 15 | 16 | } // namespace asio_net 17 | -------------------------------------------------------------------------------- /include/asio_net/udp_client.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "asio.hpp" 4 | #include "detail/udp_client_t.hpp" 5 | 6 | namespace asio_net { 7 | 8 | using udp_client = detail::udp_client_t; 9 | using domain_udp_client = detail::udp_client_t; 10 | 11 | } // namespace asio_net 12 | -------------------------------------------------------------------------------- /include/asio_net/udp_server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "asio.hpp" 4 | #include "detail/udp_server_t.hpp" 5 | 6 | namespace asio_net { 7 | 8 | using udp_server = detail::udp_server_t; 9 | using domain_udp_server = detail::udp_server_t; 10 | 11 | } // namespace asio_net 12 | -------------------------------------------------------------------------------- /include/asio_net/version.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define ASIO_NET_VER_MAJOR 2 4 | #define ASIO_NET_VER_MINOR 1 5 | #define ASIO_NET_VER_PATCH 0 6 | 7 | #define ASIO_NET_TO_VERSION(major, minor, patch) (major * 10000 + minor * 100 + patch) 8 | #define ASIO_NET_VERSION ASIO_NET_TO_VERSION(ASIO_NET_VER_MAJOR, ASIO_NET_VER_MINOR, ASIO_NET_VER_PATCH) 9 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asio_net", 3 | "description": "a tiny C++14 Async TCP/UDP/RPC/DDS library based on asio", 4 | "keywords": "tcp,udp,rpc,asio", 5 | "authors": { 6 | "name": "shuai132", 7 | "maintainer": true 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/shuai132/asio_net.git" 12 | }, 13 | "version": "2.1.0", 14 | "build": { 15 | "srcDir": "_", 16 | "flags": [ 17 | "-I include", 18 | "-I include/asio_net/rpc_core/include" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/assert_def.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | // clang-format off 7 | 8 | #define __ASSERT(e, file, line) \ 9 | ((void)printf("%s:%d: failed assertion `%s'\n", file, line, e), abort()) 10 | 11 | #define ASSERT(e) ((void)((e) ? ((void)0) : __ASSERT(#e, __FILE__, __LINE__))) 12 | -------------------------------------------------------------------------------- /test/compile_check/lib.cpp: -------------------------------------------------------------------------------- 1 | #include "asio_net.hpp" 2 | -------------------------------------------------------------------------------- /test/compile_check/main.cpp: -------------------------------------------------------------------------------- 1 | #include "asio_net.hpp" 2 | 3 | int main() { 4 | return 0; 5 | } 6 | -------------------------------------------------------------------------------- /test/dds.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "asio_net.hpp" 6 | #include "assert_def.h" 7 | #include "log.h" 8 | 9 | using namespace asio_net; 10 | 11 | #ifdef TEST_DDS_DOMAIN 12 | const char* ENDPOINT = "/tmp/foobar"; 13 | #elif defined(TEST_DDS_NORMAL) 14 | const uint16_t PORT = 6666; 15 | #elif defined(TEST_DDS_SSL) 16 | const uint16_t PORT = 6667; 17 | #endif 18 | 19 | static int interval_ms = 1000; 20 | static bool run_once = false; 21 | 22 | static std::atomic_bool received_flag[3]{}; 23 | static std::atomic_int received_all_cnt{0}; 24 | 25 | static void init_server() { 26 | std::thread([] { 27 | asio::io_context context; 28 | #ifdef TEST_DDS_DOMAIN 29 | domain_dds_server server(context, ENDPOINT); 30 | #elif defined(TEST_DDS_SSL) 31 | asio::ssl::context ssl_context(asio::ssl::context::sslv23); 32 | { 33 | ssl_context.set_options(asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::single_dh_use); 34 | ssl_context.set_password_callback([](std::size_t size, asio::ssl::context_base::password_purpose purpose) { 35 | (void)(size); 36 | (void)(purpose); 37 | return "test"; 38 | }); 39 | ssl_context.use_certificate_chain_file(OPENSSL_PEM_PATH "server.pem"); 40 | ssl_context.use_private_key_file(OPENSSL_PEM_PATH "server.pem", asio::ssl::context::pem); 41 | ssl_context.use_tmp_dh_file(OPENSSL_PEM_PATH "dh4096.pem"); 42 | } 43 | dds_server_ssl server(context, PORT, ssl_context); 44 | #elif defined(TEST_DDS_NORMAL) 45 | dds_server server(context, PORT); 46 | #endif 47 | server.start(true); 48 | }).detach(); 49 | } 50 | 51 | static void init_client() { 52 | for (int i = 0; i < 3; ++i) { 53 | std::thread([i] { 54 | asio::io_context context; 55 | #ifdef TEST_DDS_DOMAIN 56 | domain_dds_client client(context); 57 | client.open(ENDPOINT); 58 | #elif defined(TEST_DDS_SSL) 59 | asio::ssl::context ssl_context(asio::ssl::context::sslv23); 60 | ssl_context.load_verify_file(OPENSSL_PEM_PATH "ca.pem"); 61 | dds_client_ssl client(context, ssl_context); 62 | client.open("localhost", PORT); 63 | #elif defined(TEST_DDS_NORMAL) 64 | dds_client client(context); 65 | client.open("localhost", PORT); 66 | #endif 67 | // ensure open for unittest 68 | client.set_reconnect(0); 69 | client.wait_open(); 70 | 71 | client.subscribe("topic_all", [i](const std::string& data) { 72 | LOG("client_%d: topic:%s, data:%s", i, "topic_all", data.c_str()); 73 | ++received_all_cnt; 74 | }); 75 | std::string topic_tmp = "topic_" + std::to_string(i); 76 | client.subscribe(topic_tmp, [i, topic_tmp](const std::string& data) { 77 | LOG("client_%d: topic:%s, data:%s", i, topic_tmp.c_str(), data.c_str()); 78 | ASSERT(data == "to client_" + std::to_string(i)); 79 | ASSERT(!received_flag[i]); 80 | received_flag[i] = true; 81 | }); 82 | client.run(); 83 | }).detach(); 84 | } 85 | } 86 | 87 | #if 1 // IDE 88 | #ifdef TEST_DDS_DOMAIN 89 | static void interval_check(domain_dds_client& client) { 90 | #elif defined(TEST_DDS_SSL) 91 | static void interval_check(dds_client_ssl& client) { 92 | #elif defined(TEST_DDS_NORMAL) 93 | static void interval_check(dds_client& client) { 94 | #endif 95 | #else 96 | static auto interval_check = [](auto& client) { 97 | #endif 98 | /// 1. test basic 99 | static bool first_run = true; 100 | static std::atomic_bool received_flag_self{false}; 101 | static std::atomic_bool received_flag_vector{false}; 102 | static std::atomic_bool received_flag_void{false}; 103 | LOG("test... %d", first_run); 104 | if (first_run) { 105 | first_run = false; 106 | client.subscribe("topic_self", [](const std::string& msg) { 107 | LOG("received: topic_self: %s", msg.c_str()); 108 | ASSERT(!received_flag_self); 109 | received_flag_self = true; 110 | }); 111 | client.subscribe("topic_vector", [](const std::vector& msg) { 112 | LOG("received: topic_vector: %zu", msg.size()); 113 | ASSERT(!received_flag_vector); 114 | std::vector data{1, 2, 3}; 115 | ASSERT(msg == data); 116 | received_flag_vector = true; 117 | }); 118 | client.subscribe("topic_void", [] { 119 | LOG("received: topic_void"); 120 | ASSERT(!received_flag_void); 121 | received_flag_void = true; 122 | }); 123 | } else { 124 | // check and reset flag 125 | for (auto& flag : received_flag) { 126 | ASSERT(flag); 127 | flag = false; 128 | } 129 | 130 | ASSERT(received_all_cnt == 3); 131 | received_all_cnt = 0; 132 | 133 | ASSERT(received_flag_self); 134 | received_flag_self = false; 135 | 136 | ASSERT(received_flag_vector); 137 | received_flag_vector = false; 138 | 139 | ASSERT(received_flag_void); 140 | received_flag_void = false; 141 | 142 | if (run_once) { 143 | client.stop(); 144 | } 145 | } 146 | 147 | /// 2 common usage 148 | // 2.1 msg type: std::string 149 | client.publish("topic_self", "to client_self"); 150 | client.publish("topic_0", "to client_0"); 151 | client.publish("topic_1", "to client_1"); 152 | client.publish("topic_2", "to client_2"); 153 | client.publish("topic_all", "hello"); 154 | 155 | // 2.2 msg type: std::vector or any serializable type 156 | std::vector data{1, 2, 3}; 157 | client.publish("topic_vector", data); 158 | 159 | // 2.3 msg type: void 160 | client.publish("topic_void"); 161 | 162 | /// 3 test unsubscribe 163 | // 3.1 use topic 164 | client.subscribe("topic_test_0", [] { 165 | ASSERT(false); 166 | }); 167 | client.unsubscribe("topic_test_0"); 168 | client.publish("topic_test_0"); 169 | 170 | // 3.2 use id 171 | auto id = client.subscribe("topic_test_1", [] { 172 | ASSERT(false); 173 | }); 174 | client.unsubscribe(id); 175 | client.publish("topic_test_1"); 176 | }; 177 | 178 | int main(int argc, char* argv[]) { 179 | (void)(argv); 180 | run_once = argc >= 2; 181 | // run_once = true; 182 | if (run_once) { 183 | interval_ms = 100; 184 | } 185 | 186 | #ifdef TEST_DDS_DOMAIN 187 | ::unlink(ENDPOINT); 188 | #endif 189 | init_server(); 190 | // std::this_thread::sleep_for(std::chrono::milliseconds(10)); // wait server ready 191 | init_client(); 192 | 193 | asio::io_context context; 194 | #ifdef TEST_DDS_DOMAIN 195 | domain_dds_client client(context); 196 | client.open(ENDPOINT); 197 | #elif defined(TEST_DDS_SSL) 198 | asio::ssl::context ssl_context(asio::ssl::context::sslv23); 199 | ssl_context.load_verify_file(OPENSSL_PEM_PATH "ca.pem"); 200 | dds_client_ssl client(context, ssl_context); 201 | client.open("localhost", PORT); 202 | #elif defined(TEST_DDS_NORMAL) 203 | dds_client client(context); 204 | client.open("localhost", PORT); 205 | #endif 206 | // ensure open for unittest 207 | client.set_reconnect(0); 208 | client.wait_open(); 209 | 210 | std::function time_task; 211 | asio::steady_timer timer(context); 212 | time_task = [&] { 213 | timer.expires_after(std::chrono::milliseconds(interval_ms)); 214 | timer.async_wait([&](std::error_code ec) { 215 | (void)ec; 216 | interval_check(client); 217 | time_task(); 218 | }); 219 | }; 220 | time_task(); 221 | 222 | client.run(); 223 | return 0; 224 | } 225 | -------------------------------------------------------------------------------- /test/dds_c.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "asio_net.hpp" 5 | #include "log.h" 6 | 7 | using namespace asio_net; 8 | 9 | #define TEST_DDS_NORMAL 10 | 11 | #ifdef TEST_DDS_DOMAIN 12 | const char* ENDPOINT = "/tmp/foobar"; 13 | #elif defined(TEST_DDS_NORMAL) 14 | const uint16_t PORT = 6666; 15 | #elif defined(TEST_DDS_SSL) 16 | const uint16_t PORT = 6667; 17 | #endif 18 | 19 | static void init_client() { 20 | asio::io_context context; 21 | #ifdef TEST_DDS_DOMAIN 22 | domain_dds_client client(context); 23 | client.open(ENDPOINT); 24 | #elif defined(TEST_DDS_SSL) 25 | asio::ssl::context ssl_context(asio::ssl::context::sslv23); 26 | ssl_context.load_verify_file(OPENSSL_PEM_PATH "ca.pem"); 27 | dds_client_ssl client(context, ssl_context); 28 | client.open("localhost", PORT); 29 | #elif defined(TEST_DDS_NORMAL) 30 | dds_client client(context); 31 | client.open("localhost", PORT); 32 | #endif 33 | 34 | LOG("subscribe:"); 35 | client.subscribe("test_topic", [](const std::string& data) { 36 | LOG("client: topic:%s, data:%s", "test_topic", data.c_str()); 37 | }); 38 | 39 | client.publish("test_topic", "test_data"); 40 | 41 | LOG("client running..."); 42 | client.run(); 43 | } 44 | 45 | int main(int argc, char* argv[]) { 46 | (void)(argc); 47 | (void)(argv); 48 | 49 | init_client(); 50 | 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /test/dds_s.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "asio_net.hpp" 4 | #include "log.h" 5 | 6 | using namespace asio_net; 7 | 8 | #define TEST_DDS_NORMAL 9 | 10 | #ifdef TEST_DDS_DOMAIN 11 | const char* ENDPOINT = "/tmp/foobar"; 12 | #elif defined(TEST_DDS_NORMAL) 13 | const uint16_t PORT = 6666; 14 | #elif defined(TEST_DDS_SSL) 15 | const uint16_t PORT = 6667; 16 | #endif 17 | 18 | static void init_server() { 19 | asio::io_context context; 20 | 21 | #ifdef TEST_DDS_DOMAIN 22 | domain_dds_server server(context, ENDPOINT); 23 | #elif defined(TEST_DDS_SSL) 24 | asio::ssl::context ssl_context(asio::ssl::context::sslv23); 25 | { 26 | ssl_context.set_options(asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::single_dh_use); 27 | ssl_context.set_password_callback([](std::size_t size, asio::ssl::context_base::password_purpose purpose) { 28 | (void)(size); 29 | (void)(purpose); 30 | return "test"; 31 | }); 32 | ssl_context.use_certificate_chain_file(OPENSSL_PEM_PATH "server.pem"); 33 | ssl_context.use_private_key_file(OPENSSL_PEM_PATH "server.pem", asio::ssl::context::pem); 34 | ssl_context.use_tmp_dh_file(OPENSSL_PEM_PATH "dh4096.pem"); 35 | } 36 | dds_server_ssl server(context, PORT, ssl_context); 37 | #elif defined(TEST_DDS_NORMAL) 38 | dds_server server(context, PORT); 39 | #endif 40 | 41 | LOG("server running..."); 42 | server.start(true); 43 | } 44 | 45 | int main(int argc, char* argv[]) { 46 | (void)(argc); 47 | (void)(argv); 48 | 49 | #ifdef TEST_DDS_DOMAIN 50 | ::unlink(ENDPOINT); 51 | #endif 52 | init_server(); 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /test/domain_rpc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "asio_net/rpc_client.hpp" 7 | #include "asio_net/rpc_server.hpp" 8 | #include "assert_def.h" 9 | #include "log.h" 10 | 11 | using namespace asio_net; 12 | 13 | const char* ENDPOINT = "/tmp/foobar"; 14 | 15 | int main() { 16 | ::unlink(ENDPOINT); // remove previous binding 17 | 18 | // test flags 19 | static std::atomic_bool pass_flag_rpc_pass{false}; 20 | static std::atomic_bool pass_flag_session_close{false}; 21 | static std::atomic_bool pass_flag_client_close{false}; 22 | 23 | // server 24 | std::thread([] { 25 | asio::io_context context; 26 | domain_rpc_server server(context, ENDPOINT); 27 | server.on_session = [](const std::weak_ptr& rs) { 28 | LOG("on_session:"); 29 | auto session = rs.lock(); 30 | session->on_close = [] { 31 | LOG("session on_close:"); 32 | pass_flag_session_close = true; 33 | }; 34 | session->rpc->subscribe("cmd", [](const std::string& data) -> std::string { 35 | LOG("session on cmd: %s", data.c_str()); 36 | ASSERT(data == "hello"); 37 | return "world"; 38 | }); 39 | }; 40 | server.start(true); 41 | }).detach(); 42 | 43 | // wait domain create 44 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 45 | 46 | // client 47 | std::thread([] { 48 | asio::io_context context; 49 | domain_rpc_client client(context); 50 | client.on_open = [&](const std::shared_ptr& rpc) { 51 | LOG("client on_open:"); 52 | rpc->cmd("cmd") 53 | ->msg(std::string("hello")) 54 | ->rsp([&](const std::string& data) { 55 | LOG("cmd rsp: %s", data.c_str()); 56 | if (data == "world") { 57 | pass_flag_rpc_pass = true; 58 | } 59 | client.close(); 60 | }) 61 | ->call(); 62 | }; 63 | client.on_close = [&] { 64 | pass_flag_client_close = true; 65 | LOG("client on_close:"); 66 | client.stop(); 67 | }; 68 | client.open(ENDPOINT); 69 | client.run(); 70 | }).join(); 71 | 72 | ASSERT(pass_flag_rpc_pass); 73 | ASSERT(pass_flag_client_close); 74 | 75 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 76 | ASSERT(pass_flag_session_close); 77 | LOG("all rpc_session should destroyed before here!!!"); 78 | 79 | return EXIT_SUCCESS; 80 | } 81 | -------------------------------------------------------------------------------- /test/domain_tcp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "asio_net/tcp_client.hpp" 7 | #include "asio_net/tcp_server.hpp" 8 | #include "assert_def.h" 9 | #include "log.h" 10 | 11 | using namespace asio_net; 12 | 13 | const char* ENDPOINT = "/tmp/foobar"; 14 | 15 | int main(int argc, char** argv) { 16 | ::unlink(ENDPOINT); // remove previous binding 17 | 18 | static uint32_t test_count_max = 10000; 19 | static uint32_t test_count_expect = 0; 20 | if (argc >= 2) { 21 | test_count_max = std::strtol(argv[1], nullptr, 10); 22 | } 23 | 24 | static std::atomic_bool pass_flag_session_close{false}; 25 | static std::atomic_bool pass_flag_client_close{false}; 26 | std::thread([] { 27 | asio::io_context context; 28 | domain_tcp_server server(context, ENDPOINT, tcp_config{.auto_pack = true}); 29 | server.on_session = [](const std::weak_ptr& ws) { 30 | LOG("on_session:"); 31 | auto session = ws.lock(); 32 | session->on_close = [] { 33 | LOG("session on_close:"); 34 | pass_flag_session_close = true; 35 | }; 36 | session->on_data = [ws](std::string data) { 37 | (void)data; 38 | ASSERT(!ws.expired()); 39 | #ifndef ASIO_NET_DISABLE_ON_DATA_PRINT 40 | LOG("session on_data: %s", data.c_str()); 41 | #endif 42 | ws.lock()->send(std::move(data)); 43 | }; 44 | }; 45 | server.start(true); 46 | }).detach(); 47 | 48 | // wait domain create 49 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 50 | 51 | std::thread([] { 52 | asio::io_context context; 53 | domain_tcp_client client(context, tcp_config{.auto_pack = true}); 54 | client.on_open = [&] { 55 | LOG("client on_open:"); 56 | for (uint32_t i = 0; i < test_count_max; ++i) { 57 | client.send(std::to_string(i)); 58 | } 59 | }; 60 | client.on_data = [&](const std::string& data) { 61 | #ifndef ASIO_NET_DISABLE_ON_DATA_PRINT 62 | LOG("client on_data: %s", data.c_str()); 63 | #endif 64 | ASSERT(std::to_string(test_count_expect++) == data); 65 | if (test_count_expect == test_count_max - 1) { 66 | client.close(); 67 | } 68 | }; 69 | client.on_close = [&] { 70 | pass_flag_client_close = true; 71 | ASSERT(test_count_expect == test_count_max - 1); 72 | LOG("client on_close:"); 73 | client.stop(); 74 | }; 75 | client.open(ENDPOINT); 76 | client.run(); 77 | }).join(); 78 | 79 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 80 | ASSERT(pass_flag_session_close); 81 | ASSERT(pass_flag_client_close); 82 | return EXIT_SUCCESS; 83 | } 84 | -------------------------------------------------------------------------------- /test/domain_udp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "asio_net/udp_client.hpp" 7 | #include "asio_net/udp_server.hpp" 8 | #include "assert_def.h" 9 | #include "log.h" 10 | 11 | using namespace asio_net; 12 | 13 | const char* ENDPOINT = "/tmp/foobar"; 14 | 15 | int main(int argc, char** argv) { 16 | ::unlink(ENDPOINT); // remove previous binding 17 | 18 | static uint32_t test_count_max = 10000; 19 | static std::atomic_uint32_t test_count_received; 20 | if (argc >= 2) { 21 | test_count_max = std::strtol(argv[1], nullptr, 10); 22 | } 23 | 24 | std::thread([] { 25 | asio::io_context context; 26 | domain_udp_server server(context, ENDPOINT); 27 | server.on_data = [](uint8_t* data, size_t size, const domain_udp_server::endpoint& from) { 28 | (void)data; 29 | (void)size; 30 | (void)from; 31 | #ifndef ASIO_NET_DISABLE_ON_DATA_PRINT 32 | LOG("on_data: %s", std::string((char*)data, size).c_str()); 33 | #endif 34 | test_count_received++; 35 | }; 36 | server.start(); 37 | }).detach(); 38 | 39 | // wait domain create 40 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 41 | 42 | std::atomic_uint32_t send_failed_count{0}; 43 | std::thread([&] { 44 | asio::io_context context; 45 | domain_udp_client client(context); 46 | asio::post(context, [&] { 47 | domain_udp_client::endpoint endpoint(ENDPOINT); 48 | for (uint32_t i = 0; i < test_count_max; ++i) { 49 | auto data = std::to_string(i); 50 | client.send_to(data, endpoint, [&](const std::error_code& ec, std::size_t size) { 51 | (void)size; 52 | if (ec) { 53 | send_failed_count++; 54 | } 55 | }); 56 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 57 | } 58 | }); 59 | context.run(); 60 | }).join(); 61 | 62 | std::this_thread::sleep_for(std::chrono::seconds(1)); 63 | LOG("test_count_max: %d", test_count_max); 64 | LOG("test_count_received: %d", test_count_received.load()); 65 | LOG("send_failed_count: %d", send_failed_count.load()); 66 | LOG("lost: %f%%", 100 * (double)(test_count_max - test_count_received) / test_count_max); 67 | ASSERT(test_count_max - test_count_received == send_failed_count); 68 | return EXIT_SUCCESS; 69 | } 70 | -------------------------------------------------------------------------------- /test/log.h: -------------------------------------------------------------------------------- 1 | // 1. global control 2 | // L_O_G_NDEBUG disable debug log(auto by NDEBUG) 3 | // L_O_G_SHOW_DEBUG force enable debug log 4 | // L_O_G_DISABLE_ALL force disable all log 5 | // L_O_G_DISABLE_COLOR disable color 6 | // L_O_G_LINE_END_CRLF 7 | // L_O_G_SHOW_FULL_PATH 8 | // L_O_G_FOR_MCU 9 | // L_O_G_FREERTOS 10 | // L_O_G_NOT_EXIT_ON_FATAL 11 | // 12 | // C++11 enable default: 13 | // L_O_G_ENABLE_THREAD_SAFE thread safety 14 | // L_O_G_ENABLE_THREAD_ID show thread id 15 | // L_O_G_ENABLE_DATE_TIME show data time 16 | // can disable by define: 17 | // L_O_G_DISABLE_THREAD_SAFE 18 | // L_O_G_DISABLE_THREAD_ID 19 | // L_O_G_DISABLE_DATE_TIME 20 | // 21 | // 2. custom implements 22 | // L_O_G_PRINTF_CUSTOM int L_O_G_PRINTF_CUSTOM(const char *fmt, ...) 23 | // L_O_G_GET_TID_CUSTOM uint32_t L_O_G_GET_TID_CUSTOM() 24 | // 25 | // 3. use in library 26 | // 3.1. rename `LOG` to library name 27 | // 3.2. define `LOG_IN_LIB` 28 | // 3.3. configuration options 29 | // LOG_SHOW_DEBUG 30 | // LOG_SHOW_VERBOSE 31 | // LOG_DISABLE_ALL 32 | 33 | #pragma once 34 | 35 | // clang-format off 36 | 37 | //#define LOG_IN_LIB 38 | 39 | #if defined(LOG_DISABLE_ALL) || defined(L_O_G_DISABLE_ALL) 40 | 41 | #define LOG(fmt, ...) ((void)0) 42 | #define LOGT(tag, fmt, ...) ((void)0) 43 | #define LOGI(fmt, ...) ((void)0) 44 | #define LOGW(fmt, ...) ((void)0) 45 | #define LOGE(fmt, ...) ((void)0) 46 | #define LOGF(fmt, ...) ((void)0) 47 | #define LOGD(fmt, ...) ((void)0) 48 | #define LOGV(fmt, ...) ((void)0) 49 | 50 | #else 51 | 52 | #ifdef __cplusplus 53 | #include 54 | #include 55 | #if __cplusplus >= 201103L || defined(_MSC_VER) 56 | 57 | #if !defined(L_O_G_DISABLE_THREAD_SAFE) && !defined(L_O_G_ENABLE_THREAD_SAFE) 58 | #define L_O_G_ENABLE_THREAD_SAFE 59 | #endif 60 | 61 | #if !defined(L_O_G_DISABLE_THREAD_ID) && !defined(L_O_G_ENABLE_THREAD_ID) 62 | #define L_O_G_ENABLE_THREAD_ID 63 | #endif 64 | 65 | #if !defined(L_O_G_DISABLE_DATE_TIME) && !defined(L_O_G_ENABLE_DATE_TIME) 66 | #define L_O_G_ENABLE_DATE_TIME 67 | #endif 68 | 69 | #endif 70 | #else 71 | #include 72 | #include 73 | #endif 74 | 75 | #ifdef L_O_G_LINE_END_CRLF 76 | #define LOG_LINE_END "\r\n" 77 | #else 78 | #define LOG_LINE_END "\n" 79 | #endif 80 | 81 | #ifdef L_O_G_NOT_EXIT_ON_FATAL 82 | #define LOG_EXIT_PROGRAM() 83 | #else 84 | #ifdef L_O_G_FOR_MCU 85 | #define LOG_EXIT_PROGRAM() do{ for(;;); } while(0) 86 | #else 87 | #define LOG_EXIT_PROGRAM() exit(EXIT_FAILURE) 88 | #endif 89 | #endif 90 | 91 | #ifdef L_O_G_SHOW_FULL_PATH 92 | #define LOG_BASE_FILENAME (__FILE__) 93 | #else 94 | #ifdef __FILE_NAME__ 95 | #define LOG_BASE_FILENAME (__FILE_NAME__) 96 | #else 97 | #define LOG_BASE_FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) 98 | #endif 99 | #endif 100 | 101 | #define LOG_WITH_COLOR 102 | 103 | #if defined(_WIN32) || (defined(__ANDROID__) && !defined(ANDROID_STANDALONE)) || defined(L_O_G_FOR_MCU) || defined(ESP_PLATFORM) 104 | #undef LOG_WITH_COLOR 105 | #endif 106 | 107 | #ifdef L_O_G_DISABLE_COLOR 108 | #undef LOG_WITH_COLOR 109 | #endif 110 | 111 | #ifdef LOG_WITH_COLOR 112 | #define LOG_COLOR_RED "\033[31m" 113 | #define LOG_COLOR_GREEN "\033[32m" 114 | #define LOG_COLOR_YELLOW "\033[33m" 115 | #define LOG_COLOR_BLUE "\033[34m" 116 | #define LOG_COLOR_CARMINE "\033[35m" 117 | #define LOG_COLOR_CYAN "\033[36m" 118 | #define LOG_COLOR_DEFAULT 119 | #define LOG_COLOR_END "\033[m" 120 | #else 121 | #define LOG_COLOR_RED 122 | #define LOG_COLOR_GREEN 123 | #define LOG_COLOR_YELLOW 124 | #define LOG_COLOR_BLUE 125 | #define LOG_COLOR_CARMINE 126 | #define LOG_COLOR_CYAN 127 | #define LOG_COLOR_DEFAULT 128 | #define LOG_COLOR_END 129 | #endif 130 | 131 | #define LOG_END LOG_COLOR_END LOG_LINE_END 132 | 133 | #ifndef L_O_G_PRINTF 134 | #ifndef LOG_PRINTF_DEFAULT 135 | #if defined(__ANDROID__) && !defined(ANDROID_STANDALONE) 136 | #include 137 | #define LOG_PRINTF_DEFAULT(fmt, ...) __android_log_print(ANDROID_L##OG_DEBUG, "LOG", fmt, ##__VA_ARGS__) 138 | #else 139 | #define LOG_PRINTF_DEFAULT(fmt, ...) printf(fmt, ##__VA_ARGS__) 140 | #endif 141 | #endif 142 | 143 | #ifndef L_O_G_PRINTF_CUSTOM 144 | #ifdef __cplusplus 145 | #include 146 | #else 147 | #include 148 | #endif 149 | #ifdef L_O_G_ENABLE_THREAD_SAFE 150 | #ifndef L_O_G_NS_MUTEX 151 | #define L_O_G_NS_MUTEX L_O_G_NS_MUTEX 152 | #include 153 | // 1. struct instead of namespace, ensure single instance 154 | struct L_O_G_NS_MUTEX { 155 | static std::mutex& mutex() { 156 | // 2. never delete, avoid destroy before user log 157 | // 3. static memory, avoid memory fragmentation 158 | static char memory[sizeof(std::mutex)]; 159 | static std::mutex& mutex = *(new (memory) std::mutex()); 160 | return mutex; 161 | } 162 | }; 163 | #endif 164 | #define L_O_G_PRINTF(fmt, ...) { \ 165 | std::lock_guard L_O_G_NS_lock(L_O_G_NS_MUTEX::mutex()); \ 166 | LOG_PRINTF_DEFAULT(fmt, ##__VA_ARGS__); \ 167 | } 168 | #else 169 | #define L_O_G_PRINTF(fmt, ...) LOG_PRINTF_DEFAULT(fmt, ##__VA_ARGS__) 170 | #endif 171 | #else 172 | extern int L_O_G_PRINTF_CUSTOM(const char *fmt, ...); 173 | #define L_O_G_PRINTF(fmt, ...) L_O_G_PRINTF_CUSTOM(fmt, ##__VA_ARGS__) 174 | #endif 175 | #endif 176 | 177 | #ifdef L_O_G_ENABLE_THREAD_ID 178 | #ifndef L_O_G_NS_GET_TID 179 | #define L_O_G_NS_GET_TID L_O_G_NS_GET_TID 180 | #include 181 | #ifdef L_O_G_GET_TID_CUSTOM 182 | extern uint32_t L_O_G_GET_TID_CUSTOM(); 183 | #elif defined(_WIN32) 184 | #include 185 | #include 186 | struct L_O_G_NS_GET_TID { 187 | static inline uint32_t get_tid() { 188 | return GetCurrentThreadId(); 189 | } 190 | }; 191 | #elif defined(__linux__) 192 | #include 193 | #include 194 | struct L_O_G_NS_GET_TID { 195 | static inline uint32_t get_tid() { 196 | return syscall(SYS_gettid); 197 | } 198 | }; 199 | #elif defined(L_O_G_FREERTOS) || defined(FREERTOS_CONFIG_H) 200 | #include 201 | struct L_O_G_NS_GET_TID { 202 | static inline uint32_t get_tid() { 203 | return (uint32_t)xTaskGetCurrentTaskHandle(); 204 | } 205 | }; 206 | #else /* for mac, bsd.. */ 207 | #include 208 | struct L_O_G_NS_GET_TID { 209 | static inline uint32_t get_tid() { 210 | uint64_t x; 211 | pthread_threadid_np(nullptr, &x); 212 | return (uint32_t)x; 213 | } 214 | }; 215 | #endif 216 | #endif 217 | #ifdef L_O_G_GET_TID_CUSTOM 218 | #define LOG_THREAD_LABEL "%u " 219 | #define LOG_THREAD_VALUE ,L_O_G_GET_TID_CUSTOM() 220 | #else 221 | #define LOG_THREAD_LABEL "%u " 222 | #define LOG_THREAD_VALUE ,L_O_G_NS_GET_TID::get_tid() 223 | #endif 224 | #else 225 | #define LOG_THREAD_LABEL 226 | #define LOG_THREAD_VALUE 227 | #endif 228 | 229 | #ifdef L_O_G_ENABLE_DATE_TIME 230 | #include 231 | #include 232 | #include // std::put_time 233 | #ifndef L_O_G_NS_GET_TIME 234 | #define L_O_G_NS_GET_TIME L_O_G_NS_GET_TIME 235 | struct L_O_G_NS_GET_TIME { 236 | static inline std::string get_time() { 237 | auto now = std::chrono::system_clock::now(); 238 | std::time_t time = std::chrono::system_clock::to_time_t(now); 239 | auto ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; 240 | std::stringstream ss; 241 | std::tm dst; // NOLINT 242 | #ifdef _MSC_VER 243 | ::localtime_s(&dst, &time); 244 | #else 245 | dst = *std::localtime(&time); 246 | #endif 247 | ss << std::put_time(&dst, "%Y-%m-%d %H:%M:%S") << '.' << std::setw(3) << std::setfill('0') << ms.count(); 248 | return ss.str(); 249 | } 250 | }; 251 | #endif 252 | #define LOG_TIME_LABEL "%s " 253 | #define LOG_TIME_VALUE ,L_O_G_NS_GET_TIME::get_time().c_str() 254 | #else 255 | #define LOG_TIME_LABEL 256 | #define LOG_TIME_VALUE 257 | #endif 258 | 259 | #define LOG(fmt, ...) do{ L_O_G_PRINTF(LOG_COLOR_GREEN LOG_TIME_LABEL LOG_THREAD_LABEL "[*]: %s:%d " fmt LOG_END LOG_TIME_VALUE LOG_THREAD_VALUE, LOG_BASE_FILENAME, __LINE__, ##__VA_ARGS__); } while(0) 260 | #define LOGT(tag, fmt, ...) do{ L_O_G_PRINTF(LOG_COLOR_BLUE LOG_TIME_LABEL LOG_THREAD_LABEL "[" tag "]: %s:%d " fmt LOG_END LOG_TIME_VALUE LOG_THREAD_VALUE, LOG_BASE_FILENAME, __LINE__, ##__VA_ARGS__); } while(0) 261 | #define LOGI(fmt, ...) do{ L_O_G_PRINTF(LOG_COLOR_YELLOW LOG_TIME_LABEL LOG_THREAD_LABEL "[I]: %s:%d " fmt LOG_END LOG_TIME_VALUE LOG_THREAD_VALUE, LOG_BASE_FILENAME, __LINE__, ##__VA_ARGS__); } while(0) 262 | #define LOGW(fmt, ...) do{ L_O_G_PRINTF(LOG_COLOR_CARMINE LOG_TIME_LABEL LOG_THREAD_LABEL "[W]: %s:%d [%s] " fmt LOG_END LOG_TIME_VALUE LOG_THREAD_VALUE, LOG_BASE_FILENAME, __LINE__, __func__, ##__VA_ARGS__); } while(0) // NOLINT(bugprone-lambda-function-name) 263 | #define LOGE(fmt, ...) do{ L_O_G_PRINTF(LOG_COLOR_RED LOG_TIME_LABEL LOG_THREAD_LABEL "[E]: %s:%d [%s] " fmt LOG_END LOG_TIME_VALUE LOG_THREAD_VALUE, LOG_BASE_FILENAME, __LINE__, __func__, ##__VA_ARGS__); } while(0) // NOLINT(bugprone-lambda-function-name) 264 | #define LOGF(fmt, ...) do{ L_O_G_PRINTF(LOG_COLOR_CYAN LOG_TIME_LABEL LOG_THREAD_LABEL "[!]: %s:%d [%s] " fmt LOG_END LOG_TIME_VALUE LOG_THREAD_VALUE, LOG_BASE_FILENAME, __LINE__, __func__, ##__VA_ARGS__); LOG_EXIT_PROGRAM(); } while(0) // NOLINT(bugprone-lambda-function-name) 265 | 266 | #if defined(LOG_IN_LIB) && !defined(LOG_SHOW_DEBUG) && !defined(L_O_G_NDEBUG) 267 | #define LOG_NDEBUG 268 | #endif 269 | 270 | #if defined(L_O_G_NDEBUG) && !defined(LOG_NDEBUG) 271 | #define LOG_NDEBUG 272 | #endif 273 | 274 | #if (defined(NDEBUG) || defined(LOG_NDEBUG)) && !defined(L_O_G_SHOW_DEBUG) 275 | #define LOGD(fmt, ...) ((void)0) 276 | #else 277 | #define LOGD(fmt, ...) do{ L_O_G_PRINTF(LOG_COLOR_DEFAULT LOG_TIME_LABEL LOG_THREAD_LABEL "[D]: %s:%d " fmt LOG_END LOG_TIME_VALUE LOG_THREAD_VALUE, LOG_BASE_FILENAME, __LINE__, ##__VA_ARGS__); } while(0) 278 | #endif 279 | 280 | #if defined(LOG_SHOW_VERBOSE) 281 | #define LOGV(fmt, ...) do{ L_O_G_PRINTF(LOG_COLOR_DEFAULT LOG_TIME_LABEL LOG_THREAD_LABEL "[V]: %s:%d " fmt LOG_END LOG_TIME_VALUE LOG_THREAD_VALUE, LOG_BASE_FILENAME, __LINE__, ##__VA_ARGS__); } while(0) 282 | #else 283 | #define LOGV(fmt, ...) ((void)0) 284 | #endif 285 | 286 | #endif 287 | -------------------------------------------------------------------------------- /test/rpc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "asio_net/rpc_client.hpp" 7 | #include "asio_net/rpc_server.hpp" 8 | #include "assert_def.h" 9 | #include "log.h" 10 | 11 | using namespace asio_net; 12 | 13 | const uint16_t PORT = 6666; 14 | 15 | int main() { 16 | // test flags 17 | static std::atomic_bool pass_flag_rpc_pass{false}; 18 | static std::atomic_bool pass_flag_session_close{false}; 19 | static std::atomic_bool pass_flag_client_close{false}; 20 | 21 | // server 22 | std::thread([] { 23 | asio::io_context context; 24 | rpc_server server(context, PORT); 25 | server.on_session = [](const std::weak_ptr& rs) { 26 | LOG("on_session:"); 27 | auto session = rs.lock(); 28 | session->on_close = [] { 29 | LOG("session on_close:"); 30 | pass_flag_session_close = true; 31 | }; 32 | session->rpc->subscribe("cmd", [](const std::string& data) -> std::string { 33 | LOG("session on cmd: %s", data.c_str()); 34 | ASSERT(data == "hello"); 35 | return "world"; 36 | }); 37 | }; 38 | server.start(true); 39 | }).detach(); 40 | 41 | // client 42 | std::thread([] { 43 | asio::io_context context; 44 | rpc_client client(context); 45 | client.on_open = [&](const std::shared_ptr& rpc) { 46 | LOG("client on_open:"); 47 | rpc->cmd("cmd") 48 | ->msg(std::string("hello")) 49 | ->rsp([&](const std::string& data) { 50 | LOG("cmd rsp: %s", data.c_str()); 51 | if (data == "world") { 52 | pass_flag_rpc_pass = true; 53 | } 54 | client.close(); 55 | }) 56 | ->call(); 57 | }; 58 | client.on_close = [&] { 59 | pass_flag_client_close = true; 60 | LOG("client on_close:"); 61 | client.stop(); 62 | }; 63 | client.open("localhost", PORT); 64 | client.run(); 65 | }).join(); 66 | 67 | ASSERT(pass_flag_rpc_pass); 68 | ASSERT(pass_flag_client_close); 69 | 70 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 71 | ASSERT(pass_flag_session_close); 72 | LOG("all rpc_session should destroyed before here!!!"); 73 | 74 | return EXIT_SUCCESS; 75 | } 76 | -------------------------------------------------------------------------------- /test/rpc_c.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "asio_net/rpc_client.hpp" 4 | #include "log.h" 5 | 6 | using namespace asio_net; 7 | 8 | const uint16_t PORT = 6666; 9 | 10 | int main() { 11 | asio::io_context context; 12 | rpc_client client(context); 13 | client.on_open = [&](const std::shared_ptr& rpc) { 14 | LOG("client on_open:"); 15 | rpc->cmd("cmd") 16 | ->msg(std::string("hello")) 17 | ->rsp([](const std::string& data) { 18 | LOG("cmd rsp: %s", data.c_str()); 19 | }) 20 | ->call(); 21 | }; 22 | client.on_open_failed = [](std::error_code ec) { 23 | LOG("client on_open_failed: %d, %s", ec.value(), ec.message().c_str()); 24 | }; 25 | client.on_close = [] { 26 | LOG("client on_close:"); 27 | }; 28 | client.open("localhost", PORT); 29 | client.run(); 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /test/rpc_c_check_destroy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "asio_net/rpc_client.hpp" 4 | #include "log.h" 5 | 6 | using namespace asio_net; 7 | 8 | const uint16_t PORT = 6666; 9 | 10 | int main() { 11 | asio::io_context context; 12 | std::shared_ptr client; 13 | 14 | std::thread([&] { 15 | asio::post(context, [&] { 16 | client = std::make_shared(context); 17 | client->on_open = [&](const std::shared_ptr& rpc) { 18 | LOG("client on_open:"); 19 | rpc->cmd("cmd") 20 | ->msg(std::string("hello")) 21 | ->rsp([](const std::string& data) { 22 | LOG("cmd rsp: %s", data.c_str()); 23 | }) 24 | ->call(); 25 | }; 26 | client->on_open_failed = [](std::error_code ec) { 27 | LOG("client on_open_failed: %d, %s", ec.value(), ec.message().c_str()); 28 | }; 29 | client->on_close = [] { 30 | LOG("client on_close:"); 31 | }; 32 | client->open("localhost", PORT); 33 | }); 34 | 35 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 36 | asio::post(context, [&] { 37 | client = nullptr; 38 | }); 39 | 40 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 41 | context.stop(); 42 | }).detach(); 43 | 44 | auto work = asio::make_work_guard(context); 45 | context.run(); 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /test/rpc_c_coroutine.cpp: -------------------------------------------------------------------------------- 1 | #include "rpc_c_coroutine.hpp" 2 | 3 | #include 4 | 5 | #include "asio_net/rpc_client.hpp" 6 | #include "assert_def.h" 7 | #include "log.h" 8 | 9 | using namespace asio_net; 10 | 11 | const uint16_t PORT = 6666; 12 | 13 | int main() { 14 | asio::io_context context; 15 | rpc_client client(context); 16 | client.on_open = [&](const std::shared_ptr& rpc) { 17 | LOG("client on_open:"); 18 | asio::co_spawn( 19 | context, 20 | [rpc]() -> asio::awaitable { 21 | // std::string 22 | auto rsp1 = co_await rpc->cmd("cmd")->msg(std::string("hello"))->co_call(); 23 | LOG("string: type: %s: data: %s", rpc_core::finally_t_str(rsp1.type), (*rsp1).c_str()); 24 | // or 25 | auto rsp11 = co_await rpc->co_call("cmd", std::string("hello")); 26 | LOG("string: type: %s: data: %s", rpc_core::finally_t_str(rsp11.type), (*rsp11).c_str()); 27 | 28 | // void 29 | auto rsp2 = co_await rpc->cmd("cmd")->msg(std::string("hello"))->co_call(); 30 | LOG("void: type: %s", rpc_core::finally_t_str(rsp2.type)); 31 | // or 32 | auto rsp22 = co_await rpc->co_call("cmd", std::string("hello")); 33 | LOG("void: type: %s", rpc_core::finally_t_str(rsp22.type)); 34 | 35 | /// custom async call 36 | // string 37 | auto rsp3 = co_await rpc->cmd("cmd")->msg(std::string("hello"))->co_custom(); 38 | LOG("custom: string: type: %s: data: %s", rpc_core::finally_t_str(rsp3.type), rsp3.data.c_str()); 39 | // or 40 | auto rsp33 = co_await rpc->co_custom("cmd", std::string("hello")); 41 | LOG("custom: string: type: %s: data: %s", rpc_core::finally_t_str(rsp33.type), rsp33.data.c_str()); 42 | 43 | // void 44 | auto rsp4 = co_await rpc->cmd("cmd")->msg(std::string("hello"))->co_custom(); 45 | LOG("custom: void: type: %s", rpc_core::finally_t_str(rsp4.type)); 46 | // or 47 | auto rsp44 = co_await rpc->co_custom("cmd", std::string("hello")); 48 | LOG("custom: void: type: %s", rpc_core::finally_t_str(rsp44.type)); 49 | 50 | // cancel 51 | auto rsp5 = co_await rpc->cmd("cmd")->msg(std::string("hello"))->cancel()->co_custom(); 52 | LOG("custom: cancel: type: %s", rpc_core::finally_t_str(rsp5.type)); 53 | }, 54 | asio::detached); 55 | }; 56 | client.on_open_failed = [](std::error_code ec) { 57 | LOG("client on_open_failed: %d, %s", ec.value(), ec.message().c_str()); 58 | }; 59 | client.on_close = [] { 60 | LOG("client on_close:"); 61 | }; 62 | client.open("localhost", PORT); 63 | client.run(); 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /test/rpc_c_coroutine.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RPC_CORE_FEATURE_CO_CUSTOM co_custom 4 | #define RPC_CORE_FEATURE_CO_CUSTOM_R asio::awaitable> 5 | #include "asio.hpp" 6 | #include "rpc_core.hpp" 7 | 8 | namespace rpc_core { 9 | 10 | template ::value, int>::type> 11 | asio::awaitable> request::co_custom() { 12 | auto executor = co_await asio::this_coro::executor; 13 | co_return co_await asio::async_compose)>( 14 | [this, &executor](auto& self) mutable { 15 | using ST = std::remove_reference::type; 16 | auto self_sp = std::make_shared(std::forward(self)); 17 | rsp([&executor, self = std::move(self_sp)](R data, finally_t type) mutable { 18 | asio::dispatch(executor, [self = std::move(self), data = std::move(data), type]() { 19 | self->complete({type, data}); 20 | }); 21 | }); 22 | call(); 23 | }, 24 | asio::use_awaitable); 25 | } 26 | 27 | template ::value, int>::type> 28 | asio::awaitable> request::co_custom() { 29 | auto executor = co_await asio::this_coro::executor; 30 | co_return co_await asio::async_compose)>( 31 | [this, &executor](auto& self) mutable { 32 | using ST = std::remove_reference::type; 33 | auto self_sp = std::make_shared(std::forward(self)); 34 | mark_need_rsp(); 35 | finally([&executor, self = std::move(self_sp)](finally_t type) mutable { 36 | asio::dispatch(executor, [self = std::move(self), type] { 37 | self->complete({type}); 38 | }); 39 | }); 40 | call(); 41 | }, 42 | asio::use_awaitable); 43 | } 44 | 45 | template 46 | inline asio::awaitable> rpc::co_custom(cmd_type cmd) { 47 | co_return co_await this->cmd(std::move(cmd))->co_call(); 48 | } 49 | 50 | template 51 | inline asio::awaitable> rpc::co_custom(cmd_type cmd, Msg&& message) { 52 | co_return co_await this->cmd(std::move(cmd))->msg(std::forward(message))->template co_call(); 53 | } 54 | 55 | } // namespace rpc_core 56 | -------------------------------------------------------------------------------- /test/rpc_c_open_close.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "asio_net/rpc_client.hpp" 4 | #include "log.h" 5 | 6 | using namespace asio_net; 7 | 8 | const uint16_t PORT = 6666; 9 | 10 | int main() { 11 | asio::io_context context; 12 | rpc_client client(context); 13 | client.on_open = [&](const std::shared_ptr& rpc) { 14 | LOG("client on_open:"); 15 | rpc->cmd("cmd") 16 | ->msg(std::string("hello")) 17 | ->rsp([](const std::string& data) { 18 | LOG("cmd rsp: %s", data.c_str()); 19 | }) 20 | ->call(); 21 | }; 22 | client.on_close = [] { 23 | LOG("client on_close:"); 24 | }; 25 | 26 | auto test = [&] { 27 | LOG("open..."); 28 | client.open("localhost", PORT); 29 | client.close(); 30 | }; 31 | std::function time_task; 32 | asio::steady_timer timer(context); 33 | time_task = [&] { 34 | timer.expires_after(std::chrono::milliseconds(100)); 35 | timer.async_wait([&](std::error_code ec) { 36 | (void)ec; 37 | test(); 38 | time_task(); 39 | }); 40 | }; 41 | time_task(); 42 | 43 | client.run(); 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /test/rpc_c_ping.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "asio_net/rpc_client.hpp" 4 | #include "log.h" 5 | 6 | using namespace asio_net; 7 | 8 | const uint16_t PORT = 6666; 9 | 10 | int main() { 11 | asio::io_context context; 12 | rpc_client client(context); 13 | client.config().ping_interval_ms = 1000; 14 | client.set_reconnect(1000); 15 | client.on_open = [](const std::shared_ptr& rpc) { 16 | LOG("client on_open:"); 17 | rpc->cmd("cmd") 18 | ->msg(std::string("hello")) 19 | ->rsp([](const std::string& data) { 20 | LOG("cmd rsp: %s", data.c_str()); 21 | }) 22 | ->call(); 23 | }; 24 | client.on_open_failed = [](std::error_code ec) { 25 | LOG("client on_open_failed: %d, %s", ec.value(), ec.message().c_str()); 26 | }; 27 | client.on_close = [] { 28 | LOG("client on_close:"); 29 | }; 30 | client.open("localhost", PORT); 31 | client.run(); 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /test/rpc_config.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "asio_net/rpc_client.hpp" 7 | #include "asio_net/rpc_server.hpp" 8 | #include "assert_def.h" 9 | #include "log.h" 10 | 11 | using namespace asio_net; 12 | 13 | const uint16_t PORT = 6666; 14 | 15 | int main() { 16 | // test flags 17 | static std::atomic_bool pass_flag_rpc_pass{false}; 18 | static std::atomic_bool pass_flag_session_close{false}; 19 | static std::atomic_bool pass_flag_client_close{false}; 20 | 21 | // server 22 | std::thread([] { 23 | auto rpc = rpc_core::rpc::create(); 24 | rpc->subscribe("cmd", [](const std::string& data) -> std::string { 25 | LOG("session on cmd: %s", data.c_str()); 26 | ASSERT(data == "hello"); 27 | return "world"; 28 | }); 29 | 30 | asio::io_context context; 31 | rpc_server server(context, PORT, rpc_config{.rpc = rpc}); 32 | server.on_session = [&](const std::weak_ptr& rs) { 33 | LOG("on_session:"); 34 | auto session = rs.lock(); 35 | ASSERT(session->rpc == rpc); 36 | session->on_close = [] { 37 | LOG("session on_close:"); 38 | pass_flag_session_close = true; 39 | }; 40 | }; 41 | server.start(true); 42 | }).detach(); 43 | 44 | // client 45 | std::thread([] { 46 | auto rpc = rpc_core::rpc::create(); 47 | rpc->cmd("cmd")->msg(std::string("hello"))->call(); // no effect 48 | 49 | asio::io_context context; 50 | rpc_client client(context, rpc_config{.rpc = rpc}); 51 | client.on_open = [&](const std::shared_ptr& rpc_) { 52 | LOG("client on_open:"); 53 | ASSERT(rpc_ == rpc); 54 | rpc->cmd("cmd") 55 | ->msg(std::string("hello")) 56 | ->rsp([&](const std::string& data) { 57 | LOG("cmd rsp: %s", data.c_str()); 58 | if (data == "world") { 59 | pass_flag_rpc_pass = true; 60 | } 61 | client.close(); 62 | }) 63 | ->call(); 64 | }; 65 | client.on_close = [&] { 66 | pass_flag_client_close = true; 67 | LOG("client on_close:"); 68 | client.stop(); 69 | }; 70 | client.open("localhost", PORT); 71 | client.run(); 72 | 73 | LOG("client exited"); 74 | rpc->cmd("cmd")->msg(std::string("hello"))->call(); // no effect 75 | }).join(); 76 | 77 | ASSERT(pass_flag_rpc_pass); 78 | ASSERT(pass_flag_client_close); 79 | 80 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 81 | ASSERT(pass_flag_session_close); 82 | LOG("all rpc_session should destroyed before here!!!"); 83 | 84 | return EXIT_SUCCESS; 85 | } 86 | -------------------------------------------------------------------------------- /test/rpc_reconnect.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "asio_net/rpc_client.hpp" 6 | #include "asio_net/rpc_server.hpp" 7 | #include "log.h" 8 | 9 | using namespace asio_net; 10 | 11 | const uint16_t PORT = 6666; 12 | 13 | int main() { 14 | // server 15 | std::thread([] { 16 | asio::io_context context; 17 | rpc_server server(context, PORT); 18 | server.on_session = [](const std::weak_ptr& rs) { 19 | LOG("on_session:"); 20 | auto session = rs.lock(); 21 | session->close(); 22 | }; 23 | server.start(true); 24 | }).detach(); 25 | 26 | // client 27 | std::thread([] { 28 | asio::io_context context; 29 | rpc_client client(context); 30 | client.on_open = [](const std::shared_ptr& rpc) { 31 | (void)rpc; 32 | LOG("client on_open:"); 33 | }; 34 | client.on_close = [&] { 35 | LOG("client on_close:"); 36 | static int count = 0; 37 | if (++count == 3) { 38 | client.stop(); 39 | } 40 | }; 41 | client.set_reconnect(1); 42 | client.open("localhost", PORT); 43 | client.run(); 44 | }).join(); 45 | return EXIT_SUCCESS; 46 | } 47 | -------------------------------------------------------------------------------- /test/rpc_s.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "asio_net/rpc_server.hpp" 4 | #include "log.h" 5 | 6 | using namespace asio_net; 7 | 8 | const uint16_t PORT = 6666; 9 | 10 | int main() { 11 | asio::io_context context; 12 | rpc_server server(context, PORT); 13 | server.on_session = [](const std::weak_ptr& rs) { 14 | auto session = rs.lock(); 15 | LOG("on_session: %p", session.get()); 16 | session->on_close = [rs] { 17 | LOG("session on_close: %p", rs.lock().get()); 18 | }; 19 | session->rpc->subscribe("cmd", [](const std::string& data) -> std::string { 20 | LOG("session on cmd: %s", data.c_str()); 21 | return "world"; 22 | }); 23 | }; 24 | server.start(true); 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /test/rpc_s_coroutine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "asio_net/rpc_server.hpp" 4 | #include "log.h" 5 | 6 | using namespace asio_net; 7 | using namespace rpc_core; 8 | 9 | const uint16_t PORT = 6666; 10 | 11 | int main() { 12 | asio::io_context context; 13 | rpc_server server(context, PORT); 14 | server.on_session = [&context](const std::weak_ptr& rs) { 15 | auto session = rs.lock(); 16 | LOG("on_session: %p", session.get()); 17 | session->on_close = [rs] { 18 | LOG("session on_close: %p", rs.lock().get()); 19 | }; 20 | auto& rpc = session->rpc; 21 | 22 | // clang-format off 23 | /// scheduler for dispatch rsp to asio context 24 | auto scheduler_asio_dispatch = [&](auto handle) { 25 | asio::dispatch(context, std::move(handle)); 26 | }; 27 | /// scheduler for use asio coroutine 28 | auto scheduler_asio_coroutine = [&](auto handle) { 29 | asio::co_spawn(context, [handle = std::move(handle)]() -> asio::awaitable { 30 | co_await handle(); 31 | }, asio::detached); 32 | }; 33 | 34 | /// 1. common usage 35 | rpc->subscribe("cmd", [&](request_response rr) { 36 | // call rsp when data ready 37 | rr->rsp("world"); 38 | // or run on context thread 39 | asio::dispatch(context, [rr = std::move(rr)]{ rr->rsp("world"); }); 40 | // or run on context thread, use asio coroutine 41 | asio::co_spawn(context, [&, rr = std::move(rr)]() -> asio::awaitable { 42 | asio::steady_timer timer(context); 43 | timer.expires_after(std::chrono::seconds(1)); 44 | co_await timer.async_wait(); 45 | rr->rsp("world"); 46 | }, asio::detached); 47 | }); 48 | 49 | /// 2. custom scheduler, automatic dispatch 50 | rpc->subscribe("cmd", [](const request_response& rr) { 51 | rr->rsp("world"); 52 | }, scheduler_asio_dispatch); 53 | 54 | /// 3. custom scheduler, simple way to use asio coroutine 55 | rpc->subscribe("cmd", [&](request_response rr) -> asio::awaitable { 56 | LOG("session on cmd: %s", rr->req.c_str()); 57 | asio::steady_timer timer(context); 58 | timer.expires_after(std::chrono::seconds(1)); 59 | co_await timer.async_wait(); 60 | rr->rsp("world"); 61 | }, scheduler_asio_coroutine); 62 | // clang-format on 63 | }; 64 | server.start(true); 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /test/rpc_ssl.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "asio_net/rpc_client.hpp" 7 | #include "asio_net/rpc_server.hpp" 8 | #include "assert_def.h" 9 | #include "log.h" 10 | 11 | using namespace asio_net; 12 | 13 | const uint16_t PORT = 6666; 14 | 15 | int main() { 16 | // test flags 17 | static std::atomic_bool pass_flag_rpc_pass{false}; 18 | static std::atomic_bool pass_flag_session_close{false}; 19 | static std::atomic_bool pass_flag_client_close{false}; 20 | 21 | // server 22 | std::thread([] { 23 | asio::io_context context; 24 | asio::ssl::context ssl_context(asio::ssl::context::sslv23); 25 | { 26 | ssl_context.set_options(asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::single_dh_use); 27 | ssl_context.set_password_callback([](std::size_t size, asio::ssl::context_base::password_purpose purpose) { 28 | (void)(size); 29 | (void)(purpose); 30 | return "test"; 31 | }); 32 | ssl_context.use_certificate_chain_file(OPENSSL_PEM_PATH "server.pem"); 33 | ssl_context.use_private_key_file(OPENSSL_PEM_PATH "server.pem", asio::ssl::context::pem); 34 | ssl_context.use_tmp_dh_file(OPENSSL_PEM_PATH "dh4096.pem"); 35 | } 36 | rpc_server_ssl server(context, PORT, ssl_context); 37 | server.on_session = [](const std::weak_ptr& rs) { 38 | LOG("on_session:"); 39 | auto session = rs.lock(); 40 | session->on_close = [] { 41 | LOG("session on_close:"); 42 | pass_flag_session_close = true; 43 | }; 44 | session->rpc->subscribe("cmd", [](const std::string& data) -> std::string { 45 | LOG("session on cmd: %s", data.c_str()); 46 | ASSERT(data == "hello"); 47 | return "world"; 48 | }); 49 | }; 50 | server.on_handshake_error = [](std::error_code ec) { 51 | LOGE("on_handshake_error: %d, %s", ec.value(), ec.message().c_str()); 52 | }; 53 | server.start(true); 54 | }).detach(); 55 | 56 | // client 57 | std::thread([] { 58 | asio::io_context context; 59 | asio::ssl::context ssl_context(asio::ssl::context::sslv23); 60 | ssl_context.load_verify_file(OPENSSL_PEM_PATH "ca.pem"); 61 | rpc_client_ssl client(context, ssl_context); 62 | client.on_open = [&](const std::shared_ptr& rpc) { 63 | LOG("client on_open:"); 64 | rpc->cmd("cmd") 65 | ->msg(std::string("hello")) 66 | ->rsp([&](const std::string& data) { 67 | LOG("cmd rsp: %s", data.c_str()); 68 | if (data == "world") { 69 | pass_flag_rpc_pass = true; 70 | } 71 | client.close(); 72 | }) 73 | ->call(); 74 | }; 75 | client.on_close = [&] { 76 | pass_flag_client_close = true; 77 | LOG("client on_close:"); 78 | client.stop(); 79 | }; 80 | client.open("localhost", PORT); 81 | client.run(); 82 | }).join(); 83 | 84 | ASSERT(pass_flag_rpc_pass); 85 | ASSERT(pass_flag_client_close); 86 | 87 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 88 | ASSERT(pass_flag_session_close); 89 | LOG("all rpc_session should destroyed before here!!!"); 90 | 91 | return EXIT_SUCCESS; 92 | } 93 | -------------------------------------------------------------------------------- /test/serial_port.cpp: -------------------------------------------------------------------------------- 1 | #include "asio_net/serial_port.hpp" 2 | 3 | #include "log.h" 4 | 5 | using namespace asio_net; 6 | 7 | /** 8 | * socat -d -d PTY PTY 9 | */ 10 | int main(int argc, char** argv) { 11 | asio::io_context context; 12 | std::string device = "/dev/ttys01"; 13 | if (argc >= 2) { 14 | device = argv[1]; 15 | } 16 | serial_port serial(context); 17 | serial.set_reconnect(1000); 18 | serial.on_try_open = [&] { 19 | LOG("serial on_try_open: %s", serial.config().device.c_str()); 20 | }; 21 | serial.on_open = [&] { 22 | LOG("serial on_open:"); 23 | /// set_option 24 | serial.set_option(asio::serial_port::baud_rate(115200)); 25 | serial.set_option(asio::serial_port::flow_control(asio::serial_port::flow_control::none)); 26 | serial.set_option(asio::serial_port::parity(asio::serial_port::parity::none)); 27 | serial.set_option(asio::serial_port::stop_bits(asio::serial_port::stop_bits::one)); 28 | serial.set_option(asio::serial_port::character_size(asio::serial_port::character_size(8))); 29 | 30 | /// test 31 | serial.send("hello world"); 32 | }; 33 | serial.on_data = [](const std::string& data) { 34 | LOG("serial on_data: %s", data.c_str()); 35 | }; 36 | serial.on_open_failed = [](std::error_code ec) { 37 | LOG("serial on_open_failed: %d, %s", ec.value(), ec.message().c_str()); 38 | }; 39 | serial.on_close = [] { 40 | LOG("serial on_close:"); 41 | }; 42 | serial.open(device); 43 | serial.run(); 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /test/server_discovery.cpp: -------------------------------------------------------------------------------- 1 | #include "asio_net/server_discovery.hpp" 2 | 3 | #include 4 | 5 | #include "log.h" 6 | 7 | using namespace asio_net; 8 | 9 | int main() { 10 | std::thread([] { 11 | asio::io_context context; 12 | server_discovery::receiver receiver(context, [](const std::string& name, const std::string& message) { 13 | LOG("receive: name: %s, message: %s", name.c_str(), message.c_str()); 14 | }); 15 | context.run(); 16 | }).detach(); 17 | 18 | std::thread([] { 19 | asio::io_context context; 20 | auto getFirstIp = [](asio::io_context& context) { 21 | using namespace asio::ip; 22 | tcp::resolver resolver(context); 23 | auto res = resolver.resolve(host_name(), ""); 24 | if (!res.empty()) { 25 | return res.begin()->endpoint().address().to_string(); 26 | } 27 | return std::string(); 28 | }; 29 | server_discovery::sender sender_name(context, "host_name", asio::ip::host_name()); 30 | server_discovery::sender sender_ip(context, "ip", getFirstIp(context)); 31 | context.run(); 32 | }).detach(); 33 | 34 | getchar(); 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /test/ssl/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDVDCCAjygAwIBAgIUX3cVQ47QyJp7SOy0RPzP743W9MwwDQYJKoZIhvcNAQEL 3 | BQAwOzELMAkGA1UEBhMCQVUxDDAKBgNVBAgMA05TVzEPMA0GA1UEBwwGU3lkbmV5 4 | MQ0wCwYDVQQKDARhc2lvMB4XDTIxMTExMTIxMTA1MloXDTI2MTExMDIxMTA1Mlow 5 | OzELMAkGA1UEBhMCQVUxDDAKBgNVBAgMA05TVzEPMA0GA1UEBwwGU3lkbmV5MQ0w 6 | CwYDVQQKDARhc2lvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyURD 7 | LjKxTCkapmWhY0bP1NaaOPzIJTTB0dzREOlmRmBmiHpW7DaRx7qBm6jYDKQ7OCbz 8 | 30/j8K4TjHOLIwxzXhXMYTJOcN2giPHNUBvm9oEuDAhYgltArJQnBBEH+3C1hCIv 9 | 1+uhTWo0HpGXTeJnvboTZ1YgmbOgr6lMhNiu9QmPX885DxWf6sDq8mRgCDX2x8sk 10 | Ls0HuLSo88Osjx532yEhnrZgexsByhoRD3yrKHV5mWpaunk5BMsP/XMuQHayFmbO 11 | siqnHJoL1znGVH003PcBGmEDmoIUqhLiBi2gWGu1pmckP9loqQUTEn0aLOVclHf4 12 | slWq344zh4tJCpQMfQIDAQABo1AwTjAdBgNVHQ4EFgQUfiX1CMQrGDi9mIBAg9cg 13 | m0RwLJUwHwYDVR0jBBgwFoAUfiX1CMQrGDi9mIBAg9cgm0RwLJUwDAYDVR0TBAUw 14 | AwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAnDnVNSb8z/pNFaZ6YAZ+ukfNT3jjbGm1 15 | 10BOLqJj8s5A8/JkwjaWhky/DuGXDywgEvzXC18aNAxASeqO8h9pAZtszu6NWB4s 16 | h3r+dEQakMacxrZ+jBL/cYLrUv9r3KMPKxaDnplkamqFA/9eNmoV7vDyGtGPZuD6 17 | oTROtQqqDSrxthCkqnibAfusi7RmlCdvJa0kVK7krKJZAhi8W9f32+lBPv9oq3Ns 18 | dAxnOj/D3UnhNoIt0EdjqUdLo2U39dt5s5Syj2rFUAgfbc02Rwx65kq8AjTRlW/M 19 | KDpGsifwIB8b7wActMUO8c3GptptNVWmFm5+Mmk54P//P3tIAx9KXw== 20 | -----END CERTIFICATE----- 21 | -----BEGIN ENCRYPTED PRIVATE KEY----- 22 | MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIPcVUeQ7ZElgCAggA 23 | MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECGKlFVN6/gIlBIIEyJPeknsA5dvV 24 | WK27AZzs4wM6WrsD6ba+kPJyZTpon5pn4eoTiw4UCvo7C+21G9jCqbIbDrgTWHwH 25 | zu6YrBFTZgRUpdLkLsyUsp4UJrZ8xJ7N/jWlG763CyluFE5oBFLz2Vt/DSDSaWdA 26 | sKdxua/1kvw+KYoDqlObMFIspM5TrndcMnWkOgyvQAvnH0NZXFFBa4QGFrwWDrCo 27 | m12CzMLwaPMrNFBffCTqZnL1AajT+EYqPTi+cQ5mkRpZ2cuzhdug2eki1KkD12ZN 28 | 3d8Ehl8bfbLEqRE/HnggleRRt7ia1xkzruqQp14JA2UsrioGMF5nvWgWrbyHfpui 29 | KrCsDwDelrArW/GCki53QBQMuH8Q1YmoNrRZwlG257eA1LiJvmxRyIjKYu0RP+Q8 30 | EOldycy51VsPVPApUbv9r4IJldrIJiwlmRxxM36uKtFhfojnsWzJORDYI8C37uKi 31 | UEPiD4GSEH6C0lGO94IoZPjl9z/rZ0qZx1VRHl3nxZc0AQvvj9bWMbyRwsgb6wod 32 | P8JSy6uEib9UxuenNHzTd48GcNhJbhOqd4IV0XKhi8W1Kil3mMdc3QAwKaLTx12/ 33 | 1GrbMui061uyeM6Rf6Xao0PApDnUNmxcFSTsoarG0yH7Q0WZMgKTDCCGhhtZKlE6 34 | x7pRsnxiFaIpoh32EVIRI+ZXh2rXBcwa2ew0aEccRXtPFvcmdjevkrHuCggc+M+Y 35 | seFqTHVGWf8eS6o08w095DboD0vFpZXZMRfycTbA7BiE4NYE/uc7v5cH3Ax9l2ef 36 | zG7o9idDt+/RX7OcetxDFw4eQbq7PfjvrfXS1DcRUEyJ04emh7oxlkAUUNsFtabN 37 | T0ggvHxcQWkYRE5oPlkbgpwKpK4LDcApXKFwCDKPur2W5Q7KHRfDLtSvZvYarJum 38 | 8j2pGpEis/bdTih29ofNsX6a0Yo5Tlj+9+1c/6/Xi7XvRk/Vbgkoa3iVQ3ckdCuZ 39 | vO7cRYZBBs6W/Ti3hWFzPEX9FfcnSUp9bEnH4ASnmUcp8PDBJYeaI6HqpO3XbkbF 40 | l70eDNakd2cDNjQzkFpBT7HO+HtqU3xNt9K0z2gMB7iTDVFyIzh26joCR2O8tiqS 41 | wUJFkoLPb74GSB7WzE+gb4jXX6n9609PUoR3f896mM34uX3CsY8lA+/0ZGpHnFDA 42 | ToKdmz6WKIAw0E20nyzLuLwPZgj7HLcR7zza4raipe9QxIdyJr5O+jzGt+OjSM9b 43 | K1agibRE5DChqQ+P+ikOc6nG7UNyn+lKSjGEbwuzi8F0iugMgcTc/vYO8OWDNGpd 44 | D1euA5OuVPdfatFa16Fyr4MJJIfE83C4/kSj05fdoyb6pJkOjHhUppVMe+ES5kwl 45 | YI8RES2XVJzUSxnWsIM613YlMgIZ9xgcuIADnO7gaKJ4RQG+9wJ853Uo4+n89K7F 46 | Y6KzihuYAUkbAw1pPo1TODom7A/gB9stqRUSQlZWmIgtnJ8wPjt/we0gzPM8LQzb 47 | ye4KOLjH5iquFc4MU4TtN3gvp9P5R9UFrGeMImS5eMUmBQHckDNdIAtMj7M1wUpR 48 | JVrzDXWDjC21sLn95NtNMPb+FRlt/biTV3IE3ZmX0kbuCRoH7b7hhR41Zpoajwl0 49 | FqYigB5gnVodGUWuBgDUqA== 50 | -----END ENCRYPTED PRIVATE KEY----- 51 | -------------------------------------------------------------------------------- /test/ssl/dh4096.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN X9.42 DH PARAMETERS----- 2 | MIIELQKCAgEA8eEBgatlTk0QKIwUqHsteHJXzEr0xAW7RLeNid8DdGjTnFw68eV+ 3 | s1YbipEqQVTFG+VZPaof9v0CH2KOdbgWD0KRPC49HB8O/wARFKpy5eJltVe1r07w 4 | +V1Vjciqkh/vGq/yFQsbqwyWJy6AelsbHdSC9Bg9pmu4+pbVGW15zK7PkfjfoO9V 5 | JYbhdqFIPg+9NRRWsl/U9UoyeeIGImI9I4k3fqtv311C6VVErFZylCj7nn45L7IY 6 | GfPcCO5E8FD6EqBv5WnhMNYHMJu9brRvOoAyAyWprqR+aVzHd+DFamDQdsuQjKTk 7 | 3+r07znWJfjp6FVTzR5SvjrKg1knK5TibIksoKOlsFZ06bGkFWHjqXNAKhnSpCiU 8 | GJd0qe7pTD+TGQJGWskJpoph+tkICUa+6MyzP9+U6T9hISIY/BpVuWn/iwdLqsvV 9 | s5bg2RjRraww/4Rjm0NyEOTk2MxaLB9gcyu6zcrhX5XRiMgt+jhKwEMwJCEJYkxn 10 | C0yv08A5v6RceFm1jY+8FT0IzRqsWIGV7beNdqtE0B4LBTuN1yQ6x8CKEb6mbIHG 11 | d0XLGppELhD8QUwAr5HiVGTAsS9JlMGgXVkyDtxrVSeS1zHWt77skRQ9UlfJfYMm 12 | 5wxHj5QCdQ7ma3oMMZBZutNlkevMzYHIxwqhZstf4ud0j0VMftwZ3GMCggIBAJL7 13 | cU6/YSxzYDGUcPc6WGT+FAghR0LdhT3Q3Xz1rQRBwSSMPm9NZhyJC3fb+hBl/44X 14 | AHOu1/3dYIWlXPMuHZZdtlt+hkD/sIob16By20Z0p81Kvh3HQglVDJGlFzTmQbTd 15 | CM/EP8eJFiNeMcJB0RgeKyzikRsV4r/acZZM56swTlsRvSDFMh3v9YyYTZbCTwxm 16 | PIVOsxI8uBoWRCzY3yWzjmlZ2u5TR+Z0IVonmNqk6XAKRgxV12YfQVXzzpPZQA9S 17 | GHoD/NQoMdBAi3p4pLEYJ5qMRLYPQbBfSNu5eP9e5uZOiBp/28bVnzEelDGuFupr 18 | lvywkZEVL1KaYtZ/dGkPFXt+A7mNsw5xeDTRjgL4uUJvCNIkjgVEfIrLdUf5IVsU 19 | V8BgunAvgfH+Hy6Tx89v/QGsUWJPhjkSZnXwJ7Ipcsm+Zg0zAZwKDZEQtDLXVPRL 20 | OfCfXMXvlw2u6OHlhSbqO69zZG3dJTlcnjy06n4JYcUim7Mj3Kduige6VWofeEk9 21 | M/kHrPqNdFE5tHLh2nHcw9yExKHN+l/OUGAvNDfOVS+S+fNSKT0RfC9dMpHLN+Ok 22 | fGuskGzLQAVYF+DYEcnJ++0LEFtd0J381ndSc0iJuH60rKDTqxoRqv7lcdwLFlEV 23 | sQSRoas8s+y+b3FfSu7j0p44qLG9oxZ/WFcWnuUyAiEAjvAD9p3bw8AH0s7dUrX/ 24 | RT1Ge+J/n0tlsgw+LY3zafE= 25 | -----END X9.42 DH PARAMETERS----- 26 | -------------------------------------------------------------------------------- /test/ssl/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDDjCCAfYCFGGcJXlIeQHiq/qOakbISU5cihPvMA0GCSqGSIb3DQEBCwUAMDsx 3 | CzAJBgNVBAYTAkFVMQwwCgYDVQQIDANOU1cxDzANBgNVBAcMBlN5ZG5leTENMAsG 4 | A1UECgwEYXNpbzAeFw0yMTExMTEyMTExNDRaFw0yNjExMTAyMTExNDRaMEwxCzAJ 5 | BgNVBAYTAkFVMQwwCgYDVQQIDANOU1cxDzANBgNVBAcMBlN5ZG5leTENMAsGA1UE 6 | CgwEYXNpbzEPMA0GA1UECwwGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 7 | MIIBCgKCAQEAu67TzZCFNTZ5c1gKwXfdTH3bP0OB5n6dqXGK6jkPb699dyM4oPtY 8 | 2f+OyxJspk8D5pX6HuZ+hLGAsHxfCN+A3RegVZJCR/gXrrMPl/6PfDxtkeFXWy+6 9 | NAPxqppMj8gbX/czTdNFwgW4J8/RLyH53utjAGQtCRr97EbBOsTSeOmyAxbw4mRt 10 | EvHjacKFcSZzJDlyxCia37791MulZ4lwOZZlke5NzoOqkBIAusQgg7fMWDvH27PD 11 | T+taIyBsYTVJWHf5kMklAlWYXZvNjZp/QGt13lDORg2aSc/P2pE4iLyhbZcpuW+W 12 | nHB7IY1wjDL55/ORCjz3VlNIAMXHP5YQxwIDAQABMA0GCSqGSIb3DQEBCwUAA4IB 13 | AQC3iiwQTgDP4ixulisYIGlJb8iuhxH9G41hm8dF/1qlIfXWxsbjb+g7M5U/3JCb 14 | fwCGoz7uBdj/CKkIb5wx83GVC1yVLbNSvCIS827qxic4GACEYGGAy4Ub5tUCQrLr 15 | JUQTP1WVJcgiKvk7OAcXdA+k/CcNXH153gUPDZz+/BedXe2VbM6LDsqPeSxXfbZN 16 | AWXARAuly7cQk/xkYkPldRRhx4htNCbnkG1qyEqNcYQN1SY9Qhbrm2HkAUntzQ7G 17 | umo4JdbIX6MJ5fYNs+yN/lJB7PiJP8Mz1AtOz4x9JxcLyuB6sapT8Tj53n9g/+m1 18 | Tqd8lHpWe6oM0sgdWfzdBNir 19 | -----END CERTIFICATE----- 20 | -----BEGIN RSA PRIVATE KEY----- 21 | Proc-Type: 4,ENCRYPTED 22 | DEK-Info: DES-EDE3-CBC,9E0E6B9970E055F1 23 | 24 | oFvN3t50B2TSeNsRo1vwhVYlMmt0w3MD3Ar75fti4fRhBrWoH4rBPrSouWK4OMDK 25 | CakfVhzJIEvaMav843grS4V+ooAsfnAtiQiQj+5xjZ8PlCuWir5gyjgqeN4zN6ZT 26 | 2s/oEy5Kx7Ru5AWMkv+NPW5l3iS3c+Hupfb/auITwECSCCsCBxgNtJ8z0iXjv9Dr 27 | hqvqcI+4F5DSiol3QhitbpQ/k2PKu92AZhPcr9e5HrqeUD7aqfX2q3i+tnJvxC93 28 | 0mvy133bX2ZAGAKMheW/GuzcETfwU80wvmrwHXI9rnuAUaJhrDiIZQ79gubZ0Pcm 29 | w4Y6Z5xxeErb7zKBi+pHKst8MJ7rfSz/N878su6cpESRC/w1paVX7m8/q61kzBkj 30 | +h2qe9DupXBejQkriN+Z858nFtBKbWlFsmAg1FLZonworTUfV2c7S9yzMV/29VOE 31 | T0Bt8BU1Hp+bCjreF4JGbnUJnRQoASpisfUkrD6Ar4GI56Edkkx6hkF0cXdcvfCN 32 | qyhxzcvf/aV1M2kcpBRX9mIfjoFMFNz7uRIxyqPLaDkigwJemoaBo3n9/W5F7N7B 33 | AGNEZ3QhUlc8EyWpGRZRzMevu5wdz1b2+MqItOhPXUo2XcLEj2iFkVDdbj/d9jQH 34 | 9ZIe4hQTSf784tAzt2Jp2RHmm22390GB1jdzG/CzbGlAKmaTTEe4FJcd/eLOEZuD 35 | 4+iFsEb/bWcCyXkBKkdEoDDMJW8xi5d/1ezJM03nqhvkekDqyhJUJPCbmY1SCX1X 36 | 4AO40jJmWQxwj26Pnnncacz4tuecZNqovz+POpKQSIctrQSV4wgf35137KBt7/O7 37 | EU0N7Qj4xTigyGkEHCZt7BgD8fFrxiP1OkUmFRHEE7Dxiagz6omJV8ien+p9wIgS 38 | QcqkJlgKMwVepjuCuEyvg5oIkAPvp24yhePSJkEvvoDLvM2Kub/CM+UkN8dSgsZk 39 | +ZBE9Oi62AWWow8/ib8Um5DNIEZsq+5CQLeBG7f5kTIBAoA9UceaEgbxzOVUCBcX 40 | L7QsJ3pZ/ei1qx/9tChO/OXa9tleXcjGg7/q/6i6pUyZuqZlt+hk+3HtFmTXIjsM 41 | chbUSMEJ2isv9MDamTtunt9N92A6OxUkAs68kCO/bzotldXMKZVjOohNnML5q/3U 42 | vpsuN8NNwaNHCAM7pjols4V7ebX1lDZFR1hbj+szKMhVKzFfig1lRHafgRrR6H6P 43 | mUK16lKzUbmvwv2IU/3wck+4UNeB/+lRmfAfBasQytnbt+5fwwNkVSrDY2Kxe0nz 44 | 2k3eIYST+ygxGuNrW9rnFbBpTJb0OE5cpgun8xxDE91AmL550AcZff+9ulDBhZg1 45 | eMoKUALlEdDNPffVTH8Iupn0jMnJsjXynztcEVhx4h2aA5X6/2i035uEybCn33qP 46 | J7g30oe2C69BSC0YmkT3MjF6Wrhy+M8dU0dDggRhY6JSS8wESvwGenaPi7SZlbxb 47 | Elmc44bHros1YfGl/xDdhju134/aMBG1GlZlReI+Rwnp9DCp04xiiJb9YKyUNaGY 48 | 7BTGWyie/WYYKbX1ZlxqWA9jxow8PM1XYHuanau1/aYT16L7E83HG3qkyuFIlLJh 49 | -----END RSA PRIVATE KEY----- 50 | -----BEGIN CERTIFICATE----- 51 | MIIDVDCCAjygAwIBAgIUX3cVQ47QyJp7SOy0RPzP743W9MwwDQYJKoZIhvcNAQEL 52 | BQAwOzELMAkGA1UEBhMCQVUxDDAKBgNVBAgMA05TVzEPMA0GA1UEBwwGU3lkbmV5 53 | MQ0wCwYDVQQKDARhc2lvMB4XDTIxMTExMTIxMTA1MloXDTI2MTExMDIxMTA1Mlow 54 | OzELMAkGA1UEBhMCQVUxDDAKBgNVBAgMA05TVzEPMA0GA1UEBwwGU3lkbmV5MQ0w 55 | CwYDVQQKDARhc2lvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyURD 56 | LjKxTCkapmWhY0bP1NaaOPzIJTTB0dzREOlmRmBmiHpW7DaRx7qBm6jYDKQ7OCbz 57 | 30/j8K4TjHOLIwxzXhXMYTJOcN2giPHNUBvm9oEuDAhYgltArJQnBBEH+3C1hCIv 58 | 1+uhTWo0HpGXTeJnvboTZ1YgmbOgr6lMhNiu9QmPX885DxWf6sDq8mRgCDX2x8sk 59 | Ls0HuLSo88Osjx532yEhnrZgexsByhoRD3yrKHV5mWpaunk5BMsP/XMuQHayFmbO 60 | siqnHJoL1znGVH003PcBGmEDmoIUqhLiBi2gWGu1pmckP9loqQUTEn0aLOVclHf4 61 | slWq344zh4tJCpQMfQIDAQABo1AwTjAdBgNVHQ4EFgQUfiX1CMQrGDi9mIBAg9cg 62 | m0RwLJUwHwYDVR0jBBgwFoAUfiX1CMQrGDi9mIBAg9cgm0RwLJUwDAYDVR0TBAUw 63 | AwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAnDnVNSb8z/pNFaZ6YAZ+ukfNT3jjbGm1 64 | 10BOLqJj8s5A8/JkwjaWhky/DuGXDywgEvzXC18aNAxASeqO8h9pAZtszu6NWB4s 65 | h3r+dEQakMacxrZ+jBL/cYLrUv9r3KMPKxaDnplkamqFA/9eNmoV7vDyGtGPZuD6 66 | oTROtQqqDSrxthCkqnibAfusi7RmlCdvJa0kVK7krKJZAhi8W9f32+lBPv9oq3Ns 67 | dAxnOj/D3UnhNoIt0EdjqUdLo2U39dt5s5Syj2rFUAgfbc02Rwx65kq8AjTRlW/M 68 | KDpGsifwIB8b7wActMUO8c3GptptNVWmFm5+Mmk54P//P3tIAx9KXw== 69 | -----END CERTIFICATE----- 70 | -----BEGIN ENCRYPTED PRIVATE KEY----- 71 | MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIPcVUeQ7ZElgCAggA 72 | MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECGKlFVN6/gIlBIIEyJPeknsA5dvV 73 | WK27AZzs4wM6WrsD6ba+kPJyZTpon5pn4eoTiw4UCvo7C+21G9jCqbIbDrgTWHwH 74 | zu6YrBFTZgRUpdLkLsyUsp4UJrZ8xJ7N/jWlG763CyluFE5oBFLz2Vt/DSDSaWdA 75 | sKdxua/1kvw+KYoDqlObMFIspM5TrndcMnWkOgyvQAvnH0NZXFFBa4QGFrwWDrCo 76 | m12CzMLwaPMrNFBffCTqZnL1AajT+EYqPTi+cQ5mkRpZ2cuzhdug2eki1KkD12ZN 77 | 3d8Ehl8bfbLEqRE/HnggleRRt7ia1xkzruqQp14JA2UsrioGMF5nvWgWrbyHfpui 78 | KrCsDwDelrArW/GCki53QBQMuH8Q1YmoNrRZwlG257eA1LiJvmxRyIjKYu0RP+Q8 79 | EOldycy51VsPVPApUbv9r4IJldrIJiwlmRxxM36uKtFhfojnsWzJORDYI8C37uKi 80 | UEPiD4GSEH6C0lGO94IoZPjl9z/rZ0qZx1VRHl3nxZc0AQvvj9bWMbyRwsgb6wod 81 | P8JSy6uEib9UxuenNHzTd48GcNhJbhOqd4IV0XKhi8W1Kil3mMdc3QAwKaLTx12/ 82 | 1GrbMui061uyeM6Rf6Xao0PApDnUNmxcFSTsoarG0yH7Q0WZMgKTDCCGhhtZKlE6 83 | x7pRsnxiFaIpoh32EVIRI+ZXh2rXBcwa2ew0aEccRXtPFvcmdjevkrHuCggc+M+Y 84 | seFqTHVGWf8eS6o08w095DboD0vFpZXZMRfycTbA7BiE4NYE/uc7v5cH3Ax9l2ef 85 | zG7o9idDt+/RX7OcetxDFw4eQbq7PfjvrfXS1DcRUEyJ04emh7oxlkAUUNsFtabN 86 | T0ggvHxcQWkYRE5oPlkbgpwKpK4LDcApXKFwCDKPur2W5Q7KHRfDLtSvZvYarJum 87 | 8j2pGpEis/bdTih29ofNsX6a0Yo5Tlj+9+1c/6/Xi7XvRk/Vbgkoa3iVQ3ckdCuZ 88 | vO7cRYZBBs6W/Ti3hWFzPEX9FfcnSUp9bEnH4ASnmUcp8PDBJYeaI6HqpO3XbkbF 89 | l70eDNakd2cDNjQzkFpBT7HO+HtqU3xNt9K0z2gMB7iTDVFyIzh26joCR2O8tiqS 90 | wUJFkoLPb74GSB7WzE+gb4jXX6n9609PUoR3f896mM34uX3CsY8lA+/0ZGpHnFDA 91 | ToKdmz6WKIAw0E20nyzLuLwPZgj7HLcR7zza4raipe9QxIdyJr5O+jzGt+OjSM9b 92 | K1agibRE5DChqQ+P+ikOc6nG7UNyn+lKSjGEbwuzi8F0iugMgcTc/vYO8OWDNGpd 93 | D1euA5OuVPdfatFa16Fyr4MJJIfE83C4/kSj05fdoyb6pJkOjHhUppVMe+ES5kwl 94 | YI8RES2XVJzUSxnWsIM613YlMgIZ9xgcuIADnO7gaKJ4RQG+9wJ853Uo4+n89K7F 95 | Y6KzihuYAUkbAw1pPo1TODom7A/gB9stqRUSQlZWmIgtnJ8wPjt/we0gzPM8LQzb 96 | ye4KOLjH5iquFc4MU4TtN3gvp9P5R9UFrGeMImS5eMUmBQHckDNdIAtMj7M1wUpR 97 | JVrzDXWDjC21sLn95NtNMPb+FRlt/biTV3IE3ZmX0kbuCRoH7b7hhR41Zpoajwl0 98 | FqYigB5gnVodGUWuBgDUqA== 99 | -----END ENCRYPTED PRIVATE KEY----- 100 | -------------------------------------------------------------------------------- /test/tcp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "asio_net/tcp_client.hpp" 7 | #include "asio_net/tcp_server.hpp" 8 | #include "assert_def.h" 9 | #include "log.h" 10 | 11 | using namespace asio_net; 12 | 13 | const uint16_t PORT = 6666; 14 | 15 | int main(int argc, char** argv) { 16 | static uint32_t test_count_max = 10000; 17 | static uint32_t test_count_expect = 0; 18 | if (argc >= 2) { 19 | test_count_max = std::strtol(argv[1], nullptr, 10); 20 | } 21 | 22 | // server 23 | static std::atomic_bool pass_flag_session_close{false}; 24 | std::thread([] { 25 | asio::io_context context; 26 | tcp_server server(context, PORT, tcp_config{.auto_pack = true}); 27 | server.on_session = [](const std::weak_ptr& ws) { 28 | LOG("on_session:"); 29 | auto session = ws.lock(); 30 | session->on_close = [] { 31 | LOG("session on_close:"); 32 | pass_flag_session_close = true; 33 | }; 34 | session->on_data = [ws](std::string data) { 35 | ASSERT(!ws.expired()); 36 | #ifndef ASIO_NET_DISABLE_ON_DATA_PRINT 37 | LOG("session on_data: %s", data.c_str()); 38 | #endif 39 | ws.lock()->send(std::move(data)); 40 | }; 41 | }; 42 | server.start(true); 43 | }).detach(); 44 | 45 | // client 46 | static std::atomic_bool pass_flag_client_close{false}; 47 | std::thread([] { 48 | asio::io_context context; 49 | tcp_client client(context, tcp_config{.auto_pack = true}); 50 | client.on_open = [&] { 51 | LOG("client on_open:"); 52 | ASSERT(client.is_open); 53 | for (uint32_t i = 0; i < test_count_max; ++i) { 54 | client.send(std::to_string(i)); 55 | } 56 | }; 57 | client.on_data = [&](const std::string& data) { 58 | ASSERT(client.is_open); 59 | #ifndef ASIO_NET_DISABLE_ON_DATA_PRINT 60 | LOG("client on_data: %s", data.c_str()); 61 | #endif 62 | ASSERT(std::to_string(test_count_expect++) == data); 63 | if (test_count_expect == test_count_max - 1) { 64 | client.close(); 65 | } 66 | }; 67 | client.on_close = [&] { 68 | ASSERT(!client.is_open); 69 | pass_flag_client_close = true; 70 | ASSERT(test_count_expect == test_count_max - 1); 71 | LOG("client on_close:"); 72 | client.stop(); 73 | }; 74 | client.open("localhost", PORT); 75 | client.run(); 76 | }).join(); 77 | 78 | ASSERT(pass_flag_session_close); 79 | ASSERT(pass_flag_client_close); 80 | return EXIT_SUCCESS; 81 | } 82 | -------------------------------------------------------------------------------- /test/tcp_bigdata.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "asio_net/tcp_client.hpp" 6 | #include "asio_net/tcp_server.hpp" 7 | #include "assert_def.h" 8 | #include "log.h" 9 | 10 | using namespace asio_net; 11 | 12 | const uint16_t PORT = 6666; 13 | 14 | int main() { 15 | // server 16 | std::thread([] { 17 | asio::io_context context; 18 | tcp_server server(context, PORT, tcp_config{.auto_pack = true}); 19 | server.on_session = [&](const std::weak_ptr& ws) { 20 | LOG("on_session:"); 21 | auto session = ws.lock(); 22 | session->on_close = [] { 23 | LOG("session on_close:"); 24 | }; 25 | session->on_data = [ws, &context](const std::string& data) { 26 | ASSERT(!ws.expired()); 27 | LOG("session on_data: %s", data.c_str()); 28 | static int count = 0; 29 | if (++count == 3) { 30 | context.stop(); 31 | } 32 | }; 33 | }; 34 | server.start(true); 35 | }).detach(); 36 | 37 | // client 38 | std::thread([] { 39 | asio::io_context context; 40 | tcp_client client(context, tcp_config{.auto_pack = true}); 41 | client.on_open = [&] { 42 | LOG("client on_open:"); 43 | std::string msg("hello"); 44 | msg.resize(1024 * 1024 * 100); 45 | LOG("send..."); 46 | client.send(msg); 47 | LOG("send..."); 48 | client.send(msg); 49 | LOG("send..."); 50 | client.send(msg); 51 | }; 52 | client.on_data = [&](const std::string& data) { 53 | (void)data; 54 | LOG("client on_data:"); 55 | }; 56 | client.on_close = [&] { 57 | LOG("client on_close:"); 58 | client.stop(); 59 | }; 60 | client.open("localhost", PORT); 61 | client.run(); 62 | }).join(); 63 | return EXIT_SUCCESS; 64 | } 65 | -------------------------------------------------------------------------------- /test/tcp_c.cpp: -------------------------------------------------------------------------------- 1 | #include "asio_net/tcp_client.hpp" 2 | #include "log.h" 3 | 4 | using namespace asio_net; 5 | 6 | const uint16_t PORT = 6666; 7 | 8 | int main() { 9 | asio::io_context context; 10 | tcp_client client(context); 11 | client.on_open = [&] { 12 | LOG("client on_open:"); 13 | client.send("hello"); 14 | }; 15 | client.on_data = [](const std::string& data) { 16 | LOG("client on_data: %s", data.c_str()); 17 | }; 18 | client.on_open_failed = [](std::error_code ec) { 19 | LOG("client on_open_failed: %d, %s", ec.value(), ec.message().c_str()); 20 | }; 21 | client.on_close = [] { 22 | LOG("client on_close:"); 23 | }; 24 | client.open("localhost", PORT); 25 | client.run(); 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /test/tcp_reconnect.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "asio_net/tcp_client.hpp" 6 | #include "asio_net/tcp_server.hpp" 7 | #include "assert_def.h" 8 | #include "log.h" 9 | 10 | using namespace asio_net; 11 | 12 | const uint16_t PORT = 6666; 13 | 14 | int main() { 15 | // server 16 | std::thread([] { 17 | asio::io_context context; 18 | tcp_server server(context, PORT, tcp_config{.auto_pack = true}); 19 | server.on_session = [&](const std::weak_ptr& ws) { 20 | LOG("on_session:"); 21 | auto session = ws.lock(); 22 | session->close(); 23 | session->on_close = [] { 24 | LOG("session on_close:"); 25 | }; 26 | session->on_data = [ws](const std::string& data) { 27 | ASSERT(!ws.expired()); 28 | LOG("session on_data: %s", data.c_str()); 29 | }; 30 | }; 31 | server.start(true); 32 | }).detach(); 33 | 34 | // client 35 | std::thread([] { 36 | asio::io_context context; 37 | tcp_client client(context, tcp_config{.auto_pack = true}); 38 | client.on_open = [&] { 39 | LOG("client on_open:"); 40 | std::string msg("hello"); 41 | client.send(msg); 42 | }; 43 | client.on_data = [](const std::string& data) { 44 | (void)data; 45 | LOG("client on_data:"); 46 | }; 47 | client.on_close = [&] { 48 | LOG("client on_close:"); 49 | static int count = 0; 50 | if (++count == 3) { 51 | client.stop(); 52 | } 53 | }; 54 | client.set_reconnect(1); 55 | client.open("localhost", PORT); 56 | client.run(); 57 | }).join(); 58 | return EXIT_SUCCESS; 59 | } 60 | -------------------------------------------------------------------------------- /test/tcp_s.cpp: -------------------------------------------------------------------------------- 1 | #include "asio_net/tcp_server.hpp" 2 | #include "assert_def.h" 3 | #include "log.h" 4 | 5 | using namespace asio_net; 6 | 7 | const uint16_t PORT = 6666; 8 | 9 | int main() { 10 | asio::io_context context; 11 | tcp_server server(context, PORT); 12 | server.on_session = [](const std::weak_ptr& ws) { 13 | LOG("on_session:"); 14 | auto session = ws.lock(); 15 | session->on_close = [] { 16 | LOG("session on_close:"); 17 | }; 18 | session->on_data = [ws](std::string data) { 19 | ASSERT(!ws.expired()); 20 | LOG("session on_data: %zu, %s", data.size(), data.c_str()); 21 | ws.lock()->send(std::move(data)); 22 | }; 23 | }; 24 | server.start(true); 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /test/tcp_ssl_c.cpp: -------------------------------------------------------------------------------- 1 | #include "asio_net/tcp_client.hpp" 2 | #include "log.h" 3 | 4 | using namespace asio_net; 5 | 6 | const uint16_t PORT = 6666; 7 | 8 | int main() { 9 | asio::io_context context; 10 | asio::ssl::context ssl_context(asio::ssl::context::sslv23); 11 | ssl_context.load_verify_file(OPENSSL_PEM_PATH "ca.pem"); 12 | tcp_client_ssl client(context, ssl_context); 13 | 14 | client.on_open = [&] { 15 | LOG("client on_open:"); 16 | client.send("hello"); 17 | }; 18 | client.on_data = [](const std::string& data) { 19 | LOG("client on_data: %s", data.c_str()); 20 | }; 21 | client.on_open_failed = [](std::error_code ec) { 22 | LOG("client on_open_failed: %d, %s", ec.value(), ec.message().c_str()); 23 | }; 24 | client.on_close = [] { 25 | LOG("client on_close:"); 26 | }; 27 | client.open("localhost", PORT); 28 | client.run(); 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /test/tcp_ssl_s.cpp: -------------------------------------------------------------------------------- 1 | #include "asio_net/tcp_server.hpp" 2 | #include "assert_def.h" 3 | #include "log.h" 4 | 5 | using namespace asio_net; 6 | 7 | const uint16_t PORT = 6666; 8 | 9 | int main() { 10 | asio::io_context context; 11 | asio::ssl::context ssl_context(asio::ssl::context::sslv23); 12 | { 13 | ssl_context.set_options(asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::single_dh_use); 14 | ssl_context.set_password_callback([](std::size_t size, asio::ssl::context_base::password_purpose purpose) { 15 | (void)(size); 16 | (void)(purpose); 17 | return "test"; 18 | }); 19 | ssl_context.use_certificate_chain_file(OPENSSL_PEM_PATH "server.pem"); 20 | ssl_context.use_private_key_file(OPENSSL_PEM_PATH "server.pem", asio::ssl::context::pem); 21 | ssl_context.use_tmp_dh_file(OPENSSL_PEM_PATH "dh4096.pem"); 22 | } 23 | tcp_server_ssl server(context, PORT, ssl_context); 24 | server.on_session = [](const std::weak_ptr& ws) { 25 | LOG("on_session:"); 26 | auto session = ws.lock(); 27 | session->on_close = [] { 28 | LOG("session on_close:"); 29 | }; 30 | session->on_data = [ws](std::string data) { 31 | ASSERT(!ws.expired()); 32 | LOG("session on_data: %zu, %s", data.size(), data.c_str()); 33 | ws.lock()->send(std::move(data)); 34 | }; 35 | }; 36 | server.on_handshake_error = [](std::error_code ec) { 37 | LOGE("on_handshake_error: %d, %s", ec.value(), ec.message().c_str()); 38 | }; 39 | server.start(true); 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /test/udp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "asio_net/udp_client.hpp" 7 | #include "asio_net/udp_server.hpp" 8 | #include "assert_def.h" 9 | #include "log.h" 10 | 11 | using namespace asio_net; 12 | 13 | const uint16_t PORT = 6666; 14 | 15 | int main(int argc, char** argv) { 16 | static uint32_t test_count_max = 10000; 17 | static std::atomic_uint32_t test_count_received; 18 | if (argc >= 2) { 19 | test_count_max = std::strtol(argv[1], nullptr, 10); 20 | } 21 | 22 | std::thread([] { 23 | asio::io_context context; 24 | udp_server server(context, PORT); 25 | server.on_data = [](uint8_t* data, size_t size, const udp_server::endpoint& from) { 26 | (void)data; 27 | (void)size; 28 | (void)from; 29 | #ifndef ASIO_NET_DISABLE_ON_DATA_PRINT 30 | LOG("on_data: %s", std::string((char*)data, size).c_str()); 31 | #endif 32 | test_count_received++; 33 | }; 34 | server.start(); 35 | }).detach(); 36 | 37 | std::atomic_uint32_t send_failed_count{0}; 38 | std::thread([&] { 39 | asio::io_context context; 40 | udp_client client(context); 41 | asio::post(context, [&] { 42 | udp_client::endpoint endpoint(asio::ip::make_address_v4("127.0.0.1"), PORT); 43 | for (uint32_t i = 0; i < test_count_max; ++i) { 44 | auto data = std::to_string(i); 45 | client.send_to(data, endpoint, [&](const std::error_code& ec, std::size_t size) { 46 | (void)size; 47 | if (ec) { 48 | send_failed_count++; 49 | } 50 | }); 51 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 52 | } 53 | }); 54 | context.run(); 55 | }).join(); 56 | 57 | std::this_thread::sleep_for(std::chrono::seconds(1)); 58 | LOG("test_count_max: %d", test_count_max); 59 | LOG("test_count_received: %d", test_count_received.load()); 60 | LOG("send_failed_count: %d", send_failed_count.load()); 61 | LOG("lost: %f%%", 100 * (double)(test_count_max - test_count_received) / test_count_max); 62 | ASSERT(test_count_max - test_count_received == send_failed_count); 63 | return EXIT_SUCCESS; 64 | } 65 | -------------------------------------------------------------------------------- /test/udp_c.cpp: -------------------------------------------------------------------------------- 1 | #include "asio_net/udp_client.hpp" 2 | #include "log.h" 3 | 4 | using namespace asio_net; 5 | 6 | const uint16_t PORT = 6666; 7 | 8 | int main() { 9 | asio::io_context context; 10 | udp_client client(context); 11 | udp_client::endpoint endpoint(asio::ip::make_address("127.0.0.1"), PORT); 12 | client.send_to("hello", endpoint, [&](const std::error_code& ec, std::size_t size) { 13 | LOG("result: ec: %d, size: %zu", ec.value(), size); 14 | }); 15 | context.run(); 16 | return EXIT_SUCCESS; 17 | } 18 | -------------------------------------------------------------------------------- /test/udp_s.cpp: -------------------------------------------------------------------------------- 1 | #include "asio_net/udp_server.hpp" 2 | #include "log.h" 3 | 4 | using namespace asio_net; 5 | 6 | const uint16_t PORT = 6666; 7 | 8 | int main() { 9 | asio::io_context context; 10 | udp_server server(context, PORT); 11 | server.on_data = [](uint8_t* data, size_t size, const udp_server::endpoint& from) { 12 | LOG("on_data: %s, from: %s, port: %d", std::string((char*)data, size).c_str(), from.address().to_string().c_str(), from.port()); 13 | }; 14 | server.start(); 15 | return EXIT_SUCCESS; 16 | } 17 | -------------------------------------------------------------------------------- /test/version.cpp: -------------------------------------------------------------------------------- 1 | #include "asio_net.hpp" 2 | #include "log.h" 3 | 4 | int main() { 5 | LOGI("asio: %d", ASIO_VERSION); 6 | LOGI("rpc_core: %d", RPC_CORE_VERSION); 7 | LOGI("asio_net: %d", ASIO_NET_VERSION); 8 | return 0; 9 | } 10 | --------------------------------------------------------------------------------