|  | 
 
 
 楼主|
发表于 2004-3-8 03:00:47
|
显示全部楼层 
| 清空哈西表,为下面即将进行的排序工作做好准备。关于哈西表,这是数据结构中常用的一种用来快速排序的结构,如果不清楚,可以参考相关书籍,比如清华的<<数据结构>>教材或者<<数据结构的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 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 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 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=hist_year=hist_fday=hist_lday=0;
 hist_hit=hist_files=hist_site=hist_page=hist_visit=0;
 hist_xfer=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,
 &hist_year,
 &hist_hit,
 &hist_files,
 &hist_site,
 &hist_xfer,
 &hist_fday,
 &hist_lday,
 &hist_page,
 &hist_visit);
 
 if (numfields==8) /* kludge for reading 1.20.xx history files */
 {
 hist_page = 0;
 hist_visit = 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 != 0) && (hist_hit != 0))
 {
 fprintf(hist_fp,"%d %d %lu %lu %lu %.0f %d %d %lu %lu ",
 hist_month,
 hist_year,
 hist_hit,
 hist_files,
 hist_site,
 hist_xfer,
 hist_fday,
 hist_lday,
 hist_page,
 hist_visit);
 }
 }
 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.c的1311行,出现:
 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中,找到该变量的定义,从结构定义中可以看到,结构定义了一个日志文件所可能包含的所有信息(参考CLF,FTP, 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++;
 }
 }
 
 
 
 | 
 |