嵌入式上位机开发入门(二十八):JSON 与 JsonRPC 入门

目录

  • 一、前言
  • [二、JSON 是什么](#二、JSON 是什么)
  • [三、JSON 的数据类型](#三、JSON 的数据类型)
  • [四、C++ 处理 JSON:Jsoncpp](#四、C++ 处理 JSON:Jsoncpp)
  • [五、C 处理 JSON:cJSON 库函数](#五、C 处理 JSON:cJSON 库函数)
  • [六、cJSON 实战:解析嵌套 JSON](#六、cJSON 实战:解析嵌套 JSON)
  • [七、JsonRPC 是什么](#七、JsonRPC 是什么)
  • [八、JsonRPC 调用流程](#八、JsonRPC 调用流程)
  • 九、总结
  • 十、结尾

一、前言

大家好,这里是 Hello_Embed。上篇完成了 MQTT 的调试与容错优化,让客户端在弱网环境下更扛造。

本篇引入两个新概念:JSONJsonRPC。在上位机软件框架中,前台 GUI 与后台控制中心通过网络通信,数据会以 JSON 格式封装成 JsonRPC 远程调用------这是本系列后半段的技术基础。


二、JSON 是什么

JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种轻量的数据交换格式,易于人阅读和编写,也易于机器解析和生成。它独立于编程语言,几乎所有主流语言都支持 JSON 数据交换。

JSON 的核心结构是 键值对(name:value),用"名称"去索引对应的"值",类似 C 语言的字典或结构体成员。

解析:JSON 和 XML 都是常见的数据交换格式,但 JSON 更轻量------同一条数据,JSON 的字符数通常只有 XML 的 1/3~1/2,网络传输效率更高,这也是为什么现代 Web API 和上位机通信几乎都选 JSON。


三、JSON 的数据类型

JSON 的值(value)支持以下几种类型:

类型 示例 说明
字符串 "John" 双引号包裹,支持转义字符
整数 30 不带小数点的数字
浮点数 4.5 带小数点的数字
布尔值 true / false 注意是全小写
空值 null 表示"无值"
数组 ["apple", "banana"] 有序列表,元素类型可混
对象 { "name": "John" } 键值对集合,可嵌套

下面是一个包含多种类型的 JSON 示例:

json 复制代码
{
    "title": "JSON Example",
    "author": {
        "name": "John Doe",
        "age": 35,
        "isVerified": true
    },
    "tags": ["json", "syntax", "example"],
    "rating": 4.5,
    "isPublished": false,
    "comments": null
}

这个例子展示了两层嵌套:author 是一个子对象,tags 是一个字符串数组。

解析 :在上位机场景中,传感器数据通常打包成 JSON 对象,比如 { "temp": 25.6, "humid": 60 },传输效率高,解析也简单。


四、C++ 处理 JSON:Jsoncpp

上位机程序通常用 C++ 开发,业界最常用的 C++ JSON 库是 Jsoncpp

官方仓库:https://github.com/open-source-parsers/jsoncpp

Jsoncpp 功能完整,支持从字符串解析 JSON、构建 JSON 对象、序列化回字符串,是上位机处理 JSON 的首选方案。

解析:上位机(PC 端)资源充足,用 Jsoncpp 这类功能完整的库没有问题。后续移植到嵌入式的 MQTT 部分,则更倾向用轻量的 cJSON。


五、C 处理 JSON:cJSON 库函数

在 C 语言环境下,可以使用 cJSON 库。相比 Jsoncpp,cJSON 更加轻量,适合资源受限的嵌入式场景。

cJSON 的核心使用流程如下:

5.1 解析:从字符串创建 cJSON 结构体

c 复制代码
/* 从 JSON 字符串解析出 cJSON 结构体(类似 JSON.parse()) */
cJSON *cJSON_Parse(const char *value);

/* 直接创建一个空的 JSON 对象(类似 {}) */
cJSON *cJSON_CreateObject(void);

5.2 写入:往对象里添加数据

c 复制代码
/* 添加各种类型的成员 */
cJSON_AddNullToObject(object, name);          /* 添加 null */
cJSON_AddTrueToObject(object, name);           /* 添加 true */
cJSON_AddFalseToObject(object, name);          /* 添加 false */
cJSON_AddBoolToObject(object, name, boolean);  /* 添加布尔值 */
cJSON_AddNumberToObject(object, name, number);  /* 添加数字 */
cJSON_AddStringToObject(object, name, string); /* 添加字符串 */
cJSON_AddObjectToObject(object, name);         /* 添加子对象 */
cJSON_AddArrayToObject(object, name);          /* 添加数组 */

5.3 读取:根据键名或下标取值

c 复制代码
/* 根据键名(key)获取成员,类似 JS 的 obj.name */
cJSON *cJSON_GetObjectItem(const cJSON *object, const char *string);

/* 根据数组下标获取元素,类似 JS 的 arr[0] */
cJSON *cJSON_GetArrayItem(const cJSON *array, int index);

cJSON 结构体内部存储了三种数值形式:valuestring(字符串值)、valueint(整数值)、valuedouble(浮点数值),直接访问即可。

5.4 释放:删除 cJSON 结构体

c 复制代码
/* 释放 cJSON 结构体及其子节点,避免内存泄漏 */
cJSON_Delete(cJSON *item);

解析 :cJSON 的设计思路是"一切皆节点"------数组和对象在 cJSON 眼里都是 cJSON * 指针,遍历时用 cJSON_GetArrayItem 按下标取,用 cJSON_GetObjectItem 按键名取。这种统一结构让代码写起来很简洁。


六、cJSON 实战:解析嵌套 JSON

用一段测试代码演示完整的解析流程:

c 复制代码
#include <stdio.h>
#include "cJSON.h"

int main(int argc, char** argv)
{
    /* 待解析的 JSON 字符串(多行字符串字面量) */
    const char* str = " \
        { \
            \"title\": \"JSON Example\", \
            \"author\": { \
                \"name\": \"John Doe\", \
                \"age\": 35, \
                \"isVerified\": true \
            }, \
            \"tags\": [\"json\", \"syntax\", \"example\"], \
            \"rating\": 4.5, \
            \"isPublished\": false, \
            \"comments\": null \
        }";

    /* 第一步:解析字符串 → cJSON 结构体 */
    cJSON *json = cJSON_Parse(str);
    if (!json) /* 解析失败返回 NULL */
    {
        printf("cJSON_Parse err\n");
        return 0;
    }

    /* 第二步:取 "author" 子对象 */
    cJSON *author = cJSON_GetObjectItem(json, "author");

    /* 第三步:从 author 中取 "age",强转为整数值打印 */
    cJSON *age = cJSON_GetObjectItem(author, "age");
    if (age)
        printf("age = %d\n", age->valueint); /* 输出:age = 35 */

    /* 第四步:取 "tags" 数组,再按下标取第 3 个元素(index=2) */
    cJSON *tags = cJSON_GetObjectItem(json, "tags");
    cJSON *item = cJSON_GetArrayItem(tags, 2); /* 0-indexed,取第3个 */
    if (item)
        printf("item = %s\n", item->valuestring); /* 输出:item = example */

    /* 第五步:解析完毕,释放内存 */
    cJSON_Delete(json);
    return 0;
}

运行结果:

复制代码
age = 35
item = example

解析cJSON_GetArrayItem 是 0-indexed(从 0 开始),所以 index=2 取的是第三个元素 "example"。解析完记得调用 cJSON_Delete 释放整个树,否则会内存泄漏。


七、JsonRPC 是什么

RPC(Remote Procedure Call,远程过程调用) 是一种通过网络调用另一台计算机上程序子进程的技术。对调用方来说,就像在调用本地函数一样,不需要关心网络细节。

在带 GUI 的上位机系统中,程序通常拆为前台 GUI后台控制中心两部分:

  • 前台:负责界面显示
  • 后台:负责数据处理、设备控制
  • 两者通过网络通信,用 JsonRPC 封装调用

JsonRPC 是 RPC 的一种,以 JSON 作为数据格式,工作原理概括如下:

  1. 客户端像调用本地函数一样调用接口(传入方法名和参数)
  2. 客户端将调用信息组装成 JSON 数据,通过 socket 发送
  3. 服务端收到后反序列化(解码),执行对应函数
  4. 服务端将执行结果组装成 JSON,通过 socket 返回
  5. 客户端收到后解析 JSON,取出返回值

解析 :JsonRPC 的核心价值是前后端解耦------前台不需要知道后台是用什么语言实现,后台也可以独立升级。前后台通过"方法名 + 参数"的 JSON 约定来通信,和具体的编程语言无关。


八、JsonRPC 调用流程

以一个具体例子说明调用 subtract(42, 23) 的完整流程。假设客户端想调用远端的减法函数:

请求(Client → Server)

json 复制代码
{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}

响应(Server → Client)

json 复制代码
{"jsonrpc": "2.0", "result": 19, "id": 1}

其中:

  • jsonrpc:协议版本,固定为 "2.0"
  • method:想调用的远程方法名
  • params:方法参数(可以是数组或对象)
  • id:请求编号,用于匹配请求和响应

详细步骤拆解

步骤 角色 操作
1 Client 调用 rpc_sub(42, 23),内部构造 JSON 请求字符串
2 Client send() 通过 socket 发送 JSON
3 Server recv() 收到 JSON 字符串
4 Server 调用 cJSON_Parse 解析出 methodparams
5 Server 根据 method 找到本地 local_sub(int v1, int v2) 并执行,得到 19
6 Server 构造结果 JSON {"jsonrpc":"2.0","result":19,"id":1}
7 Server send() 返回给 Client
8 Client 解析响应 JSON,取出 result

解析 :对 Client 调用者来说,只需要实现一个 rpc_sub 函数,往 JSON 格式里填方法名和参数就行了,不需要知道 socket、序列化、反序列化这些底层细节------JsonRPC 库会帮你处理这些。


九、总结

知识点 要点
JSON 格式 键值对(name:value),支持 7 种数据类型,独立于语言
Jsoncpp C++ 处理 JSON 的库,功能完整,适合上位机 PC 端
cJSON 函数 解析(cJSON_Parse)/ 写入(cJSON_Add*ToObject)/ 读取(cJSON_GetObjectItem / GetArrayItem)/ 释放(cJSON_Delete
cJSON 存储 三种值形式:valuestringvalueintvaluedouble,按需访问
RPC 概念 远程调用,本地函数语法,网络透明
JsonRPC 以 JSON 为数据格式的 RPC,前后端解耦,独立升级
JsonRPC 字段 jsonrpc(版本)/ method(方法)/ params(参数)/ id(请求编号)/ result(返回值)

十、结尾

本篇完成了 JSON 和 JsonRPC 的入门:了解了 JSON 的 7 种数据类型、cJSON 的增删改查函数,并通过一个完整的调用流程理解了 JsonRPC "本地调用语法 + JSON 网络传输" 的工作原理。

下一篇将实现 JsonRPC 的 Server 端,敬请期待~

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

嵌入式上位机开发入门(二十八):JSON 与 JsonRPC 入门

目录

  • 一、前言
  • [二、JSON 是什么](#二、JSON 是什么)
  • [三、JSON 的数据类型](#三、JSON 的数据类型)
  • [四、C++ 处理 JSON:Jsoncpp](#四、C++ 处理 JSON:Jsoncpp)
  • [五、C 处理 JSON:cJSON 库函数](#五、C 处理 JSON:cJSON 库函数)
  • [六、cJSON 实战:解析嵌套 JSON](#六、cJSON 实战:解析嵌套 JSON)
  • [七、JsonRPC 是什么](#七、JsonRPC 是什么)
  • [八、JsonRPC 调用流程](#八、JsonRPC 调用流程)
  • 九、总结
  • 十、结尾

一、前言

大家好,这里是 Hello_Embed。上篇完成了 MQTT 的调试与容错优化,让客户端在弱网环境下更扛造。

本篇引入两个新概念:JSONJsonRPC。在上位机软件框架中,前台 GUI 与后台控制中心通过网络通信,数据会以 JSON 格式封装成 JsonRPC 远程调用------这是本系列后半段的技术基础。


二、JSON 是什么

JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种轻量的数据交换格式,易于人阅读和编写,也易于机器解析和生成。它独立于编程语言,几乎所有主流语言都支持 JSON 数据交换。

JSON 的核心结构是 键值对(name:value),用"名称"去索引对应的"值",类似 C 语言的字典或结构体成员。

解析:JSON 和 XML 都是常见的数据交换格式,但 JSON 更轻量------同一条数据,JSON 的字符数通常只有 XML 的 1/3~1/2,网络传输效率更高,这也是为什么现代 Web API 和上位机通信几乎都选 JSON。


三、JSON 的数据类型

JSON 的值(value)支持以下几种类型:

类型 示例 说明
字符串 "John" 双引号包裹,支持转义字符
整数 30 不带小数点的数字
浮点数 4.5 带小数点的数字
布尔值 true / false 注意是全小写
空值 null 表示"无值"
数组 ["apple", "banana"] 有序列表,元素类型可混
对象 { "name": "John" } 键值对集合,可嵌套

下面是一个包含多种类型的 JSON 示例:

json 复制代码
{
    "title": "JSON Example",
    "author": {
        "name": "John Doe",
        "age": 35,
        "isVerified": true
    },
    "tags": ["json", "syntax", "example"],
    "rating": 4.5,
    "isPublished": false,
    "comments": null
}

这个例子展示了两层嵌套:author 是一个子对象,tags 是一个字符串数组。

解析 :在上位机场景中,传感器数据通常打包成 JSON 对象,比如 { "temp": 25.6, "humid": 60 },传输效率高,解析也简单。


四、C++ 处理 JSON:Jsoncpp

上位机程序通常用 C++ 开发,业界最常用的 C++ JSON 库是 Jsoncpp

官方仓库:https://github.com/open-source-parsers/jsoncpp

Jsoncpp 功能完整,支持从字符串解析 JSON、构建 JSON 对象、序列化回字符串,是上位机处理 JSON 的首选方案。

解析:上位机(PC 端)资源充足,用 Jsoncpp 这类功能完整的库没有问题。后续移植到嵌入式的 MQTT 部分,则更倾向用轻量的 cJSON。


五、C 处理 JSON:cJSON 库函数

在 C 语言环境下,可以使用 cJSON 库。相比 Jsoncpp,cJSON 更加轻量,适合资源受限的嵌入式场景。

cJSON 的核心使用流程如下:

5.1 解析:从字符串创建 cJSON 结构体

c 复制代码
/* 从 JSON 字符串解析出 cJSON 结构体(类似 JSON.parse()) */
cJSON *cJSON_Parse(const char *value);

/* 直接创建一个空的 JSON 对象(类似 {}) */
cJSON *cJSON_CreateObject(void);

5.2 写入:往对象里添加数据

c 复制代码
/* 添加各种类型的成员 */
cJSON_AddNullToObject(object, name);          /* 添加 null */
cJSON_AddTrueToObject(object, name);           /* 添加 true */
cJSON_AddFalseToObject(object, name);          /* 添加 false */
cJSON_AddBoolToObject(object, name, boolean);  /* 添加布尔值 */
cJSON_AddNumberToObject(object, name, number);  /* 添加数字 */
cJSON_AddStringToObject(object, name, string); /* 添加字符串 */
cJSON_AddObjectToObject(object, name);         /* 添加子对象 */
cJSON_AddArrayToObject(object, name);          /* 添加数组 */

5.3 读取:根据键名或下标取值

c 复制代码
/* 根据键名(key)获取成员,类似 JS 的 obj.name */
cJSON *cJSON_GetObjectItem(const cJSON *object, const char *string);

/* 根据数组下标获取元素,类似 JS 的 arr[0] */
cJSON *cJSON_GetArrayItem(const cJSON *array, int index);

cJSON 结构体内部存储了三种数值形式:valuestring(字符串值)、valueint(整数值)、valuedouble(浮点数值),直接访问即可。

5.4 释放:删除 cJSON 结构体

c 复制代码
/* 释放 cJSON 结构体及其子节点,避免内存泄漏 */
cJSON_Delete(cJSON *item);

解析 :cJSON 的设计思路是"一切皆节点"------数组和对象在 cJSON 眼里都是 cJSON * 指针,遍历时用 cJSON_GetArrayItem 按下标取,用 cJSON_GetObjectItem 按键名取。这种统一结构让代码写起来很简洁。


六、cJSON 实战:解析嵌套 JSON

用一段测试代码演示完整的解析流程:

c 复制代码
#include <stdio.h>
#include "cJSON.h"

int main(int argc, char** argv)
{
    /* 待解析的 JSON 字符串(多行字符串字面量) */
    const char* str = " \
        { \
            \"title\": \"JSON Example\", \
            \"author\": { \
                \"name\": \"John Doe\", \
                \"age\": 35, \
                \"isVerified\": true \
            }, \
            \"tags\": [\"json\", \"syntax\", \"example\"], \
            \"rating\": 4.5, \
            \"isPublished\": false, \
            \"comments\": null \
        }";

    /* 第一步:解析字符串 → cJSON 结构体 */
    cJSON *json = cJSON_Parse(str);
    if (!json) /* 解析失败返回 NULL */
    {
        printf("cJSON_Parse err\n");
        return 0;
    }

    /* 第二步:取 "author" 子对象 */
    cJSON *author = cJSON_GetObjectItem(json, "author");

    /* 第三步:从 author 中取 "age",强转为整数值打印 */
    cJSON *age = cJSON_GetObjectItem(author, "age");
    if (age)
        printf("age = %d\n", age->valueint); /* 输出:age = 35 */

    /* 第四步:取 "tags" 数组,再按下标取第 3 个元素(index=2) */
    cJSON *tags = cJSON_GetObjectItem(json, "tags");
    cJSON *item = cJSON_GetArrayItem(tags, 2); /* 0-indexed,取第3个 */
    if (item)
        printf("item = %s\n", item->valuestring); /* 输出:item = example */

    /* 第五步:解析完毕,释放内存 */
    cJSON_Delete(json);
    return 0;
}

运行结果:

复制代码
age = 35
item = example

解析cJSON_GetArrayItem 是 0-indexed(从 0 开始),所以 index=2 取的是第三个元素 "example"。解析完记得调用 cJSON_Delete 释放整个树,否则会内存泄漏。


七、JsonRPC 是什么

RPC(Remote Procedure Call,远程过程调用) 是一种通过网络调用另一台计算机上程序子进程的技术。对调用方来说,就像在调用本地函数一样,不需要关心网络细节。

在带 GUI 的上位机系统中,程序通常拆为前台 GUI后台控制中心两部分:

  • 前台:负责界面显示
  • 后台:负责数据处理、设备控制
  • 两者通过网络通信,用 JsonRPC 封装调用

JsonRPC 是 RPC 的一种,以 JSON 作为数据格式,工作原理概括如下:

  1. 客户端像调用本地函数一样调用接口(传入方法名和参数)
  2. 客户端将调用信息组装成 JSON 数据,通过 socket 发送
  3. 服务端收到后反序列化(解码),执行对应函数
  4. 服务端将执行结果组装成 JSON,通过 socket 返回
  5. 客户端收到后解析 JSON,取出返回值

解析 :JsonRPC 的核心价值是前后端解耦------前台不需要知道后台是用什么语言实现,后台也可以独立升级。前后台通过"方法名 + 参数"的 JSON 约定来通信,和具体的编程语言无关。


八、JsonRPC 调用流程

以一个具体例子说明调用 subtract(42, 23) 的完整流程。假设客户端想调用远端的减法函数:

请求(Client → Server)

json 复制代码
{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}

响应(Server → Client)

json 复制代码
{"jsonrpc": "2.0", "result": 19, "id": 1}

其中:

  • jsonrpc:协议版本,固定为 "2.0"
  • method:想调用的远程方法名
  • params:方法参数(可以是数组或对象)
  • id:请求编号,用于匹配请求和响应

详细步骤拆解

步骤 角色 操作
1 Client 调用 rpc_sub(42, 23),内部构造 JSON 请求字符串
2 Client send() 通过 socket 发送 JSON
3 Server recv() 收到 JSON 字符串
4 Server 调用 cJSON_Parse 解析出 methodparams
5 Server 根据 method 找到本地 local_sub(int v1, int v2) 并执行,得到 19
6 Server 构造结果 JSON {"jsonrpc":"2.0","result":19,"id":1}
7 Server send() 返回给 Client
8 Client 解析响应 JSON,取出 result

解析 :对 Client 调用者来说,只需要实现一个 rpc_sub 函数,往 JSON 格式里填方法名和参数就行了,不需要知道 socket、序列化、反序列化这些底层细节------JsonRPC 库会帮你处理这些。


九、总结

知识点 要点
JSON 格式 键值对(name:value),支持 7 种数据类型,独立于语言
Jsoncpp C++ 处理 JSON 的库,功能完整,适合上位机 PC 端
cJSON 函数 解析(cJSON_Parse)/ 写入(cJSON_Add*ToObject)/ 读取(cJSON_GetObjectItem / GetArrayItem)/ 释放(cJSON_Delete
cJSON 存储 三种值形式:valuestringvalueintvaluedouble,按需访问
RPC 概念 远程调用,本地函数语法,网络透明
JsonRPC 以 JSON 为数据格式的 RPC,前后端解耦,独立升级
JsonRPC 字段 jsonrpc(版本)/ method(方法)/ params(参数)/ id(请求编号)/ result(返回值)

十、结尾

本篇完成了 JSON 和 JsonRPC 的入门:了解了 JSON 的 7 种数据类型、cJSON 的增删改查函数,并通过一个完整的调用流程理解了 JsonRPC "本地调用语法 + JSON 网络传输" 的工作原理。

下一篇将实现 JsonRPC 的 Server 端,敬请期待~

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

相关推荐
代码羊羊1 小时前
Rust-特征trait和特征对象
服务器·网络·rust
U盘失踪了1 小时前
Playwright codegen脚本录制
笔记
minji...1 小时前
Linux 网络套接字编程(一)端口号port,socket套接字,socket,bind,socket 通用结构体
linux·运维·服务器·开发语言·网络
东北甜妹2 小时前
TCP/IP和VLAN
网络协议·tcp/ip·面试
SPC的存折2 小时前
Cisco Packet Tracer 8.0 上的 VLAN 综合实验报告
运维·网络
yuezhilangniao2 小时前
告别网络排障恐惧症-告别UI版wireshark:用 curl + tcpdump + tshark + ss 构建完整工具链 - 含TCPIP老鸟常识
网络·wireshark·tcpip
你觉得脆皮鸡好吃吗2 小时前
SQL注入 手工注入
网络·数据库·sql·安全·web安全·网络安全学习
Z文的博客2 小时前
FLASHDB实战详解 - 嵌入式KV/TSD数据库开发全攻略
stm32·单片机·嵌入式·flash·flashdb·w25q256
盟接之桥2 小时前
盟接之桥®制造业EDI软件:专注制造,为制造业服务,让全球供应链协同更有底气
网络·安全·低代码·汽车·制造