1:重要的结构体

  读取配置文件信息到全局的结构体struct server_config_t server_config中,这个结构在很多文件中都有引用到很重要。

/* dhcpd.h */

struct server_config_t {
u_int32_t server; /* Our IP, in network order */
u_int32_t start; /* Start address of leases, network order */
u_int32_t end; /* End of leases, network order */
struct option_set *options;/* List of DHCP options loaded from the config file */
char *interface; /* The name of the interface to use */
int ifindex; /* Index number of the interface to use */
unsigned char arp[]; /* Our arp address */
unsigned long lease; /* lease time in seconds (host order) */
unsigned long max_leases; /* maximum number of leases (including reserved address) */
char remaining; /* should the lease file be interpreted as lease time remaining, or
       as the time the lease expires */
unsigned long auto_time; /* how long should udhcpd wait before writing a config file.
       if this is zero, it will only write one on SIGUSR1 */
unsigned long decline_time;/* how long an address is reserved if a client returns a
     decline message */
unsigned long conflict_time;/* how long an arp conflict offender is leased for */
unsigned long offer_time; /* how long an offered address is reserved */
unsigned long min_lease; /* minimum lease a client can request*/
char *lease_file;
char *pidfile;
char *notify_file; /* What to run whenever leases are written */
u_int32_t siaddr; /* next server bootp option */
char *sname; /* bootp server name */
char *boot_file; /* bootp boot file option */
};

  英文释意也很明白,比较重要的有struct option_set *options;成员,它是一个指向记录配置文件中对opt配置的链表的指针,并且data以CLV方式存储,结构如下:

/* dhcpd.h */

struct option_set {
unsigned char *data;
struct option_set *next;
};

2:读入配置文件

/* dhcpd.c */

#ifdef COMBINED_BINARY
int udhcpd_main(int argc, char *argv[])
#else
int main(int argc, char *argv[])
#endif
{
fd_set rfds;
struct timeval tv;
int server_socket = -;
int bytes, retval;
struct dhcpMessage packet;
unsigned char *state;
unsigned char *server_id, *requested;
u_int32_t server_id_align, requested_align;
unsigned long timeout_end;
struct option_set *option;
struct dhcpOfferedAddr *lease;
int pid_fd;
int max_sock;
int sig; OPEN_LOG("udhcpd");
LOG(LOG_INFO, "udhcp server (v%s) started", VERSION); memset(&server_config, , sizeof(struct server_config_t)); /* 读取配置文件到server_config结构中供全局使用 */
if (argc < )
read_config(DHCPD_CONF_FILE);/* use default config file */
else read_config(argv[]);/* use designated config file */

  在files.c里的read_config函数是读取的入口:

/* files.c */

/*
配置文件每一行的格式为'key(空格or\t)value'的格式(特殊:opt key(空格or\t)value),value值的类型有以下几种
分别对应以下的处理方法 value值类型 处理方法
ip read_ip
string read_str
number read_u32
yes/no read_yn
opt read_opt
*/
int read_config(char *file)
{
FILE *in;
char buffer[], orig[], *token, *line;
int i; /* 先将默认配置解析到server_config<全局的配置信息结构体>结构中 */
for (i = ; strlen(keywords[i].keyword); i++)
if (strlen(keywords[i].def))
keywords[i].handler(keywords[i].def, keywords[i].var); if (!(in = fopen(file, "r"))) {
LOG(LOG_ERR, "unable to open config file: %s", file);
return ;
}
/* 将外部配置文件一行一行解析 */
while (fgets(buffer, , in)) { /* fgets可能会将'\n'读入,如有就将其替换为'\0' */
if (strchr(buffer, '\n')) *(strchr(buffer, '\n')) = '\0';
strncpy(orig, buffer, ); /* 以#开头的行配置跳过 */
if (strchr(buffer, '#')) *(strchr(buffer, '#')) = '\0';
token = buffer + strspn(buffer, " \t");
if (*token == '\0') continue; line = token + strcspn(token, " \t=");
if (*line == '\0') continue;
*line = '\0';/* 截取行得到token(keyword) */
line++; /* 获得首尾无空白符的配置信息 */
/* eat leading whitespace */
line = line + strspn(line, " \t=");
/* eat trailing whitespace */
for (i = strlen(line); i > && isspace(line[i - ]); i--);
line[i] = '\0';
/* token就是key值 line即此行的配置信息 */ for (i = ; strlen(keywords[i].keyword); i++)
/* 确认key值正确(忽略大小写) */
if (!strcasecmp(token, keywords[i].keyword))
/* 将此行的配置更新到server_config中 */
if (!keywords[i].handler(line, keywords[i].var)) {
/* 如果更新失败就使用默认配置 */
LOG(LOG_ERR, "unable to parse '%s'", orig);
/* reset back to the default value */
keywords[i].handler(keywords[i].def, keywords[i].var);
}
}
fclose(in);
return ;
}

  这是我见过用C来截取字符串比较精巧的设计(用strchr,strspn,strcspn三个字符串处理函数实现),以后有读取配置文件的编码需求完全可以参考其做法,最后在得到的tokenline即配置项名字和配置内容.这里有一个辅助结构体数组不得不提:

/* files.c */

//struct config_keyword 将key、处理方法、要保存的地址、默认配置四项组在一起
static struct config_keyword keywords[] = {
/* keyword[14] handler variable address default[20] */
{"start", read_ip, &(server_config.start), "192.168.0.20"},
{"end", read_ip, &(server_config.end), "192.168.0.254"},
{"interface", read_str, &(server_config.interface), "eth0"},
{"option", read_opt, &(server_config.options), ""},
{"opt", read_opt, &(server_config.options), ""},
{"max_leases", read_u32, &(server_config.max_leases), ""},
{"remaining", read_yn, &(server_config.remaining), "yes"},
{"auto_time", read_u32, &(server_config.auto_time), ""},
{"decline_time",read_u32, &(server_config.decline_time),""},
{"conflict_time",read_u32,&(server_config.conflict_time),""},
{"offer_time", read_u32, &(server_config.offer_time), ""},
{"min_lease", read_u32, &(server_config.min_lease), ""},
{"lease_file", read_str, &(server_config.lease_file), "/var/lib/misc/udhcpd.leases"},
{"pidfile", read_str, &(server_config.pidfile), "/var/run/udhcpd.pid"},
{"notify_file", read_str, &(server_config.notify_file), ""},
{"siaddr", read_ip, &(server_config.siaddr), "0.0.0.0"},
{"sname", read_str, &(server_config.sname), ""},
{"boot_file", read_str, &(server_config.boot_file), ""},
/*ADDME: static lease */
{"", NULL, NULL, ""}
};

它的结构体原型为:
/* files.h */

struct config_keyword {
char keyword[];
int (*handler)(char *line, void *var);
void *var;
char def[];
};

  在通过read_config函数的解析后得到的token就去匹配keyword,匹配到了就调用其函数指针handler,传入line和对应的结构体成员地址进行赋值,一气呵成!(通过一个结构体数组将所有的配置关键字和对应的处理方式罗列出来,不仅看起来条理清晰而且在需要添加新的配置项时也很方便,设计思路真的值得参考).

  上面讲到过,根据配置项的默认值对应不同的处理函数,下面讲述一下这些处理函数:

/* files.c */

static int read_ip(char *line, void *arg)

/* 将字符串格式的ip地址转换为u_int32_t保存在地址arg中 */
/* on these functions, make sure you datatype matches */
static int read_ip(char *line, void *arg)
{
struct in_addr *addr = arg;
struct hostent *host;
int retval = ; if (!inet_aton(line, addr)) {
/* 当line不是ip地址而是主机或域名时解析出IP地址并保存 */
if ((host = gethostbyname(line)))
addr->s_addr = *((unsigned long *) host->h_addr_list[]);
else retval = ;
}
return retval;
}

static int read_str(char *line, void *arg)

/*
首先free掉arg指向的内存,然后根据字符串line大小分配合适内存,把字符串line拷贝
到分配的内存中(strdup函数的功能),让arg指向新分配的内存
*/
static int read_str(char *line, void *arg)
{
char **dest = arg; if (*dest) free(*dest);/* 使用前先free以防止内存泄露 */
*dest = strdup(line); return ;
}

static int read_u32(char *line, void *arg)

/* 将line指向的内容转换为unsigned long 的类型保存于arg指向的内存中 */
static int read_u32(char *line, void *arg)
{
u_int32_t *dest = arg;
char *endptr;
*dest = strtoul(line, &endptr, );
return endptr[] == '\0';
}

static int read_yn(char *line, void *arg)

/* line为yes<忽略大小写> arg所指成员赋1,否则赋值0 */
static int read_yn(char *line, void *arg)
{
char *dest = arg;
int retval = ; if (!strcasecmp("yes", line))
*dest = ;
else if (!strcasecmp("no", line))
*dest = ;
else retval = ; return retval;
}

  最后一个read_opt函数显得比较特殊,因为它的配置文件格式是opt/option name value,所以通过read_config函数之后,token是opt/option,line是name value,这样的line还得进行一次类似read_config函数的解析得到toke=name和line=value,最后将这个添加到struct option_set *options;指向的一个单链表中,类似的解析就在read_opt函数中进行.

3:配置文件中选项的读取

  read_opt的思路和read_config函数的思路是相似的,先介绍一个重要的结构体数组options

/* options.c */

/* supported options are easily added here */
struct dhcp_option options[] = {
/* name[10] flags code */
{"subnet", OPTION_IP | OPTION_REQ, 0x01},
{"timezone", OPTION_S32, 0x02},
{"router", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x03},
{"timesvr", OPTION_IP | OPTION_LIST, 0x04},
{"namesvr", OPTION_IP | OPTION_LIST, 0x05},
{"dns", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x06},
{"logsvr", OPTION_IP | OPTION_LIST, 0x07},
{"cookiesvr", OPTION_IP | OPTION_LIST, 0x08},
{"lprsvr", OPTION_IP | OPTION_LIST, 0x09},
{"hostname", OPTION_STRING | OPTION_REQ, 0x0c},
{"bootsize", OPTION_U16, 0x0d},
{"domain", OPTION_STRING | OPTION_REQ, 0x0f},
{"swapsvr", OPTION_IP, 0x10},
{"rootpath", OPTION_STRING, 0x11},
{"ipttl", OPTION_U8, 0x17},
{"mtu", OPTION_U16, 0x1a},
{"broadcast", OPTION_IP | OPTION_REQ, 0x1c},
{"ntpsrv", OPTION_IP | OPTION_LIST, 0x2a},
{"wins", OPTION_IP | OPTION_LIST, 0x2c},
{"requestip", OPTION_IP, 0x32},
{"lease", OPTION_U32, 0x33},
{"dhcptype", OPTION_U8, 0x35},
{"serverid", OPTION_IP, 0x36},
{"message", OPTION_STRING, 0x38},
{"tftp", OPTION_STRING, 0x42},
{"bootfile", OPTION_STRING, 0x43},
{"", 0x00, 0x00}
};

  这里建立了配置选项关键字及flag和code之间的联系.再看read_opt函数

/* files.c */

/*
函数将line中的name和value解析出来,通过name值定位到结构体数组
的options[X]、value通过函数转换得到的buffer、buffer的长度length、
及链表的头指针opt_list一起交由函数attach_option处理
*/
/* read a dhcp option and add it to opt_list */
static int read_opt(char *line, void *arg)
{
struct option_set **opt_list = arg;
char *opt, *val, *endptr;
struct dhcp_option *option = NULL;
int retval = , length = ;
char buffer[];
u_int16_t result_u16;
u_int32_t result_u32;
int i; if (!(opt = strtok(line, " \t="))) return ; /* 通过name找到结构体数组options中的对应项,和此name的code值联系起来 */
for (i = ; options[i].code; i++)
if (!strcmp(options[i].name, opt))
option = &(options[i]); if (!option) return ; do {
val = strtok(NULL, ", \t");
if (val) {
length = option_lengths[option->flags & TYPE_MASK];
retval = ;
switch (option->flags & TYPE_MASK) {
case OPTION_IP:
retval = read_ip(val, buffer);
break;
case OPTION_IP_PAIR:
retval = read_ip(val, buffer);
if (!(val = strtok(NULL, ", \t/-"))) retval = ;
if (retval) retval = read_ip(val, buffer + );
break;
case OPTION_STRING:
length = strlen(val);
if (length > ) {
if (length > ) length = ;
memcpy(buffer, val, length);
retval = ;
}
break;
case OPTION_BOOLEAN:
retval = read_yn(val, buffer);
break;
case OPTION_U8:
buffer[] = strtoul(val, &endptr, );
retval = (endptr[] == '\0');
break;
case OPTION_U16:
result_u16 = htons(strtoul(val, &endptr, ));
memcpy(buffer, &result_u16, );
retval = (endptr[] == '\0');
break;
case OPTION_S16:
result_u16 = htons(strtol(val, &endptr, ));
memcpy(buffer, &result_u16, );
retval = (endptr[] == '\0');
break;
case OPTION_U32:
result_u32 = htonl(strtoul(val, &endptr, ));
memcpy(buffer, &result_u32, );
retval = (endptr[] == '\0');
break;
case OPTION_S32:
result_u32 = htonl(strtol(val, &endptr, ));
memcpy(buffer, &result_u32, );
retval = (endptr[] == '\0');
break;
default:
break;
}
/* 把选项值放在以code升序的单链表中 */
if (retval)
attach_option(opt_list, option, buffer, length);
};
} while (val && retval && option->flags & OPTION_LIST);
return retval;
}

  进入attach_option函数:

/* options.c */

/* add an option to the opt_list */
void attach_option(struct option_set **opt_list, struct dhcp_option *option, char *buffer, int length)
{
struct option_set *existing, *new, **curr; /* add it to an existing option */
/* 如果此opt已存在链表中则将buff添加到原值的后面 OPT_CODE=0,OPT_LEN=1*/
if ((existing = find_option(*opt_list, option->code))) {
DEBUG(LOG_INFO, "Attaching option %s to existing member of list", option->name);
if (option->flags & OPTION_LIST) {
if (existing->data[OPT_LEN] + length <= ) {
existing->data = realloc(existing->data,
existing->data[OPT_LEN] + length + );
memcpy(existing->data + existing->data[OPT_LEN] + , buffer, length);
/*
byte byte length*byte length
- - - - - - - - - - - - - - - - - - - - - - - --
| | | | |
| code | length | buff_old | buff_new |
| | | | |
- - - - - - - - - - - - - - - - - - - - - - - --
*/
existing->data[OPT_LEN] += length;
} /* else, ignore the data, we could put this in a second option in the future */
} /* else, ignore the new data */
} else {
/* 按照code值顺序添加新节点到链表中 */
DEBUG(LOG_INFO, "Attaching option %s to list", option->name); /* make a new option */
new = malloc(sizeof(struct option_set));
new->data = malloc(length + );
new->data[OPT_CODE] = option->code;
new->data[OPT_LEN] = length;
memcpy(new->data + , buffer, length); /* curr始终作为指向新节点指针的指针 */
curr = opt_list;
while (*curr && (*curr)->data[OPT_CODE] < option->code)
curr = &(*curr)->next; new->next = *curr;
*curr = new;
}
}

  这就是上面讲述的使用CLV的方式存储选项配置,这个配置选项链表是升序的单链表,传入的是指向链表头部的指针的指针(这点很重要,以前单链表的操作会考虑插入作为第一个节点这种特殊情况,这里就不用).

4:总结

  配置文件的读取模块大概就是以上的内容,通过分析实现源码觉得有很多实现方式很值得参考.

可作为轮子用的有:

1:通过strchr,strspn,strcspn三个字符串处理函数截取配置信息

2:attach_option函数中对于升序链表的操作

值得参考的设计思路:

1:将配置信息的名字,处理方法,保存位置,默认值组织一个结构体数组,这样的管理方式结构清晰且易于扩展

udhcpd源码分析2--读取配置文件的更多相关文章

  1. mybatis源码分析之02配置文件解析

    该篇正式开始学习mybatis的源码,本篇主要学习mybatis是如何加载配置文件mybatis-config.xml的, 先从测试代码入手. public class V1Test { public ...

  2. Mybatis 源码分析--Configuration.xml配置文件加载到内存

    (补充知识点: 1 byte(字节)=8 bit(位) 通常一个标准英文字母占一个字节位置,一个标准汉字占两个字节位置:字符的例子有:字母.数字系统或标点符号) 1.创建SqlSessionFacto ...

  3. udhcpd源码分析4--获取client报文及发包动作

    1:重要的结构体 获取的报文是UDP的payload部分,结构体struct dhcpMessage描述了dhcp报文的结构. /* packet.h */ struct dhcpMessage { ...

  4. udhcpd源码分析3--IP租赁管理

    1:重要的结构体 全局链表的成员struct dhcpOfferedAddr *leases 记录了当前租赁出去的IP信息 /* leases.h */ struct dhcpOfferedAddr ...

  5. Heritrix源码分析(三) 修改配置文件order.xml加快你的抓取速度(转)

    本博客属原创文章,欢迎转载!转载请务必注明出处:http://guoyunsky.iteye.com/blog/629891       本博客已迁移到本人独立博客: http://www.yun5u ...

  6. lucene源码分析(2)读取过程实例

    1.官方提供的代码demo Analyzer analyzer = new StandardAnalyzer(); // Store the index in memory: Directory di ...

  7. 03_HibernateSessionFactory源码分析

    文章导读: 讲解了一个线程为什么要使用同一个connection, 我们分析了HiberatenSessionFactory的实现机制, 然后根据Hibernate的写法重构了我们的代码. 最后测试可 ...

  8. mybatis源码分析之06二级缓存

    上一篇整合redis框架作为mybatis的二级缓存, 该篇从源码角度去分析mybatis是如何做到的. 通过上一篇文章知道,整合redis时需要在FemaleMapper.xml中添加如下配置 &l ...

  9. 【Spring源码分析】配置文件读取流程

    前言 Spring配置文件读取流程本来是和http://www.cnblogs.com/xrq730/p/6285358.html一文放在一起的,这两天在看Spring自定义标签的时候,感觉对Spri ...

随机推荐

  1. Ext JS 6学习文档-第8章-主题和响应式设计

    Ext JS 6学习文档-第8章-主题和响应式设计 主题和响应式设计 本章重点在 ExtJS 应用的主题和响应式设计.主要有以下几点内容: SASS 介绍和入门 主题 响应式设计 SASS 介绍和入门 ...

  2. codeforces 319B Psychos in a Line(模拟)

    There are n psychos standing in a line. Each psycho is assigned a unique integer from 1 to n. At eac ...

  3. Discover the Web(栈模拟)

    Description Standard web browsers contain features to move backward and forward among the pages rece ...

  4. java定时执行任务(一)

    需求: 经常遇到这样的需求:要求每天执行一次任务,执行任务时间是凌晨3点 实现: 为了便于检测,我假设的是下一分钟执行任务,每10秒重复执行.(对应现实项目:每天3点执行任务.那么就是下一个3点执行任 ...

  5. TCP系列20—重传—10、早期重传(ER)

    一.介绍 在前面介绍thin stream时候我们介绍过有两种场景下可能不会产生足够的dup ACK来触发快速重传,一种是游戏类响应交互式tcp传输,另外一种是传输受到拥塞控制的限制,只能发送少量TC ...

  6. <Effective C++>读书摘要--Introduction

    Introduction 1.Learning the fundamentals of a programming language is one thing; learning how to des ...

  7. 3dContactPointAnnotationTool开发日志(二十)

      为了使工具更人性化,我又在每个status的text上绑了个可以拖拽实现值改变的脚本,但是不知道为啥rotx那个值越过+-90范围后连续修改就会产生抖动的现象,试了很多方法也没能弄好,不过实际用起 ...

  8. Git无法删除文件问题:fatal: pathspec 'readme.txt' did not match any files

    在使用Git时,不小心创建了一个不需要的文件,想要删除一个文件时,出现了错误: fatal: pathspec 'readme.txt' did not match any files 原因是新建的这 ...

  9. 用select模拟一个socket server成型版2

    1.字典队列测试 import queue msg_dic={} msg_dic[1]=queue.Queue() msg_dic[1].put('hello') msg_dic[1].put('bo ...

  10. 转:Simple Introduction to Dirichlet Process

    来源:http://hi.baidu.com/vyfrcemnsnbgxyd/item/2f10ecc3fc35597dced4f88b Dirichlet Process(DP)是一个很重要的统计模 ...