
[ English | 中文 ]
Introduction
This project is lightweight, has minimal dependencies, and can be integrated into various communication methods (TCP, UDP, message queues, etc.). It is simple to use (with only a few functional APIs) and has good performance (using hash-based indexing instead of polling all methods). It also supports batch calls (JSON Array), automatic generation of corresponding error messages or custom error messages based on requests, customizable memory management hooks, notification requests, and more.
Features
- Lightweight & Minimal Dependencies: Only depends on cJSON
- Hash-based Method Indexing: Fast method lookup using double hashing algorithm
- Batch Requests: Support for JSON Array batch calls
- Customizable Memory Management: User-defined malloc/free/strdup hooks
- Thread-Safe: Thread-local storage for memory hooks
- POSIX Array Params: Support for both object and array parameters
- Method Enumeration: Query registered methods at runtime
- Error Logging: Optional error logging hooks for debugging
How to Use
Simply add the project source files (mjsonrpc.c, mjsonrpc.h) and the cJSON library to your own project and compile them together. Alternatively, you can use CMake to build and install:
cmake -B build && cmake --build build
sudo cmake --install build
Examples
Basic Usage
#include <stdio.h>
#include <stdlib.h>
cJSON *result = cJSON_CreateString("Hello, World!");
return result;
}
int main() {
const char *json_request = "{\"jsonrpc\":\"2.0\",\"method\":\"hello\",\"id\":1}";
int result;
printf("Error processing request: %d\n", result);
}
if (json_response) {
printf("Response: %s\n", json_response);
free(json_response);
}
return 0;
}
int mjrpc_destroy_handle(mjrpc_handle_t *handle)
Destroy a JSON-RPC handle and free all associated memory.
mjrpc_handle_t * mjrpc_create_handle(size_t initial_capacity)
Create a new JSON-RPC handle.
int mjrpc_add_method(mjrpc_handle_t *handle, mjrpc_func function_pointer, const char *method_name, void *arg2func)
Register a new RPC method.
char * mjrpc_process_str(const mjrpc_handle_t *handle, const char *request_str, int *ret_code)
Process a JSON-RPC request string.
A lightweight JSON-RPC 2.0 message parser and generator based on cJSON.
@ MJRPC_RET_OK
Operation completed successfully.
Context structure passed to RPC method callback functions.
Main handle structure for managing RPC methods.
Method with Parameters
int a = 0, b = 0;
cJSON *a_item = cJSON_GetObjectItem(params, "a");
cJSON *b_item = cJSON_GetObjectItem(params, "b");
if (cJSON_IsNumber(a_item)) a = a_item->valueint;
if (cJSON_IsNumber(b_item)) b = b_item->valueint;
return cJSON_CreateNumber(a + b);
}
int main() {
const char *req = "{\"jsonrpc\":\"2.0\",\"method\":\"add\","
"\"params\":{\"a\":5,\"b\":3},\"id\":1}";
int code;
printf("Response: %s\n", resp);
free(resp);
return 0;
}
Batch Requests
int a = cJSON_GetArrayItem(params, 0)->valueint;
int b = cJSON_GetArrayItem(params, 1)->valueint;
return cJSON_CreateNumber(a * b);
}
int main() {
const char *batch_req = "["
"{\"jsonrpc\":\"2.0\",\"method\":\"mul\",\"params\":[3,4],\"id\":1},"
"{\"jsonrpc\":\"2.0\",\"method\":\"mul\",\"params\":[5,6],\"id\":2}"
"]";
int code;
printf("Batch Response: %s\n", resp);
free(resp);
return 0;
}
Custom Error Handling
cJSON *age = cJSON_GetObjectItem(params, "age");
if (!cJSON_IsNumber(age) || age->valueint < 0 || age->valueint > 150) {
return NULL;
}
return cJSON_CreateString("Age validated");
}
int32_t error_code
Error code to be set by the method implementation (0 = no error)
char * error_message
Error message to be set by the method implementation (will be freed automatically)
Custom Memory Management
void* my_malloc(size_t size) {
printf("Allocating %zu bytes\n", size);
return malloc(size);
}
void my_free(void* ptr) {
printf("Freeing %p\n", ptr);
free(ptr);
}
char* my_strdup(const char* str) {
char* dup = my_malloc(strlen(str) + 1);
if (dup) strcpy(dup, str);
return dup;
}
int main() {
return 0;
}
int mjrpc_set_memory_hooks(mjrpc_malloc_func malloc_func, mjrpc_free_func free_func, mjrpc_strdup_func strdup_func)
Set custom memory management functions.
Error Logging
void my_error_logger(const char* msg, int code) {
fprintf(stderr, "[MJSONRPC ERROR] %s (code: %d)\n", msg, code);
}
int main() {
return 0;
}
int mjrpc_set_error_log_hook(mjrpc_error_log_func error_log_func)
Set custom error logging function.
Performance
Hash Table Performance Comparison
| Operation | Quadratic Probing | Double Hashing (new) |
| Avg probes (low load) | ~1.5 | ~1.2 |
| Avg probes (high load) | ~4.0 | ~2.0 |
| Worst case | O(n) | O(n) |
Method Registration Throughput
| Methods | Add (ops/sec) | Delete (ops/sec) | Lookup (ops/sec) |
| 100 | ~500K | ~600K | ~800K |
| 1000 | ~450K | ~550K | ~750K |
| 10000 | ~400K | ~500K | ~700K |
Tested on Intel i7, 3.2GHz, single-threaded
FAQ
Q: Is mjsonrpc thread-safe?
A: The library uses thread-local storage for memory hooks, making it safe to use from multiple threads. However, you should protect shared mjrpc_handle_t instances with your own synchronization (mutexes) if accessed from multiple threads concurrently.
Q: How do I handle notification requests (no response)?
A: Notification requests are those without an id field. The library processes them normally but returns NULL for the response. Check the return code for MJRPC_RET_OK_NOTIFICATION.
const char *notif = "{\"jsonrpc\":\"2.0\",\"method\":\"log\",\"params\":{\"msg\":\"hello\"}}";
int code;
printf("Notification processed (no response)\n");
}
@ MJRPC_RET_OK_NOTIFICATION
Operation completed successfully (notification request)
Q: Can I use array parameters instead of object parameters?
A: Yes! Both object and array parameters are supported:
const char *req = "{\"jsonrpc\":\"2.0\",\"method\":\"add\",\"params\":[1,2],\"id\":1}";
Q: How do I enumerate all registered methods?
A: Use mjrpc_enum_methods() to get an array of method names:
cJSON_Delete(methods);
int mjrpc_enum_methods(const mjrpc_handle_t *handle, void(*callback)(const char *method_name, void *arg, void *user_data), void *user_data)
Enumerate all registered methods.
Q: What happens if the hash table needs to resize?
A: The library automatically resizes the hash table when the load factor exceeds 0.75. This is transparent to the user. The resize operation uses double hashing for rehashing, which provides better distribution.
Q: How do I clean up resources properly?
A: Always call mjrpc_destroy_handle() when done. This frees all internally allocated memory. Any cJSON objects returned to you should be freed with cJSON_Delete() or free() as documented.
References