嵌入式上位机开发入门(二十九):JsonRPC TCP Server

目录


一、前言

大家好,这里是 Hello_Embed。上篇完成了 JSON 与 JsonRPC 的概念入门------了解了 JSON 的 7 种数据类型、cJSON 的增删改查,以及 JsonRPC "本地函数语法 + JSON 网络传输" 的工作原理。

本篇进入实战,基于开源库 jsonrpc-cpp 搭建 JsonRPC TCP Server,在本机上运行起来,并用串口调试工具手动发送 JSON 报文进行测试。


二、开源库简介

本文使用的开源项目:https://github.com/s-vincent/jsonrpc-cpp

该库基于 Jsoncpp + socket 封装,提供了开箱即用的 Json::Rpc::TcpServer 类,支持方法注册、监听、消息分发等完整流程,非常适合在上位机中快速搭建 JsonRPC 服务端。

注意 :编译需要先链接 Jsoncpp 和 jsonrpc-cpp 库。如果使用 VS Code,推荐在 tasks.json 中添加 Jsoncpp 的链接选项和各源文件路径,生成可执行程序后运行 .\tcp-server.exe。本文略过编译步骤,聚焦代码结构。


三、主函数结构

示例工程的 tcp_server.cpp 主函数如下:

cpp 复制代码
int main(int argc, char** argv)
{
    TestRpc a;                                           // RPC 方法的宿主对象

    Json::Rpc::TcpServer server(std::string("127.0.0.1"), 8086); // 监听本地 8086 端口

    (void)argc;                                          // 消除编译器未使用参数的警告
    (void)argv;

    /* 初始化网络层(Windows 下需要 WSAStartup) */
    if (!networking::init())
    {
        std::cerr << "Networking initialization failed" << std::endl;
        exit(EXIT_FAILURE);
    }

    /* 注册信号处理,Ctrl+C / kill 信号时优雅退出 */
    if (signal(SIGTERM, signal_handler) == SIG_ERR)
        std::cout << "Error signal SIGTERM will not be handled" << std::endl;

    if (signal(SIGINT, signal_handler) == SIG_ERR)
        std::cout << "Error signal SIGINT will not be handled" << std::endl;

    /* 向服务器注册 RPC 方法,字符串是客户端调用时使用的方法名 */
    server.AddMethod(new Json::Rpc::RpcMethod<TestRpc>(a, &TestRpc::Print,  std::string("print")));
    server.AddMethod(new Json::Rpc::RpcMethod<TestRpc>(a, &TestRpc::add,    std::string("add")));
    server.AddMethod(new Json::Rpc::RpcMethod<TestRpc>(a, &TestRpc::Notify, std::string("notify")));

    /* server.SetEncapsulatedFormat(Json::Rpc::NETSTRING); */ // 可选:启用 Netstring 封装格式

    /* 绑定地址 + 端口 */
    if (!server.Bind())
    {
        std::cout << "Bind failed" << std::endl;
        exit(EXIT_FAILURE);
    }

    /* 开始监听连接 */
    if (!server.Listen())
    {
        std::cout << "Listen failed" << std::endl;
        exit(EXIT_FAILURE);
    }

    g_run = 1;
    std::cout << "Start JSON-RPC TCP server" << std::endl;

    /* 主循环:每次最多等待 1000ms,有消息则分发给对应 RPC 方法处理 */
    while (g_run)
    {
        server.WaitMessage(1000);
    }

    std::cout << "Stop JSON-RPC TCP server" << std::endl;
    server.Close();          // 关闭服务器套接字
    networking::cleanup();   // 清理网络层资源(Windows 下 WSACleanup)

    return EXIT_SUCCESS;
}

解析WaitMessage(1000) 是整个服务器的核心驱动------它在内部调用 select() 等待最多 1000ms,有新连接或新数据时立即分发到已注册的 RPC 方法。超时后继续循环,同时检查 g_run 标志,这样信号处理函数只需把 g_run 置 0,主循环就会自然退出。


四、注册 RPC 方法

注册的核心语句:

cpp 复制代码
server.AddMethod(new Json::Rpc::RpcMethod<TestRpc>(
    a,                  // 方法所属的对象实例
    &TestRpc::Print,    // 指向成员函数的指针
    std::string("print") // 客户端调用时使用的方法名字符串
));

这行代码的含义:客户端发来 "method": "print" 时,服务器会调用对象 aTestRpc::Print 函数。方法名是字符串映射,可以随意起名,客户端和服务端约定好即可。


五、Print 函数实现

Print 是最简单的 RPC 方法------收到请求后打印内容,并返回 "success"

cpp 复制代码
bool TestRpc::Print(const Json::Value& root, Json::Value& response)
{
    /* root 是客户端发来的完整 JSON 请求,包含 jsonrpc/method/params/id 字段 */
    std::cout << "Receive query: " << root << std::endl;

    /* 构造 JSON-RPC 2.0 规范的响应 */
    response["jsonrpc"] = "2.0";        // 协议版本,固定填 "2.0"
    response["id"]      = root["id"];   // 回显请求的 id,便于客户端匹配
    response["result"]  = "success";    // 返回值

    return true; // 返回 true 表示处理成功,库会自动把 response 序列化并发回
}

解析 :所有 RPC 方法的签名都是固定的 (const Json::Value& root, Json::Value& response)------root 是收到的请求,response 是要填写的返回值。库负责收发、序列化/反序列化,我们只需要专注业务逻辑,非常干净。


六、add 函数实现

add 是一个带参数的 RPC 方法:从 params 数组里取两个整数相加,把结果写进 response

cpp 复制代码
bool TestRpc::add(const Json::Value& root, Json::Value& response)
{
    /* 客户端调用格式:{"jsonrpc":"2.0","method":"add","params":[1,2],"id":1} */

    Json::Value params = root["params"]; // 取出 params 数组

    int a   = params[0u].asInt();  // 取第 1 个参数,0u 表示下标 0(无符号整数避免歧义)
    int b   = params[1u].asInt();  // 取第 2 个参数
    int sum = a + b;

    /* 构造响应 */
    response["jsonrpc"] = "2.0";
    response["id"]      = root["id"];
    response["result"]  = sum;     // 把计算结果直接放入 result 字段

    return true;
}

解析params[0u]0u 是无符号整数字面量。Jsoncpp 的 [] 运算符同时重载了 intUInt 版本,用 0u 可以明确调用数组下标版本,避免与按字符串键名查找产生歧义,是一个常见的写法习惯。


七、串口工具测试

编译成功后,运行 .\tcp-server.exe,打开串口调试工具,模式切换为 TCP Client ,目标 IP 127.0.0.1,端口 8086

测试 Print 方法

发送:

json 复制代码
{"jsonrpc":"2.0","method":"print","id":1}

终端输出:

复制代码
Receive query:
{
    "id" : 1,
    "jsonrpc" : "2.0",
    "method" : "print"
}

服务端收到请求后打印完整报文,如下图所示:

测试 add 方法

发送:

复制代码
[10:03:33.402] 发→ {"jsonrpc":"2.0","method":"add","params":[2,2],"id":1}
[10:03:33.403] 收← {"id":1,"jsonrpc":"2.0","result":4}

2 + 2 = 4,结果正确,测试通过。


八、总结

知识点 要点
jsonrpc-cpp 库 基于 Jsoncpp + socket 封装,TcpServer 类开箱即用
AddMethod 将 C++ 成员函数绑定到 RPC 方法名字符串,客户端按名调用
RPC 函数签名 (const Json::Value& root, Json::Value& response),固定格式
读取参数 root["params"][0u].asInt()0u 避免 [] 重载歧义
构造响应 填写 jsonrpcidresult 三个字段,返回 true
主循环 WaitMessage(1000) 驱动消息分发,信号置 g_run=0 退出

九、结尾

本篇搭建了 JsonRPC TCP Server,完成了 Printadd 两个 RPC 方法的注册与实现,并通过串口调试工具验证了请求-响应的完整流程。

下一篇将实现对应的 JsonRPC TCP Client,让两端真正联通起来,敬请期待~

Hello_Embed 继续带你从原理到实践,掌握嵌入式上位机开发的核心技能,敬请关注~

相关推荐
唔662 小时前
mDNS 就是局域网里的“零配置DNS“
网络·智能路由器
S1998_1997111609•X2 小时前
IP:/-cn,?$&-192=80~3306/- SQL if(REC)
网络协议
南境十里·墨染春水2 小时前
linux学习进展 网络基础
linux·网络·学习
笨笨饿2 小时前
# 67_MCU的几大分区
数据结构·单片机·嵌入式硬件·算法·机器人·线性回归·个人开发
Rust研习社2 小时前
Reqwest 兼顾简洁与高性能的现代 HTTP 客户端
开发语言·网络·后端·http·rust
大熊背2 小时前
ISP Pipeline中Lv实现方式探究之六--lv值计算再优化
网络·算法·自动曝光·lv
RTC老炮2 小时前
WebRTC下FlexFEC算法架构及原理
网络·算法·音视频·webrtc
SDAU20052 小时前
CH552的时钟应用
stm32·单片机·嵌入式硬件
格林威2 小时前
面阵相机 vs 线阵相机:堡盟与Basler选型差异全解析 + Python实战演示
开发语言·网络·人工智能·python·数码相机·yolo·工业相机