C语言目录结构的JSON序列化方案

C语言目录结构的JSON序列化

1、背景

最近接了一个新的项目,须要在嵌入式平台上搭建一个服务器交互用户配置界面,因而接触了一些嵌入式平台的websever框架类型。收获良多,也是第一次接触到用C语言写的mongoose webserver,也体会到了交叉编译带来的极致体验与痛苦。php

CGI(Common Gateway Interface)通用网关接口

至于为何要写C语言遍历目录结构呢?一来,为了更加熟悉C语言的体系;二来,arm平台的限制缘由,嵌入式环境,内存等环境相对恶劣,咱们会采用相对较老的CGI模式来进行后端的服务调用,webserver只是扮演简单的用户认证以及URI逻辑控制的角色。而CGI是一个黑盒,只要它输出符合http协议,无论用什么语言均可以进行编写,甚至shell均可以,其实arm板上用shell来写cgi也是很多的。例如C语言的printf,C++的cout<<,php的echo均可以做为接口的输出。node

显然这一次我使用的C语言来编写的CGI文件用来输出(响应)请求linux

2、C语言遍历目标目录的方式

查阅C语言的资料的时候踩过很多坑,开始调用#include <io.h>这个头文件,可是万万没想到,这个文件貌似已经弃用了,历经千辛万苦,抽丝剥茧,最终仍是找到了有用的头文件#include <dirent.h>这个头文件仍是系统库函数,直接引用就能够了,无需额外编译及指定连接。咱们接下来看一下,这个库函数是如何来遍历目录的。nginx

一、重要的结构体

在dirent中有两个很是重要的结构体struct DIRstruct direntweb

struct __dirstream {   
    void *__fd;    
    char *__data;    
    int __entry_data;    
    char *__ptr;    
    int __entry_ptr;    
    size_t __allocation;    
    size_t __size;    
    __libc_lock_define (, __lock)    
};   
typedef struct __dirstream DIR;
复制代码

这个结构体就是存储目录的基本信息用的。对咱们来讲用处不大,可是必不可少。再来看一下dirent:shell

struct dirent {
    ino_t          d_ino;       /* inode number */
    off_t          d_off;       /* offset to the next dirent */
    unsigned short d_reclen;    /* length of this record */
    unsigned char  d_type;      /* type of file */
    char           d_name[256]; /* filename */
};
复制代码

这个结构体就是咱们后面会着重操做的结构体,看到里面有不少有用信息,包括文件名称文件类别等等。json

二、基本用法

先上代码,在逐步分析:后端

#include<dirent.h>    
/*-----------int main---------*/

struct dirent* ent = NULL;
DIR *pDir;

if( (pDir=opendir("/home/test")) == NULL)
{
    printf("open dir %s failed\n", pszBaseDir);
    return false;
}

while( (ent=readdir(pDir)) != NULL )
{
    printf("the ent->d_type is%d the ent->d_name is%s\n", ent->d_type, ent->d_name);  

}


closedir(pDir);
复制代码

首先关注三个重点的函数opendir()readdir()closedir()数组

opendir()入参就是咱们的目标路径了,通常是以可执行文件的(编译后的C文件)运行的目录为起点的相对路径。该函数返回一个DIR的结构体指针,而readdir()closedir()入参就都是这个DIR的指针了。能够看到,readdir(pDir)返回的是dirent结构体,而经过这个结构体,咱们就能够拿到咱们想要的字段,为序列化JSON作准备了。缓存

3、定义一个文件目录链表结构

开始在想着如何承载这个目录结构的时候,想的是一个文件做为一个结构体,而后内部存放一个文件夹结构体数组和一个文件数组,可是在嵌入式平台,珍贵的内存资源不容许咱们这么作,由于若是定义了这样的结构体类型,而咱们的结构体数组又存放不满的话(没人能保证咱们的文件夹(子目录)的数量是恰好匹配你的结构体数组长度)就会有很是大的资源空间的浪费。因而我就想到了链表结构,每当遍历到一个文件夹,就将其挂在链表上,那么,就能够极大程度的减小内存资源的消耗。接下来咱们来看一下这个链表的结构:

#define MAX_FILE_NUM 1000 //1000个文件极限

struct file_dir_struct {
  char dir_name[100];
  char file_name[FILES_NUM_Max][100];
  struct file_dir_struct *next;
  struct file_dir_struct *firstchild;
};
复制代码

第一个变量依然是当前的文件目录的名称也就是文件夹名称

第二个变量是一个文件数组,这是一个最大数量为1000,字符串限制大小100字节的字符串指针数组。用来存放普通文件名

第三个变量是一个文件夹结构体,这个结构体指向它的同级目录的下一个文件夹结构体;

第四个变量是一个文件夹结构体,指向它的第一个子目录

这样看上去可能不太直观,咱们来画一个图,大体上就能懂了:我是图

4、目录的遍历以及链表的生成

遍历生成链表结构的话,咱们作了以下几步操做:

一、首先咱们须要传入咱们的目标路径以及该路径的文件夹结构体,没有错,第一个结构体须要咱们本身去生成,其实也能够放入函数的内部去实现。

二、经过opendir()打开当前目录,进行循环遍历。

三、在遍历以前,咱们须要建立一个文件夹结构体缓存指针,这样咱们能够在每次循环结束的时候记住这个结构体,以便下一次循环的时候,连接两个结构体,这个也是链表生成的关键。

四、遍历的时候咱们经过dirent结构体的d_type来判断这个对象是否是文件夹(目录)

五、判断是文件夹(目录)且不是.,..目录的状况下,咱们再为这个文件夹(目录)建立结构体并申请内存空间(嵌入式消耗不起,先判断再申请)。

六、咱们要把遍历到的第一个文件夹(目录)挂在咱们传入的文件夹(目录)结构体的firstchild上,而后把标志flagfirst置为false,而后再把咱们事先声明的缓存指针struct file_dir_struct* cacheDir指向它,留做下一轮使用。

七、接下来下一轮进入文件夹(目录)逻辑的循环,咱们就把cacheDir->next指向本轮的刚建立的结构体就完成链表链接了。

八、文件直接放到文件数组,没啥好说的。

九、接下来就是拼接传入的文件目录路径,而后进入递归遍历的逻辑啦,具体看代码:

#include <string.h>
#include <stddef.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>

#define FILES_NUM_Max 100
typedef enum{
  true=1,false=0
}bool;

struct file_dir_struct {
  char dir_name[100];
  struct file_dir_struct *next;
  struct file_dir_struct *firstchild;
  char file_name[FILES_NUM_Max][100];
};

/* * file_search函数,用户递归遍历目录文件夹并生成链表结构。 * 入参: * 一、char *dir 须要进行遍历的目标路径,相对路径以该程序运行的环境为根目录。 * 二、struct file_dir_struct* file_dir 第一个结构体须要咱们本身定义并传入。 */
int file_search(const char *dir, struct file_dir_struct *file_dirs) {
  int i = 0;
  //标志位,判断是不是第一个子目录,连接firstchild后置为false
  bool firstflag = true;
  DIR *pCurrentDir;
  struct dirent *stFileData;
  if((pCurrentDir = opendir(dir)) == NULL)
  {
    printf("Failed to open dir:%s\n",dir);
    return -1;
  }
  
  //缓存上一轮循环的目录结构体,用于连接本次的结构体使用。
  struct file_dir_struct *cacheDir = NULL;
  while ((stFileData = readdir(pCurrentDir)) != NULL)
  {
    //判断类型是否为文件夹(目录)
    if(stFileData->d_type == DT_DIR)
    {
      //跳过'.','..'目录
      if(0 == strcmp(stFileData->d_name, ".") || 0 == strcmp(stFileData->d_name, ".."))
      {
        continue;
      }
      struct file_dir_struct *tmpDir = (struct file_dir_struct *)malloc(sizeof(struct file_dir_struct));
      if(tmpDir == NULL)
      {
        printf("malloc failed! dir_name:%s\n",stFileData->d_name);
        return -1;
      }
      //printf("%s\n",stFileData->d_name);
      memset(tmpDir, 0, sizeof(tmpDir));
      strncpy(tmpDir->dir_name, stFileData->d_name, strlen(stFileData->d_name));
      /* * 这里一共作了两件事: * 一、将遍历到的第一个文件夹置为其firstchild,并把flagfirst置为false,下次循环不进该逻辑 * 二、将该子文件夹赋给缓存,下一个循环进入else逻辑,用于next指针的赋值。 */
      if(firstflag)
      {
        file_dirs->firstchild = tmpDir;
        printf("firstchild:%s\n",tmpDir->dir_name);
        cacheDir = tmpDir;
        firstflag = false;
      }
      else
      {
        tmpDir->firstchild = NULL;
        cacheDir->next = tmpDir;
        cacheDir = tmpDir;
      }
      
      //这里要开始准备递归遍历子目录文件夹了,首先把dirpath进行拼接,生成新的路径
      char dirCache[1024] = {'\0'};
      strncpy(dirCache, dir, strlen(dir));
      strncat(dirCache, "/", strlen("/")+1);
      strncat(dirCache, stFileData->d_name, strlen(stFileData->d_name)+1);
      //递归遍历
      file_search(dirCache, tmpDir);
    }
    else//文件的话直接存入文件数组内
    {
      if(i < FILES_NUM_Max)
      {
        strncpy(file_dirs->file_name[i], stFileData->d_name, strlen(stFileData->d_name)+1);
        i++;
      }
    }
  }
  //不要忘了关闭目录;
  closedir(pCurrentDir);
  return 0;
}


复制代码

5、结构体的JSON序列化

咱们将文件夹结构体化工做才作了一半,接下来就是如何进行JSON序列化了

JSON序列化不是一件很轻松的事情,幸亏我只是针对这个结构体进行序列化,那样会省去很多的条件分支处理,减小80%的工做,但就这也把我折腾的很头疼。

思路大体上是和咱们建立结构体的过程是反过来的(其实能够在建立过程就能够完成的,可是处理逻辑可能没有单独来作那么清晰),也是经过递归的思想来进行的,首先把重复工做写成函数,一个add_json_value来处理键值对的添加,一个add_json_array用来生成数组。咱们先来看一下生成的JSON字符串长啥样,这样对接下来的代码咱们大体有个数,以linux的usr目录为例(不完整):

{
    "directroyName": "user",
    "files": [],
    "directroies": [
        {
            "directroyName":"local",
            "files": ["111.c","lib","lib64","include"],
            "directroies":[
                {
                    "directroyName": "nginx",
                    "files":[],
                    "directroies":[
                        {
                            "directroyName":"conf",
                            "files": ["nginx.conf"],
                            "directroies":[]
                        }
                    ]
                }
            ]
        },
        {
            "directroyName":"lib",
            "files": ["111.c","lib","lib64","include"],
            "directroies":[
                {
                    "directroyName": "linux-x86_64",
                    "files":["xxx.so","xxx.a"],
                    "directroies":[]
                }
            ]
        }
    ]
}
复制代码

固然咱们生成的json字符串是没有那么多换行和缩进的,可能一行就完事了(也是符合JSON要求的)。 看代码吧,我会在适当的地方添加注释。

//添加键值对,在这里就是就是用来添加结构体的文件夹名称的,复杂键值对咱们在递归逻辑处理。
void add_json_value(char* json, char *key, char* value) {
  char tmpc[100] = {'\0'};
  sprintf(tmpc, "\"%s\":\"%s\",", key, value);
  strncat(json, tmpc, strlen(tmpc)+1);
}
//添加数组,实际是用来添加文件数组的,涉及文件夹对象的复杂数组咱们也都在递归逻辑里面处理。
void add_json_array(char* json, char *key, char (*arr)[100]) {
  int i = 0;
  char tmpc[100] = {'\0'};
  sprintf(tmpc, "\"%s\":[", key);
  strncat(json, tmpc, strlen(tmpc)+1);
  while (**arr != '\0' && i < FILES_NUM_Max)
  {
    char tmp[100] = {'\0'};
    sprintf(tmp, "\"%s\",", *arr);
    strncat(json, tmp, strlen(tmp)+1);
    arr++;
    i++;
  }
  if(i>0) //这里可能不太好理解i>0及时说明走了上述循环,在数组末尾会多一个','这是不符合JSON要求的
  {
    //进入该逻辑咱们就把末尾的','去掉
    json[(strlen(json)-1)] = '\0';
  }
  //添加数组尾部
  strcat(json,"],");//这个尾部是能够加','的由于咱们是顺序增长添加,文件数组后面跟的必是文件夹数组。
}


char* dir2json(struct file_dir_struct *file_dirs, char* json) {
  struct file_dir_struct *cacheDir = file_dirs;
  strcat(json,"{");
  printf("here\n");
  add_json_value(json,"directoryName", file_dirs->dir_name);  //添加文件夹名称
  add_json_array(json, "files", file_dirs->file_name); //添加文件数组

  printf("%s\n\r\n\r",json);
  //这里开始要生成文件夹数组了。
  strcat(json,"\"directories\":[");
  //判断是否有子目录,有的话递归生成子目录的字符串,注意入参,须要把json传进去拼接。
  if(cacheDir->firstchild != NULL)
  {
    dir2json(cacheDir->firstchild, json);
  }
  /* 到此为止,咱们的单个文件夹逻辑处理完了,回顾一下: * 一、完成告终构体的文件名称JSON序列化。 * 二、完成了文件数组的JSON序列化 * 三、完成了firstchild的序列化,其实是已经递归了一遍子目录了 * 接下来就是完成链表部分的递归了。 */
  if(cacheDir->next != NULL)//判断是否到了文件的结尾
  {
    strcat(json,"]},"); //关闭递归完成的子目录,加上封闭字符串。注意这个','是本层级的并列文件目录
    dir2json(cacheDir->next, json);
  }
  else //和add_json_array同样,若是是结尾了,就不须要','了
  {
    strcat(json,"]}");
  }
}
复制代码

6、结尾

写在后面的话

我历来不是一个聪明的人,可是我热爱思考,热爱钻研,热爱学习,因此在这里把此次通过记录下来,这样对本身也是一个很好的巩固反馈的过程,再次思路很是的重要,其实除了这个结构体,我以为序列化的部分是没有任何的普适性的,可是依然很是的难作,因此说思路很重要,这样,下一次在遇到困难问题的时候,咱们能够不会是无从下手,至少还能作一些挣扎~

到这里咱们就差很少结束了,固然里面还有不少的不足

好比写完时隔两天,在写文章的过程,忽然发现,我只写了申请文件结构体的函数,没有写释放结构体的函数,评论里面有木有小伙伴帮忙补上的~

相关文章
相关标签/搜索