问题
最近在做第二代边缘采集网关,我把第一代网关的软件移植过来。测试发现了个问题,数据中转功能下配置界面异常。我定位发现是之前版本网关的代码报segment fault导致,写这段代码的同事已离职,我来修好它。需要解析的json文件如下:
[
{
"Gw_vid": 336597760,
"IP": "192.168.101.66",
"Conn_count": 2,
"Name": " 336597760",
"Gw_conn": [
{
"Blk_num": 1,
"Data_id": 1659602160,
"Dev_id": 1,
"Port": 80,
"Blk_name": "1",
"Reg_blk": [
{
"Start_addr": 0,
"Len": 60,
"Data_filed_name": "area",
"Read_cmd": 3,
"Write_cmd": 10
}
]
},
{
"Blk_num": 1,
"Data_id": 1659602624,
"Dev_id": 200,
"Port": 80,
"Blk_name": "200",
"Reg_blk": [
{
"Start_addr": 0,
"Len": 60,
"Data_filed_name": "area",
"Read_cmd": 3,
"Write_cmd": 10
}
]
}
]
}
]
是用c语言来解析的,头文件部分代码如下:
typedef struct reg_blk_s
{
unsigned short start_addr;
unsigned short reg_len;
unsigned char read_cmd;
unsigned char write_cmd;
unsigned char *val;
}reg_blk_s;
typedef struct oth_conn_s
{
unsigned int data_id;
unsigned int port;
void *ctx;
unsigned char dev_id;
unsigned char online;
unsigned int reg_blk_num;
reg_blk_s *reg_blk;
}oth_conn_s;
typedef struct oth_gw_s
{
unsigned int gw_vid;
char IP[16];
bool dump_switch;
unsigned int online_count;
unsigned int gw_conn_count;
oth_conn_s *gw_conn;
}oth_gw_data_s;
程序引入了cJSON库来解析json格式数据,解析出来后保存在全局变量里,这个全局变量直接是一个比较大的数组,有点偷懒,实现代码如下:
#include "cJSON.h"
oth_gw_data_s g_trans_conf[128];
int slave_get_configure(cJSON * root)
{
int i =0;
cJSON * item = NULL;
static int gw_count = 0,conn_count = 0,blk_count = 0;
for(i=0; i<cJSON_GetArraySize(root); i++)
{
item = cJSON_GetArrayItem(root, i);
switch(item->type){
case cJSON_Object:
slave_get_configure(item);
break;
case cJSON_Array:
if(NULL != item->string ){
if(0 == strcmp("Reg_blk",item->string))
{
slave_get_configure(item);
}else if(0 == strcmp("Gw_conn",item->string))
{
conn_count = 0;
blk_count = 0;
slave_get_configure(item);
}
}
break;
case cJSON_Number:
if(0 == strcmp(item->string,"Gw_vid")){
g_trans_conf[gw_count].gw_vid = item->valueint;
}else if(0 == strcmp(item->string,"Data_id")){
g_trans_conf[gw_count].gw_conn[conn_count].data_id = item->valueint;
}else if(0 == strcmp(item->string,"Port")){
g_trans_conf[gw_count].gw_conn[conn_count].port=item->valueint;
}else if(0 == strcmp(item->string,"Dev_id")){
g_trans_conf[gw_count].gw_conn[conn_count].dev_id=item->valueint;
}else if(0 == strcmp(item->string,"Blk_num")){
g_trans_conf[gw_count].gw_conn[conn_count].online = METER_STATE_OFFLINE;
g_trans_conf[gw_count].gw_conn[conn_count].ctx = NULL;
g_trans_conf[gw_count].gw_conn[conn_count].reg_blk_num=item->valueint;
g_trans_conf[gw_count].gw_conn[conn_count].reg_blk = \
malloc(item->valueint * sizeof(reg_blk_s));
g_trans_conf[gw_count].gw_conn[conn_count].reg_blk[blk_count].read_cmd = 0x03;
g_trans_conf[gw_count].gw_conn[conn_count].reg_blk[blk_count].write_cmd = 0x10;
}else if(0 == strcmp(item->string,"Start_addr")){
g_trans_conf[gw_count].gw_conn[conn_count].reg_blk[blk_count].start_addr = item->valueint;
}else if(0 == strcmp(item->string,"Read_cmd")){
g_trans_conf[gw_count].gw_conn[conn_count].reg_blk[blk_count].read_cmd = item->valueint;
}else if(0 == strcmp(item->string,"Write_cmd")){
g_trans_conf[gw_count].gw_conn[conn_count].reg_blk[blk_count].write_cmd = item->valueint;
}else if(0 == strcmp(item->string,"Len")){
g_trans_conf[gw_count].gw_conn[conn_count].reg_blk[blk_count].reg_len =item->valueint;
g_trans_conf[gw_count].gw_conn[conn_count].reg_blk[blk_count].val = malloc(item->valueint * 2);
blk_count++;
if(blk_count == g_trans_conf[gw_count].gw_conn[conn_count].reg_blk_num)
{
conn_count++;
blk_count = 0;
if(conn_count == g_trans_conf[gw_count].gw_conn_count)
{
conn_count = 0;
gw_count++;
}
}
}else if(0 == strcmp(item->string,"Conn_count"))
{
g_trans_conf[gw_count].dump_switch = true;
g_trans_conf[gw_count].online_count = 0;
g_trans_conf[gw_count].gw_conn_count = item->valueint;
g_trans_conf[gw_count].gw_conn = malloc(item->valueint * sizeof(oth_conn_s));
}
break;
case cJSON_String:
if(0 == strcmp(item->string,"IP")){
strcpy(g_trans_conf[gw_count].IP,item->valuestring);
}
break;
default:
break;
}
}
return gw_count;
}
我看了上面代码的逻辑,应该是遇到合适的数据就缓存,如果是对象就递归,这样代码倒是比较紧凑,风险在于不是逐层解析的,容易有隐患。上面代码解析到"Len"这个key时(54-66行间),会执行"conn_count++",原来json文件"Len"是最后一个key,没有问题。后来又加了几个key,就出事了。
"Reg_blk": [
{
"Start_addr": 0,
"Len": 60,
"Data_filed_name": "area",
"Read_cmd": 3,
"Write_cmd": 10
}
"conn_count"改变后,全局变量的下标指到了下一个位置,此为null,所以解析"Read_cmd"时就报segment fault了
}else if(0 == strcmp(item->string,"Read_cmd")){
g_trans_conf[gw_count].gw_conn[conn_count].reg_blk[blk_count].read_cmd = item->valueint;
}
注意,代码里面没有解析"Data_filed_name"所以是在"Read_cmd"那里报的错。原因很清楚了,想要快点修复这个bug只要改变"conn_count++"的位置就好了,但是我觉得代码有隐患,所以花时间一并改了。
解决方案
新的解析代码如下,长了很多。。。
#include "cJSON.h"
oth_gw_data_s *g_trans_conf;
unsigned char g_gw_count;
/**
* @brief 释放 g_trans_conf
* @param
* @retval
* @author zhongwei.peng
* @date 2022-8-10
*/
void slave_deinit_confs(void)
{
int i,j,k;
if (g_trans_conf == NULL)
return;
for (i = 0; i < g_gw_count; i++)
{
if (g_trans_conf[i].gw_conn != NULL)
{
for (j = 0; j < g_trans_conf[i].gw_conn_count; j++)
{
if (g_trans_conf[i].gw_conn[j].reg_blk != NULL)
{
for (k = 0; k < g_trans_conf[i].gw_conn[j].reg_blk_num; k++)
{
if (g_trans_conf[i].gw_conn[j].reg_blk[k].val != NULL)
free(g_trans_conf[i].gw_conn[j].reg_blk[k].val);
}
free(g_trans_conf[i].gw_conn[j].reg_blk);
}
}
free(g_trans_conf[i].gw_conn);
}
}
free(g_trans_conf);
g_trans_conf = NULL;
g_gw_count = 0;
return;
}
/**
* @brief 获取 reg_blk_s
* @param[out] reg_blk, 寄存器数据区缓存,解析出来的数据缓存到这里
* @param[in] root, 要解析的json对象
* @retval 成功返回0,否则返回<0
* @author zhongwei.peng
* @date 2022-8-10
*/
int slave_get_reg_blk(reg_blk_s *reg_blk, cJSON *root)
{
cJSON *object_item = NULL;
object_item = cJSON_GetObjectItem(root, "Start_addr");
if (object_item != NULL)
reg_blk->start_addr = object_item->valueint;
else
return -1;
object_item = cJSON_GetObjectItem(root, "Len");
if (object_item != NULL)
{
reg_blk->reg_len = object_item->valueint;
reg_blk->val = malloc(reg_blk->reg_len * 2);
if (reg_blk->val == NULL)
return -1;
}
else
{
return -1;
}
object_item = cJSON_GetObjectItem(root, "Read_cmd");
if (object_item != NULL)
reg_blk->read_cmd = object_item->valueint;
else
return -1;
object_item = cJSON_GetObjectItem(root, "Write_cmd");
if (object_item != NULL)
reg_blk->write_cmd = object_item->valueint;
else
return -1;
return 0;
}
/**
* @brief 获取 oth_conn_s
* @param[out] gw_conn, 连接对象首指针,解析出来的数据缓存到这里
* @param[in] root, 要解析的json对象
* @retval 成功返回0,否则返回<0
* @author zhongwei.peng
* @date 2022-8-10
*/
int slave_get_gw_conn(oth_conn_s *gw_conn, cJSON *root)
{
int i;
int ret;
cJSON *object_item = NULL;
object_item = cJSON_GetObjectItem(root, "Data_id");
if (object_item != NULL)
gw_conn->data_id = object_item->valueint;
else
return -1;
object_item = cJSON_GetObjectItem(root, "Dev_id");
if (object_item != NULL)
gw_conn->dev_id = object_item->valueint;
else
return -1;
object_item = cJSON_GetObjectItem(root, "Port");
if (object_item != NULL)
gw_conn->port = object_item->valueint;
else
return -1;
object_item = cJSON_GetObjectItem(root, "Reg_blk");
if (object_item != NULL)
{
gw_conn->reg_blk_num = cJSON_GetArraySize(object_item);
if (gw_conn->reg_blk_num > 0)
{
gw_conn->reg_blk = (reg_blk_s *)malloc(gw_conn->reg_blk_num * sizeof(reg_blk_s));
if (gw_conn->reg_blk != NULL)
{
ret = 0;
for (i = 0; i < gw_conn->reg_blk_num; i++)
ret |= slave_get_reg_blk(&(gw_conn->reg_blk[i]), cJSON_GetArrayItem(object_item, i));
if (ret == 0)
return 0;
}
}
}
return -1;
}
/**
* @brief 获取 oth_gw_data_s
* @param[out] p_gw_data, 网关数据区对象首指针,解析出来的数据缓存到这里
* @param[in] root, 要解析的json对象
* @retval 成功返回0,否则返回<0
* @author zhongwei.peng
* @date 2022-8-10
*/
int slave_get_gw_data(oth_gw_data_s *p_gw_data, cJSON *root)
{
cJSON *object_item = NULL;
int i;
int ret;
object_item = cJSON_GetObjectItem(root, "Gw_vid");
if (object_item != NULL)
p_gw_data->gw_vid = object_item->valueint;
else
return -1;
object_item = cJSON_GetObjectItem(root, "IP");
if (object_item != NULL)
strcpy(p_gw_data->IP, object_item->valuestring);
else
return -1;
object_item = cJSON_GetObjectItem(root, "Gw_conn");
if (object_item != NULL)
{
p_gw_data->gw_conn_count = cJSON_GetArraySize(object_item);
if (p_gw_data->gw_conn_count > 0)
{
p_gw_data->gw_conn = (oth_conn_s *)malloc(p_gw_data->gw_conn_count * sizeof(oth_conn_s));
if (p_gw_data->gw_conn != NULL)
{
ret = 0;
for (i = 0; i < p_gw_data->gw_conn_count; i++)
ret |= slave_get_gw_conn(&(p_gw_data->gw_conn[i]), cJSON_GetArrayItem(object_item, i));
if (ret == 0)
return 0;
}
}
}
return -1;
}
/**
* @brief 解析配置文件,数据保存到全局变量 g_trans_conf
* @param[in] root, 要解析的json对象
* @retval 返回网关个数
* @author zhongwei.peng
* @date 2022-8-10
*/
unsigned char slave_get_configure(cJSON *root)
{
int i, count;
int ret;
count = cJSON_GetArraySize(root);
if (count > 0)
{
g_trans_conf = (oth_gw_data_s *)malloc(count * sizeof(oth_gw_data_s));
if (g_trans_conf != NULL)
{
memset(g_trans_conf, 0, (count * sizeof(oth_gw_data_s)));
g_gw_count = count;
ret = 0;
for (i = 0; i < count; i++)
ret |= slave_get_gw_data(&(g_trans_conf[i]), cJSON_GetArrayItem(root, i));
if (ret == 0)
return count;
}
}
slave_deinit_confs();
return 0;
}
修改的地方有3个,一是全局变量原来是固定128个的数组,改为动态申请。二是分层解析json配置,一个json对象对应一个数据结构对应一个解析函数。三是添加空间释放代码。
总结
json的解析使用广泛,后面应该把c语言相关JSON库源码读读,加深了解。另外,自己觉得不够精简,最好参考一下别人怎么做。理想的是有个通用的API,直接把json对象解析到结构体去,例如:
int parse_json_object(cJSON *root, object_data_s *obj);