最近接了一個新的項目,須要在嵌入式平臺上搭建一個服務器交互用戶配置界面,因而接觸了一些嵌入式平臺的websever框架類型。收穫良多,也是第一次接觸到用C語言寫的mongoose webserver,也體會到了交叉編譯帶來的極致體驗與痛苦。php
至於爲何要寫C語言遍歷目錄結構呢?一來,爲了更加熟悉C語言的體系;二來,arm平臺的限制緣由,嵌入式環境,內存等環境相對惡劣,咱們會採用相對較老的CGI模式來進行後端的服務調用,webserver只是扮演簡單的用戶認證以及URI邏輯控制的角色。而CGI是一個黑盒,只要它輸出符合http協議,無論用什麼語言均可以進行編寫,甚至shell均可以,其實arm板上用shell來寫cgi也是很多的。例如C語言的printf
,C++的cout<<
,php的echo
均可以做爲接口的輸出。node
顯然這一次我使用的C語言來編寫的CGI文件用來輸出(響應)請求linux
查閱C語言的資料的時候踩過很多坑,開始調用#include <io.h>
這個頭文件,可是萬萬沒想到,這個文件貌似已經棄用了,歷經千辛萬苦,抽絲剝繭,最終仍是找到了有用的頭文件#include <dirent.h>
這個頭文件仍是系統庫函數,直接引用就能夠了,無需額外編譯及指定連接。咱們接下來看一下,這個庫函數是如何來遍歷目錄的。nginx
在dirent中有兩個很是重要的結構體struct DIR
和struct dirent
:web
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作準備了。緩存
開始在想着如何承載這個目錄結構的時候,想的是一個文件做爲一個結構體,而後內部存放一個文件夾結構體數組和一個文件數組,可是在嵌入式平臺,珍貴的內存資源不容許咱們這麼作,由於若是定義了這樣的結構體類型,而咱們的結構體數組又存放不滿的話(沒人能保證咱們的文件夾(子目錄)的數量是恰好匹配你的結構體數組長度)就會有很是大的資源空間的浪費。因而我就想到了鏈表結構,每當遍歷到一個文件夾,就將其掛在鏈表上,那麼,就能夠極大程度的減小內存資源的消耗。接下來咱們來看一下這個鏈表的結構:
#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字節的字符串指針數組。用來存放普通文件名;
第三個變量是一個文件夾結構體,這個結構體指向它的同級目錄的下一個文件夾結構體;
第四個變量是一個文件夾結構體,指向它的第一個子目錄。
這樣看上去可能不太直觀,咱們來畫一個圖,大體上就能懂了:我是圖
遍歷生成鏈表結構的話,咱們作了以下幾步操做:
一、首先咱們須要傳入咱們的目標路徑以及該路徑的文件夾結構體,沒有錯,第一個結構體須要咱們本身去生成,其實也能夠放入函數的內部去實現。
二、經過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;
}
複製代碼
咱們將文件夾結構體化工做才作了一半,接下來就是如何進行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,"]}");
}
}
複製代碼
我歷來不是一個聰明的人,可是我熱愛思考,熱愛鑽研,熱愛學習,因此在這裏把此次通過記錄下來,這樣對本身也是一個很好的鞏固反饋的過程,再次思路很是的重要,其實除了這個結構體,我以爲序列化的部分是沒有任何的普適性的,可是依然很是的難作,因此說思路很重要,這樣,下一次在遇到困難問題的時候,咱們能夠不會是無從下手,至少還能作一些掙扎~
到這裏咱們就差很少結束了,固然裏面還有不少的不足
好比寫完時隔兩天,在寫文章的過程,忽然發現,我只寫了申請文件結構體的函數,沒有寫釋放結構體的函數,評論裏面有木有小夥伴幫忙補上的~