好久没更新博客了哈,今天抽空把之前写了一部分的东西拿出来继续分享。
linux的getopt()和getopt_long()大家都用过,读取命令行参数,比如./test -h 127.0.0.1 -c 100 --port 8080类似这样的。好多脚本语言python,shell这样获取比较简单直接sh就可以了(比如echo "./server --start --port 8080" >> ./start.sh)。有些软件为了方便,都有一个configure文件,通过对configure文件的修改来实现软件的配置控制,熟悉Java开发的对此应该很有感触,Tomcat/Apache的webserver,配置文件一大堆,基本不太可能通过启动时候传入,很多Linux软件都是通过configure文件来设置。
通过configure文件的方式,既方便了传入配置的启动,还可以在文件中针对某一个配置进行注释性的说明,而且也方便查阅。
OK,下面就来说一下这个ConfigureLoader(一下简称cfgloader),它主要实现了对文件中文本的行解析,配置都是通过KeyValue对来编写,可以添加自己的注释(以#开头) :
#server端口号
port : 9999
#这是关于线程数的
#设置
threads : 10
#最大连接数
max_conns : 1024
KeyValue通过":"冒号来分割,并忽略其中的空格。下面来说一下接口,首先一个conf_load(const char*)来获取用户的数据,为什么不用FILE*或者filedescriptor,因为这样限制比较大,这些配置也接受来自getopt哈。然后就是相应的获取KV的接口,conf_get(const char* key),通过传入key获取value,这里可以得到的数据类型是整数或者字符串。如果查询不到或者转换失败会返回NULL或者0。这里有一个小问题待考究,就是conf_get_integer返回long,而long是atol()函数获得的,所以即使用户写的不是数字,也会转化成0,这个可以通过自己写转换,或者传入一个int* error,或者返回 INT_MIN来设置成功失败。
接口:
#ifdef __cplusplus
extern "C" {
#endif
typedef struct kv_t {
char* k;
char* v;
}kv_t;
typedef struct config_t{
kv_t* kv_list;
size_t count;
size_t used;
}config_t;
/**
*@brief : load configure string from char* ,
* and parse. string MUST like "key:value"
*
*@param : [in] configure string
*
*@return : configure structure
*/
config_t* ub_config_load(const char*);
/**
*@brief : get number from spicified key
*
*@param : [in] configure structrue to be released
* [out] is error
*/
long ub_config_get_integer(config_t* cfg, const char* key);
**
*@brief : get CONST char string from spicified key
*
*@param : configure structrue to be released
*/
const char* ub_config_get_string(config_t* cfg, const char* key);
/**
*@brief : load configure string from char* ,
* and parse.
*
*@param : configure structrue to be released
*/
void ub_config_release(config_t*);
size_t ub_config_used(config_t*);
void ub_config_print(config_t*);
#ifdef __cplusplus
}
#endif
实现的话其实比较简单 , 晒出parse整个字串的代码把 ,其他的先不care :
const char* line = cfg;
const char* cur = cfg;
// iterator char string
while((line-cfg)!=len+1) {
// still in same line
if (*(line)!='\n' && (*line)!='\0') {
line++;
continue;
} else {
// first check the cacpacity
if (config->used >= config->count) {
// extends space
config->kv_list = (kv_t*)realloc(config->kv_list,sizeof(kv_t)*config->count*2);
if (NULL == config->kv_list)
goto cleanup;
else {
config->count *= 2;
}
}
kv_t* kv_pair = &config->kv_list[config->used];
/**
* For Key
*/
// skip blank sapce
while(*cur==' ')
cur++;
// ignore the comment (start with "#")
if (*cur == '#' || *cur == '\n' || *cur == '\r')
goto keepgo;
const char* key = cur;
while(*key!=' '&&*key!=':'&&key!=line)
key++;
// alloc len(key) + '\0' space
char* tmp_key = (char*)calloc(1,key-cur+1);
memcpy(tmp_key,cur,key-cur);
int breakout = 0;
while(*cur++!=':') {
if (cur==line) {
free(tmp_key);
breakout = 1;
}
}
// don't find value
if (breakout)
goto keepgo;
/**
* For Value
*/
// skip blank space after ": "
while(*cur==' ')
cur++;
// oops , bad string
if (cur>=line) {
free(tmp_key);
goto keepgo;
}
const char* value = cur;
while(value<=line) {
if (*(value+1)==' ' || value+1==line)
break;
else
value++;
}
char* tmp_value = (char*)calloc(1,value-cur+2);
// don't copy "\n"
memcpy(tmp_value,cur,value-cur+1);
// kv ok
config->used++;
kv_pair->k = tmp_key;
kv_pair->v = tmp_value;
keepgo:
line++;
cur = line;
}
}
return config;
cleanup :
free(config);
check_exception :
return NULL;
}
这个些地方又可以优化的部分,大家可以自己考虑一下,比如放入kv_list数组时候可以有序,这样检索时候可以做二分。或者用Hash,但在小数据量下,Hash的效果并不一定比二分优,反而可能更低。字符切割是自己写的。双指针移动,这里可以考虑省去小部分代码。。。