程序肯定需要一份配置文件,要不然,自己的程序不是“可配置”的,自己都不好意思往“高大上”靠拢。言归正传,以前自己写代码,配置文件的读写都是各式各样的,有用过xml,有用过其他项目copy过来的。看开源代码的时候,也是各式各样的,比如redis的,Nginx等等。有时候就在想,配置文件的解析还真是麻烦,要自己处理一堆的字符串,有空的时候自己整理一下Nginx的源码,复用Nginx的配置代码,加强自己的代码库。但最近才发现,原来已经有一个很优秀的C/C++配置库libconfig一直在等着我了。
认识libconfig
libconfig库的官方网站在:http://www.hyperrealm.com/libconfig/
一个配置项,可以理解为我们最常见的key-value的形式。key就是你的配置的名字了。那优秀就优秀在value上了。value支持的类型有:
1、常见的数据类型:
整数(int):可以用10进制和16进制表示。0x打头的数字libconfig会自动解析为16进制的数字。
64位整数(int64_t):在数字的后面加上L即可。
浮点数(float):个人不太喜欢用这个类型。
布尔数(bool):true或者false。不区分大小写。
字符串(string):这个字符串非常强大。
a、支持转义字符'\\’, ‘\f’, ‘\n’, ‘\r’,‘\x’ and ‘\t’
。
b、相邻的字符串libconfig会自动拼接。这样太常的内容,我们可以多行来写,可读性非常好。比如:
example = “hello world”; 等价于
example = “hello”
” world”;
【注意】
我们可以使用’=’,也可以使用’:’来作为赋值号。既然是C/C++程序员,还是使用’=’号看得舒服一些。
和C/C++的注释一样,/**/
就是跨行的注释。 //
就是单行注释。当然还支持脚本语言的注释符号#
,#
也是单行注释。但是特殊的是,如果注释符在双引号中使用,那就不再是注释符了,libconfig会解析为正常的明文。
2、数组结构
和平常我们使用的数组是一样一样的,数组的各个元素都必须是相同的数据类型。
3、群组结构
这个可以理解为一个容器。这个容器里面,我们可以放置很多个配置项。当然这些配置项的value也可以继续是群组。
4、列表结构
这个列表和我们C++常用的STL里的list结构可不太一样。这个列表结构里面的元素不要求具备相同的数据类型,元素1是int,元素2可以是string,元素3可以是数组,元素4可以是一个群组,元素5可以是另一个列表。
可以说,正是因为value的多姿多彩,才给了我们程序员无限的发挥空间。通过群组结构和列表结构,我们可以很方便灵活的进行各种变态的配置读取。除了读取配置,可不要忘记了libconfig还有两只手的哦:必要的时候,我们可以把内存里面的一些值,通过libconfig生成一份标准的配置文件。
体验libconfig
动手用libconfig进行一个hello world的配置吧!把value支持的所有数据类型都用上,加深理解。
//example.conf
/* 这个叫Settings */
version = "1.0.0.0";
/* 这个叫Groups */
log = {
log_path = "../logs/sys.log"; /* 日志文件路径 */
log_size = 1024000000L; /* 日志文件大小 */
log_level = 20000; /* 日志等级 */
};
/* 这个叫Lists */
// 业务服务器列表
server = (
{
addr = "10.1.88.1";
port = 9090;
},
{
addr = "10.1.88.2";
port = 9090;
},
{
addr = "10.1.88.3";
port = 9090;
}
);
// 测试配置
test = {
// 这个是数组结构。
uin = [10086L, 10087L, 10088L, 10089L]; /* 测试号码 */
/* 测试服务器 */
server = {
addr = "10.1.99.1";
port = 9090;
};
};
值得说明的是,libconfig是通过路径来读取某一个配置的。比如log.log_path
这个路径对应的是log_path
这个配置项,
server.[0].addr
这个路径对应的是业务服务器列表的第一个元素里面的addr
这个配置项。
libconfig的代码样例
不写一段hello world的代码,是算不上真正接触了libconfig的。libconfig提供了C和C++的API。先用C++来爽一下吧。
首先就是要下载安装libconfig的库。这个很简单,到官网下载,然后./configure & make & make install
就可以了。
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <libconfig.h++>
using namespace std;
using namespace libconfig;
int main(){
Config cfg;
/*
解析配置文件。
*/
try
{
cfg.readFile("example.conf");
}
catch(const FileIOException &fioex)
{
std::cerr << "I/O error while reading file." << std::endl;
return(EXIT_FAILURE);
}
catch(const ParseException &pex)
{
std::cerr << "Parse error at " << pex.getFile() << ":" << pex.getLine()
<< " - " << pex.getError() << std::endl;
return(EXIT_FAILURE);
}
/* 从配置文件中,得到version这个配置项的值。 */
try
{
string version = cfg.lookup("version");
cout << "version: " << version << endl << endl;
}
catch(const SettingNotFoundException &nfex)
{
cerr << "No 'version' setting in configuration file." << endl;
}
/* 从配置文件中,得到日志相关配置值 */
try
{
string log_path = cfg.lookup("log.log_path");
cout << "log_path: " << log_path << endl;
int64_t log_size = cfg.lookup("log.log_size");
cout << "log_size: " << log_size << endl;
int log_level = cfg.lookup("log.log_level");
cout << "log_level: " << log_level << endl << endl;
}
catch(const SettingNotFoundException &nfex)
{
cerr << "log setting mistake in configuration file." << endl;
}
try
{
const Setting &server = cfg.lookup("server");
int count = server.getLength();
cout << "server.count = " << count << endl;
for (int i = 0; i < count; i++)
{
string addr = "";
int port = 0;
if (!server[i].lookupValue("addr", addr)
|| !server[i].lookupValue("port", port))
{
cerr << "lookupValue error" << endl;
continue;
}
cout << "server[" << i << "] = " << addr << ":" << port << endl;
}
{
string addr = "";
int port = 0;
if (!cfg.lookupValue("server.[0].addr", addr)
|| !cfg.lookupValue("server.[0].port", port)) {
cerr << "lookupValue 'server.[0].addr' error" << endl;
}
else
cout << "server[0] = " << addr << ":" << port << endl << endl;
}
}
catch(const SettingNotFoundException &nfex)
{
cerr << "server setting mistake in configuration file." << endl;
}
try
{
const Setting& root = cfg.getRoot();
const Setting &uin = root["test"]["uin"];
int count = uin.getLength();
cout << "uin.count = " << count << endl;
const Setting &test = cfg.lookup("test");
const Setting &test2 = cfg.lookup("test.uin");
for (int i = 0 ; i < count; i++)
{
int64_t u = test["uin"][i];
int64_t uu = uin[i];
int64_t uuu = test2[i];
cout << "uin[" << i << "] = " << u << ", " << uu << ", " << uuu << endl;
}
const Setting &server = root["test"]["server"];
string addr = "";
int port = 0;
if (!server.lookupValue("addr", addr)
|| !server.lookupValue("port", port))
{
cerr << "test server lookupValue error" << endl;
}
else
cout << "test server = " << addr << ":" << port << endl << endl;
}
catch(const SettingNotFoundException &nfex)
{
cerr << "test setting mistake in configuration file." << endl;
}
}
编译和运行一下:
version: 1.0.0.0
log_path: ../logs/sys.log
log_size: 1024000000
log_level: 20000
server.count = 3
server[0] = 10.1.88.1:9090
server[1] = 10.1.88.2:9090
server[2] = 10.1.88.3:9090
server[0] = 10.1.88.1:9090
uin.count = 4
uin[0] = 10086, 10086, 10086
uin[1] = 10087, 10087, 10087
uin[2] = 10088, 10088, 10088
uin[3] = 10089, 10089, 10089
test server = 10.1.99.1:9090