ARM Linux Qt使用JSON-RPC实现前后台分离

ops/2025/2/11 5:31:26/

文章目录

  • 1、前言
  • 2、解决方案
    • 2.1、JSON-RPC
    • 2.2、Qt中应用JSON-RPC的框架图
    • 2.3、优点
    • 2.4、JSON-RPC 1.0 协议规范
  • 3、程序示例
    • 3.1、Linux C(只例举RPC Server相关程序)
    • 3.2、Qt程序(只例举RPC Client相关程序)
  • 4、编译程序
    • 4.1、交叉编译库文件
      • 4.1.1、编译libev库
      • 4.1.2、编译jsonrpc库
    • 4.2、编译应用程序
      • 4.2.1、编译RPC Server应用程序
      • 4.2.2、编译RPC Client应用程序
  • 5、测试
  • 6、总结

1、前言

在个人以往的嵌入式Linux Qt开发中,尽管想着将硬件操作和上层应用解耦,但所有的程序实现还是堆在了一个Qt项目工程里,没有实现真正的分离。例举几个痛点:

1、在获取bmp280温湿度传感器的数据时,一般需要open()设备节点,再获取数据。但类似open()等初始化的操作,没必要在Qt程序中实现。

2、在Qt程序中容易留有C语言的味道。

2、解决方案

2.1、JSON-RPC

使用JSON-RPC。JSON-RPC(JavaScript Object Notation Remote Procedure Call)是一种远程过程调用(RPC)协议,它使用JSON(JavaScript Object Notation)作为数据格式,并通过HTTP或其他传输协议(如TCP)在网络上发送请求和接收响应。

2.2、Qt中应用JSON-RPC的框架图

画板

使用Linux C部署RPC服务器,同时实现硬件HAL,提供接口函数。

Qt则作为RPC客户端,同时实现具体业务和UI。

RPC客户端与RPC服务器之间使用特定格式的JSON字符串作为数据进行传输。

2.3、优点

使用JSON-RPC后的优点:

1、无需在Qt程序中实现硬件HAL,尽最大可能解耦,毕竟Qt只是一个GUI框架。

2、开发过程中,可以在RPC Server创建伪数据,供Qt程序测试应用功能。

2.4、JSON-RPC 1.0 协议规范

客户端发送一个请求对象至服务端代表一个rpc调用, 一个请求对象包含下列成员:

{ "method" : "add", "params": [1, 2],"id": 1
}
  • <font style="background-color:#F4F5F5;">method</font>:指定了要调用的远程过程或方法的名称。
  • <font style="background-color:#F4F5F5;">params</font>:是一个数组,包含了调用方法时所需的参数。
  • <font style="background-color:#F4F5F5;">id</font>:是一个可选字段,用于标识请求。

当客户端发起一个rpc调用时,除通知之外,服务端都必须回复响应:

{ "result" : 3,  "error": null,    "id": 1
}
  • <font style="background-color:#F4F5F5;">result</font>:返回结果。
  • <font style="background-color:#F4F5F5;">error</font>:错误,没有错误返回null。
  • <font style="background-color:#F4F5F5;">id</font>:调用标识符,与传入时的一致。

如果是 JSON-RPC 2.0,请求和响应数据必须添加jsonrpc字段:

# 请求
{ "jsonrpc" : "2.0","method" : "add", "params": [1, 2],"id": 1
}# 响应
{ "jsonrpc" : "2.0","result" : 3,  "error": null,    "id": 1
}

3、程序示例

Linux C实现RPC Server。Qt程序实现RPC Client,同时向RPC Server发起远程调用,实现LED控制和dht11数据获取。

3.1、Linux C(只例举RPC Server相关程序)

/* rpc_server.c */#include <jsonrpc-c.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include "rpc.h"
#include "led.h"
#include "dht11.h"static struct jrpc_server my_server;/* 参数: {"params" : [0|1]} */
cJSON * server_led_control(jrpc_context * ctx, cJSON * params, cJSON *id) {cJSON * status = cJSON_GetArrayItem(params,0);led_control(status->valueint);	return cJSON_CreateNumber(0);
}/* 参数: {"params" : null} */
cJSON * server_dht11_read(jrpc_context * ctx, cJSON * params, cJSON *id) {int array[2];array[0] = array[1] = 0;while (0 != dht11_read((char *)&array[0], (char *)&array[1]));return cJSON_CreateIntArray(array, 2);
}int RPC_Server_Init(void) 
{int err;err = jrpc_server_init(&my_server, PORT);if (err){printf("jrpc_server_init err : %d\n", err);}jrpc_register_procedure(&my_server, server_led_control, "led_control", NULL );jrpc_register_procedure(&my_server, server_dht11_read, "dht11_read", NULL );jrpc_server_run(&my_server);jrpc_server_destroy(&my_server);return 0;
}int main(int argc, char **argv)
{led_init();dht11_init();RPC_Server_Init();   return 0;
}

3.2、Qt程序(只例举RPC Client相关程序)

/* rpc_client.cpp */#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include "cJSON.h"
#include "rpc.h"static int g_iSocketClient;int rpc_led_control(int on)
{char buf[100];int iLen;int ret = -1;int iSocketClient = g_iSocketClient;sprintf(buf, "{\"method\": \"led_control\", \"params\": [%d], \"id\": \"2\" }", on);iLen = send(iSocketClient, buf, strlen(buf), 0);if (iLen ==  strlen(buf)){while (1) {iLen = read(iSocketClient, buf, sizeof(buf));buf[iLen] = 0;if (iLen == 1 && (buf[0] == '\r' || buf[0] == '\n'))continue;elsebreak;} if (iLen > 0){cJSON *root = cJSON_Parse(buf);cJSON *result = cJSON_GetObjectItem(root, "result");ret = result->valueint;cJSON_Delete(root);return ret;}else{printf("read rpc reply err : %d\n", iLen);return -1;}}else{printf("send rpc request err : %d, %s\n", iLen, strerror(errno));return -1;}
}int rpc_dht11_read(char *humi, char *temp)
{char buf[300];int iLen;int iSocketClient = g_iSocketClient;sprintf(buf, "{\"method\": \"dht11_read\"," \"\"params\": [0], \"id\": \"2\" }");        iLen = send(iSocketClient, buf, strlen(buf), 0);if (iLen ==  strlen(buf)){while (1) {iLen = read(iSocketClient, buf, sizeof(buf));buf[iLen] = 0;if (iLen == 1 && (buf[0] == '\r' || buf[0] == '\n'))continue;elsebreak;} if (iLen > 0){cJSON *root = cJSON_Parse(buf);cJSON *result = cJSON_GetObjectItem(root, "result");if (result){cJSON * a = cJSON_GetArrayItem(result,0);cJSON * b = cJSON_GetArrayItem(result,1);*humi = a->valueint;*temp = b->valueint;cJSON_Delete(root);return 0;}else{cJSON_Delete(root);return -1;}}else{printf("read rpc reply err : %d\n", iLen);return -1;}}else{printf("send rpc request err : %d, %s\n", iLen, strerror(errno));return -1;}
}/* 连接RPC Server* 返回值: (>0)socket, (-1)失败*/
int RPC_Client_Init(void) 
{int iSocketClient;struct sockaddr_in tSocketServerAddr;int iRet;iSocketClient = socket(AF_INET, SOCK_STREAM, 0);tSocketServerAddr.sin_family      = AF_INET;tSocketServerAddr.sin_port        = htons(PORT);  /* host to net, short *///tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;inet_aton("127.0.0.1", &tSocketServerAddr.sin_addr);memset(tSocketServerAddr.sin_zero, 0, 8);iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));	if (-1 == iRet){printf("connect error!\n");return -1;}g_iSocketClient = iSocketClient;return iSocketClient;    
}

4、编译程序

4.1、交叉编译库文件

在RPC Server应用程序中,会引用一个头文件jsonrpc-c.h,使用该头文件需要提前交叉编译jsonrpc-c库。而jsonrpc-c库还依赖libev库。

4.1.1、编译libev库

# 1、解压库文件
tar xjf libev.tar.bz2
cd libev/# 2、配置
./configure --host=arm-buildroot-linux-gnueabihf --prefix=$PWD/tmp# 3、编译
make -j 16# 4、安装
make install# 5、查看安装后的目录
ls tmp/

4.1.2、编译jsonrpc库

# 1、安装编译jsonrpc库时需要用到的工具
sudo apt install libtool# 2、解压库文件
tar xjf jsonrpc-c.tar.bz2
cd jsonrpc-c/# 3、配置
autoreconf -i
./configure --host=arm-buildroot-linux-gnueabihf --prefix=$PWD/tmp CFLAGS="-I$PWD/../libev/tmp/include" LDFLAGS="-L$PWD/../libev/tmp/lib"# 4、编译
make -j 16# 5、安装
make install# 6、查看安装后的目录
ls tmp/

4.2、编译应用程序

4.2.1、编译RPC Server应用程序

Makefile参考:

TARGET=rpc_server
CC=arm-buildroot-linux-gnueabihf-gccTOP_DIR=$(shell pwd)/../
LIBEV_DIR=${TOP_DIR}/libev/tmp/
JSONRPC_DIR=${TOP_DIR}/jsonrpc-c/tmp/CFLAGS=-I${LIBEV_DIR}/include -I${JSONRPC_DIR}/include
LDFLAGS=${JSONRPC_DIR}/lib/libjsonrpcc.a  ${LIBEV_DIR}/lib/libev.a -lm -lpthreadc_files = cJSON.c rpc_server.c led.c dht11.c all:${CC} ${CFLAGS} -o ${TARGET} ${c_files} ${LDFLAGS}clean:rm -f *.o ${TARGET}

4.2.2、编译RPC Client应用程序

在Qt creator中正常编译。

5、测试

将编译出来的rpc_server和qt可执行程序拷贝到单板中,先运行rpc_server,等待约5s后,运行qt程序。

6、总结

韦东山视频教程:5-1_把程序拆分为前后台_哔哩哔哩_bilibili


http://www.ppmy.cn/ops/157162.html

相关文章

Linux内核数据结构之链表

对于链表的优缺点,我们对比数组可以说出一些,但在随机存储的情况下,我们会选择链表来处理,而我们使用双向链表时,经常会定义成如下形式: struct list_node {TYPE data;struct list_node *prev,*next; }; 相对应的链表结构如下: 对于该数据结构定义,存在一个局限,整个…

【sqlite】python操作sqlite3(含测试)

个人小项目或者小团队&#xff0c;sqllite很适用&#xff0c;数据库封装操作如下 #!/usr/bin/env python # -*- coding: utf-8 -*- # Time : 2025-02-08 13:57 # Author : duxiaowei # File : connect_sqllite.py # Software: PyCharm """ sqllite操作, …

在 Spring Boot 项目中,bootstrap.yml 和 application.yml文件区别

在 Spring Boot 项目中&#xff0c;bootstrap.yml 和 application.yml 是两个常用的配置文件&#xff0c;它们的作用和加载顺序有所不同。以下是它们的详细说明&#xff1a; 1. bootstrap.yml 作用&#xff1a; bootstrap.yml 是 Spring Cloud 项目中的配置文件&#xff0c;用于…

无人机目标飞行跟踪

无人机目标飞行跟踪主要通过无人机搭载的摄像头或其他传感器采集环境信息&#xff0c;通过算法分析识别目标物体&#xff0c;并对其进行精确跟踪‌。‌无人机采用先进的控制算法和导航系统&#xff0c;根据目标的位置和运动状态动态调整飞行路径‌。这些算法能够处理传感器传来…

基于RK3588/RK3576+FPGA的巡检机器人六自由度机械臂的系统设计

当今巡检机器人机械臂在管廊隧道等复杂环境的作业过程中&#xff0c;经常面临空间狭窄 且障碍物密集的问题&#xff0c;这就要求机械臂具备在狭窄空间进行避障路径规划的能力。此 外&#xff0c;一些不确定性因素如在突发或异常环境条件下&#xff0c;机械臂的全局状态信息感知…

Excel中对单列数据进行去重筛选

在Excel中对单列数据进行去重筛选&#xff0c;可以按照以下步骤操作&#xff1a; 方法一&#xff1a;使用“删除重复项”功能 选择数据列&#xff1a;点击要处理的列头&#xff08;如A列&#xff09;。打开“删除重复项”&#xff1a; Excel 2007及以后版本&#xff1a;点击“…

Neo4j图数据库学习(二)——SpringBoot整合Neo4j

一. 前言 本文介绍如何通过SpringBoot整合Neo4j的方式&#xff0c;对图数据库进行简单的操作。 Neo4j和SpringBoot的知识不再赘述。关于Neo4j的基础知识&#xff0c;有兴趣可以看看作者上一篇的文章&#xff1a;Neo4j图数据库学习(一)——初识CQL 二. 前置准备 新建SpringBo…

To `panic!` or Not to `panic!`: Rust 中错误处理策略的选择

一、何时选择 panic! 1.1 当错误不可恢复时 调用 panic! 表示程序遇到了无法继续执行的严重错误。在以下情况中&#xff0c;使用 panic! 是合适的&#xff1a; 不可预期的错误状态&#xff1a;当某个假定或合同被破坏时&#xff0c;继续执行可能导致更严重的问题。例如&…