最近接了一个新的项目,须要在嵌入式平台上搭建一个服务器交互用户配置界面,因而接触了一些嵌入式平台的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,"]}");
}
}
复制代码
我历来不是一个聪明的人,可是我热爱思考,热爱钻研,热爱学习,因此在这里把此次通过记录下来,这样对本身也是一个很好的巩固反馈的过程,再次思路很是的重要,其实除了这个结构体,我以为序列化的部分是没有任何的普适性的,可是依然很是的难作,因此说思路很重要,这样,下一次在遇到困难问题的时候,咱们能够不会是无从下手,至少还能作一些挣扎~
到这里咱们就差很少结束了,固然里面还有不少的不足
好比写完时隔两天,在写文章的过程,忽然发现,我只写了申请文件结构体的函数,没有写释放结构体的函数,评论里面有木有小伙伴帮忙补上的~