正文

如何阅读源代码之三(转帖)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文件和程序注释都能很清楚的得知这一函数的功能,不一定要去看代码。而如果要猜想的话,也可以想到,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);}elseif (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_DNSmemset(&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_DNSstruct 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 */....

阅读(3125) | 评论(0)


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

评论

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