<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客网-测试点滴</title><link>http://www.cnitblog.com/charester/</link><description>我说我是搞测试的。可是我还要系统维护。

我说我是搞系统维护的。我又要搞网络维护，

我说我是搞网络维护的，我又要。。。

所以说我是个打杂的。

</description><language>zh-cn</language><lastBuildDate>Tue, 02 Dec 2008 17:19:20 GMT</lastBuildDate><pubDate>Tue, 02 Dec 2008 17:19:20 GMT</pubDate><ttl>60</ttl><item><title>好久没来了，谢谢大家</title><link>http://www.cnitblog.com/charester/archive/2008/06/05/45025.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Thu, 05 Jun 2008 06:23:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2008/06/05/45025.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/45025.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2008/06/05/45025.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/45025.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/45025.html</trackback:ping><description><![CDATA[这个blog是我刚工作的时候开的。好久更新了<br>人懒了。<br>这里大概都以转载为主，我争取多一些原创性的东西<br>
<img src ="http://www.cnitblog.com/charester/aggbug/45025.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2008-06-05 14:23 <a href="http://www.cnitblog.com/charester/archive/2008/06/05/45025.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>网络Ghost克隆教程(ZT)</title><link>http://www.cnitblog.com/charester/archive/2007/01/04/21506.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Thu, 04 Jan 2007 07:19:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2007/01/04/21506.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21506.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2007/01/04/21506.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21506.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21506.html</trackback:ping><description><![CDATA[ <tr></tr>/TR /&gt;/TR /&gt;TD&gt;/TD /&gt;/TD /&gt;FONT color=#0000ff size=2&gt;1.我们做好一张母盘.并将操作系统目录里的TEMP目录里的东西,以及临时文件夹全部清空.之后将母盘系统备份到另外一块硬盘上.<br /><br />首先确认备份的系统有一个比较大的分区.并确认能装下你的硬盘备份.<br /><br />将准备好的另外一块硬盘挂在你做好的机器上.之后重新启动到DOS模式,启动GHOST.<br /><br />选择LOCAL→DISK→TO IMAGE.<p align="center"><img height="480" src="http://moonfir.51.net/images/001.gif" width="640" border="0" />/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD&gt;/TD /&gt;/TD /&gt;<p align="center"><img height="480" src="http://moonfir.51.net/images/002.gif" width="640" border="0" />/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD&gt;/TD /&gt;/TD /&gt;<p align="center"><img height="480" src="http://moonfir.51.net/images/003.gif" width="640" border="0" />/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD&gt;/TD /&gt;/TD /&gt;<p align="center"><img height="480" src="http://moonfir.51.net/images/004.gif" width="640" border="0" />/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD&gt;/TD /&gt;/TD /&gt;/P&gt;
<p align="center">　</p><p align="center"><img height="480" src="http://moonfir.51.net/images/005.gif" width="640" border="0" />/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD&gt;/TD /&gt;/TD /&gt;/P&gt;
<p align="left">　</p><p align="left"><font color="#0000ff" size="2">2、给文件起个名字...回车.它会问你压缩方式.选NO是不压缩.FAST是快速压缩.HIGH是高压缩率,由于我选的是FAST,而且从来没出过什么问题,在这里也推荐你使用.</font></p><p align="center"><img height="480" src="http://moonfir.51.net/images/006.gif" width="640" border="0" />/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD&gt;/TD /&gt;/TD /&gt;<p><font color="#0000ff" size="2">3、因为FAT32格式分区最大只能识别2G的文件.所以每到达2G时候,会提示你会重新建立文件.</font></p><p align="center"><img height="480" src="http://moonfir.51.net/images/005.gif" width="640" border="0" />/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD&gt;/TD /&gt;/TD /&gt;<p><font color="#0000ff" size="2">4、文件建立完了,我们该建立GHOST服务器了</font></p><p><font color="#0000ff" size="2">启动WINDOWS打开GHOSTSRV.EXE文件.我们讲在后面提供下载,也可以在GHOST企业版里找到.之后给服务器起</font></p><p><font color="#0000ff" size="2">一个名字.我起的名字是shenzi<br /><br />选择克隆客户端<br /><br />之后选择你备份到另外一个硬盘的备份文件.<br /><br />点击 接受客户的按扭</font></p><p align="center"><img height="321" src="http://moonfir.51.net/images/010.gif" width="575" border="0" /></p><p align="center">　/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD&gt;/TD /&gt;/TD /&gt;FONT color=#0000ff size=2&gt;5、好了,现在服务器已经进入接收状态了.<p align="center"><img height="318" src="http://moonfir.51.net/images/011.gif" width="576" border="0" /></p><p align="center">　/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD&gt;/TD /&gt;/TD /&gt;FONT color=#0000ff size=2&gt;6、之后到客户端,就是你准备要克地机器.在GHOST.EXE文件所在的目录里,创建一个WATTCP.CFG的文件.此文件是指定IP以及网关的.如果没有此文件GHOST会自动扫描DHCP服务器.不推荐使用DHCP服务器.所以我们用DOS的命令,EDIT建立一个WATTCP.CFG的文件吧.<p align="center"><img height="400" src="http://moonfir.51.net/images/009.gif" width="640" border="0" /></p><p align="center">　/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD valign="top"&gt;/TD /&gt;/TD /&gt;FONT color=#0000ff size=2&gt;7、好了之后我们建立以下内容<p><font color="#0000ff" size="2">IP=你这台机器所指定的IP.为了避免重复,尽量使用机器号.</font></p><p><font color="#0000ff" size="2">NETMASK=子网掩玛,根据IP规则设置。</font></p><p><font color="#0000ff" size="2">GATEWAY=网关.就是建立了GHOST服务器的IP地址.</font></p><p align="center"><img height="400" src="http://moonfir.51.net/images/007.gif" width="640" border="0" /></p><p>　/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD valign="top"&gt;/TD /&gt;/TD /&gt;FONT color=#0000ff size=2&gt;8、好了,之后驱动网卡.执行RTSPKT空格0X60,0X60是给网卡的一个中断.这里必须给网卡一个中断.否则你也驱动不起你的网卡.我用的中断是0X60.<p align="center"><img height="400" src="http://moonfir.51.net/images/008.gif" width="640" border="0" /></p><p align="left">　/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD valign="top"&gt;/TD /&gt;/TD /&gt;FONT color=#0000ff size=2&gt;9、之后进入GHOST.选择MULTICASTING<p align="center"><img height="480" src="http://moonfir.51.net/images/012.gif" width="640" border="0" /></p><p>　/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD valign="top"&gt;/TD /&gt;/TD /&gt;FONT color=#0000ff size=2&gt;10、它会问你服务器名字.在这里输入你起的服务器名字.按回车<p align="center"><img height="480" src="http://moonfir.51.net/images/018.gif" width="640" border="0" /></p><p>　/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD valign="top"&gt;/TD /&gt;/TD /&gt;FONT color=#0000ff size=2&gt;11、选择硬盘.按回车<p align="center"><img height="480" src="http://moonfir.51.net/images/013.gif" width="640" border="0" /></p><p align="left">　/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD valign="top"&gt;/TD /&gt;/TD /&gt;FONT color=#0000ff size=2&gt;12、在这里可以重新修改分区大小,如果没有特别要求,选择OK继续.<p align="center"><img height="480" src="http://moonfir.51.net/images/015.gif" width="640" border="0" /></p><p>　/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD valign="top"&gt;/TD /&gt;/TD /&gt;FONT color=#0000ff size=2&gt;13、确认开始.选YES,好了,现在你的客户端已经进入接收状态了<p align="center"><img height="480" src="http://moonfir.51.net/images/016.gif" width="640" border="0" /></p><p align="left">　/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD valign="top"&gt;/TD /&gt;/TD /&gt;FONT color=#0000ff size=2&gt;14、等把客户端全部连接上之后,就可以选择开始传送进行网络克隆了.<p align="center"><img height="321" src="http://moonfir.51.net/images/017.gif" width="575" border="0" /></p><p align="left">　/TR /&gt; </p><tr></tr>/TR /&gt;/TR /&gt;TD valign="top"&gt;/TD /&gt;/TD /&gt;FONT color=#0000ff size=2&gt;好了,我们的教程到这里就结束了,不过提醒大家,网络GHOST的机器尽量不要超过50台.如果出现问题,那麻烦的可是你了哦.如果有机器断线,请关闭那台机器等待,大约5分钟后会记为超时.GHOST还会继续运行.另外有一些网卡驱动执行完后会锁住键盘.请大家自己建立批处理文件就可以的。/TR /&gt;<img src ="http://www.cnitblog.com/charester/aggbug/21506.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2007-01-04 15:19 <a href="http://www.cnitblog.com/charester/archive/2007/01/04/21506.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux/Unix环境下的make和makefile详解(ZT)</title><link>http://www.cnitblog.com/charester/archive/2007/01/04/21504.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Thu, 04 Jan 2007 06:55:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2007/01/04/21504.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21504.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2007/01/04/21504.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21504.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21504.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Linux/Unix环境下的make和makefile详解　　 																		　　无论是在Linux还是在Unix环境中，make都是一个非常重要的编译命令。不管是自己进行项目开发还是安装应用软件，我们都经常要用到make或make install。利用make工具，我们可以将大型的开发项目分解成为多个更易于管理的模块，对于一个包括几百个源文件的应用程序，使用make...&nbsp;&nbsp;<a href='http://www.cnitblog.com/charester/archive/2007/01/04/21504.html'>阅读全文</a><img src ="http://www.cnitblog.com/charester/aggbug/21504.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2007-01-04 14:55 <a href="http://www.cnitblog.com/charester/archive/2007/01/04/21504.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux操作系统下c语言编程入门(ZT)</title><link>http://www.cnitblog.com/charester/archive/2007/01/04/21503.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Thu, 04 Jan 2007 06:50:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2007/01/04/21503.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21503.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2007/01/04/21503.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21503.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21503.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: linux操作系统下c语言编程入门								linux操作系统下c语言编程入门 整理编写：007xiong 原文：Hoyt等 								(一)目录介绍 								1)Linux程序设计入门--基础知识 2)Linux程序设计入门--进程介绍 3)Linux程序设计入门--文件操作 4)Linux程序设计入门--时间概念 5)Linux程序设计入门--信号处理 6)Li...&nbsp;&nbsp;<a href='http://www.cnitblog.com/charester/archive/2007/01/04/21503.html'>阅读全文</a><img src ="http://www.cnitblog.com/charester/aggbug/21503.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2007-01-04 14:50 <a href="http://www.cnitblog.com/charester/archive/2007/01/04/21503.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何阅读源代码(三)(ZT) </title><link>http://www.cnitblog.com/charester/archive/2007/01/02/21427.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Tue, 02 Jan 2007 12:42:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2007/01/02/21427.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21427.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2007/01/02/21427.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21427.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21427.html</trackback:ping><description><![CDATA[在parse_record分析完数据之后，做日期的分析，把日志中的月份等数据转换成机器可读（可理解)的数据，并存入到log_rec中去。<br /><br /><br />if ((i&gt;=12)||(rec_min&gt;59)||(rec_sec&gt;59)||(rec_year&lt;1990))<br />{<br />total_bad++; /* if a bad date, bump counter */<br />if (verbose)<br />{<br />fprintf(stderr,"%s: %s [%lu]",<br />msg_bad_date,log_rec.datetime,total_rec);<br />......<br /><br /><br /><br />　　 如果日期，时间错误，则把total_bad计数器增加1，并且打印错误信息到标准错误输出。<br /><br /><br />good_rec = 1;<br />/* get current records timestamp<br />(seconds since epoch) */<br />req_tstamp=cur_tstamp;<br />rec_tstamp=((jdate(rec_day,rec_month,rec_year)-epoch)<br />*86400)+<br />(rec_hour*3600)+(rec_min*60)+rec_sec;<br />/* Do we need to check for duplicate records?<br />(incremental mode) */<br />if (check_dup)<br />{<br />/* check if less than/equal to last record processed */<br />if ( rec_tstamp &lt;= cur_tstamp )<br />{<br />/* if it is, assume we have already<br />processed and ignore it */<br />total_ignore++;<br />continue;<br />}<br />else<br />{<br />/* if it isn't.. disable any more checks this run */<br />check_dup=0;<br />/* now check if it's a new month */<br />if (cur_month != rec_month)<br />{<br />clear_month();<br />cur_sec = rec_sec; /* set current counters */<br />cur_min = rec_min;<br />cur_hour = rec_hour;<br />cur_day = rec_day;<br />cur_month = rec_month;<br />cur_year = rec_year;<br />cur_tstamp= rec_tstamp;<br />f_day=l_day=rec_day; /* reset first and last day */<br />}<br />}<br />}<br />/* check for out of sequence records */<br />if (rec_tstamp/3600 &lt; cur_tstamp/3600)<br />{<br />if (!fold_seq_err &amp;&amp; ((rec_tstamp+SLOP_VAL)<br />/3600<br /><br /><br />　　如果该日期、时间没有错误，则该数据是一个好的数据，将good_record计数器加1，并且检查时间戳，和数据是否重复数据。这里有一个函数，jdate()在主程序一开头我们就遇到了，当时跳了过去没有深究，这里留给读者做一个练习。（提示：该函数根据一个日期产生一个字符串，这个字符串是惟一的，可以检查时间的重复性，是一个通用函数，可以在别的程序中拿来使用）<br /><br /><br />/*********************************************/<br />/* DO SOME PRE-PROCESS FORMATTING */<br />/*********************************************/<br />/* fix URL field */<br />cp1 = cp2 = log_rec.url;<br />/* handle null '-' case here... */<br />if (*++cp1 == '-') { *cp2++ = '-'; *cp2 = ''; }<br />else<br />{<br />/* strip actual URL out of request */<br />while ( (*cp1 != ' ') &amp;&amp; (*cp1 != '') ) cp1++;<br />if (*cp1 != '')<br />{<br />/* scan to begin of actual URL field */<br />while ((*cp1 == ' ') &amp;&amp; (*cp1 != '')) cp1++;<br />/* remove duplicate / if needed */<br />if (( *cp1=='/') &amp;&amp; (*(cp1+1)=='/')) cp1++;<br />while ((*cp1 != ' ')&amp;&amp;(*cp1 != '"')&amp;&amp;(*cp1 != ''))<br />*cp2++ = *cp1++;<br />*cp2 = '';<br />}<br />}<br />/* un-escape URL */<br />unescape(log_rec.url);<br />/* check for service (ie: <a onfocus="this.blur()" href="http://)/" target="_blank"><font color="#000033" size="2">http://)</font></a> and lowercase if found */<br />if ( (cp2=strstr(log_rec.url,"://")) != NULL)<br />{<br />cp1=log_rec.url;<br />while (cp1!=cp2)<br />{<br />if ( (*cp1&gt;='A') &amp;&amp; (*cp1&lt;='Z')) *cp1 += 'a'-'A';<br />cp1++;<br />}<br />}<br />/* strip query portion of cgi scripts */<br />cp1 = log_rec.url;<br />while (*cp1 != '')<br />if (!isurlchar(*cp1)) { *cp1 = ''; break; }<br />else cp1++;<br />if (log_rec.url[0]=='')<br />{ log_rec.url[0]='/'; log_rec.url[1]=''; }<br />/* strip off index.html (or any aliases) */<br />lptr=index_alias;<br />while (lptr!=NULL)<br />{<br />if ((cp1=strstr(log_rec.url,lptr-&gt;string))!=NULL)<br />{<br />if ((cp1==log_rec.url)||(*(cp1-1)=='/'))<br />{<br />*cp1='';<br />if (log_rec.url[0]=='')<br />{ log_rec.url[0]='/'; log_rec.url[1]=''; }<br />break;<br />}<br />}<br />lptr=lptr-&gt;next;<br />}<br />/* unescape referrer */<br />unescape(log_rec.refer);<br />......<br /><br /><br /><br />　　这一段，做了一些URL字符串中的字符转换工作，很长，我个人认为为了程序的模块化，结构化和可复用性，应该将这一段代码改为函数，避免主程序体太长，造成可读性不强和没有移植性，和不够结构化。跳过这一段乏味的代码，进入到下面一个部分---后处理。<br /><br /><br />if (gz_log) gzclose(gzlog_fp);<br />else if (log_fname) fclose(log_fp);<br />if (good_rec) /* were any good records? */<br />{<br />tm_site[cur_day-1]=dt_site; /* If yes, clean up a bit */<br />tm_visit[cur_day-1]=tot_visit(sd_htab);<br />t_visit=tot_visit(sm_htab);<br />if (ht_hit &gt; mh_hit) mh_hit = ht_hit;<br />if (total_rec &gt; (total_ignore+total_bad))<br />/* did we process any? */<br />{<br />if (incremental)<br />{<br />if (save_state()) /* incremental stuff */<br />{<br />/* Error: Unable to save current run data */<br />if (verbose) fprintf(stderr,"%s ",msg_data_err);<br />unlink(state_fname);<br />}<br />}<br />month_update_exit(rec_tstamp); /* calculate exit pages */<br />write_month_html(); /* write monthly HTML file */<br />write_main_index(); /* write main HTML file */<br />put_history(); /* write history */<br />}<br />end_time = times(&amp;mytms);<br />/* display timing totals? */<br />if (time_me' '(verbose&gt;1))<br />{<br />printf("%lu %s ",total_rec, msg_records);<br />if (total_ignore)<br />{<br />printf("(%lu %s",total_ignore,msg_ignored);<br />if (total_bad) printf(", %lu %s) ",total_bad,msg_bad);<br />else printf(") ");<br />}<br />else if (total_bad) printf("(%lu %s) ",total_bad,msg_bad);<br />/* get processing time (end-start) */<br />temp_time = (float)(end_time-start_time)/CLK_TCK;<br />printf("%s %.2f %s", msg_in, temp_time, msg_seconds);<br />/* calculate records per second */<br />if (temp_time)<br />i=( (int)( (float)total_rec/temp_time ) );<br />else i=0;<br />if ( (i&gt;0) &amp;&amp; (i&lt;=total_rec) ) printf(", %d/sec ", i);<br />else printf(" ");<br />}<br /><br /><br /><br />　　这一段，做了一些后期的处理。接下来的部分，我想在本文中略过，留给感兴趣的读者自己去做分析。原因有两点：<br /><br />1、这个程序在前面结构化比较强，而到了结构上后面有些乱，虽然代码效率还是比较高，但是可重用性不够强, 限于篇幅，我就不再一一解释了。 2、前面分析程序过程中，也对后面的代码做了一些预测和估计，也略微涉及到了后面的代码，而且读者可以根据上面提到的原则来自己分析代码，也作为一个实践吧。<br />　　最后，对于在这篇文章中提到的分析源代码程序的一些方法做一下小结，以作为本文的结束。<br /><br />　　分析一个源代码，一个有效的方法是：<br /><br />　　1、阅读源代码的说明文档，比如本例中的README, 作者写的非常的详细，仔细读过之后，在阅读程序的时候往往能够从README文件中找到相应的说明，从而简化了源程序的阅读工作。<br /><br />　　2、如果源代码有文档目录，一般为doc或者docs， 最好也在阅读源程序之前仔细阅读，因为这些文档同样起了很好的说明注释作用。<br /><br />　　3、从makefile文件入手，分析源代码的层次结构，找出哪个是主程序，哪些是函数包。这对于快速把握程序结构有很大帮助。<br /><br />　　4、从main函数入手，一步一步往下阅读，遇到可以猜测出意思来的简单的函数，可以跳过。但是一定要注意程序中使用的全局变量（如果是C程序），可以把关键的数据结构说明拷贝到一个文本编辑器中以便随时查找。<br /><br />　　5、分析函数包（针对C程序），要注意哪些是全局函数，哪些是内部使用的函数，注意extern关键字。对于变量，也需要同样注意。先分析清楚内部函数，再来分析外部函数，因为内部函数肯定是在外部函数中被调用的。<br /><br />　　6、需要说明的是数据结构的重要性：对于一个C程序来说，所有的函数都是在操作同一些数据，而由于没有较好的封装性，这些数据可能出现在程序的任何地方，被任何函数修改，所以一定要注意这些数据的定义和意义，也要注意是哪些函数在对它们进行操作，做了哪些改变。<br /><br />　　7、在阅读程序的同时，最好能够把程序存入到cvs之类的版本控制器中去，在需要的时候可以对源代码做一些修改试验，因为动手修改是比仅仅是阅读要好得多的读程序的方法。在你修改运行程序的时候，可以从cvs中把原来的代码调出来与你改动的部分进行比较(diff命令), 可以看出一些源代码的优缺点并且能够实际的练习自己的编程技术。<br /><br />　　8、阅读程序的同时，要注意一些小工具的使用，能够提高速度，比如vi中的查找功能，模式匹配查找，做标记，还有grep，find这两个最强大最常用的文本搜索工具的使用。<br /><br />　　对于一个Unix/Linux下面以命令行方式运行的程序，有这么一些套路，大家可以在阅读程序的时候作为参考。<br /><br />　　1、在程序开头，往往都是分析命令行，根据命令行参数对一些变量或者数组，或者结构赋值，后面的程序就是根据这些变量来进行不同的操作。<br /><br />　　2、分析命令行之后，进行数据准备，往往是计数器清空，结构清零等等。<br /><br />　　3、在程序中间有一些预编译选项，可以在makefile中找到相应部分。<br /><br />　　4、注意程序中对于日志的处理，和调试选项打开的时候做的动作，这些对于调试程序有很大的帮助。<br /><br />　　5、注意多线程对数据的操作。（这在本例中没有涉及）<br /><img src ="http://www.cnitblog.com/charester/aggbug/21427.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2007-01-02 20:42 <a href="http://www.cnitblog.com/charester/archive/2007/01/02/21427.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何阅读源代码(二)(ZT) </title><link>http://www.cnitblog.com/charester/archive/2007/01/02/21426.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Tue, 02 Jan 2007 12:41:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2007/01/02/21426.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21426.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2007/01/02/21426.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21426.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21426.html</trackback:ping><description><![CDATA[我们所阅读的这个软件是用来分析日志并且做出统计的，那么这个函数的名字已经告诉了我们，这是一个初始化计数器的函数。简略的看看吧！<br /><br /><br />$ grep init_counters *.h<br />webalizer.h:extern void init_counters();<br />在webalizer.c中找到：<br />void init_counters()<br />{<br />int i;<br />for (i=0;i<br /><br /><br />　　根据在最开始读过的README文件，这个page_type是用来定义处理的页面的类型的。在README文件中，<br /><br /><br />-P name Page type. This is the extension of files you consider to<br />be pages for Pages calculations (sometimes called 'pageviews').<br />The default is 'htm*' and 'cgi' (plus whatever HTMLExtension<br />you specified if it is different). Don't use a period!<br /><br /><br /><br />　　我们在程序中也可以看到，如果没有在命令行中或者config文件中指定，则根据处理的日志文件的类型来添加缺省的文件类型。比如对于CLF文件(WWW日志)，处理html, htm, cgi文件<br /><br /><br />if (log_type == LOG_FTP)<br />{<br />/* disable stuff for ftp logs */<br />ntop_entry=ntop_exit=0;<br />ntop_search=0;<br />}<br />else<br />.....<br />这一段是对于FTP的日志格式，设置搜索列表。<br />for (i=0;i<br /><br /><br />　　清空哈西表，为下面即将进行的排序工作做好准备。关于哈西表，这是数据结构中常用的一种用来快速排序的结构，如果不清楚，可以参考相关书籍，比如清华的&lt;&lt;数据结构&gt;&gt;教材或者&lt;&lt;数据结构的C++实现&gt;&gt;等书。<br /><br /><br />if (verbose&gt;1)<br />{<br />uname(&amp;system_info);<br />printf("Webalizer V%s-%s (%s %s) %s ",<br />version,editlvl,system_info.sysname,<br />system_info.release,language);<br />}<br /><br /><br /><br />　　这一段，是打印有关系统的信息和webalizer程序的信息（可以参考uname的函数说明）。<br /><br /><br />#ifndef USE_DNS<br />if (strstr(argv[0],"webazolver")!=0)<br />{<br />printf("DNS support not present, aborting... ");<br />exit(1);<br />}<br />#endif /* USE_DNS */<br /><br /><br /><br />　　这一段，回忆我们在看README文件的时候，曾经提到过可以在编译的时候设置选项开关来设定DNS支持，在源代码中可以看到多次这样的代码段出现，如果不指定DNS支持，这些代码段则会出现（ifdef)或者不出现(ifndef).下面略过这些代码段，不再重复。<br /><br /><br />/* open log file */<br />if (gz_log)<br />{<br />gzlog_fp = gzopen(log_fname,"rb");<br />if (gzlog_fp==Z_NULL)<br />{<br />/* Error: Can't open log file ... */<br />fprintf(stderr, "%s %s ",msg_log_err,log_fname);<br />exit(1);<br />}<br />}<br />else<br />{<br />if (log_fname)<br />{<br />log_fp = fopen(log_fname,"r");<br />if (log_fp==NULL)<br />{<br />/* Error: Can't open log file ... */<br />fprintf(stderr, "%s %s ",msg_log_err,log_fname);<br />exit(1);<br />}<br />}<br />}<br /><br /><br /><br />　　这一段，回忆在README文件中曾经读到过，如果log文件是gzip压缩格式，则用gzopen函数打开（可以猜想gz***是一套针对gzip压缩格式的实时解压缩函数），如果不是，则用fopen打开。<br /><br /><br />/* switch directories if needed */<br />if (out_dir)<br />{<br />if (chdir(out_dir) != 0)<br />{<br />/* Error: Can't change directory to ... */<br />fprintf(stderr, "%s %s ",msg_dir_err,out_dir);<br />exit(1);<br />}<br />}<br /><br /><br /><br />　　同样，回忆在README文件中读到过，如果参数行有-o out_dir, 则将输出结果到该目录，否则，则输出到当前目录。在这一段中，如果输出目录不存在(chdir(out_dir) != 0)则出错。<br /><br /><br />#ifdef USE_DNS<br />if (strstr(argv[0],"webazolver")!=0)<br />{<br />if (!dns_children) dns_children=5; /* default dns children if needed */<br />if (!dns_cache)<br />{<br />/* No cache file specified, aborting... */<br />fprintf(stderr,"%s ",msg_dns_nocf); /* Must have a cache file */<br />exit(1);<br />}<br />}<br />......<br /><br /><br /><br />　　在上面曾经提到过，这是DNS解析的代码部分，可以略过不看，不会影响对整个程序的理解。<br /><br /><br />/* prep hostname */<br />if (!hname)<br />{<br />if (uname(&amp;system_info)) hname="localhost";<br />else hname=system_info.nodename;<br />}<br /><br /><br /><br />　　这一段继续处理参数做准备工作。如果在命令行中指定了hostname(机器名）则采用指定的名称，否则调用uname查找机器名，如果没有，则用localhost来作为机器名。(同样在README中说得很详细）<br /><br /><br />/* get past history */<br />if (ignore_hist) {if (verbose&gt;1) printf("%s ",msg_ign_hist); }<br />else get_history();<br /><br /><br /><br />　　如果在命令行中指定了忽略历史文件，则不读取历史文件，否则调用get_history()来读取历史数据。在这里，我们可以回想在README文件中同样说过这一细节，在命令行或者配置文件中都能指定这一开关。需要说明的是，我们在这里并不一定需要去看get_history这一函数，因为从函数的名称，README文件和程序注释都能很清楚的得知这一函数的功能，不一定要去看代码。而如果要猜想的话，也可以想到，history是webalizer在上次运行的时候记录下来的一个文件，而这个文件则是去读取它，并将它的数据包括到这次的分析中去。不信，我们可以来看看。<br /><br /><br />void get_history()<br />{<br />int i,numfields;<br />FILE *hist_fp;<br />char buffer[BUFSIZE];<br /><br />/* first initalize internal array */<br />for (i=0;i&lt;12;i++)<br />{<br />hist_month[i]=hist_year[i]=hist_fday[i]=hist_lday[i]=0;<br />hist_hit[i]=hist_files[i]=hist_site[i]=hist_page[i]=hist_visit[i]=0;<br />hist_xfer[i]=0.0;<br />}<br />hist_fp=fopen(hist_fname,"r");<br />if (hist_fp)<br />{<br />if (verbose&gt;1) printf("%s %s ",msg_get_hist,hist_fname);<br />while ((fgets(buffer,BUFSIZE,hist_fp)) != NULL)<br />{<br />i = atoi(buffer) -1;<br />if (i&gt;11)<br />{<br />if (verbose)<br />fprintf(stderr,"%s (mth=%d) ",msg_bad_hist,i+1);<br />continue;<br />}<br />/* month# year# requests files sites xfer firstday lastday */<br />numfields = sscanf(buffer,"%d %d %lu %lu %lu %lf %d %d %lu %lu",<br />&amp;hist_month[i],<br />&amp;hist_year[i],<br />&amp;hist_hit[i],<br />&amp;hist_files[i],<br />&amp;hist_site[i],<br />&amp;hist_xfer[i],<br />&amp;hist_fday[i],<br />&amp;hist_lday[i],<br />&amp;hist_page[i],<br />&amp;hist_visit[i]);<br /><br />if (numfields==8) /* kludge for reading 1.20.xx history files */<br />{<br />hist_page[i] = 0;<br />hist_visit[i] = 0;<br />}<br />}<br />fclose(hist_fp);<br />}<br />else if (verbose&gt;1) printf("%s ",msg_no_hist);<br />}<br />/*********************************************/<br />/* PUT_HISTORY - write out history file */<br />/*********************************************/<br />void put_history()<br />{<br />int i;<br />FILE *hist_fp;<br /><br />hist_fp = fopen(hist_fname,"w");<br /><br />if (hist_fp)<br />{<br />if (verbose&gt;1) printf("%s ",msg_put_hist);<br />for (i=0;i&lt;12;i++)<br />{<br />if ((hist_month[i] != 0) &amp;&amp; (hist_hit[i] != 0))<br />{<br />fprintf(hist_fp,"%d %d %lu %lu %lu %.0f %d %d %lu %lu ",<br />hist_month[i],<br />hist_year[i],<br />hist_hit[i],<br />hist_files[i],<br />hist_site[i],<br />hist_xfer[i],<br />hist_fday[i],<br />hist_lday[i],<br />hist_page[i],<br />hist_visit[i]);<br />}<br />}<br />fclose(hist_fp);<br />}<br />else<br />if (verbose)<br />fprintf(stderr,"%s %s ",msg_hist_err,hist_fname);<br />}<br /><br /><br /><br />　　在preserve.c中，这两个函数是成对出现的。get_history()读取文件中的数据，并将其记录到hist_开头的一些数组中去。而put_history()则是将一些数据记录到同样的数组中去。我们可以推测得知，hist_数组是全局变量（在函数中没有定义），也可以查找源代码验证。同样，我们可以找一找put_history()出现的地方，来验证刚才的推测是否正确。在webalizer.c的1311行，出现：<br /><br /><br />month_update_exit(rec_tstamp); /* calculate exit pages */<br />write_month_html(); /* write monthly HTML file */<br />write_main_index(); /* write main HTML file */<br />put_history(); /* write history */<br />可以知道，推测是正确的。再往下读代码，<br />if (incremental) /* incremental processing? */<br />{<br />if ((i=restore_state())) /* restore internal data structs */<br />{<br />/* Error: Unable to restore run data (error num) */<br />/* if (verbose) fprintf(stderr,"%s (%d) ",msg_bad_data,i); */<br />fprintf(stderr,"%s (%d) ",msg_bad_data,i);<br />exit(1);<br />}<br />......<br />}<br /><br /><br /><br />　　同样，这也是处理命令行和做数据准备，而且和get_history(), put_history()有些类似，读者可以自己练习一下。下面，终于进入了程序的主体部分, 在做完了命令行分析，数据准备之后，开始从日志文件中读取数据并做分析了。<br /><br /><br />/*********************************************/<br />/* MAIN PROCESS LOOP - read through log file */<br />/*********************************************/<br /><br />while ( (gz_log)?(our_gzgets(gzlog_fp,buffer,BUFSIZE) != Z_NULL):<br />(fgets(buffer,BUFSIZE,log_fname?log_fp:stdin) != NULL))<br /><br /><br /><br />　　我看到这里的时候，颇有一些不同意作者的这种写法。这一段while中的部分写的比较复杂而且效率不高。因为从程序推断和从他的代码看来，作者是想根据日志文件的类型不同来采用不同的方法读取文件，如果是gzip格式，则用our_gzgets来读取其中一行，如果是普通的文本文件格式，则用fgets()来读取。但是，这段代码是写在while循环中的，每次读取一行就要重复判断一次，明显是多余的而且降低了程序的性能。可以在while循环之前做一次这样的判断，然后就不用重复了。<br /><br /><br />total_rec++;<br />if (strlen(buffer) == (BUFSIZE-1))<br />{<br />if (verbose)<br />{<br />fprintf(stderr,"%s",msg_big_rec);<br />if (debug_mode) fprintf(stderr,": %s",buffer);<br />else fprintf(stderr," ");<br />}<br />total_bad++; /* bump bad record counter */<br />/* get the rest of the record */<br />while ( (gz_log)?(our_gzgets(gzlog_fp,buffer,BUFSIZE)!=Z_NULL):<br />(fgets(buffer,BUFSIZE,log_fname?log_fp:stdin)!=NULL))<br />{<br />if (strlen(buffer) &lt; BUFSIZE-1)<br />{<br />if (debug_mode &amp;&amp; verbose) fprintf(stderr,"%s ",buffer);<br />break;<br />}<br />if (debug_mode &amp;&amp; verbose) fprintf(stderr,"%s",buffer);<br />}<br />continue; /* go get next record if any */<br />}<br /><br /><br /><br />　　这一段代码，读入一行，如果这一行超过了程序允许的最大字符数（则是错误的日志数据纪录），则跳过本行剩下的数据，忽略掉（continue进行下一次循环）。同时把total_bad增加一个。如果没有超过程序允许的最大字符数（则是正确的日志数据纪录），则<br /><br /><br />/* got a record... */<br />strcpy(tmp_buf, buffer); /* save buffer in case of error */<br />if (parse_record(buffer)) /* parse the record */<br /><br /><br /><br />　　将该数据拷贝到一个缓冲区中，然后调用parse_record()进行处理。我们可以同样的推测一下，get_record()是这个程序的一个主要处理部分，分析了日志数据。在parse_record.c中，有此函数，<br /><br /><br />/*********************************************/<br />/* PARSE_RECORD - uhhh, you know... */<br />/*********************************************/<br />int parse_record(char *buffer)<br />{<br />/* clear out structure */<br />memset(&amp;log_rec,0,sizeof(struct log_struct));<br />/*<br />log_rec.hostname[0]=0;<br />log_rec.datetime[0]=0;<br />log_rec.url[0]=0;<br />log_rec.resp_code=0;<br />log_rec.xfer_size=0;<br />log_rec.refer[0]=0;<br />log_rec.agent[0]=0;<br />log_rec.srchstr[0]=0;<br />log_rec.ident[0]=0;<br />*/<br />#ifdef USE_DNS<br />memset(&amp;log_rec.addr,0,sizeof(struct in_addr));<br />#endif<br /><br />/* call appropriate handler */<br />switch (log_type)<br />{<br />default:<br />case LOG_CLF: return parse_record_web(buffer); break;<br />/* clf */<br />case LOG_FTP: return parse_record_ftp(buffer); break;<br />/* ftp */<br />case LOG_SQUID: return parse_record_squid(buffer); break;<br />/* squid */<br />}<br />}<br /><br /><br /><br />　　可以看到，log_rec是一个全局变量，该函数根据日志文件的类型，分别调用三种不同的分析函数。在webalizer.h中，找到该变量的定义，从结构定义中可以看到，结构定义了一个日志文件所可能包含的所有信息（参考CLF，FTP, SQUID日志文件的格式说明）。<br /><br /><br />/* log record structure */<br />struct log_struct { char hostname[MAXHOST]; /* hostname */<br />char datetime[29]; /* raw timestamp */<br />char url[MAXURL]; /* raw request field */<br />int resp_code; /* response code */<br />u_long xfer_size; /* xfer size in bytes */<br />#ifdef USE_DNS<br />struct in_addr addr; /* IP address structure */<br />#endif /* USE_DNS */<br />char refer[MAXREF]; /* referrer */<br />char agent[MAXAGENT]; /* user agent (browser) */<br />char srchstr[MAXSRCH]; /* search string */<br />char ident[MAXIDENT]; }; /* ident string (user) */<br /><br />extern struct log_struct log_rec;<br /><br /><br /><br />　　先看一下一个parser.c用的内部函数，然后再来以parse_record_web()为例子看看这个函数是怎么工作的，parse_record_ftp, parse_record_squid留给读者自己分析作为练习。<br /><br /><br />/*********************************************/<br />/* FMT_LOGREC - terminate log fields w/zeros */<br />/*********************************************/<br />void fmt_logrec(char *buffer)<br />{<br />char *cp=buffer;<br />int q=0,b=0,p=0;<br /><br />while (*cp != '')<br />{<br />/* break record up, terminate fields with '' */<br />switch (*cp)<br />{<br />case ' ': if (b || q || p) break; *cp=''; break;<br />case '"': q^=1; break;<br />case '[': if (q) break; b++; break;<br />case ']': if (q) break; if (b&gt;0) b--; break;<br />case '(': if (q) break; p++; break;<br />case ')': if (q) break; if (p&gt;0) p--; break;<br />}<br />cp++;<br />}<br />}<br /><br /><br /><br />　　从parser.h头文件中就可以看到，这个函数是一个内部函数，这个函数把一行字符串中间的空格字符用''字符（结束字符）来代替，同时考虑了不替换在双引号，方括号，圆括号中间的空格字符以免得将一行数据错误的分隔开了。（请参考WEB日志的文件格式，可以更清楚的理解这一函数）<br /><br /><br />int parse_record_web(char *buffer)<br />{<br />int size;<br />char *cp1, *cp2, *cpx, *eob, *eos;<br />size = strlen(buffer); /* get length of buffer */<br />eob = buffer+size; /* calculate end of buffer */<br />fmt_logrec(buffer); /* seperate fields with 's */<br />/* HOSTNAME */<br />cp1 = cpx = buffer; cp2=log_rec.hostname;<br />eos = (cp1+MAXHOST)-1;<br />if (eos &gt;= eob) eos=eob-1;<br />while ( (*cp1 != '') &amp;&amp; (cp1 != eos) ) *cp2++ = *cp1++;<br />*cp2 = '';<br />if (*cp1 != '')<br />{<br />if (verbose)<br />{<br />fprintf(stderr,"%s",msg_big_host);<br />if (debug_mode) fprintf(stderr,": %s ",cpx);<br />else fprintf(stderr," ");<br />}<br />while (*cp1 != '') cp1++;<br />}<br />if (cp1 &lt; eob) cp1++;<br />/* skip next field (ident) */<br />while ( (*cp1 != '') &amp;&amp; (cp1 &lt; eob) ) cp1++;<br />if (cp1 &lt; eob) cp1++;<br />/* IDENT (authuser) field */<br />cpx = cp1;<br />cp2 = log_rec.ident;<br />eos = (cp1+MAXIDENT-1);<br />if (eos &gt;= eob) eos=eob-1;<br />while ( (*cp1 != '[') &amp;&amp; (cp1 &lt; eos) ) /* remove embeded spaces */<br />{<br />if (*cp1=='') *cp1=' ';<br />*cp2++=*cp1++;<br />}<br />*cp2--='';<br />if (cp1 &gt;= eob) return 0;<br />/* check if oversized username */<br />if (*cp1 != '[')<br />{<br />if (verbose)<br />{<br />fprintf(stderr,"%s",msg_big_user);<br />if (debug_mode) fprintf(stderr,": %s ",cpx);<br />else fprintf(stderr," ");<br />}<br />while ( (*cp1 != '[') &amp;&amp; (cp1 &lt; eob) ) cp1++;<br />}<br />/* strip trailing space(s) */<br />while (*cp2==' ') *cp2--='';<br />/* date/time string */<br />cpx = cp1;<br />cp2 = log_rec.datetime;<br />eos = (cp1+28);<br />if (eos &gt;= eob) eos=eob-1;<br />while ( (*cp1 != '') &amp;&amp; (cp1 != eos) ) *cp2++ = *cp1++;<br />*cp2 = '';<br />if (*cp1 != '')<br />{<br />if (verbose)<br />{<br />fprintf(stderr,"%s",msg_big_date);<br />if (debug_mode) fprintf(stderr,": %s ",cpx);<br />else fprintf(stderr," ");<br />}<br />while (*cp1 != '') cp1++;<br />}<br />if (cp1 &lt; eob) cp1++;<br />/* minimal sanity check on timestamp */<br />if ( (log_rec.datetime[0] != '[') ||<br />(log_rec.datetime[3] != '/') ||<br />(cp1 &gt;= eob)) return 0;<br />/* HTTP request */<br />cpx = cp1;<br />cp2 = log_rec.url;<br />eos = (cp1+MAXURL-1);<br />if (eos &gt;= eob) eos = eob-1;<br />while ( (*cp1 != '') &amp;&amp; (cp1 != eos) ) *cp2++ = *cp1++;<br />*cp2 = '';<br />if (*cp1 != '')<br />{<br />if (verbose)<br />{<br />fprintf(stderr,"%s",msg_big_req);<br />if (debug_mode) fprintf(stderr,": %s ",cpx);<br />else fprintf(stderr," ");<br />}<br />while (*cp1 != '') cp1++;<br />}<br />if (cp1 &lt; eob) cp1++;<br />if ( (log_rec.url[0] != '"') ||<br />(cp1 &gt;= eob) ) return 0;<br />/* response code */<br />log_rec.resp_code = atoi(cp1);<br />/* xfer size */<br />while ( (*cp1 != '') &amp;&amp; (cp1 &lt; eob) ) cp1++;<br />if (cp1 &lt; eob) cp1++;<br />if (*cp1&lt;'0'||*cp1&gt;'9') log_rec.xfer_size=0;<br />else log_rec.xfer_size = strtoul(cp1,NULL,10);<br />/* done with CLF record */<br />if (cp1&gt;=eob) return 1;<br />while ( (*cp1 != '') &amp;&amp; (*cp1 != ' ') &amp;&amp; (cp1 &lt; eob) )<br />cp1++;<br />if (cp1 &lt; eob) cp1++;<br />/* get referrer if present */<br />cpx = cp1;<br />cp2 = log_rec.refer;<br />eos = (cp1+MAXREF-1);<br />if (eos &gt;= eob) eos = eob-1;<br />while ( (*cp1 != '') &amp;&amp; (*cp1 != ' ') &amp;&amp; (cp1 != eos) )<br />*cp2++ = *cp1++;<br />*cp2 = '';<br />if (*cp1 != '')<br />{<br />if (verbose)<br />{<br />fprintf(stderr,"%s",msg_big_ref);<br />if (debug_mode) fprintf(stderr,": %s ",cpx);<br />else fprintf(stderr," ");<br />}<br />while (*cp1 != '') cp1++;<br />}<br />if (cp1 &lt; eob) cp1++;<br />cpx = cp1;<br />cp2 = log_rec.agent;<br />eos = cp1+(MAXAGENT-1);<br />if (eos &gt;= eob) eos = eob-1;<br />while ( (*cp1 != '') &amp;&amp; (cp1 != eos) )<br />*cp2++ = *cp1++;<br />*cp2 = '';<br />return 1; /* maybe a valid record, return with TRUE */<br />}<br /><br /><br /><br />　　该函数，一次读入一行（其实是一段日志数据中间的一个域，因为该行数据已经被fmt_logrec分开成多行数据了。根据CLF中的定义，检查该数据并将其拷贝到log_rec结构中去，如果检查该数据有效，则返回1。回到主程序,<br /><br /><br />/* convert month name to lowercase */<br />for (i=4;i&lt;7;i++)<br />log_rec.datetime[i]=tolower(log_rec.datetime[i]);<br />/* get year/month/day/hour/min/sec values */<br />for (i=0;i&lt;12;i++)<br />{<br />if (strncmp(log_month[i],&amp;log_rec.datetime[4],3)==0)<br />{ rec_month = i+1; break; }<br />}<br />rec_year=atoi(&amp;log_rec.datetime[8]);<br />/* get year number (int) */<br />rec_day =atoi(&amp;log_rec.datetime[1]);<br />/* get day number */<br />rec_hour=atoi(&amp;log_rec.datetime[13]);<br />/* get hour number */<br />rec_min =atoi(&amp;log_rec.datetime[16]);<br />/* get minute number */<br />rec_sec =atoi(&amp;log_rec.datetime[19]);<br />/* get second number */<br />....<br /><img src ="http://www.cnitblog.com/charester/aggbug/21426.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2007-01-02 20:41 <a href="http://www.cnitblog.com/charester/archive/2007/01/02/21426.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何阅读源代码(一)(ZT)</title><link>http://www.cnitblog.com/charester/archive/2007/01/02/21425.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Tue, 02 Jan 2007 12:39:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2007/01/02/21425.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21425.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2007/01/02/21425.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21425.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21425.html</trackback:ping><description><![CDATA[由于工作的关系，我常常需要读一些源代码，并在上面做一些修改并且拿来使用，或者是借鉴其中的某些部分。可以说，open source对于程序员来说，是很有意义的事情。根据我的经验，读源代码，至少有3个好处。第一个好处是可以学习到很多编程的方法，看好的源代码，对于提高自己的编程水平，比自己写源代码的帮助更大。当然不是说不用自己写，而是说，自己写代码的同时，可以从别人写的好的源代码中间学习到更多的编程方法和技巧。第二个好处是，可以提高自己把握大规模源代码的能力。一个比较大型的程序，往往都是经过了很多个版本很长的时间，有很多人参与开发，修正错误，添加功能而发展起来的。所以往往源代码的规模都比较大，少则10-100多k, 多的有好几十个MB. 在阅读大量源代码的时候，能够提高自己对大的软件的把握能力，快速了解脉络，熟悉细节，不仅仅是编程技巧，还能在程序的架构，设计方面提高自己的能力。（这里说一句题外话，&lt;&lt;设计模式&gt;&gt;这本书相信很多人都看过，而且很多人对它推崇备至，奉为经典。现在也出了不少书，都是冠以"设计模式"这一名称。在书中就提到，设计模式并不是一本教材，不是教你如何去编程序，而是把平时编程中一些固定的模式记录下来，加以不断的测试和改进，分发给广大程序员的一些经验之谈。我在看这本书的时候，有一些地方一些设计方法往往让我有似曾相识的感觉，另外一些则是我以前就常常用到的。而这些经验的获得，一部分得益于自己的编码过程，另外一个很重要的来源就是阅读别人写的源代码。)阅读源代码第三个好处，就是获得一些好的思想。比如，有很多人在开始一个软件项目之前都喜欢到sourceforge.net上去找一下，是否有人以前做过相同或者相似的软件，如果有，则拿下来读一读，可以使自己对这个软件项目有更多更深的认识。我以前曾经想找一本关于如何阅读源代码的书来看看，却没有找到。相反，倒是找到了不少分析源代码的书，比如Linux kernel, Apache source, 等等。所以我想，为什么不自己来写下一些经验和大家交流呢？（当然不是写书，没有那个能力也没有那个时间。）所以在这里我准备用一个例子来写一下如何阅读源代码，分享一些经验，算是抛砖引玉吧！<br /><br />　　我找的例子是一个统计日志的工具，webalizer. (这个工具我以前用过，似乎记得以前的版本是用perl写的，不知道现在为什么作者把它完全修改成了C，可能是为了效率，也可能根本就是我记错了。）之所以选择这个软件来作为例子，一方面是因为它是用C写的，流程比较简单，没有C++的程序那么多的枝节，而且软件功能不算复杂，代码规模不大，能够在一篇文章的篇幅里面讲完; 另外一个方面是因为恰巧前段时间我因为工作的关系把它拿来修改了一下，刚看过，还没有忘记。 :-)我采用的例子是webalizer2.01-09, 也可以到它的网站<a onfocus="this.blur()" href="http://www.mrunix.net/webalizer/" target="_blank"><font color="#000033" size="2">http://www.mrunix.net/webalizer/</font></a> 下载最新的版本。这是一个用C写的，处理文本文件（简单的说是这样，实际上它支持三种日志文本格式：CLF, FTP, SQUID), 并且用html的方式输出结果。读者可以自己去下载它的源代码包，并一边读文章，一边看程序。解压缩它的tar包(我download的是它的源代码tar包），在文件目录中看到这样的结果：<br /><br /><br />$ ls<br />aclocal.m4 dns_resolv.c lang output.h webalizer.1<br />CHANGES dns_resolv.h lang.h parser.c webalizer.c<br />configure graphs.c linklist.c parser.h webalizer.h<br />configure.in graphs.h linklist.h preserve.c webalizer_lang.h<br />COPYING hashtab.c Makefile.in preserve.h webalizer.LSM<br />Copyright hashtab.h Makefile.std README webalizer.png<br />country-codes.txt INSTALL msfree.png README.FIRST<br />DNS.README install-sh output.c sample.conf<br /><br /><br /><br />　　首先，我阅读了它的README(这是很重要的一个环节）, 大体了解了软件的功能，历史状况，修改日志，安装方法等等。然后是安装并且按照说明中的缺省方式来运行它，看看它的输出结果。(安装比较简单，因为它带了一个configure, 在没有特殊情况出现的时候，简单的./configure, make, make install就可以安装好。)然后就是阅读源代码了。我从makefile开始入手（我觉得这是了解一个软件的最好的方法）在makefile开头，有这些内容：<br /><br /><br />prefix = /usr/local<br />exec_prefix = ${prefix}<br />BINDIR = ${exec_prefix}/bin<br />MANDIR = ${prefix}/man/man1<br />ETCDIR = /etc<br />CC = gcc<br />CFLAGS = -Wall -O2<br />LIBS = -lgd -lpng -lz -lm<br />DEFS = -DETCDIR="/etc" -DHAVE_GETOPT_H=1 -DHAVE_MATH_H=1<br />LDFLAGS=<br />INSTALL= /usr/bin/install -c<br />INSTALL_PROGRAM=${INSTALL}<br />INSTALL_DATA=${INSTALL} -m 644<br /># where are the GD header files?<br />GDLIB=/usr/include<br /><br /><br /><br />　　这些定义了安装的路径，执行程序的安装路径，编译器，配置文件的安装路径，编译的选项，安装程序，安装程序的选项等等。要注意的是，这些并不是软件的作者写的，而是./configure的输出结果。呵呵. :-)下面才是主题内容，也是我们关心的。<br /><br /><br /># Shouldn't have to touch below here!<br />all: webalizer<br />webalizer: webalizer.o webalizer.h hashtab.o hashtab.h<br />linklist.o linklist.h preserve.o preserve.h<br />dns_resolv.o dns_resolv.h parser.o parser.h<br />output.o output.h graphs.o graphs.h lang.h<br />webalizer_lang.h<br />$(CC) ${LDFLAGS} -o webalizer webalizer.o hashtab.o linklist.o preserv<br />e.o parser.o output.o dns_resolv.o graphs.o ${LIBS}<br />rm -f webazolver<br />ln -s webalizer webazolver<br />webalizer.o: webalizer.c webalizer.h parser.h output.h preserve.h<br />graphs.h dns_resolv.h webalizer_lang.h<br />$(CC) ${CFLAGS} ${DEFS} -c webalizer.c<br />parser.o: parser.c parser.h webalizer.h lang.h<br />$(CC) ${CFLAGS} ${DEFS} -c parser.c<br />hashtab.o: hashtab.c hashtab.h dns_resolv.h webalizer.h lang.h<br />$(CC) ${CFLAGS} ${DEFS} -c hashtab.c<br />linklist.o: linklist.c linklist.h webalizer.h lang.h<br />$(CC) ${CFLAGS} ${DEFS} -c linklist.c<br />output.o: output.c output.h webalizer.h preserve.h<br />hashtab.h graphs.h lang.h<br />$(CC) ${CFLAGS} ${DEFS} -c output.c<br />preserve.o: preserve.c preserve.h webalizer.h parser.h<br />hashtab.h graphs.h lang.h<br />$(CC) ${CFLAGS} ${DEFS} -c preserve.c<br />dns_resolv.o: dns_resolv.c dns_resolv.h lang.h webalizer.h<br />$(CC) ${CFLAGS} ${DEFS} -c dns_resolv.c<br />graphs.o: graphs.c graphs.h webalizer.h lang.h<br />$(CC) ${CFLAGS} ${DEFS} -I${GDLIB} -c graphs.c<br /><br /><br /><br />　　好了，不用再往下看了，这些就已经足够了。从这里我们可以看到这个软件的几个源代码文件和他们的结构。webalizer.c是主程序所在的文件，其他的是一些辅助程序模块。对比一下目录里面的文件，<br /><br /><br />$ ls *.c *.h<br />dns_resolv.c graphs.h lang.h output.c parser.h webalizer.c<br />dns_resolv.h hashtab.c linklist.c output.h preserve.c webalizer.h<br />graphs.c hashtab.h linklist.h parser.c preserve.h webalizer_lang.h<br /><br /><br /><br />　　于是，让我们从webalizer.c开始吧。<br /><br />　　作为一个C程序，在头文件里面，和C文件里面定义的extern变量，结构等等肯定不会少，但是，单独看这些东西我们不可能对这个程序有什么认识。所以，从main函数入手，逐步分析，在需要的时候再回头来看这些数据结构定义才是好的方法。（顺便说一句，Visual C++, 等windows下的IDE工具提供了很方便的方法来获取函数列表，C++的类列表以及资源文件，对于阅读源代码很有帮助。Unix/Linux也有这些工具，但是，我们在这里暂时不说，而只是通过最简单的文本编辑器vi来讲)。跳过webalizer.c开头的版权说明部分（GPL的），和数据结构定义，全局变量声明部分，直接进入main()函数。在函数开头，我们看到：<br /><br /><br />/* initalize epoch */<br />epoch=jdate(1,1,1970); /* used for timestamp adj. */<br />/* add default index. alias */<br />add_nlist("index.",&amp;index_alias);<br /><br /><br /><br />　　这两个函数暂时不用仔细看，后面会提到，略过。<br /><br /><br />sprintf(tmp_buf,"%s/webalizer.conf",ETCDIR);<br />/* check for default config file */<br />if (!access("webalizer.conf",F_OK))<br />get_config("webalizer.conf");<br />else if (!access(tmp_buf,F_OK))<br />get_config(tmp_buf);<br /><br /><br /><br />　　从注释和程序本身可以看出，这是查找是否存在一个叫做webalizer.conf的配置文件，如果当前目录下有，则用get_config来读入其中内容，如果没有，则查找ETCDIR/webalizer.conf是否存在。如果都没有，则进入下一部分。(注意：ETCDIR = @ETCDIR@在makefile中有定义）<br /><br /><br />/* get command line options */<br />opterr = 0; /* disable parser errors */<br />while ((i=getopt(argc,argv,"a:A:c:C:dD:e:E:fF:<br />g:GhHiI:l:Lm:M:n:N:o:pP:qQr:R:s:S:t:Tu:U:vVx:XY"))!=EOF)<br />{<br />switch (i)<br />{<br />case 'a': add_nlist(optarg,&amp;hidden_agents); break;<br />/* Hide agents */<br />case 'A': ntop_agents=atoi(optarg); break;<br />/* Top agents */<br />case 'c': get_config(optarg); break;<br />/* Config file */<br />case 'C': ntop_ctrys=atoi(optarg); break;<br />/* Top countries */<br />case 'd': debug_mode=1; break;<br />/* Debug */<br />case 'D': dns_cache=optarg; break;<br />/* DNS Cache filename */<br />case 'e': ntop_entry=atoi(optarg); break;<br />/* Top entry pages */<br />case 'E': ntop_exit=atoi(optarg); break;<br />/* Top exit pages */<br />case 'f': fold_seq_err=1; break;<br />/* Fold sequence errs */<br />case 'F': log_type=(optarg[0]=='f')?<br />LOG_FTP:(optarg[0]=='s')?<br />LOG_SQUID:LOG_CLF; break;<br />/* define log type */<br />case 'g': group_domains=atoi(optarg); break;<br />/* GroupDomains (0=no) */<br />case 'G': hourly_graph=0; break;<br />/* no hourly graph */<br />case 'h': print_opts(argv[0]); break;<br />/* help */<br />case 'H': hourly_stats=0; break;<br />/* no hourly stats */<br />case 'i': ignore_hist=1; break;<br />/* Ignore history */<br />case 'I': add_nlist(optarg,&amp;index_alias); break;<br />/* Index alias */<br />case 'l': graph_lines=atoi(optarg); break;<br />/* Graph Lines */<br />case 'L': graph_legend=0; break;<br />/* Graph Legends */<br />case 'm': visit_timeout=atoi(optarg); break;<br />/* Visit Timeout */<br />case 'M': mangle_agent=atoi(optarg); break;<br />/* mangle user agents */<br />case 'n': hname=optarg; break;<br />/* Hostname */<br />case 'N': dns_children=atoi(optarg); break;<br />/* # of DNS children */<br />case 'o': out_dir=optarg; break;<br />/* Output directory */<br />case 'p': incremental=1; break;<br />/* Incremental run */<br />case 'P': add_nlist(optarg,&amp;page_type); break;<br />/* page view types */<br />case 'q': verbose=1; break;<br />/* Quiet (verbose=1) */<br />case 'Q': verbose=0; break;<br />/* Really Quiet */<br />case 'r': add_nlist(optarg,&amp;hidden_refs); break;<br />/* Hide referrer */<br />case 'R': ntop_refs=atoi(optarg); break;<br />/* Top referrers */<br />case 's': add_nlist(optarg,&amp;hidden_sites); break;<br />/* Hide site */<br />case 'S': ntop_sites=atoi(optarg); break;<br />/* Top sites */<br />case 't': msg_title=optarg; break;<br />/* Report title */<br />case 'T': time_me=1; break; /* TimeMe */<br />case 'u': add_nlist(optarg,&amp;hidden_urls); break;<br />/* hide URL */<br />case 'U': ntop_urls=atoi(optarg); break;<br />/* Top urls */<br />case 'v':<br />case 'V': print_version(); break;<br />/* Version */<br />case 'x': html_ext=optarg; break;<br />/* HTML file extension */<br />case 'X': hide_sites=1; break;<br />/* Hide ind. sites */<br />case 'Y': ctry_graph=0; break;<br />/* Supress ctry graph */<br />}<br />}<br />if (argc - optind != 0) log_fname = argv[optind];<br />if ( log_fname &amp;&amp; (log_fname[0]=='-')) log_fname=NULL;<br />/* force STDIN? */<br />/* check for gzipped file - .gz */<br />if (log_fname) if (!strcmp((log_fname+strlen(log_fname)-3),".gz"))<br />gz_log=1;<br /><br /><br /><br />　　这一段是分析命令行参数及开关。（getopt()的用法我在另外一篇文章中讲过，这里就不再重复了。）可以看到，这个软件虽然功能不太复杂，但是开关选项还是不少。大多数的unix/linux程序的开头部分都是这个套路，初始化配置文件，并且读入分析命令行。在这段程序中，我们需要注意一个函数：add_nlist(). print_opts(), get_config()等等一看就明白，就不用多讲了。这里我们已经是第二次遇到add_nlist这个函数了，就仔细看看吧。<br /><br /><br />$ grep add_nlist *.h<br />linklist.h:extern int add_nlist(char *, NLISTPTR *);<br />/* add list item */<br /><br /><br /><br />　　可以发现它定义在linklist.h中。<br /><br />　　在这个h文件中，当然会有一些数据结构的定义，比如：<br /><br /><br />struct nlist { char string[80];<br />/* list struct for HIDE items */<br />struct nlist *next; };<br />typedef struct nlist *NLISTPTR;<br />struct glist { char string[80];<br />/* list struct for GROUP items */<br />char name[80];<br />struct glist *next; };<br />typedef struct glist *GLISTPTR;<br /><br /><br /><br />　　这是两个链表结构。还有<br /><br /><br />extern GLISTPTR group_sites ; /* "group" lists */<br />extern GLISTPTR group_urls ;<br />extern GLISTPTR group_refs ;<br /><br /><br /><br />　　这些都是链表， 太多了，不用一一看得很仔细，因为目前也看不出来什么东西。当然要注意它们是extern的， 也就是说，可以在其他地方(文件）看到它们的数值（类似于C++中的public变量）。这里还定义了4个函数：<br /><br /><br />extern char *isinlist(NLISTPTR, char *);<br />/* scan list for str */<br />extern char *isinglist(GLISTPTR, char *);<br />/* scan glist for str */<br />extern int add_nlist(char *, NLISTPTR *);<br />/* add list item */<br />extern int add_glist(char *, GLISTPTR *);<br />/* add group list item */<br /><br /><br /><br />　　注意，这些都是extern的，也就是说，可以在其他地方见到它们的调用(有点相当于C++中的public函数）。再来看看linklist.c，<br /><br /><br />NLISTPTR new_nlist(char *); /* new list node */<br />void del_nlist(NLISTPTR *); /* del list */<br /><br />GLISTPTR new_glist(char *, char *); /* new group list node */<br />void del_glist(GLISTPTR *); /* del group list */<br />int isinstr(char *, char *);<br /><br /><br /><br />　　这5个函数是内部使用的（相当于C++中的private), 也就是说，这些函数只被isinlist(NLISTPTR, char *), isinglist(GLISTPTR, char *), add_nlist(char *, NLISTPTR *), add_glist(char *, GLISTPTR *)调用，而不会出现在其他地方。所以，我们先来看这几个内部函数。举例来说，<br /><br /><br />add_nlist(char *)<br />NLISTPTR new_nlist(char *str)<br />{<br />NLISTPTR newptr;<br />if (sizeof(newptr-&gt;string) &lt; strlen(str))<br />{<br />if (verbose)<br />fprintf(stderr,"[new_nlist] %s ",msg_big_one);<br />}<br />if (( newptr = malloc(sizeof(struct nlist))) != NULL)<br />{strncpy(newptr-&gt;string, str, sizeof(newptr-&gt;string));<br />newptr-&gt;next=NULL;}<br />return newptr;<br />}<br /><br /><br /><br />　　这个函数分配了一个struct nlist, 并且把其中的string赋值为str, next赋值为NULL.这实际上是创建了链表中的一个节点。verbose是一个全局变量，定义了输出信息的类型，如果verbose为1，则输出很详细的信息，否则输出简略信息。这是为了调试或者使用者详细了解程序情况来用的。不是重要内容，虽然我们常常可以在这个源程序的其他地方看到它。另外一个函数：<br /><br /><br />void del_nlist(NLISTPTR *list)<br />{<br />NLISTPTR cptr,nptr;<br />cptr=*list;<br />while (cptr!=NULL)<br />{<br />nptr=cptr-&gt;next;<br />free(cptr);<br />cptr=nptr;<br />}<br />}<br /><br /><br /><br />　　这个函数删除了一个nlist（也可能是list所指向的那一个部分开始知道链表结尾），比较简单。看完了这两个内部函数，可以来看<br /><br /><br />/*********************************************/<br />/* ADD_NLIST - add item to FIFO linked list */<br />/*********************************************/<br />int add_nlist(char *str, NLISTPTR *list)<br />{<br />NLISTPTR newptr,cptr,pptr;<br />if ( (newptr = new_nlist(str)) != NULL)<br />{<br />if (*list==NULL) *list=newptr;<br />else<br />{<br />cptr=pptr=*list;<br />while(cptr!=NULL) { pptr=cptr; cptr=cptr-&gt;next; };<br />pptr-&gt;next = newptr;<br />}<br />}<br />return newptr==NULL;<br />}<br /><br /><br /><br />　　这个函数是建立了一个新的节点，把参数str赋值给新节点的string, 并把它连接到list所指向链表的结尾。另外的三个函数：new_glist(), del_glist(), add_glist()完成的功能和上述三个差不多，所不同的只是它们所处理的数据结构不同。看完了这几个函数，我们回到main程序。接下来是，<br /><br /><br />/* setup our internal variables */<br />init_counters(); /* initalize main counters */<br /><br /><br /><img src ="http://www.cnitblog.com/charester/aggbug/21425.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2007-01-02 20:39 <a href="http://www.cnitblog.com/charester/archive/2007/01/02/21425.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入理解C语言指针(ZT)</title><link>http://www.cnitblog.com/charester/archive/2007/01/02/21424.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Tue, 02 Jan 2007 12:37:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2007/01/02/21424.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21424.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2007/01/02/21424.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21424.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21424.html</trackback:ping><description><![CDATA[指针的概念 <br /><br />　　指针是一个特殊的变量，它里面存储的数值被解释成为内存里的一个地址。 要搞清一个指针需要搞清指针的四方面的内容：指针的类型，指针所指向的 类型，指针的值或者叫指针所指向的内存区，还有指针本身所占据的内存区。让我们分别说明。 <br /><br />　　先声明几个指针放着做例子： <br /><br />　　例一： <br /><br />　　(1)int*ptr; <br /><br />　　(2)char*ptr; <br /><br />　　(3)int**ptr; <br /><br />　　(4)int(*ptr)[3]; <br /><br />　　(5)int*(*ptr)[4]; <br /><br /><br />　　指针的类型<br /><br />　　从语法的角度看，你只要把指针声明语句里的指针名字去掉，剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型： <br /><br />　　(1)int*ptr;//指针的类型是int* <br /><br />　　(2)char*ptr;//指针的类型是char* <br /><br />　　(3)int**ptr;//指针的类型是int** <br /><br />　　(4)int(*ptr)[3];//指针的类型是int(*)[3] <br /><br />　　(5)int*(*ptr)[4];//指针的类型是int*(*)[4] <br /><br />　　<br />　　指针所指向的类型<br /><br />　　当你通过指针来访问指针所指向的内存区时，指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。 <br /><br />　　从语法上看，你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉，剩下的就是指针所指向的类型。例如： <br /><br />　　(1)int*ptr;//指针所指向的类型是int <br /><br />　　(2)char*ptr;//指针所指向的的类型是char <br /><br />　　(3)int**ptr;//指针所指向的的类型是int* <br /><br />　　(4)int(*ptr)[3];//指针所指向的的类型是int()[3] <br /><br />　　(5)int*(*ptr)[4];//指针所指向的的类型是int*()[4] <br /><br />　　在指针的算术运算中，指针所指向的类型有很大的作用。 <br /><br />　　指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C越来越熟悉时，你会发现，把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念，是精通指针的关键点之一。我看了不少书，发现有些写得差的书中，就把指针的这两个概念搅在一起了，所以看起书来前后矛盾，越看越糊涂。 <br /><br />　　<br /><br /><br /><br />指针的值，或者叫指针所指向的内存区或地址<br /><br />　　指针的值是指针本身存储的数值，这个值将被编译器当作一个地址，而不是一个一般的数值。在32位程序里，所有类型的指针的值都是一个32位整数，因为32位程序里内存地址全都是32位长。 指针所指向的内存区就是从指针的值所代表的那个内存地址开始，长度为si zeof(指针所指向的类型)的一片内存区。以后，我们说一个指针的值是XX，就相当于说该指针指向了以XX为首地址的一片内存区域；我们说一个指针指向了某块内存区域，就相当于说该指针的值是这块内存区域的首地址。 <br /><br />　　指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中，指针所指向的类型已经有了，但由于指针还未初始化，所以它所指向的内存区是不存在的，或者说是无意义的。 <br /><br />　　以后，每遇到一个指针，都应该问问：这个指针的类型是什么？指针指的类型是什么？该指针指向了哪里？ <br /><br />　　指针本身所占据的内存区<br /><br />　　指针本身占了多大的内存？你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里，指针本身占据了4个字节的长度。 <br /><br />　　指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。<br /><br /><br /><br />指针的算术运算 <br /><br />　　指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。例如： <br /><br />　　例二： <br /><br />　　1、chara[20]; <br /><br />　　2、int*ptr=a; <br /><br />　　... <br /><br />　　... <br /><br />　　3、ptr++; <br /><br />　　在上例中，指针ptr的类型是int*,它指向的类型是int，它被初始化为指向整形变量a。接下来的第3句中，指针ptr被加了1，编译器是这样处理的：它把指针ptr的值加上了sizeof(int)，在32位程序中，是被加上了4。由于地址是用字节做单位的，故ptr所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。 <br />由于char类型的长度是一个字节，所以，原来ptr是指向数组a的第0号单元开始的四个字节，此时指向了数组a中从第4号单元开始的四个字节。 <br /><br />　　我们可以用一个指针和一个循环来遍历一个数组，看例子：<br /><br />　　例三： <br /><br /><br />intarray[20]; <br />int*ptr=array; <br />... <br />//此处略去为整型数组赋值的代码。 <br />... <br />for(i=0;i&lt;20;i++) <br />{ <br />　(*ptr)++; <br />　ptr++； <br />} <br /><br /><br />　　这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1，所以每次循环都能访问数组的下一个单元。 <br /><br />　　再看例子： <br /><br />　　例四： <br /><br />　　1、chara[20]; <br /><br />　　2、int*ptr=a; <br /><br />　　... <br />　　... <br /><br />　　3、ptr+=5;<br /><br />　　在这个例子中，ptr被加上了5，编译器是这样处理的：将指针ptr的值加上5乘sizeof(int)，在32位程序中就是加上了5乘4=20。由于地址的单位是字节，故现在的ptr所指向的地址比起加5后的ptr所指向的地址来说，向高地址方向移动了20个字节。在这个例子中，没加5前的ptr指向数组a的第0号单元开始的四个字节，加5后，ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题，但在语法上却是可以的。这也体现出了指针的灵活性。 <br /><br />　　如果上例中，ptr是被减去5，那么处理过程大同小异，只不过ptr的值是被减去5乘sizeof(int)，新的ptr指向的地址将比原来的ptr所指向的地址向低地址方向移动了20个字节。 <br /><br />　　总结一下，一个指针ptrold加上一个整数n后，结果是一个新的指针ptrnew，ptrnew的类型和ptrold的类型相同，ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值增加了n乘sizeof(ptrold所指向的类型)个字节。就是说，ptrnew所指向的内存区将比ptrold所指向的内存区向高地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。 <br /><br />　　一个指针ptrold减去一个整数n后，结果是一个新的指针ptrnew，ptrnew的类型和ptrold的类型相同，ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值减少了n乘sizeof(ptrold所指向的类型)个字节，就是说，ptrnew所指向的内存区将比ptrold所指向的内存区向低地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。<br /><br /><br /><br />运算符&amp;和* <br /><br /><br />　　这里&amp;是取地址运算符，*是...书上叫做"间接运算符"。 <br /><br />　　&amp;a的运算结果是一个指针，指针的类型是a的类型加个*，指针所指向的类型是a的类型，指针所指向的地址嘛，那就是a的地址。 <br /><br />　　*p的运算结果就五花八门了。总之*p的结果是p所指向的东西，这个东西有这些特点：它的类型是p指向的类型，它所占用的地址是p所指向的地址。 <br /><br />　　例五： <br /><br /><br />inta=12; <br />intb; <br />int*p; <br />int**ptr; <br />p=&amp;a;<br />//&amp;a的结果是一个指针，类型是int*，指向的类型是int，指向的地址是a的地址。 <br />*p=24;<br />//*p的结果，在这里它的类型是int，它所占用的地址是p所指向的地址，显然，*p就是变量a。 <br />ptr=&amp;p;<br />//&amp;p的结果是个指针，该指针的类型是p的类型加个*，在这里是int **。该指针所指向的类型是p的类型，这里是int*。该指针所指向的地址就是指针p自己的地址。 <br />*ptr=&amp;b;<br />//*ptr是个指针，&amp;b的结果也是个指针，且这两个指针的类型和所指向的类型是一样的，所以用&amp;b来给*ptr赋值就是毫无问题的了。 <br />**ptr=34;<br />//*ptr的结果是ptr所指向的东西，在这里是一个指针，对这个指针再做一次*运算，结果就是一个int类型的变量。 <br /><br /><br />指针表达式 <br /><br />　　一个表达式的最后结果如果是一个指针，那么这个表达式就叫指针表式。 <br /><br />　　下面是一些指针表达式的例子： <br /><br />　　例六： <br /><br /><br />inta,b; <br />intarray[10]; <br />int*pa; <br />pa=&amp;a;//&amp;a是一个指针表达式。 <br />int**ptr=&amp;pa;//&amp;pa也是一个指针表达式。 <br />*ptr=&amp;b;//*ptr和&amp;b都是指针表达式。 <br />pa=array; <br />pa++;//这也是指针表达式。 <br /><br /><br />　　例七： <br /><br /><br />char*arr[20]; <br />char**parr=arr;//如果把arr看作指针的话，arr也是指针表达式 <br />char*str; <br />str=*parr;//*parr是指针表达式 <br />str=*(parr+1);//*(parr+1)是指针表达式 <br />str=*(parr+2);//*(parr+2)是指针表达式 <br /><br /><br />　　由于指针表达式的结果是一个指针，所以指针表达式也具有指针所具有的四个要素：指针的类型，指针所指向的类型，指针指向的内存区，指针自身占据的内存。<br /><br />　　好了，当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话，这个指针表达式就是一个左值，否则就不是一个左值。 <br /><br />　　在例七中，&amp;a不是一个左值，因为它还没有占据明确的内存。*ptr是一个左值，因为*ptr这个指针已经占据了内存，其实*ptr就是指针pa，既然pa已经在内存中有了自己的位置，那么*ptr当然也有了自己的位置。 <br /><br />　　数组和指针的关系 <br /><br />　　如果对声明数组的语句不太明白的话，请参阅我前段时间贴出的文章&lt;&lt;如何理解c和c++的复杂类型声明&gt;&gt;。 <br /><br />　　数组的数组名其实可以看作一个指针。看下例： <br /><br />　　例八： <br /><br /><br />intarray[10]={0,1,2,3,4,5,6,7,8,9},value; <br />... <br />... <br />value=array[0];//也可写成：value=*array; <br />value=array[3];//也可写成：value=*(array+3); <br />value=array[4];//也可写成：value=*(array+4); <br /><br /><br />　　上例中，一般而言数组名array代表数组本身，类型是int[10]，但如果把array看做指针的话，它指向数组的第0个单元，类型是int*，所指向的类型是数组单元的类型即int。因此*array等于0就一点也不奇怪了。同理，array+3是一个指向数组第3个单元的指针，所以*(array+3)等于3。其它依此类推。 <br /><br /><br />例九： <br /><br /><br />char*str[3]={ <br />　"Hello,thisisasample!", <br />　"Hi,goodmorning.", <br />　"Helloworld" <br />}; <br />chars[80]； <br />strcpy(s,str[0]);//也可写成strcpy(s,*str); <br />strcpy(s,str[1]);//也可写成strcpy(s,*(str+1)); <br />strcpy(s,str[2]);//也可写成strcpy(s,*(str+2)); <br /><br /><br />　　上例中，str是一个三单元的数组，该数组的每个单元都是一个指针，这些指针各指向一个字符串。把指针数组名str当作一个指针的话，它指向数组的第0号单元，它的类型是char**，它指向的类型是char*。 <br />*str也是一个指针，它的类型是char*，它所指向的类型是char，它指向的地址是字符串"Hello,thisisasample!"的第一个字符的地址，即'H'的地址。 str+1也是一个指针，它指向数组的第1号单元，它的类型是char**，它指向的类型是char*。 <br /><br />　　*(str+1)也是一个指针，它的类型是char*，它所指向的类型是char，它指向 "Hi,goodmorning."的第一个字符'H'，等等。 <br /><br />　　下面总结一下数组的数组名的问题。声明了一个数组TYPEarray[n]，则数组名称array就有了两重含义：第一，它代表整个数组，它的类型是TYPE[n]；第二 ，它是一个指针，该指针的类型是TYPE*，该指针指向的类型是TYPE，也就是数组单元的类型，该指针指向的内存区就是数组第0号单元，该指针自己占有单独的内存区，注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修改的，即类似array++的表达式是错误的。 <br /><br />　　在不同的表达式中数组名array可以扮演不同的角色。 <br /><br />　　在表达式sizeof(array)中，数组名array代表数组本身，故这时sizeof函数测出的是整个数组的大小。 <br />在表达式*array中，array扮演的是指针，因此这个表达式的结果就是数组第0号单元的值。sizeof(*array)测出的是数组单元的大小。 <br /><br />　　表达式array+n（其中n=0，1，2，....。）中，array扮演的是指针，故array+n的结果是一个指针，它的类型是TYPE*，它指向的类型是TYPE，它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。 <br /><br />　　例十： <br /><br /><br />intarray[10]; <br />int(*ptr)[10]; <br />ptr=&amp;array; <br /><br /><br />　　上例中ptr是一个指针，它的类型是int(*)[10]，他指向的类型是int[10] ，我们用整个数组的首地址来初始化它。在语句ptr=&amp;array中，array代表数组本身。 <br /><br />　　本节中提到了函数sizeof()，那么我来问一问，sizeof(指针名称)测出的究竟是指针自身类型的大小呢还是指针所指向的类型的大小？答案是前者。例如： <br /><br />int(*ptr)[10]; <br /><br />　　则在32位程序中，有： <br /><br /><br />sizeof(int(*)[10])==4 <br />sizeof(int[10])==40 <br />sizeof(ptr)==4 <br /><br /><br />　　实际上，sizeof(对象)测出的都是对象自身的类型的大小，而不是别的什么类型的大小。<br /><br />　　指针和结构类型的关系 <br /><br /><br />　　可以声明一个指向结构类型对象的指针。 <br /><br />　　例十一： <br /><br /><br />structMyStruct <br />{ <br />　inta; <br />　intb; <br />　intc; <br />} <br />MyStructss={20,30,40};<br />//声明了结构对象ss，并把ss的三个成员初始化为20，30和40。 <br />MyStruct*ptr=&amp;ss;<br />//声明了一个指向结构对象ss的指针。它的类型是MyStruct*,它指向的类型是MyStruct。 <br />int*pstr=(int*)&amp;ss;<br />//声明了一个指向结构对象ss的指针。但是它的类型和它指向的类型和ptr是不同的。 <br /><br /><br />　　请问怎样通过指针ptr来访问ss的三个成员变量？ <br /><br />答案： <br /><br />ptr-&gt;a; <br />ptr-&gt;b; <br />ptr-&gt;c; <br /><br />　　又请问怎样通过指针pstr来访问ss的三个成员变量？ <br /><br />　　答案： <br /><br />*pstr；//访问了ss的成员a。 <br />*(pstr+1);//访问了ss的成员b。 <br />*(pstr+2)//访问了ss的成员c。 <br /><br />　　虽然我在我的MSVC++6.0上调式过上述代码，但是要知道，这样使用pstr来访问结构成员是不正规的，为了说明为什么不正规，让我们看看怎样通过指针来访问数组的各个单元： <br /><br />　　例十二： <br /><br />intarray[3]={35,56,37}; <br />int*pa=array; <br /><br />　　通过指针pa访问数组array的三个单元的方法是： <br /><br />*pa;//访问了第0号单元 <br />*(pa+1);//访问了第1号单元 <br />*(pa+2);//访问了第2号单元 <br /><br />　　从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。 <br /><br />　　所有的C/C++编译器在排列数组的单元时，总是把各个数组单元存放在连续的存储区里，单元和单元之间没有空隙。但在存放结构对象的各个成员时，在某种编译环境下，可能会需要字对齐或双字对齐或者是别的什么对齐，需要在相邻两个成员之间加若干个"填充字节"，这就导致各个成员之间可能会有若干个字节的空隙。 <br /><br />　　所以，在例十二中，即使*pstr访问到了结构对象ss的第一个成员变量a，也不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a和成员b之间可能会有若干填充字节，说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节，嘿，这倒是个不错的方法。 <br /><br />　　通过指针访问结构成员的正确方法应该是象例十二中使用指针ptr的方法。<br /><br /><br /><br />指针和函数的关系 <br /><br />　　可以把一个指针声明成为一个指向函数的指针。 <br /><br /><br />intfun1(char*,int); <br />int(*pfun1)(char*,int); <br />pfun1=fun1; <br />.... <br />.... <br />inta=(*pfun1)("abcdefg",7);//通过函数指针调用函数。 <br /><br /><br />　　可以把指针作为函数的形参。在函数调用语句中，可以用指针表达式来作为实参。 <br /><br />　　例十三： <br /><br /><br />intfun(char*); <br />inta; <br />charstr[]="abcdefghijklmn"; <br />a=fun(str); <br />... <br />... <br />intfun(char*s) <br />{ <br />intnum=0; <br />for(inti=0;i{ <br />num+=*s;s++; <br />} <br />returnnum; <br />} <br /><br /><br />　　这个例子中的函数fun统计一个字符串中各个字符的ASCII码值之和。前面说了，数组的名字也是一个指针。在函数调用中，当把str作为实参传递给形参s后，实际是把str的值传递给了s，s所指向的地址就和str所指向的地址一致，但是str和s各自占用各自的存储空间。在函数体内对s进行自加1运算，并不意味着同时对str进行了自加1运算。<br /><br />指针类型转换 <br /><br /><br />　　当我们初始化一个指针或给一个指针赋值时，赋值号的左边是一个指针，赋值号的右边是一个指针表达式。在我们前面所举的例子中，绝大多数情况下，指针的类型和指针表达式的类型是一样的，指针所指向的类型和指针表达式所指向的类型是一样的。 <br /><br />　　例十四： <br /><br />　　1、floatf=12.3; <br /><br />　　2、float*fptr=&amp;f; <br /><br />　　3、int*p; <br />　<br />　　在上面的例子中，假如我们想让指针p指向实数f，应该怎么搞？是用下面的语句吗？ <br /><br />　　p=&amp;f; <br /><br />　　不对。因为指针p的类型是int*，它指向的类型是int。表达式&amp;f的结果是一个指针，指针的类型是float*,它指向的类型是float。两者不一致，直接赋值的方法是不行的。至少在我的MSVC++6.0上，对指针的赋值语句要求赋值号两边的类型一致，所指向的类型也一致，其它的编译器上我没试过，大家可以试试。为了实现我们的目的，需要进行"强制类型转换"： <br /><br /><br />p=(int*)&amp;f; <br /><br /><br />　　如果有一个指针p，我们需要把它的类型和所指向的类型改为TYEP*TYPE， 那么语法格式是： <br /><br />　　(TYPE*)p； <br /><br />　　这样强制类型转换的结果是一个新指针，该新指针的类型是TYPE*，它指向的类型是TYPE，它指向的地址就是原指针指向的地址。而原来的指针p的一切属性都没有被修改。 <br /><br />　　一个函数如果使用了指针作为形参，那么在函数调用语句的实参和形参的结合过程中，也会发生指针类型的转换。 <br /><br />　　例十五： <br /><br /><br />voidfun(char*); <br />inta=125,b; <br />fun((char*)&amp;a); <br />... <br />... <br />voidfun(char*s) <br />{ <br />charc; <br />c=*(s+3);*(s+3)=*(s+0);*(s+0)=c; <br />c=*(s+2);*(s+2)=*(s+1);*(s+1)=c; <br />} <br />} <br /><br /><br />　　注意这是一个32位程序，故int类型占了四个字节，char类型占一个字节。函数fun的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗？在函数调用语句中，实参&amp;a的结果是一个指针，它的类型是int*，它指向的类型是int。形参这个指针的类型是char*，它指向的类型是char。这样，在实参和形参的结合过程中，我们必须进行一次从int*类型到char*类型的转换。结合这个例子，我们可以这样来想象编译器进行转换的过程：编译器先构造一个临时指针char*temp， 然后执行temp=(char*)&amp;a，最后再把temp的值传递给s。所以最后的结果是：s的类型是char*,它指向的类型是char，它指向的地址就是a的首地址。 <br /><br />我们已经知道，指针的值就是指针指向的地址，在32位程序中，指针的值其实是一个32位整数。那可不可以把一个整数当作指针的值直接赋给指针呢？就象下面的语句： <br /><br /><br />unsignedinta; <br />TYPE*ptr;//TYPE是int，char或结构类型等等类型。 <br />... <br />... <br />a=20345686; <br />ptr=20345686;//我们的目的是要使指针ptr指向地址20345686（十进制 <br />） <br />ptr=a;//我们的目的是要使指针ptr指向地址20345686（十进制） <br /><br /><br />　　编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗？不，还有办法： <br /><br /><br />unsignedinta; <br />TYPE*ptr;//TYPE是int，char或结构类型等等类型。 <br />... <br />... <br />a=某个数，这个数必须代表一个合法的地址； <br />ptr=(TYPE*)a；//呵呵，这就可以了。 <br /><br /><br />　　严格说来这里的(TYPE*)和指针类型转换中的(TYPE*)还不一样。这里的(TYPE*)的意思是把无符号整数a的值当作一个地址来看待。上面强调了a的值必须代表一个合法的地址，否则的话，在你使用ptr的时候，就会出现非法操作错误。 <br /><br />　　想想能不能反过来，把指针指向的地址即指针的值当作一个整数取出来。完 全可以。下面的例子演示了把一个指针的值当作一个整数取出来，然后再把这个整数当作一个地址赋给一个指针： <br /><br />　　例十六： <br /><br /><br />inta=123,b; <br />int*ptr=&amp;a; <br />char*str; <br />b=(int)ptr;//把指针ptr的值当作一个整数取出来。 <br />str=(char*)b;//把这个整数的值当作一个地址赋给指针str。 <br /><br /><br />　　现在我们已经知道了，可以把指针的值当作一个整数取出来，也可以把一个整数值当作地址赋给一个指针。 <br /><br />　　指针的安全问题 <br /><br />　　看下面的例子： <br /><br />　　例十七： <br /><br /><br />chars='a'; <br />int*ptr; <br />ptr=(int*)&amp;s; <br />*ptr=1298； <br /><br />　　指针ptr是一个int*类型的指针，它指向的类型是int。它指向的地址就是s的首地址。在32位程序中，s占一个字节，int类型占四个字节。最后一条语句不但改变了s所占的一个字节，还把和s相临的高地址方向的三个字节也改变了。这三个字节是干什么的？只有编译程序知道，而写程序的人是不太可能知道的。也许这三个字节里存储了非常重要的数据，也许这三个字节里正好是程序的一条代码，而由于你对指针的马虎应用，这三个字节的值被改变了！这会造成崩溃性的错误。 <br /><br />　　让我们再来看一例： <br /><br />　　例十八： <br /><br />　　1、chara; <br /><br />　　2、int*ptr=&amp;a; <br /><br />　　... <br />　　... <br /><br />　　3、ptr++; <br /><br />　　4、*ptr=115; <br /><br />　　该例子完全可以通过编译，并能执行。但是看到没有？第3句对指针ptr进行自加1运算后，ptr指向了和整形变量a相邻的高地址方向的一块存储区。这块存储区里是什么？我们不知道。有可能它是一个非常重要的数据，甚至可能是一条代码。而第4句竟然往这片存储区里写入一个数据！这是严重的错误。所以在使用指针时，程序员心里必须非常清楚：我的指针究竟指向了哪里。在用指针访问数组的时候，也要注意不要超出数组的低端和高端界限，否则也会造成类似的错误。 <br /><br />　　在指针的强制类型转换：ptr1=(TYPE*)ptr2中，如果sizeof(ptr2的类型)大于sizeof(ptr1的类型)，那么在使用指针ptr1来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2的类型)小于sizeof(ptr1的类型)，那么在使用指针ptr1来访问ptr2所指向的存储区时是不安全的。至于为什么，读者结合例十七来想一想，应该会明白的。<br /><img src ="http://www.cnitblog.com/charester/aggbug/21424.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2007-01-02 20:37 <a href="http://www.cnitblog.com/charester/archive/2007/01/02/21424.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Bugzilla安装指南(ZT)</title><link>http://www.cnitblog.com/charester/archive/2006/12/28/21242.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Thu, 28 Dec 2006 13:10:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2006/12/28/21242.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21242.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2006/12/28/21242.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21242.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21242.html</trackback:ping><description><![CDATA[
		<div class="postTitle">
				<a href="http://my.donews.com/stevenyu/2005/11/30/bugzilla%e5%ae%89%e8%a3%85%e6%8c%87%e5%8d%97/" rel="bookmark">
						<font color="#0066ff">Bugzilla安装指南</font>
				</a>
		</div>
		<div class="postText">
				<!-- Content Start -->
				<p>
						<font color="#0066ff">
						</font> </p>
				<ol>
						<li>
								<div align="left">
										<font face="宋体, SimSun">准备</font>
								</div>
						</li>
				</ol>
				<p>Bugzilla<font face="宋体, SimSun">在</font>Windows<font face="宋体, SimSun">下的安装颇为复杂，所以有很多人写了安装指南。但是使用安装的时候发现每个指南写的都有缺陷。这里我仅仅是把我安装的过程记录下来，给大家一个参考。同时还列出了一些我觉得有帮助的参考文章和站点。</font></p>
				<p>
						<font face="宋体, SimSun">工欲善其事必先利其器，建议你在开始安装之前把所有需要的软件下载齐全，这样可以提高效率和成功率。</font>Bugzilla<font face="宋体, SimSun">所需的软件都是开源的，都可以从它们的官方网站上下载到（我个人不喜欢去华军软件园之类的下载网站上找，因为即不安全，找到的也不一定是最新的版本）。下面把所需东西和下载网站罗列一下：</font></p>
				<ul>
						<ul>
								<li>MySQL<font face="宋体, SimSun">（</font>4.1<font face="宋体, SimSun">）</font></li>
						</ul>
				</ul>
				<p>
						<a href="http://dev.mysql.com/downloads/mysql">
								<font color="#0066ff">http://dev.mysql.com/downloads/mysql</font>
						</a>
				</p>
				<ul>
						<ul>
								<li>Perl <font face="宋体, SimSun">（</font>5.8.7.815<font face="宋体, SimSun">）</font></li>
						</ul>
				</ul>
				<p>
						<a href="http://www.activestate.com/Products/Download/Download.plex?id=ActivePerl">
								<font color="#0066ff">http://www.activestate.com/Products/Download/Download.plex?id=ActivePerl</font>
						</a>
				</p>
				<ul>
						<ul>
								<li>Perl<font face="宋体, SimSun">模块</font></li>
						</ul>
				</ul>
				<p>
						<font face="宋体, SimSun">有两个简单的途径可以获得</font>Bugzilla<font face="宋体, SimSun">所需的</font>Perl<font face="宋体, SimSun">模块。一个是</font><font color="#0000ff"><a href="http://sourceforge.net/project/showfiles.php?group_id=75477&amp;package_id=111950&amp;release_id=374258"><font color="#5b9eff">Bugzilla<font face="宋体, SimSun">汉化项目</font></font></a>整理的，收集的很全而且比较新，还有一个安装批处理程序，所以推荐大家用这个；另外一个是</font><font color="#0000ff"><a href="http://landfill.bugzilla.org/ppm/"><font color="#0066ff">Bugzilla<font face="宋体, SimSun">的测试服务器</font></font></a>，它也提供了完整的</font>Perl<font face="宋体, SimSun">模块集合，但是版本似乎比较老。第三条道路也是有的，但是需要自己去找然后再编译。对于像我一样不懂</font>Perl<font face="宋体, SimSun">德人来说是在复杂，因此不推荐大家这样做。</font></p>
				<p>
						<a href="http://sourceforge.net/project/showfiles.php?group_id=75477">
								<font color="#0066ff">http://sourceforge.net/project/showfiles.php?group_id=75477</font>
						</a>
				</p>
				<p>
						<a href="http://landfill.bugzilla.org/ppm/">
								<font color="#0066ff">http://landfill.bugzilla.org/ppm/</font>
						</a>
				</p>
				<ul>
						<ul>
								<li>Bugzilla<font face="宋体, SimSun">（</font>2.20<font face="宋体, SimSun">）</font></li>
						</ul>
				</ul>
				<p>
						<a href="http://www.bugzilla.org/download/">
								<font color="#0066ff">http://www.bugzilla.org/download/</font>
						</a>
				</p>
				<ul>
						<ul>
								<li>Bugzilla<font face="宋体, SimSun">汉化包（</font>2.20<font face="宋体, SimSun">）</font></li>
						</ul>
				</ul>
				<p>
						<a href="http://sourceforge.net/project/showfiles.php?group_id=75477">
								<font color="#0066ff">http://sourceforge.net/project/showfiles.php?group_id=75477</font>
						</a>
				</p>
				<ol>
						<li>
								<font face="宋体, SimSun">安装和配置</font>MySQL </li>
				</ol>
				<p>
						<font face="宋体, SimSun">安装</font>MySQL<font face="宋体, SimSun">很简单，只要按照安装程序的提示一步一步的做就可以了，如果有问题可以到</font>MySQL<font face="宋体, SimSun">官方网站（</font>http://dev.mysql.com/doc/<font face="宋体, SimSun">）上查看在线手册。</font></p>
				<p>
						<font face="宋体, SimSun">接下来要配置</font>MySQL<font face="宋体, SimSun">。有些文章里写道需要手工修改</font>root<font face="宋体, SimSun">用户的密码，其实这一步在</font>MySQL<font face="宋体, SimSun">安装程序里就已经完成了（可能那些文档写的较早，</font>MySQL<font face="宋体, SimSun">的安装程序可能不太好用吧），因此不用再去设置。我们要新建一个</font>Bug<font face="宋体, SimSun">数据库和一个</font>Bugzilla<font face="宋体, SimSun">访问这个数据库的用户。操作如下：</font></p>
				<dl>
						<dd>
								<table cellspacing="0" cellpadding="7" width="529" border="1">
										<tbody>
												<tr>
														<td valign="top" bgcolor="#000000">C:\mysql\bin&gt;<font color="#ffff00">mysql –user=root -p mysql</font>  
<p></p><p>Enter password: <font color="#ffff00">********</font></p><p>Welcome to the MySQL monitor. Commands end with ; or \g.</p><p>Your MySQL connection id is 15 to server version: 4.0.20a-debug</p><p>Type ‘help;’ or ‘\h’ for help. Type ‘\c’ to clear the buffer.</p><p>mysql&gt; <font color="#ffff00">create database </font><font color="#ff0000">&lt;database_name&gt;</font><font color="#ffff00">;</font></p><p>Query OK, 1 row affected (0.11 sec)</p><p>mysql&gt; <font color="#ffff00">grant all privileges on </font><font color="#ff0000">&lt;database_name&gt;</font><font color="#ffff00">.* to ‘</font><font color="#ff0000">&lt;user_name&gt;</font><font color="#ffff00">‘@’</font><font color="#ff0000">&lt;server_name&gt;</font><font color="#ffff00">‘ identified by ‘</font><font color="#ff0000">&lt;password&gt;</font><font color="#ffff00">‘;</font></p><p>Query OK, 0 rows affected (0.03 sec)</p><p>mysql&gt; <font color="#ffff00">flush privileges;</font></p><p>Query OK, 0 rows affected (0.00 sec)</p><p>mysql&gt; <font color="#ffff00">quit</font></p><p>Bye</p><p>C:\mysql\bin&gt;</p></td>
												</tr>
										</tbody>
								</table>
						</dd>
				</dl>
				<ol>
						<li>
								<font face="宋体, SimSun">安装</font>Perl<font face="宋体, SimSun">及其模块</font></li>
				</ol>
				<p>
						<font face="宋体, SimSun">安装</font>Perl<font face="宋体, SimSun">也很容易，按照安装程序提示一步一步装就可以了。稍微复杂一点的是安装它的模块。不过有了</font><font color="#0000ff"><a href="http://sourceforge.net/project/showfiles.php?group_id=75477&amp;package_id=111950&amp;release_id=374258"><font color="#5b9eff">Bugzilla<font face="宋体, SimSun">汉化项目</font></font></a>提供的批处理程序，这个步骤也非常简单了。大家只要记住一个简单的命令就可以了：</font></p>
				<dl>
						<dd>
								<table cellspacing="0" cellpadding="7" width="529" border="1">
										<tbody>
												<tr>
														<td valign="top" bgcolor="#d9d9d9">ppn install <font color="#ff0000">&lt;module_name&gt;</font>  
<p></p><p>ppn uninstall <font color="#ff0000">&lt;module_name&gt;</font></p></td>
												</tr>
										</tbody>
								</table>
						</dd>
				</dl>
				<ol>
						<li>
								<font face="宋体, SimSun">安装</font>Bugzilla </li>
				</ol>
				<p>
						<font face="宋体, SimSun">把下载到压缩包解压到一个文件夹，然后运行</font>Bugzilla<font face="宋体, SimSun">的安装检查程序（</font>CheckSetup.pl<font face="宋体, SimSun">）。它会自动验证是不是安装了必须的软件。如果没有什么问题它会在</font>Bugzilla<font face="宋体, SimSun">目录里生成一个</font>localconfig<font face="宋体, SimSun">文件（没有扩展名）。</font></p>
				<p>
						<font face="宋体, SimSun">用文本编辑器打开</font>localconfig<font face="宋体, SimSun">文件，找到下面两段文字。</font>$db_host<font face="宋体, SimSun">表示服务器名称，</font>$db_name<font face="宋体, SimSun">表示数据库名称，</font>$db_user<font face="宋体, SimSun">表示登录用户名，</font>$db_pass<font face="宋体, SimSun">表示密码。修改这几个值并保存。</font></p>
				<dl>
						<dd>
								<table cellspacing="0" cellpadding="7" width="529" border="1">
										<tbody>
												<tr>
														<td valign="top" bgcolor="#d9d9d9">#  
<p></p><p># How to access the SQL database:</p><p>#</p><p>$db_host = <font color="#ff0000">‘localhost’</font>; # where is the database?</p><p>$db_name = <font color="#ff0000">‘bugs’</font>; # name of the SQL database</p><p>$db_user = <font color="#ff0000">‘bugs’</font>; # user to attach to the SQL database</p><p>#</p><p># Enter your database password here. It’s normally advisable to specify</p><p># a password for your bugzilla database user.</p><p># If you use apostrophe (’) or a backslash (\) in your password, you’ll</p><p># need to escape it by preceding it with a ‘\’ character. (\’) or (\)</p><p># (Far simpler just not to use those characters.)</p><p>#</p><p>$db_pass = <font color="#ff0000">‘bugs@agfa’</font>;</p></td>
												</tr>
										</tbody>
								</table>
						</dd>
				</dl>
				<p>
						<font face="宋体, SimSun">再次运行</font>Bugzilla<font face="宋体, SimSun">的安装检查程序（</font>CheckSetup.pl<font face="宋体, SimSun">）。这时如果正常它将初始化数据库结构和</font>Demo<font face="宋体, SimSun">数据。不过不要高兴得太早，可能会出现“</font><strong><font face="Verdana, sans-serif"><font color="#000000">Client does not support authentication protocol requested by server ……</font></font></strong>”<font face="宋体, SimSun">错误信息。这个问题整整困扰了我一个上午，幸亏后来找到</font>Byron Jones<font face="宋体, SimSun">写的《</font><a href="http://www.bugzilla.org/docs/win32install.html"><font color="#0066ff">Installing Bugzilla on Microsoft Windows</font></a><font face="宋体, SimSun">》。产生这个错误是因为</font>MySQL 4.1<font face="宋体, SimSun">及以后的版本使用了新的密码加密算法，而使用的</font>Perl<font face="宋体, SimSun">的</font>DBD::MySql<font face="宋体, SimSun">模块不够新，不支持新的加密算法。你可以采取两种方式来解决这个问题：一是使用<a href="http://www.bugzilla.org/docs/2.20/html/trbl-passwd-encryption.html"><font color="#0066ff">新的</font></a></font><font color="#0000ff"><a href="http://www.bugzilla.org/docs/2.20/html/trbl-passwd-encryption.html"><font color="#0066ff">DBD::MySql<font face="宋体, SimSun">模块</font></font></a>，不过需要自己编译；另一种是在</font>MySQL<font face="宋体, SimSun">中强制使用兼容老版本的密码加密算法：</font></p>
				<dl>
						<dd>
								<table cellspacing="0" cellpadding="7" width="529" border="1">
										<tbody>
												<tr>
														<td valign="top" bgcolor="#000000">
																<font color="#ffffff">C:\mysql\bin&gt;</font>
																<font color="#ffff00">mysql –user=root -p mysql</font>  
<p></p><p><font color="#ffffff">Enter password: </font><font color="#ffff00">********</font></p><p><font color="#ffffff">Welcome to the MySQL monitor. Commands end with ; or \g.</font></p><p><font color="#ffffff">Your MySQL connection id is 15 to server version: 4.1.11-nt</font></p><p><font color="#ffffff">Type ‘help;’ or ‘\h’ for help. Type ‘\c’ to clear the buffer.</font></p><p><font color="#ffffff">mysql&gt; </font><font color="#ffff00">set password for ‘</font><font color="#ff0000">&lt;user_name&gt;</font><font color="#ffff00">‘@’</font><font color="#ff0000">&lt;server_name&gt;</font><font color="#ffff00">‘ </font><font color="#ffff00">= </font><font color="#ff0000">OLD_PASSWORD </font><font color="#ffff00">(’</font><font color="#ff0000">&lt;password&gt;</font><font color="#ffff00">‘);</font></p><p><font color="#ffffff">Query OK, 0 rows affected (0.00 sec)</font></p><p><font color="#ffffff">mysql&gt; </font><font color="#ffff00">quit</font></p><p><font color="#ffffff">Bye</font></p><p><font color="#ffffff">C:\mysql\bin&gt;</font></p></td>
												</tr>
										</tbody>
								</table>
						</dd>
				</dl>
				<p> </p>
				<ol>
						<li>
								<font face="宋体, SimSun">配置</font>IIS </li>
				</ol>
				<p>
						<font face="宋体, SimSun">打开</font>IIS<font face="宋体, SimSun">管理界面。新建一个虚拟路径，指向</font>Bugzilla<font face="宋体, SimSun">所在文件夹。</font></p>
				<p align="center">
						<img height="465" src="http://www.writely.com/File.aspx?id=bcdcgf4ckn9tg" width="472" align="bottom" border="0" />
				</p>
				<p align="center"> </p>
				<p align="left">
						<font size="3">然后按应用程序设置按钮。增加一个映射，将</font>
						<font face="宋体, SimSun">
								<font size="3">.cgi</font>
						</font>
						<font size="3">文件映射到</font>
						<font face="宋体, SimSun">
								<font size="3">perl.exe</font>
						</font>
						<font size="3">。这里特别注意，有些文档里写成：</font>
						<font face="宋体, SimSun">
								<font size="3">perl.exe “%s” %s</font>
						</font>
						<font size="3">，这样不正确，在运行时出错（又花去一个小时）。正确的配置应该如下：</font>
				</p>
				<p align="left"> </p>
				<dl>
						<dd>
								<table cellspacing="0" cellpadding="7" width="529" border="1">
										<tbody>
												<tr>
														<td valign="top" bgcolor="#d9d9d9">
																<p align="left"> </p>
																<p align="left">
																		<font face="宋体, SimSun">
																				<font size="3">&lt;perl</font>
																		</font>
																		<font size="3">完整路径</font>
																		<font face="宋体, SimSun">
																				<font size="3">&gt;\perl.exe -x&lt;Bugzilla</font>
																		</font>
																		<font size="3">完整路径</font>
																		<font face="宋体, SimSun">
																				<font size="3">&gt; -wT “%s” %s</font>
																		</font>
																</p>
																<p align="justify">
																		<font size="3">例如：</font>
																</p>
																<p align="justify">
																		<font face="宋体, SimSun">
																				<font size="3">c:\perl\bin\perl.exe -xc:\bugzilla -wT “%s” %s</font>
																		</font>
																</p>
																<p align="left"> </p>
														</td>
												</tr>
										</tbody>
								</table>
						</dd>
				</dl>
				<p align="center">
						<img height="455" src="http://www.writely.com/File.aspx?id=bcdcgf5nm9358" width="406" align="bottom" border="0" />
				</p>
				<p align="center">
						<img height="250" src="http://www.writely.com/File.aspx?id=bcdcgf6p85h49" width="429" align="bottom" border="0" />
				</p>
				<p>
						<font face="宋体, SimSun">最后，将</font>index.cgi<font face="宋体, SimSun">加入到默认文档列表中。最好移到最前面，这样可以加快查询速度。如果不希望</font>/<font face="宋体, SimSun">不能把</font>index.cgi<font face="宋体, SimSun">加入到默认文档列表中，也可以在安装</font>Bugzilla<font face="宋体, SimSun">的时候，将</font>localconfig<font face="宋体, SimSun">文件中</font>$index_html<font face="宋体, SimSun">的值改为</font>1<font face="宋体, SimSun">。这样运行</font>checksetup.pl<font face="宋体, SimSun">时，就会生成一个</font>index.html<font face="宋体, SimSun">，自动重定向到</font>index.cgi<font face="宋体, SimSun">。</font></p>
				<dl>
						<dd>
								<table cellspacing="0" cellpadding="7" width="529" border="1">
										<tbody>
												<tr>
														<td valign="top" bgcolor="#d9d9d9">#  
<p></p><p># With the introduction of a configurable index page using the</p><p># template toolkit, Bugzilla’s main index page is now index.cgi.</p><p># Most web servers will allow you to use index.cgi as a directory</p><p># index, and many come preconfigured that way, but if yours doesn’t</p><p># then you’ll need an index.html file that provides redirection</p><p># to index.cgi. Setting $index_html to 1 below will allow</p><p># checksetup.pl to create one for you if it doesn’t exist.</p><p># NOTE: checksetup.pl will not replace an existing file, so if you</p><p># wish to have checksetup.pl create one for you, you must</p><p># make sure that index.html doesn’t already exist</p><p>$index_html = <font color="#ff0000">1</font>;</p></td>
												</tr>
										</tbody>
								</table>
						</dd>
				</dl>
				<p align="center">
						<img height="465" src="http://www.writely.com/File.aspx?id=bcdcgf7f2xqx6" width="472" align="bottom" border="0" />
				</p>
				<ol>
						<li>
								<font face="宋体, SimSun">配置</font>Bugzilla </li>
				</ol>
				<p>
						<font face="宋体, SimSun">不想多写了，在浏览器中打开</font>
						<a href="http://localhost/bugzilla">
								<font color="#0066ff">http://localhost/bugzilla</font>
						</a>
						<font face="宋体, SimSun">（根据你的具体情况而定）。如果你的</font>Bugzilla<font face="宋体, SimSun">是第一次使用，它会自动转向到</font>Setup<font face="宋体, SimSun">页面，按部就班的做就可以了。</font></p>
				<ol>
						<li>
								<font face="宋体, SimSun">汉化</font>Bugzilla </li>
				</ol>
				<p>
						<font face="宋体, SimSun">最后要做的就是汉化了，不过你不想汉化也没有问题。将汉化包解压解压到</font>cn<font face="宋体, SimSun">文件夹，将整个文件目录 </font>cn <font face="宋体, SimSun">拷贝至 </font>Bugzilla <font face="宋体, SimSun">的子目录 </font>template<font face="宋体, SimSun">下；然后以管理员身份登录</font>Bugzilla<font face="宋体, SimSun">，点击页脚的 </font>Parameters<font face="宋体, SimSun">（系统参数设置）链接，将 </font>languages <font face="宋体, SimSun">一项的值改为 </font>cn<font face="宋体, SimSun">，保存，则以后见到的</font>Bugzilla<font face="宋体, SimSun">页面就是汉语页面了。如果想返回英文界面，将 </font>cn <font face="宋体, SimSun">改回 </font>en <font face="宋体, SimSun">即可。</font></p>
				<p>
						<font face="宋体, SimSun">为保证向后兼容，汉化的文件全部存为 </font>UTF-8 <font face="宋体, SimSun">格式。但不管你是否汉化</font>Bugzilla<font face="宋体, SimSun">，为强迫</font>Bugzilla<font face="宋体, SimSun">采用</font>UTF-8<font face="宋体, SimSun">来处理字符串，避免</font>Bugzilla<font face="宋体, SimSun">偶然出现的乱码，强烈建议大家将文件 </font>&lt;Bugzilla<font face="宋体, SimSun">安装目录</font>&gt;\Bugzilla\CGI.pm <font face="宋体, SimSun">的第</font>55<font face="宋体, SimSun">行改为 </font>$self-&gt;charset(’<font color="#ff0000">UTF-8</font>‘)<font face="宋体, SimSun">。</font></p>
				<ol>
						<li>
								<font face="宋体, SimSun">总结</font>
						</li>
				</ol>
				<p>
						<font face="宋体, SimSun">到这里，</font>Bugzilla<font face="宋体, SimSun">的安装就基本上搞定了。也许你已经发现了，这篇文档没有说明关于邮件的问题。这时因为我没有配置，不过按照</font>Bugzilla<font face="宋体, SimSun">文档的说明，它已经提供了内置的</font>SMTP<font face="宋体, SimSun">支持。可是它不支持需要认证的</font>SMTP<font face="宋体, SimSun">，<a href="http://www.glob.com.au/sendmail/"><font color="#000000">可以使用</font></a></font><a href="http://www.glob.com.au/sendmail/"><font color="#0000ff"><font face="Verdana, sans-serif">Glob’s sendmail wrapper</font></font></a><font face="宋体, SimSun"><font color="#000000">来解决。</font></font></p>
				<ol>
						<li>
								<font face="宋体, SimSun">参考</font>
						</li>
				</ol>
				<p>Bugzilla<font face="宋体, SimSun">官方网站</font><a href="http://www.bugzilla.org/"><font color="#0066ff">http://www.bugzilla.org</font></a></p>
				<p>Bugzilla<font face="宋体, SimSun">汉化项目</font><a href="http://sourceforge.net/projects/bugzilla-cn"><font color="#5b9eff">http://sourceforge.net/projects/bugzilla-cn</font></a></p>
				<p>
						<a href="http://cosoft.org.cn/projects/bugzillchinese/">
								<font color="#5b9eff">http://cosoft.org.cn/projects/bugzillchinese/</font>
						</a>
				</p>
				<p>Perl<font face="宋体, SimSun">官方网站</font><a href="http://www.perl.com/"><font color="#0066ff">http://www.perl.com</font></a></p>
				<p>ActivePerl<font face="宋体, SimSun">官方网站</font><a href="http://www.activestate.com/Products/ActivePerl"><font color="#0066ff">http://www.activestate.com/Products/ActivePerl</font></a></p>
				<p>MySQL<font face="宋体, SimSun">官方网站</font><a href="http://www.mysql.com/"><font color="#0066ff">http://www.mysql.com</font></a></p>
				<p>Fake Sendmait for Windows <a href="http://www.glob.com.au/sendmail/"><font color="#0066ff">http://www.glob.com.au/sendmail/</font></a></p>
				<p>Installing Bugzilla on Microsoft Windows</p>
				<p>
						<a href="http://www.bugzilla.org/docs/win32install.html">
								<font color="#0066ff">http://www.bugzilla.org/docs/win32install.html</font>
						</a>
				</p>
				<p>
						<a name="AEN2">
						</a>The Bugzilla Guide <a href="http://www.bugzilla.org/docs/2.20/html"><font color="#0066ff">http://www.bugzilla.org/docs/2.20/html</font></a></p>
				<p>Bugzilla windows<font face="宋体, SimSun">安装红宝书</font><a href="http://blog.fz0132.com/trackback.asp?tbID=654"><font color="#5b9eff">http://blog.fz0132.com/trackback.asp?tbID=654</font></a></p>
				<ol>
						<li>
								<font face="宋体, SimSun">附录</font>
						</li>
				</ol>
				<p>
						<font face="宋体, SimSun">
								<strong>安装配置</strong>
						</font>
						<strong>Bugzilla</strong>
						<font face="宋体, SimSun">
								<strong>的工作清单</strong>
						</font>
				</p>
				<p>□ <font face="宋体, SimSun">下载</font>Perl</p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">下载</font>Perl<font face="宋体, SimSun">模块</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">下载</font><font face="宋体, SimSun">MySQL</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">下载</font><font face="宋体, SimSun">Bugzilla</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">下载</font><font face="宋体, SimSun">Bugzilla</font><font face="宋体, SimSun">汉化包</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">安装</font>MySQL</p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">生成</font><font face="宋体, SimSun">Bug</font><font face="宋体, SimSun">数据库</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">生成</font><font face="宋体, SimSun">Bugzilla</font><font face="宋体, SimSun">数据库用户并分配权限</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">安装</font><font face="宋体, SimSun">Perl</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">安装</font><font face="宋体, SimSun">Perl</font><font face="宋体, SimSun">模块</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">解压</font>Bugzilla<font face="宋体, SimSun">压缩包</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">运行</font>CheckSetup.pl<font face="宋体, SimSun">检查安装</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">修改</font>localconfig<font face="宋体, SimSun">文件，设置数据库访问方式</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">再次运行</font>CheckSetup.pl<font face="宋体, SimSun">完成数据库初始化</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">修改</font><font face="宋体, SimSun">Bugzilla</font><font face="宋体, SimSun">数据库用户密码加密方式（视情况而定）</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">在</font>IIS<font face="宋体, SimSun">管理器中为</font>Bugzilla<font face="宋体, SimSun">建立虚拟路径</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">将</font>.cgi<font face="宋体, SimSun">文件映射到</font>perl.exe</p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">将</font>index.cgi<font face="宋体, SimSun">加入到默认文档列表中（可选）</font></p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">配置</font>Bugzilla</p>
				<p>□<font face="宋体, SimSun"></font><font face="宋体, SimSun">汉化</font>Bugzilla</p>
				<p>
				</p>
				<!-- Content End -->
		</div>
<img src ="http://www.cnitblog.com/charester/aggbug/21242.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2006-12-28 21:10 <a href="http://www.cnitblog.com/charester/archive/2006/12/28/21242.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>mysql 5.0的安装（ZT）</title><link>http://www.cnitblog.com/charester/archive/2006/12/28/21241.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Thu, 28 Dec 2006 12:20:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2006/12/28/21241.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21241.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2006/12/28/21241.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21241.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21241.html</trackback:ping><description><![CDATA[1. 在<a onfocus="this.blur()" href="http://dev.mysql.com/downloads/mysql/5.0.html" target="_blank">http://dev.mysql.com/downloads/mysql/5.0.html</a>下载mysql-noinstall-5.0.15- win32.zip. <br /><br />2. 解压到C:\mysql, 拷贝my-medium.ini到C:\WINDOWS, 并重命名为my.ini. 3. 编辑 my.ini,在[mysqld]部分中增加如下两句: basedir = 你装mysql的地址 datadir = F:/Data/mysqldata 因安装CodeLib .Net, [mysqld]修改以下几句为:(原因见:<a onfocus="this.blur()" href="http://codelib.threeus.com/Manual/ConnectionMySQL.htm" target="_blank">http://codelib.threeus.com/Manual/ConnectionMySQL.htm</a> 《安装设定Code Library(MySQL)三步骤》) max_allowed_packet = 32M (默认为1M) sort_buffer_size = 4M (MySQL 5.0.15 my-medium.ini默认为512K) 新增default-character-set = utf8 在[client]中同样增加default-charac<br /><br />一、连接MYSQL。 <br />格式： mysql -h主机地址 -u用户名 －p用户密码 <br />1、例1：连接到本机上的MYSQL。 <br />首先在打开DOS窗口，然后进入目录 mysqlbin，再键入命令mysql -uroot -p，回车后提示你输密码，如果刚安装好MYSQL，超级用户root是没有密码的，故直接回车即可进入到MYSQL中了，MYSQL的提示符是：mysql&gt; <br />2、例2：连接到远程主机上的MYSQL。假设远程主机的IP为：110.110.110.110，用户名为root,密码为abcd123。则键入以下命令： <br />mysql -h110.110.110.110 -uroot -pabcd123 <br />（注:u与root可以不用加空格，其它也一样） <br />3、退出MYSQL命令： exit （回车） <br />二、修改密码。 <br />格式：mysqladmin -u用户名 -p旧密码 password 新密码 <br />1、例1：给root加个密码ab12。首先在DOS下进入目录mysqlbin，然后键入以下命令 <br />mysqladmin -uroot -password ab12 <br />注：因为开始时root没有密码，所以-p旧密码一项就可以省略了。 <br />2、例2：再将root的密码改为djg345。 <br />mysqladmin -uroot -pab12 password djg345 <br />三、增加新用户。（注意：和上面不同，下面的因为是MYSQL环境中的命令，所以后面都带一个分号作为命令结束符） <br />格式：grant select on 数据库.* to 用户名@登录主机 identified by "密码" <br />例1、增加一个用户test1密码为abc，让他可以在任何主机上登录，并对所有数据库有查询、插入、修改、删除的权限。首先用以root用户连入MYSQL，然后键入以下命令： <br />grant select,insert,update,delete on *.* to test1@"%" Identified by "abc"; <br />但例1增加的用户是十分危险的，你想如某个人知道test1的密码，那么他就可以在internet上的任何一台电脑上登录你的mysql数据库并对你的数据可以为所欲为了，解决办法见例2。 <br />例2、增加一个用户test2密码为abc,让他只可以在localhost上登录，并可以对数据库mydb进行查询、插入、修改、删除的好作（localhost指本地主机，即MYSQL数据库所在的那台主机），这样用户即使用知道test2的密码，他也无法从internet上直接访问数据库，只能通过MYSQL主机上的web页来访问了。 <br />grant select,insert,update,delete on mydb.* to test2@localhost identified by "abc"; <br />如果你不想test2有密码，可以再打一个命令将密码消掉。 <br />grant select,insert,update,delete on mydb.* to test2@localhost identified by ""; <br /><br /><br />我们来看看MYSQL中有关数据库方面的好作。注意：你必须首先登录到MYSQL中，以下好作都是在MYSQL的提示符下进行的，而且每个命令以分号结束。 <br /><br />一、好作技巧 <br />1、如果你打命令时，回车后发现忘记加分号，你无须重打一遍命令，只要打个分号回车就可以了。也就是说你可以把一个完整的命令分成几行来打，完后用分号作结束标志就OK。 <br />2、你可以使用光标上下键调出以前的命令。但以前我用过的一个MYSQL旧版本不支持。我现在用的是mysql-3.23.27-beta-win。 <br /><br />二、显示命令 <br />1、显示数据库列表。 <br />show databases; <br />刚开始时才两个数据库：mysql和test。mysql库很重要它里面有MYSQL的系统信息，我们改密码和新增用户，实际上就是用这个库进行好作。 <br />2、显示库中的数据表： <br />use mysql； ／／打开库，学过FOXBASE的一定不会陌生吧 <br />show tables; <br />3、显示数据表的结构： <br />describe 表名; <br />4、建库： <br />create database 库名; <br />5、建表： <br />use 库名； <br />create table 表名 (字段设定列表)； <br />6、删库和删表: <br />drop database 库名; <br />drop table 表名； <br />7、将表中记录清空： <br />delete from 表名; <br />8、显示表中的记录： <br />select * from 表名; <br /><br />三、一个建库和建表以及插入数据的实例 <br />drop database if exists school; //如果存在SCHOOL则删除 <br />create database school; //建立库SCHOOL <br />use school; //打开库SCHOOL <br />create table teacher //建立表TEACHER <br />( <br />id int(3) auto_increment not null primary key, <br />name char(10) not null, <br />address varchar(50) default '深圳', <br />year date <br />); //建表结束 <br />//以下为插入字段 <br />insert into teacher values('','glchengang','深圳一中','1976-10-10'); <br />insert into teacher values('','jack','深圳一中','1975-12-23'); <br /><br />注：在建表中（1）将ID设为长度为3的数字字段:int(3)并让它每个记录自动加一:auto_increment并不能为空:not null而且让他成为主字段primary key（2）将NAME设为长度为10的字符字段（3）将ADDRESS设为长度50的字符字段，而且缺省值为深圳。varchar和char有什么区别呢，只有等以后的文章再说了。 （4）将YEAR设为好期字段。 <br />如果你在mysql提示符键入上面的命令也可以，但不方便调试。你可以将以上命令原样写入一个文本文件中假设为school.sql，然后复制到c:\下，并在DOS状态进入目录\mysql\bin，然后键入以下命令： <br />mysql -uroot -p密码 &lt; c:\school.sql <br />如果成功，空出一行无任何显示；如有错误，会有提示。（以上命令已经调试，你只要将//的注释去掉即可使用）。 <br /><br />四、将文本数据转到数据库中 <br />1、文本数据应符合的格式：字段数据之间用tab键隔开，null值用\n来代替. <br />例： <br />3 rose 深圳二中 1976-10-10 <br />4 mike 深圳一中 1975-12-23 <br />2、数据传入命令 load data local infile "文件名" into table 表名; <br />注意：你最好将文件复制到\mysql\bin目录下，并且要先用use命令打表所在的库 。 <br /><br />五、备份数据库：（命令在DOS的\mysql\bin目录下执行） <br />mysqldump --opt school&gt;school.bbb <br />注释:将数据库school备份到school.bbb文件，school.bbb是一个文本文件，文件名任取，打开看看你会有新发现。 <br /><img src ="http://www.cnitblog.com/charester/aggbug/21241.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2006-12-28 20:20 <a href="http://www.cnitblog.com/charester/archive/2006/12/28/21241.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>win2000 下安装配置 BUGZILLA 心得（ZT）</title><link>http://www.cnitblog.com/charester/archive/2006/12/28/21240.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Thu, 28 Dec 2006 12:14:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2006/12/28/21240.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21240.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2006/12/28/21240.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21240.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21240.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 决定将上星期安装BUGZILLA的经验告诉大家，希望能对那些正在为装BUGZILLA的朋友们有所帮助										网上有很多关于						bugzilla 						在						windows						环境下安装的资料，但是也有部分不适合自已，走到某一步总会出现一些错误提示，我整整花了一周的时间才配置好，现将我的安装步骤写下来，希望对大家有些启发。				...&nbsp;&nbsp;<a href='http://www.cnitblog.com/charester/archive/2006/12/28/21240.html'>阅读全文</a><img src ="http://www.cnitblog.com/charester/aggbug/21240.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2006-12-28 20:14 <a href="http://www.cnitblog.com/charester/archive/2006/12/28/21240.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>bugfree学习（一）</title><link>http://www.cnitblog.com/charester/archive/2006/12/28/21231.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Thu, 28 Dec 2006 11:04:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2006/12/28/21231.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21231.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2006/12/28/21231.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21231.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21231.html</trackback:ping><description><![CDATA[
		<strong>这个工具很好，可是不会采用。因为不方便，芝麻大的事情要去搞数据库。稳定性很难保证<br />停止学习这个工具</strong>
		<br />
		<br />1：BugFree的7种解决方案各自的含义是什么？ 
<p>By Design - 就是这么设计的，无效的Bug<br />Duplicate - 这个问题别人已经发现了，重复的Bug<br />External - 是个外部因素(比如浏览器、操作系统、其他第3方软件)造成的问题<br />Fixed - 问题被修理掉了。Tester要尽可能找到这种Bug<br />Not Repro - 无法复现你这个问题，无效的Bug<br />Postponed - 是个问题，但是目前不必修理了，推迟到以后再解<br />Won't Fix - 是个问题，但是不值得修理了，不管它吧<br /><br />2：如何成为管理员？ </p><p>打开Include/ConfigBug.inc.php文件:<br />$BugConfig["AdminUser"] = array("admin","你的用户名");<br />要注意引号和逗号的间隔。 <br /><br />3：如何增加上传附件的大小? </p><p>修改ConfigBug.inc.php文件。<br />$BugConfig["File"]["MaxFileSize"] = 1024 * 100; //注意单位是字节。 <br /><br /></p><dt>1.4 保护你的Shell目录，以免造成密码泄漏。 
</dt><dd>Shell目录下面的脚本程序是用来自动发送邮件通知的，这个目录必须加以保护，以免造成安全方面的隐患。<br />保护的措施有以下几个方法： 
<ul><li>1.4.1 将Shell目录移到网站目录之外。 
</li><li>1.4.2 如果是linux 系统下面，可以通过chmod o-rwx Shell -R 来去掉Shell目录的读写权限。 
</li><li>1.4.3 可以将扩展名为.sh结尾的文件删除，通过.php文件来完成自动发信的功能。具