asio2 Documentation

repository·main·Indexed 21 days ago

https://github.com/zhllxt/asio2

A header-only C++17 network library built on top of asio (standalone or Boost). asio2 provides high-level abstractions for TCP, UDP, HTTP, WebSocket, RPC, SSL, ICMP, and serial ports, focusing on ease of use and automatic resource management. It features a non-blocking start/blocking stop server lifecycle, automatic TCP reconnection, Reliable UDP via KCP, an RPC system supporting various function bindings, and an HTTP server with AOP-based interceptors.

Tokens
17.3K
Snippets
50
Records
54
Agent score
75%

What's inside asio2

  1. Overview of asio2

    main

    asio2 is a header-only C++ network library based on asio. It supports a wide range of protocols including TCP, UDP, HTTP, WebSocket, RPC, SSL, ICMP, and Serial Port.

    Key features:

    • Header-only: No need to build a library, but requires C++17.
    • No Boost dependency: Can use standalone asio or boost::asio.
    • Reliable UDP: Supports KMP-based reliable UDP.
    • TCP Unpacking: Supports data unpacking via specific characters, strings, or user-defined protocols.
    • Cross-platform: Supports Windows, Linux, macOS, ARM, and Android (32/64-bit) using MSVC, GCC, Clang, NDK, or MinGW.
  2. Use Manually Triggered Condition Events

    main

    You can post a condition_event to a client that remains in a pending state until explicitly notified. This is useful for implementing custom state-machine logic where an asynchronous action should only occur after a specific data pattern is received.

    1. Use client.post_condition_event([](){ ... }) to create the event.
    2. Store the returned std::shared_ptr<asio2::condition_event>.
    3. Call event_ptr->notify() inside a bind_recv callback when your condition is met.
    asio2::tcp_client client;
    auto event_ptr = client.post_condition_event([]() {
        // This runs only when notified
    });
    
    client.bind_recv([&](std::string_view data) {
        if (data == "trigger") {
            event_ptr->notify();
        }
    });
  3. Use AOP (Aspect-Oriented Programming) in HTTP handlers

    main

    You can inject logic before and after HTTP request processing by passing an object that implements before and after methods to the bind function.

    • before(http::web_request& req, http::web_response& rep): Called before the handler. Return true to continue, false to abort.
    • after(std::shared_ptr<asio2::http_session>& session_ptr, http::web_request& req, http::web_response& rep): Called after the handler.

    Note: Some hooks may accept std::shared_ptr<asio2::http_session> while others do not, depending on the specific binding context.

    struct aop_log
    {
        bool before(http::web_request& req, http::web_response& rep)
        {
            printf("before %s\n", req.method_string().data());
            return true;
        }
        bool after(std::shared_ptr<asio2::http_session>& session_ptr, http::web_request& req, http::web_response& rep)
        {
            printf("after\n");
            return true;
        }
    };
    
    // Usage in bind
    server.bind<http::verb::get>("/api/user/*", [](http::web_request& req, http::web_response& rep) {
        rep.fill_text("data");
    }, aop_log{});
  4. How asio2 manages server lifecycle

    main

    Unlike many asio-based frameworks where server.run() is a blocking call that requires manual handling of shutdowns and resource cleanup, asio2 uses a non-blocking start() and a blocking stop() pattern.

    • server.start(...): Non-blocking. You can call this from initialization routines (like OnInitDialog in MFC).
    • server.stop(): Blocking. It ensures that all pending data is sent and all connections are normally closed before exiting, handling resource cleanup automatically.
  5. Trigger Manually Triggered Events

    main

    You can post a condition_event to a client that remains unexecuted until explicitly notified. This is useful for implementing custom state-driven logic in asynchronous flows.

    1. Use client.post_condition_event(callback) to create the event.
    2. Store the returned std::shared_ptr<asio2::condition_event>.
    3. Call event_ptr->notify() inside a callback (like bind_recv) to trigger the execution of the original callback.
    asio2::tcp_client client;
    std::shared_ptr<asio2::condition_event> event_ptr = client.post_condition_event([]() {
        // This runs only when notified
    });
    
    client.bind_recv([&](std::string_view data) {
        if (data == "trigger") {
            event_ptr->notify();
        }
    });
  6. Perform HTTP/HTTPS client operations

    main

    The asio2::http_client and asio2::https_client provide several ways to interact with web services:

    1. Direct Download: download(url, filename) saves a file directly to disk.
    2. Streaming Download: Use download(ssl_context, url, body_callback) to process chunks of data manually (e.g., writing to a file stream).
    3. Execute Request: execute(url) or execute(host, port, request) performs a synchronous request and returns a response.
    4. URL Utilities:
      • http::url_to_path(url)
      • http::url_to_query(url)
      • http::url_encode(string)
      • http::url_decode(string)
    // Download file directly
    asio2::https_client::download("https://example.com/file.exe", "file.exe");
    
    // Execute request and get response
    auto rep = asio2::http_client::execute("http://www.baidu.com/get_user?name=abc");
    if (asio2::get_last_error()) {
        std::cout << asio2::last_error_msg() << std::endl;
    } else {
        std::cout << rep << std::endl;
    }
  7. Implement an RPC Server with asio2

    main

    The asio2::rpc_server allows you to bind global functions, member functions, or lambdas to be called remotely.

    Server Initialization: asio2::rpc_server server(init_recv_buffer_size, max_recv_buffer_size, thread_count);

    • max_recv_buffer_size is used to prevent malicious packets; if a packet exceeds this, the client is automatically disconnected.

    Binding Methods:

    • Global functions: server.bind("name", func);
    • Member functions (by reference): server.bind("name", &Class::method, instance);
    • Member functions (by pointer): server.bind("name", &Class::method, &instance);
    • Lambdas: server.bind("name", [](args...) { ... });

    Function Signature for Session Access: If a function needs to know which client called it, the first parameter should be std::shared_ptr<asio2::rpc_session>& session_ptr.

    int add(std::shared_ptr<asio2::rpc_session>& session_ptr, int a, int b)
    {
    	return a + b;
    }
    
    asio2::rpc_server server(
    	512,  // initialize recv buffer size
    	1024, // max recv buffer size
    	4     // thread count
    );
    
    server.bind("add", add);
    server.start("0.0.0.0", 8080);
  8. Use UDP and Reliable UDP (KCP) in asio2

    main

    asio2 provides both standard UDP and reliable UDP based on KCP.

    UDP Server/Client:

    • Standard UDP: Use server.start("0.0.0.0", 8080) or client.start("0.0.0.0", 8080).
    • Reliable UDP (KCP): Use asio2::use_kcp as the third argument in start or async_start.

    Note: For the client, use client.async_start("0.0.0.0", 8080, asio2::use_kcp); for asynchronous reliable UDP.

    // UDP Server
    asio2::udp_server server;
    server.start("0.0.0.0", 8080); // general UDP
    // server.start("0.0.0.0", 8080, asio2::use_kcp); // Reliable UDP
    
    // UDP Client
    asio2::udp_client client;
    client.start("0.0.0.0", 8080);
    // client.async_start("0.0.0.0", 8080, asio2::use_kcp); // Reliable UDP
  9. Cross-compile OpenSSL for Arm

    main

    To cross-compile OpenSSL for Arm (e.g., for Raspberry Pi 4), follow these steps to set up the GNU Arm Toolchain and configure the build.

    1. Toolchain Setup

    1. Download the arm gcc compiler (specifically gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf.tar.xz) from the Arm Developer website.
      • Note: Avoid packages containing none-linux as they are intended for kernels, not applications. Ensure the package supports -pthread.
    2. Extract the toolchain to a directory (e.g., /usr/local/gcc-arm-...).
    3. Add the toolchain bin directory to your PATH in /etc/profile: export PATH=$PATH:/usr/local/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin
    4. Verify installation with arm-linux-gnueabihf-gcc -v.

    2. OpenSSL Cross-Compilation

    1. Navigate to the OpenSSL source directory.
    2. Run the configuration command with the CROSS_COMPILE and CC flags. Use --api=0.9.8 to maintain compatibility with older, deprecated APIs.
    3. Run make and make install.

    Troubleshooting:

    • If you encounter -m64 errors, edit the Makefile and remove all -m64 flags.
    • When including OpenSSL headers in your C/C++ code, add #define OPENSSL_API_COMPAT 0x00908000L before the includes to ensure compatibility.

    3. Compiling and Linking your Application

    Once OpenSSL is cross-compiled, use the arm-specific compiler to build your project, linking against the static libraries (libssl.a and libcrypto.a).

    # Configure OpenSSL for Arm
    ./config no-asm no-shared --api=0.9.8 --prefix=/opt/openssl --openssldir=/usr/local/ssl CROSS_COMPILE=arm-linux-gnueabihf- CC=gcc
    
    # Build and Install
    make
    make install
    
    # Example: Compiling a C++ application
    arm-linux-gnueabihf-g++ -c -x c++ main.cpp -I /usr/local/include -I /opt/openssl/include -g2 -gdwarf-2 -o main.o -Wall -Wswitch -W"no-deprecated-declarations" -W"empty-body" -Wconversion -W"return-type" -W"parentheses" -W"no-format" -W"uninitialized" -W"unreachable-code" -W"unused-function" -W"unused-value" -W"unused-variable" -O3 -fno-strict-aliasing -fno-omit-frame-pointer -fthreadsafe-statics -fexceptions -frtti -std=c++17
    
    # Example: Linking the application
    arm-linux-gnueabihf-g++ -o main.out -Wl,--no-undefined -Wl,-L/opt/openssl/lib -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -pthread -lrt -ldl -Wl,-rpath=. main.o -lstdc++fs -l:libssl.a -l:libcrypto.a
  10. Implement an HTTP Server

    main

    Use asio2::http_server to create a web server. You can bind lifecycle callbacks for connection events and define route handlers using bind. Route handlers can be matched by path and HTTP verb (e.g., GET, POST).

    Key lifecycle callbacks:

    • bind_recv: Triggered when a request is received.
    • bind_connect: Triggered when a client connects.
    • bind_disconnect: Triggered when a client leaves.
    • bind_start: Triggered when the server starts.
    • bind_stop: Triggered when the server stops.
    • bind_not_found: Triggered when a requested path does not match any route.

    Route handlers can also accept AOP (Aspect Oriented Programming) objects to intercept requests/responses.

    asio2::http_server server;
    
    server.bind_recv([&](http::request& req, http::response& rep) {
        std::cout << req.path() << std::endl;
    });
    
    server.bind<http::verb::get>("/index.*", [](http::request& req, http::response& rep) {
        rep.fill_file("../index.html");
        rep.chunked(true);
    });
    
    server.start(host, port);
  11. Configure SSL for TCP/HTTP/WebSocket

    main

    To enable SSL, ensure #define ASIO2_USE_SSL is uncommented in config.hpp. Use asio2::tcps_server for SSL-enabled TCP servers.

    Server Configuration:

    • set_verify_mode: Sets SSL verification behavior (e.g., asio::ssl::verify_peer).
    • set_cert_buffer: Loads certificates from memory strings.
    • set_cert_file: Loads certificates from files.
    • set_dh_buffer / set_dh_file: Configures Diffie-Hellman parameters.

    Client Configuration:

    • Set verify_mode (e.g., verify_none or verify_peer). If verify_peer is used, a CA certificate buffer must be provided.
    asio2::tcps_server server;
    server.set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert);
    server.set_cert_file("ca.crt", "server.crt", "server.key", "server");
    server.set_dh_file("dh1024.pem");
  12. Implement a TCP Server

    main

    To create a TCP server, instantiate asio2::tcp_server and bind callbacks for receiving data, connection events, and disconnections. Use start() to begin listening.

    Key callbacks:

    • bind_recv: Triggered when data is received. Provides a std::shared_ptr<asio2::tcp_session> and the received data as a std::string_view.
    • bind_connect: Triggered when a client connects. Allows configuring session properties like no_delay(true) or starting session-specific timers.
    • bind_disconnect: Triggered when a client disconnects.

    start() supports various packet framing (splitting) strategies:

    • Single character: server.start("0.0.0.0", "8080", '\n')
    • String delimiter: server.start("0.0.0.0", "8080", "\r\n")
    • Fixed size: server.start("0.0.0.0", "8080", asio::transfer_exactly(100))
    • Datagram mode (guaranteed whole packets): server.start("0.0.0.0", "8080", asio2::use_dgram)
    asio2::tcp_server server;
    server.bind_recv([&](std::shared_ptr<asio2::tcp_session> & session_ptr, std::string_view s)
    {
    	printf("recv : %zu %.*s\n", s.size(), (int)s.size(), s.data());
    	session_ptr->async_send(s);
    }).bind_connect([&](auto & session_ptr)
    {
    	session_ptr->no_delay(true);
        // session_ptr->start_timer(1, std::chrono::seconds(1), []() {});
    }).bind_disconnect([&](auto & session_ptr)
    {
    	printf("client leave : %s %u %s %u\n",
    		session_ptr->remote_address().c_str(), session_ptr->remote_port(),
    		asio2::last_error_msg().c_str());
    });
    server.start("0.0.0.0", "8080");