Programming 版 (精华区)

发信人: worldguy (蓝色天际), 信区: Programming
标  题: [转]如何阅读源代码(3)
发信站: 哈工大紫丁香 (2004年04月16日20:51:11 星期五), 站内信件


/* prep hostname */
 if (!hname)
 {
    if (uname(&system_info)) hname="localhost";
    else hname=system_info.nodename;
 }
这一段继续处理参数做准备工作。如果在命令行中指定了hostname(机器名)则采用指定
的名称,否则调用uname查找机器名,如果没有,则用localhost来作为机器名。(同样在RE
ADME中说得很详细)
 /* 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_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_b
ad增加一个。如果没有超过程序允许的最大字符数(则是正确的日志数据纪录),则
    /* 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_recor
d.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++;
 }
}
从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_l
o
grec分开成多行数据了。根据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 */
....
在parse_record分析完数据之后,做日期的分析,把日志中的月份等数据转换成机器可

(可理解)的数据,并存入到log_rec中去。
       if ((i>=12)||(rec_min>59)||(rec_sec>59)||(rec_year<1990))
       {
          total_bad++; /* if a bad date, bump counter */
          if (verbose)
          {
             fprintf(stderr,"%s: %s [%lu]",
               msg_bad_date,log_rec.datetime,total_rec);
......
如果日期,时间错误,则把total_bad计数器增加1,并且打印错误信息到标准错误输出。

         good_rec = 1;
       /* get current records timestamp (seconds since epoch) */
       req_tstamp=cur_tstamp;
       rec_tstamp=((jdate(rec_day,rec_month,rec_year)-epoch)*86400)+
                   (rec_hour*3600)+(rec_min*60)+rec_sec;
       /* Do we need to check for duplicate records? (incremental mode) */
       if (check_dup)
       {
          /* check if less than/equal to last record processed */
          if ( rec_tstamp <= cur_tstamp )
          {
             /* if it is, assume we have already processed and ignore it */
             total_ignore++;
             continue;
          }
          else
          {
             /* if it isn't.. disable any more checks this run */
             check_dup=0;
             /* now check if it's a new month */
             if (cur_month != rec_month)
             {
                clear_month();
                cur_sec = rec_sec; /* set current counters */
                cur_min = rec_min;
                cur_hour = rec_hour;
                cur_day = rec_day;
                cur_month = rec_month;
                cur_year = rec_year;
                cur_tstamp= rec_tstamp;
                f_day=l_day=rec_day; /* reset first and last day */
             }
          }
       }
       /* check for out of sequence records */
       if (rec_tstamp/3600 < cur_tstamp/3600)
       {
          if (!fold_seq_err && ((rec_tstamp+SLOP_VAL)/3600<cur_tstamp/3600) )
             { total_ignore++; continue; }
          else
          {
             rec_sec = cur_sec; /* if folding sequence */
             rec_min = cur_min; /* errors, just make it */
             rec_hour = cur_hour; /* look like the last */
             rec_day = cur_day; /* good records timestamp */
             rec_month = cur_month;
             rec_year = cur_year;
             rec_tstamp= cur_tstamp;
          }
       }
       cur_tstamp=rec_tstamp; /* update current timestamp */
如果该日期、时间没有错误,则该数据是一个好的数据,将good_record计数器加1,并
且检查时间戳,和数据是否重复数据。这里有一个函数,jdate()在主程序一开头我们就遇
到了,当时跳了过去没有深究,这里留给读者做一个练习。(提示:该函数根据一个日期
产生一个字符串,这个字符串是惟一的,可以检查时间的重复性,是一个通用函数,可以
在别的程序中拿来使用)
       /*********************************************/
       /* DO SOME PRE-PROCESS FORMATTING */
       /*********************************************/
       /* fix URL field */
       cp1 = cp2 = log_rec.url;
       /* handle null '-' case here... */
       if (*++cp1 == '-') { *cp2++ = '-'; *cp2 = ''; }
       else
       {
          /* strip actual URL out of request */
          while ( (*cp1 != ' ') && (*cp1 != '') ) cp1++;
          if (*cp1 != '')
          {
             /* scan to begin of actual URL field */
             while ((*cp1 == ' ') && (*cp1 != '')) cp1++;
             /* remove duplicate / if needed */
             if (( *cp1=='/') && (*(cp1+1)=='/')) cp1++;
             while ((*cp1 != ' ')&&(*cp1 != '"')&&(*cp1 != ''))
                *cp2++ = *cp1++;
             *cp2 = '';
          }
       }
       /* un-escape URL */
       unescape(log_rec.url);
       /* check for service (ie: http://) and lowercase if found */
       if ( (cp2=strstr(log_rec.url,"://")) != NULL)
       {
          cp1=log_rec.url;
          while (cp1!=cp2)
          {
             if ( (*cp1>='A') && (*cp1<='Z')) *cp1 += 'a'-'A';
             cp1++;
          }
       }
       /* strip query portion of cgi scripts */
       cp1 = log_rec.url;
       while (*cp1 != '')
         if (!isurlchar(*cp1)) { *cp1 = ''; break; }
         else cp1++;
       if (log_rec.url[0]=='')
         { log_rec.url[0]='/'; log_rec.url[1]=''; }
       /* strip off index.html (or any aliases) */
       lptr=index_alias;
       while (lptr!=NULL)
       {
          if ((cp1=strstr(log_rec.url,lptr->string))!=NULL)
          {
             if ((cp1==log_rec.url)||(*(cp1-1)=='/'))
             {
                *cp1='';
                if (log_rec.url[0]=='')
                 { log_rec.url[0]='/'; log_rec.url[1]=''; }
                break;
             }
          }
          lptr=lptr->next;
       }
       /* unescape referrer */
       unescape(log_rec.refer);
......
这一段,做了一些URL字符串中的字符转换工作,很长,我个人认为为了程序的模块化,
结构化和可复用性,应该将这一段代码改为函数,避免主程序体太长,造成可读性不强和
没有移植性,和不够结构化。跳过这一段乏味的代码,进入到下面一个部分---后处理。
if (gz_log) gzclose(gzlog_fp);
 else if (log_fname) fclose(log_fp);
 if (good_rec) /* were any good records? */
 {
    tm_site[cur_day-1]=dt_site; /* If yes, clean up a bit */
    tm_visit[cur_day-1]=tot_visit(sd_htab);
    t_visit=tot_visit(sm_htab);
    if (ht_hit > mh_hit) mh_hit = ht_hit;
    if (total_rec > (total_ignore+total_bad)) /* did we process any? */
    {
       if (incremental)
       {
          if (save_state()) /* incremental stuff */
          {
             /* Error: Unable to save current run data */
             if (verbose) fprintf(stderr,"%s ",msg_data_err);
             unlink(state_fname);
          }
       }
       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 */
    }
    end_time = times(&mytms); /* display timing totals? */
    if (time_me' '(verbose>1))
    {
       printf("%lu %s ",total_rec, msg_records);
       if (total_ignore)
       {
          printf("(%lu %s",total_ignore,msg_ignored);
          if (total_bad) printf(", %lu %s) ",total_bad,msg_bad);
             else printf(") ");
       }
       else if (total_bad) printf("(%lu %s) ",total_bad,msg_bad);
       /* get processing time (end-start) */
       temp_time = (float)(end_time-start_time)/CLK_TCK;
       printf("%s %.2f %s", msg_in, temp_time, msg_seconds);
       /* calculate records per second */
       if (temp_time)
         i=( (int)( (float)total_rec/temp_time ) );
       else i=0;
       if ( (i>0) && (i<=total_rec) ) printf(", %d/sec ", i);
          else printf(" ");
    }
这一段,做了一些后期的处理。接下来的部分,我想在本文中略过,留给感兴趣的读者
自己去做分析。原因有两点:
1、这个程序在前面结构化比较强,而到了结构上后面有些乱,虽然代码效率还是比较高
,但是可重用性不够强, 限于篇幅,我就不再一一解释了。
2、前面分析程序过程中,也对后面的代码做了一些预测和估计,也略微涉及到了后面的
代码,而且读者可以根据上面提到的原则来自己分析代码,也作为一个实践吧。


--
No more you blind me heaven,
no more you bury me hell,
be powderize indifferent gods,
be lighted all the hears.

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 202.118.239.18]
※ 修改:·worldguy 於 04月16日20:51:27 修改本文·[FROM: 202.118.239.18]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:208.129毫秒