 |
|
 |
|
 |
| 如何阅读源代码 |
|
作者:4Lzx.com 文章来源:bbs.4Lzx.com 点击数: 更新时间:2005-9-4 18:51:52  |
[0]=='-')) log_fname=NULL;
/* force STDIN? */
/* check for gzipped file - .gz */
if (log_fname) if (!strcmp((log_fname+strlen(log_fname)-3),".gz"))
gz_log=1;
这一段是分析命令行参数及开关。(getopt()的用法我在另外一篇文章中讲过,这里就不再重复了。)可以看到,这个软件虽然功能不太复杂,但是开关选项还是不少。大多数的unix/linux程序的开头部分都是这个套路,初始化配置文件,并且读入分析命令行。在这段程序中,我们需要注意一个函数:add_nlist(). print_opts(), get_config()等等一看就明白,就不用多讲了。这里我们已经是第二次遇到add_nlist这个函数了,就仔细看看吧。
$ grep add_nlist *.h
linklist.h:extern int add_nlist(char *, NLISTPTR *);
/* add list item */
可以发现它定义在linklist.h中。
在这个h文件中,当然会有一些数据结构的定义,比如:
struct nlist { char string[80];
/* list struct for HIDE items */
struct nlist *next; };
typedef struct nlist *NLISTPTR;
struct glist { char string[80];
/* list struct for GROUP items */
char name[80];
struct glist *next; };
typedef struct glist *GLISTPTR;
这是两个链表结构。还有
extern GLISTPTR group_sites ; /* "group" lists */
extern GLISTPTR group_urls ;
extern GLISTPTR group_refs ;
这些都是链表, 太多了,不用一一看得很仔细,因为目前也看不出来什么东西。当然要注意它们是extern的, 也就是说,可以在其他地方(文件)看到它们的数值(类似于C++中的public变量)。这里还定义了4个函数:
extern char *isinlist(NLISTPTR, char *);
/* scan list for str */
extern char *isinglist(GLISTPTR, char *);
/* scan glist for str */
extern int add_nlist(char *, NLISTPTR *);
/* add list item */
extern int add_glist(char *, GLISTPTR *);
/* add group list item */
注意,这些都是extern的,也就是说,可以在其他地方见到它们的调用(有点相当于C++中的public函数)。再来看看linklist.c,
NLISTPTR new_nlist(char *); /* new list node */
void del_nlist(NLISTPTR *); /* del list */
GLISTPTR new_glist(char *, char *); /* new group list node */
void del_glist(GLISTPTR *); /* del group list */
int isinstr(char *, char *);
这5个函数是内部使用的(相当于C++中的private), 也就是说,这些函数只被isinlist(NLISTPTR, char *), isinglist(GLISTPTR, char *), add_nlist(char *, NLISTPTR *), add_glist(char *, GLISTPTR *)调用,而不会出现在其他地方。所以,我们先来看这几个内部函数。举例来说,
add_nlist(char *)
NLISTPTR new_nlist(char *str)
{
NLISTPTR newptr;
if (sizeof(newptr->string) < strlen(str))
{
if (verbose)
fprintf(stderr,"[new_nlist] %s ",msg_big_one);
}
if (( newptr = malloc(sizeof(struct nlist))) != NULL)
{strncpy(newptr->string, str, sizeof(newptr->string));
newptr->next=NULL;}
return newptr;
}
这个函数分配了一个struct nlist, 并且把其中的string赋值为str, next赋值为NULL.这实际上是创建了链表中的一个节点。verbose是一个全局变量,定义了输出信息的类型,如果verbose为1,则输出很详细的信息,否则输出简略信息。这是为了调试或者使用者详细了解程序情况来用的。不是重要内容,虽然我们常常可以在这个源程序的其他地方看到它。另外一个函数:
void del_nlist(NLISTPTR *list)
{
NLISTPTR cptr,nptr;
cptr=*list;
while (cptr!=NULL)
{
nptr=cptr->next;
free(cptr);
cptr=nptr;
}
}
这个函数删除了一个nlist(也可能是list所指向的那一个部分开始知道链表结尾),比较简单。看完了这两个内部函数,可以来看
/*********************************************/
/* ADD_NLIST - add item to FIFO linked list */
/*********************************************/
int add_nlist(char *str, NLISTPTR *list)
{
NLISTPTR newptr,cptr,pptr;
if ( (newptr = new_nlist(str)) != NULL)
{
if (*list==NULL) *list=newptr;
else
{
cptr=pptr=*list;
while(cptr!=NULL) { pptr=cptr; cptr=cptr->next; };
pptr->next = newptr;
}
}
return newptr==NULL;
}
这个函数是建立了一个新的节点,把参数str赋值给新节点的string, 并把它连接到list所指向链表的结尾。另外的三个函数:new_glist(), del_glist(), add_glist()完成的功能和上述三个差不多,所不同的只是它们所处理的数据结构不同。看完了这几个函数,我们回到main程序。接下来是,
/* setup our internal variables */
init_counters(); /* initalize main counters */
我们所阅读的这个软件是用来分析日志并且做出统计的,那么这个函数的名字已经告诉了我们,这是一个初始化计数器的函数。简略的看看吧!
$ grep init_counters *.h
webalizer.h:extern void init_counters();
在webalizer.c中找到:
void init_counters()
{
int i;
for (i=0;i
根据在最开始读过的README文件,这个page_type是用来定义处理的页面的类型的。在README文件中,
-P name Page type. This is the extension of files you consider to
be pages for Pages calculations (sometimes called 'pageviews').
The default is 'htm*' and 'cgi' (plus whatever HTMLExtension
you specified if it is different). Don't use a period!
我们在程序中也可以看到,如果没有在命令行中或者config文件中指定,则根据处理的日志文件的类型来添加缺省的文件类型。比如对于CLF文件(WWW日志),处理html, htm, cgi文件
if (log_type == LOG_FTP)
{
/* disable stuff for ftp logs */
ntop_entry=ntop_exit=0;
ntop_search=0;
}
else
.....
这一段是对于FTP的日志格式,设置搜索列表。
for (i=0;i
清空哈西表,为下面即将进行的排序工作做好准备。关于哈西表,这是数据结构中常用的一种用来快速排序的结构,如果不清楚,可以参考相关书籍,比如清华的<<数据结构>>教材或者<<数据结构的C++实现>>等书。
if (verbose>1)
{
uname(&system_info);
printf("Webalizer V%s-%s (%s %s) %s ",
version,editlvl,system_info.sysname,
system_info.release,language);
}
这一段,是打印有关系统的信息和webalizer程序的信息(可以参考uname的函数说明)。
#ifndef USE_DNS
if (strstr(argv[0],"webazolver")!=0)
{
printf("DNS support not present, aborting... ");
exit(1);
}
#endif /* USE_DNS */
这一段,回忆我们在看README文件的时候,曾经提到过可以在编译的时候设置选项开关来设定DNS支持,在源代码中可以看到多次这样的代码段出现,如果不指定DNS支持,这些代码段则会出现(ifdef)或者不出现(ifndef).下面略过这些代码段,不再重复。
/* open log file */
if (gz_log)
{
gzlog_fp = gzopen(log_fname,"rb");
if (gzlog_fp==Z_NULL)
{
/* Error: Can't open log file ... */
fprintf(stderr, "%s %s ",msg_log_err,log_fname);
exit(1);
}
}
else
{
if (log_fname)
{
log_fp = fopen(log_fname,"r");
if (log_fp==NULL)
{
/* Error: Can't open log file ... */
fprintf(stderr, "%s %s ",msg_log_err,log_fname);
exit(1);
}
}
}
这一段,回忆在README文件中曾经读到过,如果log文件是gzip压缩格式,则用gzopen函数打开(可以猜想gz***是一套针对gzip压缩格式的实时解压缩函数),如果不是,则用fopen打开。
/* switch directories if needed */
if (out_dir)
{
if (chdir(out_dir) != 0)
{
/* Error: Can't change directory to ... */
fprintf(stderr, "%s %s ",msg_dir_err,out_dir);
exit(1);
}
}
同样,回忆在README文件中读到过,如果参数行有-o out_dir, 则将输出结果到该目录,否则,则输出到当前目录。在这一段中,如果输出目录不存在(chdir(out_dir) != 0)则出错。
#ifdef USE_DNS
if (strstr(argv[0],"webazolver")!=0)
{
if (!dns_children) dns_children=5; /* default dns children if needed */
if (!dns_cache)
{
/* No cache file specified, aborting... */
fprintf(stderr,"%s ",msg_dns_nocf); /* Must have a cache file */
exit(1);
}
}
......
在上面曾经提到过,这是DNS解析的代码部分,可以略过不看,不会影响对整个程序的理解。
/* prep hostname */
if (!hname)
{
if (uname(&system_info)) hname="localhost";
else hname=system_info.nodename;
}
这一段继续处理参数做准备工作。如果在命令行中指定了hostname(机器名)则采用指定的名称,否则调用uname查找机器名,如果没有,则用localhost来作为机器名。(同样在README中说得很详细)
/* get past history */
if (ignore_hist) {if (verbose>1) printf("%s ",msg_ign_hist); }
else get_history();
如果在命令行中指定了忽略历史文件,则不读取历史文件,否则调用get_history()来读取历史数据。在这里,我们可以回想在README文件中同样说过这一细节,在命令行或者配置文件中都能指定这一开关。需要说明的是,我们在这里并不一定需要去看get_history这一函数,因为从函数的名称,README文件和程序注释都能很清楚的得知这一函数的功能,不一定要去看代码。而如果要猜想的话,也可以想到,history是webalizer在上次运行的时候记录下来的一个文件,而这个文件则是去读取它,并将它的数据包括到这次的分析中去。不信,我们可以来看看。
void get_history()
{
int i,numfields;
FILE *hist_fp;
char buffer[BUFSIZE];
/* first initalize internal array */
for (i=0;i<12;i++)
{
hist_month[i]=hist_year[i]=hist_fday[i]=hist_lday[i]=0;
hist_hit[i]=hist_files[i]=hist_site[i]=hist_page[i]=hist_visit[i]=0;
hist_xfer[i]=0.0;
}
hist_fp=fopen(hist_fname,"r");
if (hist_fp)
{
if (verbose>1) printf("%s %s ",msg_get_hist,hist_fname);
while ((fgets(buffer,BUFSIZE,hist_fp)) != NULL)
{
i = atoi(buffer) -1;
if (i>11)
{
if (verbose)
fprintf(stderr,"%s (mth=%d) ",msg_bad_hist,i+1);
continue;
}
/* month# year# requests files sites xfer firstday lastday */
numfields = sscanf(buffer,"%d %d %lu %lu %lu %lf %d %d %lu %lu",
&hist_month[i],
&hist_year[i],
&hist_hit[i],
&hist_files[i],
&hist_site[i],
&hist_xfer[i],
&hist_fday[i],
&hist_lday[i],
&hist_page[i],
&hist_visit[i]);
if (numfields==8) /* kludge for reading 1.20.xx history files */
{
hist_page[i] = 0;
hist_visit[i] = 0;
}
}
fclose(hist_fp);
}
else if (verbose>1) printf("%s ",msg_no_hist);
}
/*********************************************/
/* PUT_HISTORY - write out history file */
/*********************************************/
void put_history()
{
int i;
FILE *hist_fp;
hist_fp = fopen(hist_fname,"w");
if (hist_fp)
{
if (verbose>1) printf("%s ",msg_put_hist);
for (i=0;i<12;i++)
{
if ((hist_month[i] != 0) && (hist_hit[i] != 0))
{
fprintf(hist_fp,"%d %d %lu %lu %lu %.0f %d %d %lu %lu ",
hist_month[i],
hist_year[i],
hist_hit[i],
hist_files[i],
hist_site[i],
hist_xfer[i],
hist_fday[i],
hist_lday[i],
hist_page[i],
hist_visit[i]);
}
}
fclose(hist_fp);
}
else
if (verbose)
fprintf(stderr,"%s %s ",msg_hist_err,hist_fname);
}
在preserve.c中,这两个函数是成对出现的。get_hist上一页 [1] [2] [3] [4] 下一页 |
| 学院录入:4Lzx.com 责任编辑:4Lzx.com |
|
上一篇学院: gdb (GNU 调试器):基础
下一篇学院: 某个数据文件损坏,如何打开数据库?(for Linux) |
| 【字体:小 大】【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口】 |
网友评论:(评论内容只代表网友观点,与本站立场无关!) |
| |
|
 |
|
|