正文

如何阅读源代码之三(转帖)2006-12-06 22:51:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/goal00001111/21339.html

分享到:

在上面曾经提到过,这是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文件和程序注释都能很清楚的得知这一函数的功能,不一定要去看代码。而如果要猜想的话,也可以想到,historywebalizer在上次运行的时候记录下来的一个文件,而这个文件则是去读取它,并将它的数据包括到这次的分析中去。不信,我们可以来看看。
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_history()读取文件中的数据,并将其记录到hist_开头的一些数组中去。而put_history()则是将一些数据记录到同样的数组中去。我们可以推测得知,hist_数组是全局变量(在函数中没有定义),也可以查找源代码验证。同样,我们可以找一找put_history()出现的地方,来验证刚才的推测是否正确。在webalizer.c1311行,出现:
month_update_exit(rec_tstamp); /* calculate exit pages */
write_month_html(); /* write monthly HTML file */
write_main_index(); /* write main HTML file */
put_history(); /* write history */
可以知道,推测是正确的。再往下读代码,
if (incremental) /* incremental processing? */
{
if ((i=restore_state())) /* restore internal data structs */
{
/* Error: Unable to restore run data (error num) */
/* if (verbose) fprintf(stderr,"%s (%d) ",msg_bad_data,i); */
fprintf(stderr,"%s (%d) ",msg_bad_data,i);
exit(1);
}
......
}
同样,这也是处理命令行和做数据准备,而且和get_history(), put_history()有些类似,读者可以自己练习一下。下面,终于进入了程序的主体部分在做完了命令行分析,数据准备之后,开始从日志文件中读取数据并做分析了。
/*********************************************/
/* MAIN PROCESS LOOP - read through log file */
/*********************************************/

while ( (gz_log)?(our_gzgets(gzlog_fp,buffer,BUFSIZE) != Z_NULL):
(fgets(buffer,BUFSIZE,log_fname?log_fp:stdin) != NULL))
我看到这里的时候,颇有一些不同意作者的这种写法。这一段while中的部分写的比较复杂而且效率不高。因为从程序推断和从他的代码看来,作者是想根据日志文件的类型不同来采用不同的方法读取文件,如果是gzip格式,则用our_gzgets来读取其中一行,如果是普通的文本文件格式,则用fgets()来读取。但是,这段代码是写在while循环中的,每次读取一行就要重复判断一次,明显是多余的而且降低了程序的性能。可以在while循环之前做一次这样的判断,然后就不用重复了。
total_rec++;
if (strlen(buffer) == (BUFSIZE-1))
{
if (verbose)
{
fprintf(stderr,"%s",msg_big_rec);
if (debug_mode) fprintf(stderr,": %s",buffer);
else fprintf(stderr," ");
}

total_bad++; /* bump bad record counter */

/* get the rest of the record */
while ( (gz_log)?(our_gzgets(gzlog_fp,buffer,BUFSIZE)!=Z_NULL):
(fgets(buffer,BUFSIZE,log_fname?log_fp:stdin)!=NULL))
{
if (strlen(buffer) < BUFSIZE-1)
{
if (debug_mode && verbose) fprintf(stderr,"%s ",buffer);
break;
}
if (debug_mode && verbose) fprintf(stderr,"%s",buffer);
}
continue; /* go get next record if any */
}
这一段代码,读入一行,如果这一行超过了程序允许的最大字符数(则是错误的日志数据纪录),则跳过本行剩下的数据,忽略掉(continue进行下一次循环)。同时把total_bad增加一个。如果没有超过程序允许的最大字符数(则是正确的日志数据纪录),则
/* got a record... */
strcpy(tmp_buf, buffer); /* save buffer in case of error */
if (parse_record(buffer)) /* parse the record */
将该数据拷贝到一个缓冲区中,然后调用parse_record()进行处理。我们可以同样的推测一下,get_record()是这个程序的一个主要处理部分,分析了日志数据。在parse_record.c中,有此函数,
/*********************************************/
/* PARSE_RECORD - uhhh, you know... */
/*********************************************/

int parse_record(char *buffer)
{
/* clear out structure */
memset(&log_rec,0,sizeof(struct log_struct));
/*
log_rec.hostname[0]=0;
log_rec.datetime[0]=0;
log_rec.url[0]=0;
log_rec.resp_code=0;
log_rec.xfer_size=0;
log_rec.refer[0]=0;
log_rec.agent[0]=0;
log_rec.srchstr[0]=0;
log_rec.ident[0]=0;
*/
#ifdef USE_DNS
memset(&log_rec.addr,0,sizeof(struct in_addr));
#endif

/* call appropriate handler */
switch (log_type)
{
default:
case LOG_CLF: return parse_record_web(buffer); break; /* clf */
case LOG_FTP: return parse_record_ftp(buffer); break; /* ftp */
case LOG_SQUID: return parse_record_squid(buffer); break; /* squid */
}
}

可以看到,log_rec是一个全局变量,该函数根据日志文件的类型,分别调用三种不同的分析函数。在webalizer.h中,找到该变量的定义,从结构定义中可以看到,结构定义了一个日志文件所可能包含的所有信息(参考CLFFTP, SQUID日志文件的格式说明)。
/* log record structure */
struct log_struct { char hostname[MAXHOST]; /* hostname */
char datetime[29]; /* raw timestamp */
char url[MAXURL]; /* raw request field */
int resp_code; /* response code */
u_long xfer_size; /* xfer size in bytes */
#ifdef USE_DNS
struct in_addr addr; /* IP address structure */
#endif /* USE_DNS */
char refer[MAXREF]; /* referrer */
char agent[MAXAGENT]; /* user agent (browser) */
char srchstr[MAXSRCH]; /* search string */
char ident[MAXIDENT]; }; /* ident string (user) */

extern struct log_struct log_rec;

先看一下一个parser.c用的内部函数,然后再来以parse_record_web()为例子看看这个函数是怎么工作的,parse_record_ftp, parse_record_squid留给读者自己分析作为练习。
/*********************************************/
/* FMT_LOGREC - terminate log fields w/zeros */
/*********************************************/

void fmt_logrec(char *buffer)
{
char *cp=buffer;
int q=0,b=0,p=0;

while (*cp != '')
{
/* break record up, terminate fields with '' */
switch (*cp)
{
case ' ': if (b || q || p) break; *cp=''; break;
case '"': q^=1; break;
case '[': if (q) break; b++; break;
case ']': if (q) break; if (b>0) b--; break;
case '(': if (q) break; p++; break;
case ')': if (q) break; if (p>0) p--; break;
}
cp++;
}
}
parser.h头文件中就可以看到,这个函数是一个内部函数,这个函数把一行字符串中间的空格字符用''字符(结束字符)来代替,同时考虑了不替换在双引号,方括号,圆括号中间的空格字符以免得将一行数据错误的分隔开了。(请参考WEB日志的文件格式,可以更清楚的理解这一函数)
int parse_record_web(char *buffer)
{
int size;
char *cp1, *cp2, *cpx, *eob, *eos;

size = strlen(buffer); /* get length of buffer */
eob = buffer+size; /* calculate end of buffer */
fmt_logrec(buffer); /* seperate fields with 's */

/* HOSTNAME */
cp1 = cpx = buffer; cp2=log_rec.hostname;
eos = (cp1+MAXHOST)-1;
if (eos >= eob) eos=eob-1;

while ( (*cp1 != '') && (cp1 != eos) ) *cp2++ = *cp1++;
*cp2 = '';
if (*cp1 != '')
{
if (verbose)
{
fprintf(stderr,"%s",msg_big_host);
if (debug_mode) fprintf(stderr,": %s ",cpx);
else fprintf(stderr," ");
}
while (*cp1 != '') cp1++;
}
if (cp1 < eob) cp1++;

/* skip next field (ident) */
while ( (*cp1 != '') && (cp1 < eob) ) cp1++;
if (cp1 < eob) cp1++;

/* IDENT (authuser) field */
cpx = cp1;
cp2 = log_rec.ident;
eos = (cp1+MAXIDENT-1);
if (eos >= eob) eos=eob-1;

while ( (*cp1 != '[') && (cp1 < eos) ) /* remove embeded spaces */
{
if (*cp1=='') *cp1=' ';
*cp2++=*cp1++;
}
*cp2--='';

if (cp1 >= eob) return 0;

/* check if oversized username */
if (*cp1 != '[')
{
if (verbose)
{
fprintf(stderr,"%s",msg_big_user);
if (debug_mode) fprintf(stderr,": %s ",cpx);
else fprintf(stderr," ");
}
while ( (*cp1 != '[') && (cp1 < eob) ) cp1++;
}

/* strip trailing space(s) */
while (*cp2==' ') *cp2--='';

/* date/time string */
cpx = cp1;
cp2 = log_rec.datetime;
eos = (cp1+28);
if (eos >= eob) eos=eob-1;

while ( (*cp1 != '') && (cp1 != eos) ) *cp2++ = *cp1++;
*cp2 = '';
if (*cp1 != '')
{
if (verbose)
{
fprintf(stderr,"%s",msg_big_date);
if (debug_mode) fprintf(stderr,": %s ",cpx);
else fprintf(stderr," ");
}
while (*cp1 != '') cp1++;
}
if (cp1 < eob) cp1++;

/* minimal sanity check on timestamp */
if ( (log_rec.datetime[0] != '[') ||
(log_rec.datetime[3] != '/') ||
(cp1 >= eob)) return 0;

/* HTTP request */
cpx = cp1;
cp2 = log_rec.url;
eos = (cp1+MAXURL-1);
if (eos >= eob) eos = eob-1;

while ( (*cp1 != '') && (cp1 != eos) ) *cp2++ = *cp1++;
*cp2 = '';
if (*cp1 != '')
{
if (verbose)
{
fprintf(stderr,"%s",msg_big_req);
if (debug_mode) fprintf(stderr,": %s ",cpx);
else fprintf(stderr," ");
}
while (*cp1 != '') cp1++;
}
if (cp1 < eob) cp1++;

if ( (log_rec.url[0] != '"') ||
(cp1 >= eob) ) return 0;

/* response code */
log_rec.resp_code = atoi(cp1);

/* xfer size */
while ( (*cp1 != '') && (cp1 < eob) ) cp1++;
if (cp1 < eob) cp1++;
if (*cp1<'0'||*cp1>'9') log_rec.xfer_size=0;
else log_rec.xfer_size = strtoul(cp1,NULL,10);

/* done with CLF record */
if (cp1>=eob) return 1;

while ( (*cp1 != '') && (*cp1 != ' ') && (cp1 < eob) ) cp1++;
if (cp1 < eob) cp1++;
/* get referrer if present */
cpx = cp1;
cp2 = log_rec.refer;
eos = (cp1+MAXREF-1);
if (eos >= eob) eos = eob-1;

while ( (*cp1 != '') && (*cp1 != ' ') && (cp1 != eos) ) *cp2++ = *cp1++;
*cp2 = '';
if (*cp1 != '')
{
if (verbose)
{
fprintf(stderr,"%s",msg_big_ref);
if (debug_mode) fprintf(stderr,": %s ",cpx);
else fprintf(stderr," ");
}
while (*cp1 != '') cp1++;
}
if (cp1 < eob) cp1++;

cpx = cp1;
cp2 = log_rec.agent;
eos = cp1+(MAXAGENT-1);
if (eos >= eob) eos = eob-1;

while ( (*cp1 != '') && (cp1 != eos) ) *cp2++ = *cp1++;
*cp2 = '';

return 1; /* maybe a valid record, return with TRUE */
}
该函数,一次读入一行(其实是一段日志数据中间的一个域,因为该行数据已经被fmt_logrec分开成多行数据了。根据CLF中的定义,检查该数据并将其拷贝到log_rec结构中去,如果检查该数据有效,则返回1。回到主程序,
/* convert month name to lowercase */
for (i=4;i<7;i++)
log_rec.datetime[i]=tolower(log_rec.datetime[i]);

/* get year/month/day/hour/min/sec values */
for (i=0;i<12;i++)
{
if (strncmp(log_month[i],&log_rec.datetime[4],3)==0)
{ rec_month = i+1; break; }
}

rec_year=atoi(&log_rec.datetime[8]); /* get year number (int) */
rec_day =atoi(&log_rec.datetime[1]); /* get day number */
rec_hour=atoi(&log_rec.datetime[13]); /* get hour number */
rec_min =atoi(&log_rec.datetime[16]); /* get minute number */
rec_sec =atoi(&log_rec.datetime[19]); /* get second number */
....

阅读(2907) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册