文章目录
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;
else
break;
}
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;
else
break;
}
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-gcc
TOP_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 -lpthread
c_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