C++ RPC Utility Library Based on rpclib in Occlum Environment

RPC Utils

Project Address:View OriginalThis is a C++ RPC utility library based on rpclib in the Occlum environment, providing simplified and user-friendly RPC client and server wrappers.

  • • Compiled rpclib into a static link file using occlum-g++ in the Occlum environment. If used in other environments, it needs to be recompiled.
  • • A quick implementation done while learning about confidential computing.

Project Structure

rpc_utils/
├── include/                    # Header files
│   ├── rpc/                   # rpclib header files (integrated)
│   ├── rpc_client_wrapper.h   # RPC client wrapper class
│   ├── rpc_server_wrapper.h   # RPC server wrapper class
│   └── rpc_utils.h            # Common utility class (logging, timer, etc.)
├── lib/                       # Library files
│   └── librpc.a              # rpclib static library
├── src/                       # Source files
│   ├── client/                # Client implementation
│   │   └── rpc_client_wrapper.cpp
│   ├── server/                # Server implementation
│   │   └── rpc_server_wrapper.cpp
│   └── common/                # Common utility implementation
│       └── rpc_utils.cpp
├── examples/                  # Example programs
│   ├── example_server.cpp     # Server example
│   └── example_client.cpp     # Client example
├── CMakeLists.txt            # CMake build configuration
└── README.md                 # This file

Features

RPCClientWrapper – Client Wrapper

  • • ✅ Simplified connection management
  • • ✅ Synchronous and asynchronous function calls
  • • ✅ Notification sending (calls without return values)
  • • ✅ Timeout configuration
  • • ✅ Connection status query
  • • ✅ Exception handling and error encapsulation

RPCServerWrapper – Server Wrapper

  • • ✅ Simplified server initialization
  • • ✅ Flexible function binding (supports function pointers, lambdas, member functions, etc.)
  • • ✅ Synchronous and asynchronous running modes
  • • ✅ Graceful shutdown
  • • ✅ Exception suppression mode
  • • ✅ Session management

Common Utilities

  • • ✅ Logger: Hierarchical logging system (DEBUG, INFO, WARNING, ERROR)
  • • ✅ Timer: High-precision performance timer
  • • ✅ RPCUtils: Utility functions for address validation, error formatting, etc.

Build Instructions

Prerequisites

  1. 1. A C++14 or higher compiler (recommended to use the Occlum toolchain)
  2. 2. CMake 3.10 or higher
  3. 3. rpclib library (included in the project’s include/rpc and lib/ directories)

Build Steps

# 1. Enter the rpc_utils directory
cd /workspaces/te/confidential_computing/rpc_utils

# 2. Create a build directory
mkdir -p build
cd build

# 3. Configure CMake
cmake ..

# 4. Compile
make

# 5. After compilation, the following files will be generated:
#    - librpc_utils_common.a    # Common utility library
#    - librpc_utils_client.a    # Client library
#    - librpc_utils_server.a    # Server library
#    - example_server            # Server example program
#    - example_client            # Client example program

Compilation Options

# Option 1: Use standard compiler
cmake ..

# Option 2: Explicitly use Occlum compiler (recommended)
cmake -DCMAKE_C_COMPILER=occlum-gcc -DCMAKE_CXX_COMPILER=occlum-g++ ..

# Do not compile example programs
cmake -DBUILD_EXAMPLES=OFF ..

# Specify installation path
cmake -DCMAKE_INSTALL_PREFIX=/usr/local ..

Usage Examples

Server Example

#include "rpc_server_wrapper.h"
#include "rpc_utils.h"

// Define RPC function
double add(double a, double b) {
    return a + b;
}

int main() {
    // Set log level
    rpc_utils::Logger::set_log_level(rpc_utils::LogLevel::INFO);
    
    // Create server
    rpc_utils::RPCServerWrapper server(8080);
    
    // Bind function
    server.bind("add", &add);
    
    // Bind lambda
    server.bind("multiply", [](double a, double b) {
        return a * b;
    });
    
    // Run server (blocking)
    rpc_utils::Logger::info("Server starting on port 8080");
    server.run();
    
    return 0;
}

Client Example

#include "rpc_client_wrapper.h"
#include "rpc_utils.h"

int main() {
    // Create client, connect to localhost:8080, timeout 5 seconds
    rpc_utils::RPCClientWrapper client("localhost", 8080, 5000);
    
    // Synchronous call
    double result = client.call<double>("add", 10.0, 20.0);
    rpc_utils::Logger::info("add(10, 20) = " + std::to_string(result));
    
    // Asynchronous call
    auto future = client.async_call("multiply", 5.0, 6.0);
    // Do other things...
    double async_result = future.get().as<double>();
    rpc_utils::Logger::info("multiply(5, 6) = " + std::to_string(async_result));
    
    // Send notification (no wait for return)
    client.send_notification("log", "Hello from client");
    
    return 0;
}

Running Example Programs

1. Start the Server

# Use default port 8080
./example_server

# Or specify port
./example_server 9000

After the server starts, it will display:

[2025-11-05 12:00:00.000] [INFO] Server started successfully on port 8080
[2025-11-05 12:00:00.000] [INFO] Available functions:
[2025-11-05 12:00:00.000] [INFO]   - add(double, double) -> double
[2025-11-05 12:00:00.000] [INFO]   - subtract(double, double) -> double
...

2. Run the Client

In another terminal:

# Connect to localhost:8080
./example_client

# Or specify server address and port
./example_client 127.0.0.1 9000

The client will perform a series of tests and display results:

[2025-11-05 12:01:00.000] [INFO] Connected successfully
----------------------------------------
[2025-11-05 12:01:00.000] [INFO] Testing arithmetic operations:
[2025-11-05 12:01:00.100] [INFO] add(10.5, 20.3) = 30.800000 (took 2.5 ms)
...

API Documentation

RPCClientWrapper

// Constructor
RPCClientWrapper(const std::string& host, uint16_t port, int64_t timeout_ms = 5000);

// Synchronous call
template<typename R, typename... Args>
R call(const std::string& func_name, Args&&... args);

// Asynchronous call
template<typename... Args>
auto async_call(const std::string& func_name, Args&&... args) 
    -> std::future<RPCLIB_MSGPACK::object_handle>;

// Send notification
template<typename... Args>
void send_notification(const std::string& func_name, Args&&... args);

// Timeout management
void set_timeout(int64_t timeout_ms);
void clear_timeout();

// Status query
bool is_connected() const;
rpc::client::connection_state get_connection_state() const;
void wait_all_responses();

RPCServerWrapper

// Constructor
explicit RPCServerWrapper(uint16_t port);
RPCServerWrapper(const std::string& address, uint16_t port);

// Bind function
template<typename F>
void bind(const std::string& name, F&& func);

// Run control
void run();                              // Synchronous run (blocking)
void async_run(size_t worker_threads);   // Asynchronous run
void stop();                             // Stop server

// Configuration
void suppress_exceptions(bool suppress);
uint16_t port() const;

// Session management
void close_all_sessions();
bool is_running() const;

Logger

// Set log level
static void set_log_level(LogLevel level);

// Log recording
static void debug(const std::string& message);
static void info(const std::string& message);
static void warning(const std::string& message);
static void error(const std::string& message);

Timer

Timer();                      // Start timing upon construction
void reset();                 // Reset timer
double elapsed_ms() const;    // Get elapsed milliseconds
double elapsed_sec() const;   // Get elapsed seconds

Error Handling

All RPC call failures will throw a <span>std::runtime_error</span> exception, containing detailed error information:

try {
    double result = client.call&lt;double&gt;("divide", 10.0, 0.0);
} catch (const std::runtime_error&amp; e) {
    // Error message format:
    // "RPC call failed for function 'divide': Division by zero"
    std::cerr &lt;&lt; "Error: " &lt;&lt; e.what() &lt;&lt; std::endl;
}

Best Practices

  1. 1. Exception Handling: Always wrap RPC calls in try-catch blocks
  2. 2. Timeout Settings: Set appropriate timeout for long-running operations
  3. 3. Log Level: Use INFO or WARNING level in production, DEBUG level during development
  4. 4. Asynchronous Calls: Use async_call for batch operations to improve performance
  5. 5. Resource Cleanup: Use RAII to automatically clean up resources when objects are destructed

Notes

  • • All types passed through RPC must support msgpack serialization
  • • Custom types need to use the <span>MSGPACK_DEFINE</span> macro for serialization
  • • The client and server must have a consistent understanding of function signatures
  • • The default port 8080 may conflict with other services, please modify as needed

License

This project follows the license of rpclib.

Contributions

Contributions via Issues and Pull Requests are welcome!

Contact

If you have any questions, please contact via Issues.

Leave a Comment