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、結尾

寫在後面的話

我歷來不是一個聰明的人,可是我熱愛思考,熱愛鑽研,熱愛學習,因此在這裏把此次通過記錄下來,這樣對本身也是一個很好的鞏固反饋的過程,再次思路很是的重要,其實除了這個結構體,我以爲序列化的部分是沒有任何的普適性的,可是依然很是的難作,因此說思路很重要,這樣,下一次在遇到困難問題的時候,咱們能夠不會是無從下手,至少還能作一些掙扎~

到這裏咱們就差很少結束了,固然裏面還有不少的不足

好比寫完時隔兩天,在寫文章的過程,忽然發現,我只寫了申請文件結構體的函數,沒有寫釋放結構體的函數,評論裏面有木有小夥伴幫忙補上的~

相關文章
相關標籤/搜索