﻿<?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博客-roguishangel-文章分类-转载文章</title><link>http://www.cnitblog.com/roguishangel/category/1124.html</link><description /><language>zh-cn</language><lastBuildDate>Mon, 26 Sep 2011 21:34:05 GMT</lastBuildDate><pubDate>Mon, 26 Sep 2011 21:34:05 GMT</pubDate><ttl>60</ttl><item><title>如何在C语言中巧用正则表达式</title><link>http://www.cnitblog.com/roguishangel/articles/6080.html</link><dc:creator>牙刷</dc:creator><author>牙刷</author><pubDate>Sat, 07 Jan 2006 14:10:00 GMT</pubDate><guid>http://www.cnitblog.com/roguishangel/articles/6080.html</guid><wfw:comment>http://www.cnitblog.com/roguishangel/comments/6080.html</wfw:comment><comments>http://www.cnitblog.com/roguishangel/articles/6080.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/roguishangel/comments/commentRss/6080.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/roguishangel/services/trackbacks/6080.html</trackback:ping><description><![CDATA[<A href="http://bbs.chinaunix.net/viewthread.php?tid=303346&amp;extra=page%3D5%26filter%3Ddigest">http://bbs.chinaunix.net/viewthread.php?tid=303346&amp;extra=page%3D5%26filter%3Ddigest</A><BR><BR><FONT size=2>如果用户熟悉Linux下的sed、awk、grep或vi，那么对正则表达式这一概念肯定不会陌生。由于它可以极大地简化处理字符串时的复杂度，因此现在已经在许多Linux实用工具中得到了应用。千万不要以为正则表达式只是Perl、Python、Bash等脚本语言的专利，作为C语言程序员，用户同样可以在自己的程序中运用正则表达式。 <BR><BR>标准的C和C++都不支持正则表达式，但有一些函数库可以辅助C/C++程序员完成这一功能，其中最著名的当数Philip Hazel的Perl-Compatible Regular Expression库，许多Linux发行版本都带有这个函数库。 <BR><BR>编译正则表达式 <BR><BR>为了提高效率，在将一个字符串与正则表达式进行比较之前，首先要用regcomp()函数对它进行编译，将其转化为regex_t结构： <BR><BR>int regcomp(regex_t *preg, const char *regex, int cflags);<BR><BR><BR><BR>参数regex是一个字符串，它代表将要被编译的正则表达式；参数preg指向一个声明为regex_t的数据结构，用来保存编译结果；参数cflags决定了正则表达式该如何被处理的细节。 <BR><BR>如果函数regcomp()执行成功，并且编译结果被正确填充到preg中后，函数将返回0，任何其它的返回结果都代表有某种错误产生。 <BR><BR>匹配正则表达式 <BR><BR>一旦用regcomp()函数成功地编译了正则表达式，接下来就可以调用regexec()函数完成模式匹配： <BR><BR>int regexec(const&nbsp;&nbsp;regex_t&nbsp;&nbsp;*preg,&nbsp;&nbsp;const&nbsp;&nbsp;char *string, size_t nmatch,regmatch_t pmatch[], int eflags);<BR>typedef struct {<BR>&nbsp;&nbsp;regoff_t rm_so;<BR>&nbsp;&nbsp;regoff_t rm_eo;<BR>} regmatch_t;<BR><BR><BR><BR>参数preg指向编译后的正则表达式，参数string是将要进行匹配的字符串，而参数nmatch和pmatch则用于把匹配结果返回给调用程序，最后一个参数eflags决定了匹配的细节。 <BR><BR>在调用函数regexec()进行模式匹配的过程中，可能在字符串string中会有多处与给定的正则表达式相匹配，参数pmatch就是用来保存这些匹配位置的，而参数nmatch则告诉函数regexec()最多可以把多少个匹配结果填充到pmatch数组中。当regexec()函数成功返回时，从string+pmatch[0].rm_so到string+pmatch[0].rm_eo是第一个匹配的字符串，而从string+pmatch[1].rm_so到string+pmatch[1].rm_eo，则是第二个匹配的字符串，依此类推。 <BR><BR>释放正则表达式 <BR><BR>无论什么时候，当不再需要已经编译过的正则表达式时，都应该调用函数regfree()将其释放，以免产生内存泄漏。 <BR><BR>void regfree(regex_t *preg);<BR><BR><BR><BR>函数regfree()不会返回任何结果，它仅接收一个指向regex_t数据类型的指针，这是之前调用regcomp()函数所得到的编译结果。 <BR><BR>如果在程序中针对同一个regex_t结构调用了多次regcomp()函数，POSIX标准并没有规定是否每次都必须调用regfree()函数进行释放，但建议每次调用regcomp()函数对正则表达式进行编译后都调用一次regfree()函数，以尽早释放占用的存储空间。 <BR><BR>报告错误信息 <BR><BR>如果调用函数regcomp()或regexec()得到的是一个非0的返回值，则表明在对正则表达式的处理过程中出现了某种错误，此时可以通过调用函数regerror()得到详细的错误信息。 <BR><BR>size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size);<BR><BR><BR><BR>参数errcode是来自函数regcomp()或regexec()的错误代码，而参数preg则是由函数regcomp()得到的编译结果，其目的是把格式化消息所必须的上下文提供给regerror()函数。在执行函数regerror()时，将按照参数errbuf_size指明的最大字节数，在errbuf缓冲区中填入格式化后的错误信息，同时返回错误信息的长度。 <BR><BR>应用正则表达式 <BR><BR>最后给出一个具体的实例，介绍如何在C语言程序中处理正则表达式。 <BR><BR>#include &lt;stdio.h&gt;;<BR>#include &lt;sys/types.h&gt;;<BR>#include &lt;regex.h&gt;;<BR><BR>/* 取子串的函数 */<BR>static char* substr(const char*str, unsigned start, unsigned end)<BR>{<BR>&nbsp;&nbsp;unsigned n = end - start;<BR>&nbsp;&nbsp;static char stbuf[256];<BR>&nbsp;&nbsp;strncpy(stbuf, str + start, n);<BR>&nbsp;&nbsp;stbuf[n] = 0;<BR>&nbsp;&nbsp;return stbuf;<BR>}<BR>/* 主程序 */<BR>int main(int argc, char** argv)<BR>{<BR>&nbsp;&nbsp;char * pattern;<BR>&nbsp;&nbsp;int x, z, lno = 0, cflags = 0;<BR>&nbsp;&nbsp;char ebuf[128], lbuf[256];<BR>&nbsp;&nbsp;regex_t reg;<BR>&nbsp;&nbsp;regmatch_t pm[10];<BR>&nbsp;&nbsp;const size_t nmatch = 10;<BR>&nbsp;&nbsp;/* 编译正则表达式*/<BR>&nbsp;&nbsp;pattern = argv[1];<BR>&nbsp;&nbsp;z = regcomp(&amp;reg, pattern, cflags);<BR>&nbsp;&nbsp;if (z != 0){<BR>&nbsp; &nbsp; regerror(z, &amp;reg, ebuf, sizeof(ebuf));<BR>&nbsp; &nbsp; fprintf(stderr, "%s: pattern '%s' \n", ebuf, pattern);<BR>&nbsp; &nbsp; return 1;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;/*&nbsp;&nbsp;逐行处理输入的数据 */<BR>&nbsp;&nbsp;while(fgets(lbuf, sizeof(lbuf), stdin)) {<BR>&nbsp; &nbsp; ++lno;<BR>&nbsp; &nbsp; if ((z = strlen(lbuf)) &gt;; 0 &amp;&amp; lbuf[z-1] == '\n')<BR>&nbsp; &nbsp;&nbsp; &nbsp;lbuf[z - 1] = 0;<BR>&nbsp; &nbsp; /* 对每一行应用正则表达式进行匹配 */<BR>&nbsp; &nbsp; z = regexec(&amp;reg, lbuf, nmatch, pm, 0);<BR>&nbsp; &nbsp; if (z == REG_NOMATCH) continue;<BR>&nbsp; &nbsp; else if (z != 0) {<BR>&nbsp; &nbsp;&nbsp; &nbsp;regerror(z, &amp;reg, ebuf, sizeof(ebuf));<BR>&nbsp; &nbsp;&nbsp; &nbsp;fprintf(stderr, "%s: regcom('%s')\n", ebuf, lbuf);<BR>&nbsp; &nbsp;&nbsp; &nbsp;return 2;<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; /* 输出处理结果 */<BR>&nbsp; &nbsp; for (x = 0; x &lt; nmatch &amp;&amp; pm[x].rm_so != -1; ++ x) {<BR>&nbsp; &nbsp;&nbsp; &nbsp;if (!x) printf("%04d: %s\n", lno, lbuf);<BR>&nbsp; &nbsp;&nbsp; &nbsp;printf("&nbsp;&nbsp;$%d='%s'\n", x, substr(lbuf, pm[x].rm_so, pm[x].rm_eo));<BR>&nbsp; &nbsp; }<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;/* 释放正则表达式&nbsp;&nbsp;*/<BR>&nbsp;&nbsp;regfree(&amp;reg);<BR>&nbsp;&nbsp;return 0;<BR>}<BR><BR><BR><BR>上述程序负责从命令行获取正则表达式，然后将其运用于从标准输入得到的每行数据，并打印出匹配结果。执行下面的命令可以编译并执行该程序： <BR><BR>#&nbsp;&nbsp;gcc regexp.c -o regexp<BR>#&nbsp;&nbsp;./regexp&nbsp;&nbsp;'regex[a-z]*' &lt; regexp.c<BR>0003: #include &lt;regex.h&gt;;<BR>&nbsp;&nbsp;$0='regex'<BR>0027:&nbsp; &nbsp;regex_t reg;<BR>&nbsp;&nbsp;$0='regex'<BR>0054:&nbsp; &nbsp;&nbsp;&nbsp;z = regexec(&amp;reg, lbuf, nmatch, pm, 0);<BR>&nbsp;&nbsp;$0='regexec'<BR><BR><BR><BR>小结 <BR><BR>对那些需要进行复杂数据处理的程序来说，正则表达式无疑是一个非常有用的工具。本文重点在于阐述如何在C语言中利用正则表达式来简化字符串处理，以便在数据处理方面能够获得与Perl语言类似的灵活性。</FONT> <BR><BR><img src ="http://www.cnitblog.com/roguishangel/aggbug/6080.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/roguishangel/" target="_blank">牙刷</a> 2006-01-07 22:10 <a href="http://www.cnitblog.com/roguishangel/articles/6080.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>进程如何分辨谁在kill()自己</title><link>http://www.cnitblog.com/roguishangel/articles/6079.html</link><dc:creator>牙刷</dc:creator><author>牙刷</author><pubDate>Sat, 07 Jan 2006 13:58:00 GMT</pubDate><guid>http://www.cnitblog.com/roguishangel/articles/6079.html</guid><wfw:comment>http://www.cnitblog.com/roguishangel/comments/6079.html</wfw:comment><comments>http://www.cnitblog.com/roguishangel/articles/6079.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/roguishangel/comments/commentRss/6079.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/roguishangel/services/trackbacks/6079.html</trackback:ping><description><![CDATA[<FONT size=2>============================================<BR>24.5 进程如何分辨谁在kill()自己<BR><BR>A: scz &lt;scz@nsfocus.com&gt;; 2003-10-11 19:31<BR><BR>至少对于Linux、FreeBSD、Solaris、AIX这四种操作系统，有一种办法。不要安装传<BR>统sa_handler信号句柄，而是安装sa_sigaction信号句柄。细节请man sigaction并<BR>参照头文件加强理解。下面是一个可移植演示程序。<BR><BR>--------------------------------------------------------------------------<BR>/*<BR>* For x86/Linux RedHat_8 2.4.18-14<BR>* For x86/FreeBSD 4.5-RELEASE<BR>* For SPARC/Solaris 8<BR>* For AIX 4.3.3.0<BR>*<BR>* gcc -Wall -pipe -O3 -s -o siginfo_test siginfo_test.c<BR>*/<BR><BR>/************************************************************************<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; Head File&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>************************************************************************/<BR><BR>#include &lt;stdio.h&gt;;<BR>#include &lt;stdlib.h&gt;;<BR>#include &lt;string.h&gt;;<BR>#include &lt;strings.h&gt;;<BR>#include &lt;signal.h&gt;;<BR>#include &lt;unistd.h&gt;;<BR>#include &lt;setjmp.h&gt;;<BR>#include &lt;sys/time.h&gt;;<BR><BR>/************************************************************************<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; Macro&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>************************************************************************/<BR><BR>/*<BR>* for signal handlers<BR>*/<BR>typedef void Sigfunc ( int, siginfo_t *, void * );<BR><BR>#define PRIVATE_SIG_ERR&nbsp; &nbsp;&nbsp;&nbsp;((Sigfunc *)-1)<BR><BR>/************************************************************************<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; Function Prototype&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>************************************************************************/<BR><BR>static void&nbsp; &nbsp;&nbsp; &nbsp;Atexit&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;( void ( * func ) ( void ) );<BR>static void&nbsp; &nbsp;&nbsp; &nbsp;init_signal&nbsp; &nbsp;( void );<BR>static void&nbsp; &nbsp;&nbsp; &nbsp;init_timer&nbsp; &nbsp; ( unsigned int s );<BR>static void&nbsp; &nbsp;&nbsp; &nbsp;on_alarm&nbsp; &nbsp;&nbsp; &nbsp;( int signo, siginfo_t *si, void *unused );<BR>static void&nbsp; &nbsp;&nbsp; &nbsp;on_segvbus&nbsp; &nbsp; ( int signo, siginfo_t *si, void *unused );<BR>static void&nbsp; &nbsp;&nbsp; &nbsp;on_terminate&nbsp;&nbsp;( int signo, siginfo_t *si, void *unused );<BR>static Sigfunc * PrivateSignal ( int signo, Sigfunc *func );<BR>static int&nbsp; &nbsp;&nbsp; &nbsp; Setitimer&nbsp; &nbsp;&nbsp;&nbsp;( int&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;which,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;struct itimerval *value,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;struct itimerval *ovalue );<BR>static Sigfunc * Signal&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;( int signo, Sigfunc *func );<BR>static void&nbsp; &nbsp;&nbsp; &nbsp;terminate&nbsp; &nbsp;&nbsp;&nbsp;( void );<BR><BR>/************************************************************************<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; Static Global Var&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>************************************************************************/<BR><BR>static sigjmp_buf&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;jmpbuf;<BR>static volatile sig_atomic_t canjump = 0;<BR><BR>/************************************************************************/<BR><BR>static void Atexit ( void ( * func ) ( void ) )<BR>{<BR>&nbsp; &nbsp; if ( atexit( func ) != 0 )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;exit( EXIT_FAILURE );<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; return;<BR>}&nbsp;&nbsp;/* end of Atexit */<BR><BR>/*<BR>* 初始化信号句柄<BR>*/<BR>static void init_signal ( void )<BR>{<BR>&nbsp; &nbsp; unsigned int i;<BR><BR>&nbsp; &nbsp; Atexit( terminate );<BR>&nbsp; &nbsp; for ( i = 1; i &lt; 9; i++ )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;Signal( i, on_terminate );<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; Signal( SIGTERM, on_terminate );<BR>&nbsp; &nbsp; Signal( SIGALRM, on_alarm&nbsp; &nbsp;&nbsp;&nbsp;);<BR>&nbsp; &nbsp; Signal( SIGSEGV, on_segvbus&nbsp; &nbsp;);<BR>&nbsp; &nbsp; Signal( SIGBUS , on_segvbus&nbsp; &nbsp;);<BR>&nbsp; &nbsp; return;<BR>}&nbsp;&nbsp;/* end of init_signal */<BR><BR>/*<BR>* 我们的定时器精度只支持到秒<BR>*/<BR>static void init_timer ( unsigned int s )<BR>{<BR>&nbsp; &nbsp; struct itimerval value;<BR><BR>&nbsp; &nbsp; value.it_value.tv_sec&nbsp; &nbsp;&nbsp;&nbsp;= s;<BR>&nbsp; &nbsp; value.it_value.tv_usec&nbsp; &nbsp; = 0;<BR>&nbsp; &nbsp; /*<BR>&nbsp; &nbsp;&nbsp;&nbsp;* 只生效一次<BR>&nbsp; &nbsp;&nbsp;&nbsp;*/<BR>&nbsp; &nbsp; value.it_interval.tv_sec&nbsp;&nbsp;= 0;<BR>&nbsp; &nbsp; value.it_interval.tv_usec = 0;<BR>&nbsp; &nbsp; Setitimer( ITIMER_REAL, &amp;value, NULL );<BR>&nbsp; &nbsp; return;<BR>}&nbsp;&nbsp;/* end of init_timer */<BR><BR>static void on_alarm ( int signo, siginfo_t *si, void *unused )<BR>{<BR>&nbsp; &nbsp; fprintf<BR>&nbsp; &nbsp; (<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;stderr,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"signo&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"si&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 0x%08X\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"unused&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;= 0x%08X\n",<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;signo,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;( unsigned int )si,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;( unsigned int )unused<BR>&nbsp; &nbsp; );<BR>&nbsp; &nbsp; if ( NULL != si )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;fprintf<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;(<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;stderr,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_signo&nbsp;&nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_errno&nbsp;&nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_code&nbsp; &nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_pid&nbsp; &nbsp; = %u\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_uid&nbsp; &nbsp; = %u\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_status = %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_addr&nbsp; &nbsp;= 0x%08X\n",<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;si-&gt;;si_signo,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;si-&gt;;si_errno,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;si-&gt;;si_code,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )si-&gt;;si_pid,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )si-&gt;;si_uid,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( int&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; )si-&gt;;si_status,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )si-&gt;;si_addr<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;);<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; return;<BR>}&nbsp;&nbsp;/* end of on_alarm */<BR><BR>static void on_segvbus ( int signo, siginfo_t *si, void *unused )<BR>{<BR>&nbsp; &nbsp; fprintf<BR>&nbsp; &nbsp; (<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;stderr,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"signo&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"si&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 0x%08X\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"unused&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;= 0x%08X\n",<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;signo,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;( unsigned int )si,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;( unsigned int )unused<BR>&nbsp; &nbsp; );<BR>&nbsp; &nbsp; if ( NULL != si )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;fprintf<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;(<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;stderr,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_signo&nbsp;&nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_errno&nbsp;&nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_code&nbsp; &nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_pid&nbsp; &nbsp; = %u\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_uid&nbsp; &nbsp; = %u\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_status = %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_addr&nbsp; &nbsp;= 0x%08X\n",<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;si-&gt;;si_signo,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;si-&gt;;si_errno,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;si-&gt;;si_code,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )si-&gt;;si_pid,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )si-&gt;;si_uid,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( int&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; )si-&gt;;si_status,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )si-&gt;;si_addr<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;);<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; if ( 0 == canjump )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;/*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* unexpected signal, ignore<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*/<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return;<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; canjump = 0;<BR>&nbsp; &nbsp; /*<BR>&nbsp; &nbsp;&nbsp;&nbsp;* jump back to main, don't return<BR>&nbsp; &nbsp;&nbsp;&nbsp;*/<BR>&nbsp; &nbsp; siglongjmp( jmpbuf, signo );<BR>}&nbsp;&nbsp;/* end of on_segvbus */<BR><BR>static void on_terminate ( int signo, siginfo_t *si, void *unused )<BR>{<BR>&nbsp; &nbsp; if ( NULL != si )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;/*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* 演示用，不推荐在信号句柄中使用fprintf()<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*/<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;fprintf<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;(<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;stderr,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"signo&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 0x%08X\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"unused&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;= 0x%08X\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_signo&nbsp;&nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_errno&nbsp;&nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_code&nbsp; &nbsp;= %d\n",<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;signo,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )si,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )unused,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;si-&gt;;si_signo,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;si-&gt;;si_errno,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;si-&gt;;si_code<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;);<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;/*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* si_code为SI_USER时意味着"signal sent by another process with kill()"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* 就上四种OS而言，我所测试的FreeBSD反应与其他三种不同，kill进程时<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* si_code始终为0，而FreeBSD有如下定义:<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* #define SI_USER&nbsp;&nbsp;0x10001<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* 如果不判断si_code，强行显示si_pid、si_uid，对于FreeBSD而言总是0。<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* 下面出于方便演示目的，没有判断si_code。正确作法应该判断si_code，<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* 然后显示联合的不同成员。<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*/<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;fprintf<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;(<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;stderr,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_pid&nbsp; &nbsp; = %u\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_uid&nbsp; &nbsp; = %u\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_status = %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_addr&nbsp; &nbsp;= 0x%08X\n",<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )si-&gt;;si_pid,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )si-&gt;;si_uid,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( int&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; )si-&gt;;si_status,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )si-&gt;;si_addr<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;);<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; else<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;fprintf<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;(<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;stderr,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"signo&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 0x%08X\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"unused&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;= 0x%08X\n",<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;signo,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )si,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )unused<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;);<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; /*<BR>&nbsp; &nbsp;&nbsp;&nbsp;* 这次我们使用atexit()函数<BR>&nbsp; &nbsp;&nbsp;&nbsp;*/<BR>&nbsp; &nbsp; exit( EXIT_SUCCESS );<BR>}&nbsp;&nbsp;/* end of on_terminate */<BR><BR>static Sigfunc * PrivateSignal ( int signo, Sigfunc *func )<BR>{<BR>&nbsp; &nbsp; struct sigaction act, oact;<BR><BR>&nbsp; &nbsp; memset( &amp;act, 0, sizeof( act ) );<BR>&nbsp; &nbsp; sigemptyset( &amp;act.sa_mask );<BR>&nbsp; &nbsp; /*<BR>&nbsp; &nbsp;&nbsp;&nbsp;* Invoke signal-catching function with three arguments instead of one.<BR>&nbsp; &nbsp;&nbsp;&nbsp;*/<BR>&nbsp; &nbsp; act.sa_flags&nbsp; &nbsp;&nbsp;&nbsp;= SA_SIGINFO;<BR>&nbsp; &nbsp; act.sa_sigaction = func;<BR>&nbsp; &nbsp; if ( SIGALRM == signo )<BR>&nbsp; &nbsp; {<BR>#ifdef&nbsp;&nbsp;SA_INTERRUPT<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;/*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* SunOS 4.x<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*/<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;act.sa_flags |= SA_INTERRUPT;<BR>#endif<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; else<BR>&nbsp; &nbsp; {<BR>#ifdef&nbsp;&nbsp;SA_RESTART<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;/*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* SVR4, 4.4BSD<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*/<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;act.sa_flags |= SA_RESTART;<BR>#endif<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; if ( sigaction( signo, &amp;act, &amp;oact ) &lt; 0 )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return( PRIVATE_SIG_ERR );<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; return( oact.sa_sigaction );<BR>}&nbsp;&nbsp;/* end of PrivateSignal */<BR><BR>static int Setitimer ( int which, struct itimerval *value, struct itimerval *ovalue )<BR>{<BR>&nbsp; &nbsp; int ret;<BR><BR>&nbsp; &nbsp; if ( ( ret = setitimer( which, value, ovalue ) ) &lt; 0 )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;perror( "setitimer error" );<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;exit( EXIT_FAILURE );<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; return( ret );<BR>}&nbsp;&nbsp;/* end of Setitimer */<BR><BR>static Sigfunc * Signal ( int signo, Sigfunc *func )<BR>{<BR>&nbsp; &nbsp; Sigfunc *sigfunc;<BR><BR>&nbsp; &nbsp; if ( PRIVATE_SIG_ERR == ( sigfunc = PrivateSignal( signo, func ) ) )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;perror( "signal error" );<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;exit( EXIT_FAILURE );<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; return( sigfunc );<BR>}&nbsp;&nbsp;/* end of Signal */<BR><BR>static void terminate ( void )<BR>{<BR>&nbsp; &nbsp; /*<BR>&nbsp; &nbsp;&nbsp;&nbsp;* _exit( EXIT_SUCCESS );<BR>&nbsp; &nbsp;&nbsp;&nbsp;*/<BR>&nbsp; &nbsp; return;<BR>}&nbsp;&nbsp;/* end of terminate */<BR><BR>int main ( int argc, char * argv[] )<BR>{<BR>&nbsp; &nbsp; /*<BR>&nbsp; &nbsp;&nbsp;&nbsp;* for autovar, must be volatile<BR>&nbsp; &nbsp;&nbsp;&nbsp;*/<BR>&nbsp; &nbsp; volatile unsigned char *p;<BR><BR>&nbsp; &nbsp; init_signal();<BR>&nbsp; &nbsp; p&nbsp; &nbsp;&nbsp; &nbsp; = ( unsigned char * )&amp;<BR>&nbsp; &nbsp; if ( 0 != sigsetjmp( jmpbuf, 1 ) )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;printf<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;(<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"p&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; = 0x%08X\n",<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )p<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;);<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;goto main_continue;<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; /*<BR>&nbsp; &nbsp;&nbsp;&nbsp;* now sigsetjump() is OK<BR>&nbsp; &nbsp;&nbsp;&nbsp;*/<BR>&nbsp; &nbsp; canjump = 1;<BR>&nbsp; &nbsp; while ( 1 )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;/*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* 诱发SIGSEGV、SIGBUS<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*/<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;*p = *p;<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;p++;<BR>&nbsp; &nbsp; }<BR><BR>main_continue:<BR><BR>&nbsp; &nbsp; /*<BR>&nbsp; &nbsp;&nbsp;&nbsp;* 启动定时器<BR>&nbsp; &nbsp;&nbsp;&nbsp;*/<BR>&nbsp; &nbsp; init_timer( 1 );<BR>&nbsp; &nbsp; while ( 1 )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;/*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* 形成阻塞，降低CPU占用率<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*/<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;getchar();<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; return( EXIT_SUCCESS );<BR>}&nbsp;&nbsp;/* end of main */<BR><BR>/************************************************************************/<BR><BR>--------------------------------------------------------------------------<BR><BR>这种技术是操作系统实现相关的。FreeBSD的si_code与头文件不相符。除了FreeBSD，<BR>其他三种OS的si_addr如愿反映了栈底地址、si_pid/si_uid也能正确反映kill()信号<BR>源。而Solaris会出现si为NULL的情形。下面是Linux上执行示例:<BR><BR>[scz@ /home/scz/src]&gt;; ./siginfo_test<BR><BR>signo&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 11<BR>si&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 0xBFFFF6B0<BR>unused&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;= 0xBFFFF730<BR>si-&gt;;si_signo&nbsp;&nbsp;= 11<BR>si-&gt;;si_errno&nbsp;&nbsp;= 0<BR>si-&gt;;si_code&nbsp; &nbsp;= 1<BR>si-&gt;;si_pid&nbsp; &nbsp; = 3221225472<BR>si-&gt;;si_uid&nbsp; &nbsp; = 1869479936<BR>si-&gt;;si_status = 2712942<BR>si-&gt;;si_addr&nbsp; &nbsp;= 0xC0000000&nbsp;&nbsp;&lt;= 栈底地址<BR>p&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; = 0xC0000000<BR><BR>signo&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 14<BR>si&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 0xBFFFF5A8<BR>unused&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;= 0xBFFFF628<BR>si-&gt;;si_signo&nbsp;&nbsp;= 14<BR>si-&gt;;si_errno&nbsp;&nbsp;= 0<BR>si-&gt;;si_code&nbsp; &nbsp;= 128&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&lt;= SI_KERNEL 0x80 Send by kernel.<BR>si-&gt;;si_pid&nbsp; &nbsp; = 0<BR>si-&gt;;si_uid&nbsp; &nbsp; = 0<BR>si-&gt;;si_status = 896820224<BR>si-&gt;;si_addr&nbsp; &nbsp;= 0x00000000<BR>^Z<BR>[scz@ /home/scz/src]&gt;; bg %1<BR>[scz@ /home/scz/src]&gt;; kill %1<BR><BR>signo&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 15<BR>si&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 0xBFFFF5A8<BR>unused&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;= 0xBFFFF628<BR>si-&gt;;si_signo&nbsp;&nbsp;= 15<BR>si-&gt;;si_errno&nbsp;&nbsp;= 0<BR>si-&gt;;si_code&nbsp; &nbsp;= 0&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; &lt;= SI_USER 0x00 Sent by kill, sigsend, raise.<BR>si-&gt;;si_pid&nbsp; &nbsp; = 27712&nbsp; &nbsp;&nbsp; &nbsp;&lt;= kill()信号源<BR>si-&gt;;si_uid&nbsp; &nbsp; = 1000&nbsp; &nbsp;&nbsp; &nbsp; &lt;= kill()信号源<BR>si-&gt;;si_status = 896820224<BR>si-&gt;;si_addr&nbsp; &nbsp;= 0x00006C40<BR>[scz@ /home/scz/src]&gt;; echo $$<BR>27712<BR>[scz@ /home/scz/src]&gt;; id<BR>uid=1000(scz) gid=0(root) groups=0(root)<BR>[scz@ /home/scz/src]&gt;;<BR><BR>最后结论，对于x86/FreeBSD 4.5-RELEASE，无法利用该技术分辨kill()信号源。其<BR>他三种操作系统可以利用该技术。<BR><BR>一个有趣的想法，进程分辨出kill()信号源，反向kill信号源。<BR><BR>D: law@bbs.apue.net 2003-10-13 10:03<BR><BR>修改一下gstack.c，考虑栈向内存高址方向增长的情形。用递归方式确定堆栈增长方<BR>向比较可靠，否则由于不同函数的不同优化可能导致误判，测试中碰上这种情形了。<BR><BR>--------------------------------------------------------------------------<BR>/*<BR>* For x86/Linux RedHat_8 2.4.18-14<BR>* For x86/FreeBSD 4.5-RELEASE<BR>* For SPARC/Solaris 8<BR>* For AIX 4.3.3.0<BR>*<BR>* gcc -Wall -pipe -O3 -s -o gstack gstack.c<BR>*/<BR><BR>/************************************************************************<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; Head File&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>************************************************************************/<BR><BR>#include &lt;stdio.h&gt;;<BR>#include &lt;stdlib.h&gt;;<BR>#include &lt;string.h&gt;;<BR>#include &lt;strings.h&gt;;<BR>#include &lt;signal.h&gt;;<BR>#include &lt;unistd.h&gt;;<BR>#include &lt;setjmp.h&gt;;<BR><BR>/************************************************************************<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; Macro&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>************************************************************************/<BR><BR>/*<BR>* for signal handlers<BR>*/<BR>typedef void Sigfunc ( int, siginfo_t *, void * );<BR><BR>#define PRIVATE_SIG_ERR&nbsp; &nbsp;&nbsp;&nbsp;((Sigfunc *)-1)<BR>/*<BR>* 向上指向高址方向增长，向下指向低址方向增长，后者最常见<BR>*/<BR>#define STACKUP&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; 0<BR>#define STACKDOWN&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;1<BR><BR>/************************************************************************<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; Function Prototype&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>************************************************************************/<BR><BR>static unsigned char * get_stack_bottom ( void );<BR>static void&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;on_segvbus&nbsp; &nbsp;&nbsp; &nbsp; ( int&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;signo,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;siginfo_t *si,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;void&nbsp; &nbsp;&nbsp; &nbsp;*unused );<BR>static Sigfunc *&nbsp; &nbsp;&nbsp; &nbsp; PrivateSignal&nbsp; &nbsp; ( int signo, Sigfunc *func );<BR>static Sigfunc *&nbsp; &nbsp;&nbsp; &nbsp; Signal&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;( int signo, Sigfunc *func );<BR>static unsigned int&nbsp; &nbsp; stack_grow&nbsp; &nbsp;&nbsp; &nbsp; ( unsigned int&nbsp;&nbsp;level,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;unsigned int *addr );<BR><BR>/************************************************************************<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; Static Global Var&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>*&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; *<BR>************************************************************************/<BR><BR>/*<BR>* start of .text<BR>*/<BR>extern int&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;_etext;<BR>/*<BR>* start of .data<BR>*/<BR>extern int&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;_edata;<BR>/*<BR>* start of heap<BR>*/<BR>extern int&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;_end;<BR><BR>static sigjmp_buf&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; jmpbuf;<BR>static volatile sig_atomic_t&nbsp;&nbsp;canjump&nbsp; &nbsp;= 0;<BR>static Sigfunc&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*orig_segv = PRIVATE_SIG_ERR;<BR>static Sigfunc&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*orig_bus&nbsp;&nbsp;= PRIVATE_SIG_ERR;<BR><BR>/************************************************************************/<BR><BR>static unsigned char * get_stack_bottom ( void )<BR>{<BR>&nbsp; &nbsp; /*<BR>&nbsp; &nbsp;&nbsp;&nbsp;* for autovar, must be volatile<BR>&nbsp; &nbsp;&nbsp;&nbsp;*/<BR>&nbsp; &nbsp; volatile unsigned char *p = NULL;<BR><BR>&nbsp; &nbsp; orig_segv = Signal( SIGSEGV, on_segvbus&nbsp; &nbsp;);<BR>&nbsp; &nbsp; orig_bus&nbsp;&nbsp;= Signal( SIGBUS , on_segvbus&nbsp; &nbsp;);<BR>&nbsp; &nbsp; p&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= ( unsigned char * )&amp;<BR>&nbsp; &nbsp; if ( 0 != sigsetjmp( jmpbuf, 1 ) )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;Signal( SIGSEGV, orig_segv );<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;Signal( SIGBUS , orig_bus&nbsp;&nbsp;);<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;goto get_stack_bottom_exit;<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; /*<BR>&nbsp; &nbsp;&nbsp;&nbsp;* now sigsetjump() is OK<BR>&nbsp; &nbsp;&nbsp;&nbsp;*/<BR>&nbsp; &nbsp; canjump = 1;<BR>&nbsp; &nbsp; if ( STACKUP == stack_grow( 0, NULL ) )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;while ( 1 )<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;{<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;/*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; * 诱发SIGSEGV、SIGBUS<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; */<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*p = *p;<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;p--;<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; else<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;while ( 1 )<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;{<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;/*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; * 诱发SIGSEGV、SIGBUS<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; */<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*p = *p;<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;p++;<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<BR>&nbsp; &nbsp; }<BR><BR>get_stack_bottom_exit:<BR><BR>&nbsp; &nbsp; return( ( unsigned char * )p );<BR>}&nbsp;&nbsp;/* end of get_stack_bottom */<BR><BR>static void on_segvbus ( int signo, siginfo_t *si, void *unused )<BR>{<BR>&nbsp; &nbsp; fprintf<BR>&nbsp; &nbsp; (<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;stderr,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"signo&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"si&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 0x%08X\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"unused&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;= 0x%08X\n",<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;signo,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;( unsigned int )si,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;( unsigned int )unused<BR>&nbsp; &nbsp; );<BR>&nbsp; &nbsp; if ( NULL != si )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;fprintf<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;(<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;stderr,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_signo&nbsp;&nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_errno&nbsp;&nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_code&nbsp; &nbsp;= %d\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"si-&gt;;si_addr&nbsp; &nbsp;= 0x%08X\n",<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;si-&gt;;si_signo,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;si-&gt;;si_errno,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;si-&gt;;si_code,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )si-&gt;;si_addr<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;);<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; if ( 0 == canjump )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;/*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* unexpected signal, ignore<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*/<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return;<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; canjump = 0;<BR>&nbsp; &nbsp; /*<BR>&nbsp; &nbsp;&nbsp;&nbsp;* jump back to get_stack_bottom, don't return<BR>&nbsp; &nbsp;&nbsp;&nbsp;*/<BR>&nbsp; &nbsp; siglongjmp( jmpbuf, signo );<BR>}&nbsp;&nbsp;/* end of on_segvbus */<BR><BR>static Sigfunc * PrivateSignal ( int signo, Sigfunc *func )<BR>{<BR>&nbsp; &nbsp; struct sigaction act, oact;<BR><BR>&nbsp; &nbsp; memset( &amp;act, 0, sizeof( act ) );<BR>&nbsp; &nbsp; sigemptyset( &amp;act.sa_mask );<BR>&nbsp; &nbsp; /*<BR>&nbsp; &nbsp;&nbsp;&nbsp;* Invoke signal-catching function with three arguments instead of one.<BR>&nbsp; &nbsp;&nbsp;&nbsp;*/<BR>&nbsp; &nbsp; act.sa_flags&nbsp; &nbsp;&nbsp;&nbsp;= SA_SIGINFO;<BR>&nbsp; &nbsp; act.sa_sigaction = func;<BR>&nbsp; &nbsp; if ( SIGALRM == signo )<BR>&nbsp; &nbsp; {<BR>#ifdef&nbsp;&nbsp;SA_INTERRUPT<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;/*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* SunOS 4.x<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*/<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;act.sa_flags |= SA_INTERRUPT;<BR>#endif<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; else<BR>&nbsp; &nbsp; {<BR>#ifdef&nbsp;&nbsp;SA_RESTART<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;/*<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;* SVR4, 4.4BSD<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*/<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;act.sa_flags |= SA_RESTART;<BR>#endif<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; if ( sigaction( signo, &amp;act, &amp;oact ) &lt; 0 )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return( PRIVATE_SIG_ERR );<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; return( oact.sa_sigaction );<BR>}&nbsp;&nbsp;/* end of PrivateSignal */<BR><BR>static Sigfunc * Signal ( int signo, Sigfunc *func )<BR>{<BR>&nbsp; &nbsp; Sigfunc *sigfunc;<BR><BR>&nbsp; &nbsp; if ( PRIVATE_SIG_ERR == ( sigfunc = PrivateSignal( signo, func ) ) )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;perror( "signal error" );<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;exit( EXIT_FAILURE );<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; return( sigfunc );<BR>}&nbsp;&nbsp;/* end of Signal */<BR><BR>static unsigned int stack_grow ( unsigned int level, unsigned int *addr )<BR>{<BR>&nbsp; &nbsp; unsigned int dummy;<BR>&nbsp; &nbsp; unsigned int ret;<BR><BR>&nbsp; &nbsp; if ( 0 == level )<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;ret = stack_grow( level + 1, &amp;dummy );<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; else<BR>&nbsp; &nbsp; {<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;if ( ( unsigned int )addr &gt;; ( unsigned int )&amp;dummy )<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;{<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;ret = STACKDOWN;<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;else<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;{<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;ret = STACKUP;<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;printf<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;(<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"stack_level_0 = 0x%08X\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"stack_level_1 = 0x%08X\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;"stack grow&nbsp; &nbsp; = %s/%u\n",<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )addr,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( unsigned int )&amp;dummy,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;( STACKUP == ret ) ? "UP/HIGH" : "DOWN/LOW",<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;ret<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;);<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; return( ret );<BR>}&nbsp;&nbsp;/* end of stack_grow */<BR><BR>int main ( int argc, char * argv[] )<BR>{<BR>&nbsp; &nbsp; unsigned char *p;<BR><BR>&nbsp; &nbsp; p = get_stack_bottom();<BR>&nbsp; &nbsp; printf<BR>&nbsp; &nbsp; (<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"_etext&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;= 0x%08X\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"_edata&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;= 0x%08X\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"_end&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; = 0x%08X\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"stack bottom&nbsp;&nbsp;= 0x%08X\n"<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;"&amp;p&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 0x%08X\n",<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;( unsigned int )&amp;_etext,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;( unsigned int )&amp;_edata,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;( unsigned int )&amp;_end,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;( unsigned int )p,<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;( unsigned int )&amp;p<BR>&nbsp; &nbsp; );<BR>&nbsp; &nbsp; return( EXIT_SUCCESS );<BR>}&nbsp;&nbsp;/* end of main */<BR><BR>/************************************************************************/<BR><BR>--------------------------------------------------------------------------<BR><BR>这是在AIX 4.3.3.0上的执行效果:<BR><BR>&gt;; ./gstack<BR>stack_level_0 = 0x2FF22B40<BR>stack_level_1 = 0x2FF22AF8<BR>stack grow&nbsp; &nbsp; = DOWN/LOW/1&nbsp;&nbsp;&lt;= 栈向低址方向增长<BR>signo&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 11<BR>si&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 0x2FF22A10<BR>unused&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;= 0x2FF22780<BR>si-&gt;;si_signo&nbsp;&nbsp;= 11<BR>si-&gt;;si_errno&nbsp;&nbsp;= 0<BR>si-&gt;;si_code&nbsp; &nbsp;= 51<BR>si-&gt;;si_addr&nbsp; &nbsp;= 0x2FF23000&nbsp;&nbsp;&lt;= 栈底地址<BR>_etext&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;= 0x10000E40<BR>_edata&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;= 0x20000ED4<BR>_end&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; = 0x200010C0<BR>stack bottom&nbsp;&nbsp;= 0x2FF23000&nbsp;&nbsp;&lt;= 栈底地址<BR>&amp;p&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;= 0x2FF22BC8</FONT> <BR><img src ="http://www.cnitblog.com/roguishangel/aggbug/6079.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/roguishangel/" target="_blank">牙刷</a> 2006-01-07 21:58 <a href="http://www.cnitblog.com/roguishangel/articles/6079.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>错误处理和异常处理，你用哪一个</title><link>http://www.cnitblog.com/roguishangel/articles/6078.html</link><dc:creator>牙刷</dc:creator><author>牙刷</author><pubDate>Sat, 07 Jan 2006 13:13:00 GMT</pubDate><guid>http://www.cnitblog.com/roguishangel/articles/6078.html</guid><wfw:comment>http://www.cnitblog.com/roguishangel/comments/6078.html</wfw:comment><comments>http://www.cnitblog.com/roguishangel/articles/6078.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/roguishangel/comments/commentRss/6078.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/roguishangel/services/trackbacks/6078.html</trackback:ping><description><![CDATA[<A href="http://bbs.chinaunix.net/viewthread.php?tid=142587&amp;extra=page%3D4%26filter%3Ddigest">http://bbs.chinaunix.net/viewthread.php?tid=142587&amp;extra=page%3D4%26filter%3Ddigest</A><BR><BR><SPAN style="FONT-SIZE: 13px">张笑猛<BR>2003年1月<BR>1.简介<BR>&nbsp; &nbsp; 异常是由语言提供的运行时刻错误处理的一种方式。提到错误处理，即使不提到异常，你大概也已经有了丰富的经验，但是为了可以清楚的看到异常的好处，我们还是不妨来回顾一下常用的以及不常用的错误处理方式。<BR><BR>1.1 常用的错误处理方式<BR>返回值。我们常用函数的返回值来标志成功或者失败，甚至是失败的原因。但是这种做法最大的问题是如果调用者不主动检查返回值也是可以被编译器接受的，你也奈何不了他<IMG src="http://bbs.chinaunix.net/images/smilies/icon_smile.gif" align=absMiddle border=0> 这在C++中还导致另外一个问题，就是重载函数不能只有不同的返回值，而有相同的参数表，因为如果调用者不检查返回值，则编译器会不知道应该调用哪个重载函数。当然这个问题与本文无关，我们暂且放下。只要谨记返回值可能被忽略的情况即可。<BR><BR>全局状态标志。例如系统调用使用的errno。返回值不同的是，全局状态标志可以让函数的接口（返回值、参数表）被充分利用。函数在退出前应该设置这个全局变量的值为成功或者失败（包括原因），而与返回值一样，它隐含的要求调用者要在调用后检查这个标志，这种约束实在是同样软弱。全局变量还导致了另外一个问题，就是多线程不安全：如果多个线程同时为一个全局变量赋值，则调用者在检查这个标志的时候一定会非常迷惑。如果希望线程安全，可以参照errno的解决办法，它是线程安全的。<BR><BR>1.2 不常用的处理方式<BR>setjmp()/longjmp()。可以认为它们是远程的goto语句。根据我的经验，它们好象确实不常被用到，也许是多少破坏了结构化编程风格的原因吧。在C++中，应该是更加的不要用它们，因为致命的弱点是longjmp()虽然会unwinding stack（这个词后面再说），但是不会调用栈中对象的析构函数--够致命吧。对于不同的编译器，可能可以通过加某个编译开关来解决这个问题，但太不通用了，会导致程序很难移植。<BR><BR>1.3 异常<BR>现在我们再来看看异常能解决什么问题。对于返回值和errno遇到的尴尬，对异常来说基本上不存在，如果你不捕获(catch)程序中抛出的异常，默认行为是导致abort()被调用，程序被终止(core dump)。因此你的函数如果抛出了异常，这个函数的调用者或者调用者的调用者，也就是在当前的call stack上，一定要有一个地方捕获这个异常。而对于setjmp()/longjmp()带来的栈上对象不被析构的问题对异常来说也是不存在的。那么它是否破坏了结构化（对于OO paradigms，也许应该说是破坏了流程？）呢？显然不是，有了异常之后你可以放心的只书写正确的逻辑，而将所有的错误处理归结到一个地方，这不是更好么？<BR><BR>综上所述，在C++中大概异常可以全面替代其它的错误处理方式了，可是如果代码中到处充斥着try/throw/catch也不是件好事，欲知异常的使用技巧，请保持耐心继续阅读<IMG src="http://bbs.chinaunix.net/images/smilies/icon_smile.gif" align=absMiddle border=0></SPAN> <BR><BR><SPAN style="FONT-SIZE: 13px">2. 异常的语法<BR>在这里我们只讨论一些语法相关的问题。<BR><BR>2.1 try<BR>try总是与catch一同出现，伴随一个try语句，至少应该有一个catch()语句。try随后的block是可能抛出异常的地方。<BR><BR>2.2 catch<BR>catch带有一个参数，参数类型以及参数名字都由程序指定，名字可以忽略，如果在catch随后的block中并不打算引用这个异常对象的话。参数类型可以是build-in type，例如int, long, char等，也可以是一个对象，一个对象指针或者引用。如果希望捕获任意类型的异常，可以使用“...”作为catch的参数。<BR><BR>catch不一定要全部捕获try block中抛出的异常，剩下没有捕获的可以交给上一级函数处理。<BR><BR>2.3 throw<BR>throw后面带一个类型的实例，它和catch的关系就象是函数调用，catch指定形参，throw给出实参。编译器按照catch出现的顺序以及catch指定的参数类型确定一个异常应该由哪个catch来处理。<BR><BR>throw不一定非要出现在try随后的block中，它可以出现在任何需要的地方，只要最终有catch可以捕获它即可。即使在catch随后的block中，仍然可以继续throw。这时候有两种情况，一是throw一个新类型的异常，这与普通的throw一样。二是要rethrow当前这个异常，在这种情况下，throw不带参数即可表达。例如：<BR><BR>try{<BR>&nbsp; &nbsp; ...<BR>}<BR>catch(int){<BR>&nbsp; &nbsp; throw MyException("hello exception"<IMG src="http://bbs.chinaunix.net/images/smilies/icon_wink.gif" align=absMiddle border=0>;&nbsp; &nbsp; // 抛出一个新的异常<BR>}<BR>catch(float){<BR>&nbsp; &nbsp; throw;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; // 重新抛出当前的浮点数异常<BR>}<BR><BR>2.4 函数声明<BR>还有一个地方与throw关键字有关，就是函数声明。例如：<BR><BR>void foo() throw (int);&nbsp; &nbsp;&nbsp; &nbsp;// 只能抛出int型异常<BR>void bar() throw ();&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;// 不抛出任何异常<BR>void baz();&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;// 可以抛出任意类型的异常或者不抛出异常<BR><BR>如果一个函数的声明中带有throw限定符，则在函数体中也必须同样出现：<BR><BR>void foo() throw (int)<BR>{<BR>&nbsp; &nbsp; ...<BR>}<BR><BR>这里有一个问题，非常隐蔽，就是即使你象上面一样编写了foo()函数，指定它只能抛出int异常，而实际上它还是可能抛出其他类型的异常而不被编译器发现：<BR><BR>void foo() throw (int)<BR>{<BR>&nbsp; &nbsp; throw float;&nbsp; &nbsp;&nbsp;&nbsp;// 错误！异常类型错误！会被编译器指出<BR>&nbsp; &nbsp; ...<BR>&nbsp; &nbsp; baz();&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;// 正确！baz()可能抛出非int异常而编译器又不能发现！<BR>}<BR><BR>void baz()<BR>{<BR>&nbsp; &nbsp; throw float;<BR>}<BR><BR>这种情况的直接后果就是如果baz()抛出了异常，而调用foo()的代码又严格遵守foo()的声明来编写，那么程序将abort()。这曾经让我很恼火，认为这种机制形同虚设，但是还是有些解决的办法，请参照“使用技巧”中相关的问题。</SPAN> <BR><BR><SPAN style="FONT-SIZE: 13px">3. 异常使用技巧<BR><BR>3.1 异常是如何工作的<BR>为了可以有把握的使用异常，我们先来看看异常处理是如何工作的。<BR><BR>3.1.1 unwinding stack<BR>我们知道，每次函数调用发生的时候，都会执行保护现场寄存器、参数压栈、为被调用的函数创建堆栈这几个对堆栈的操作，它们都使堆栈增长。每次函数返回则是恢复现场，使堆栈减小。我们把函数返回过程中恢复现场的过程称为unwinding stack。<BR><BR>异常处理中的throw语句产生的效果与函数返回相同，它也引发unwinding stack。如果catch不是在throw的直接上层函数中，那么这个unwinding的过程会一直持续，直到找到合适的catch。如果没有合适的catch，则最后std::unexpected()函数被调用，说明发现了一个没想到的异常，这个函数会调用std::terminate()，这个terminate()调用abort()，程序终止(core dump)。<BR><BR>在“简介”中提到的longjmp()也同样会unwinding stack，但是这是一个C函数，它就象free()不会调用对象的析构函数一样，它也不知道在unwinding stack的过程中调用栈上对象的析构函数。这是它与异常的主要区别。<BR><BR>3.1.2 RTTI<BR>在unwinding stack的过程中，程序会一直试图找到一个“合适”的catch来处理这个异常。前面我们提到throw和catch的关系很象是函数调用和函数原型的关系，多个catch就好象一个函数被重载为可以接受不同的类型。根据这样的猜测，好象找到合适的catch来处理异常与函数重载的过程中找到合适的函数原型是一样的，没有什么大不了的。但实际情况却很困难，因为重载的调用在编译时刻就可以确定，而异常的抛出却不能，考虑下面的代码：<BR><BR>void foo() throw (int)<BR>{<BR>&nbsp; &nbsp; throw int;<BR>}<BR><BR>void bar()<BR>{<BR>&nbsp; &nbsp; try{<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;foo();<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; catch(int){<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;...<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; catch(float){<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;...<BR>&nbsp; &nbsp; }<BR>}<BR><BR>void baz()<BR>{<BR>&nbsp; &nbsp; try{<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;foo();<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; catch(int){<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;...<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; catch(float){<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;...<BR>&nbsp; &nbsp; }<BR>}<BR><BR>foo()在两个地方被调用，这两次异常被不同的catch捕获，所以在为throw产生代码的时候，无法明确的指出要由哪个catch捕获，也就是说，无法在编译时刻确定。<BR><BR>仍然考虑这个例子，让我们来看看既然不能在编译时刻确定throw的去向，那么在运行时刻如何确定。在bar()中，一列catch就象switch语句中的case一样排列，实际上是一系列的判断过程，依次检查当前异常的类型是否满足catch指定的类型，这种动态的，在运行时刻确定类型的技术就是RTTI(Runtime Type Identification/Information)。深度探索C++对象模型[1]中提到，RTTI就是异常处理的副产品。关于RTTI又是一个话题，在这里就不详细讨论了。<BR><BR>3.2 是否继承std::exception？<BR>是的。而且std::exception已经有了一些派生类，如果需要可以直接使用它们，不需要再重复定义了。<BR><BR>3.3 每个函数后面都要写throw()?<BR>尽管前面已经分析了这样做也有漏洞，但是它仍然是一个好习惯，可以让调用者从头文件得到非常明确的信息，而不用翻那些可能与代码不同步的文档。如果你提供一个库，那么在库的入口函数中应该使用catch(...)来捕获所有异常，在catch(...)中捕获的异常应该被转换（rethrow）为throw列表中的某一个异常，这样就可以保证不会产生意外的异常。<BR><BR>3.4 guard模式<BR>异常处理在unwinding stack的时候，会析构所有栈上的对象，但是却不会自动删除堆上的对象，甚至你的代码中虽然写了delete语句，但是却被throw跳过，导致内存泄露，或者其它资源的泄露。例如：<BR><BR>void foo()<BR>{<BR>&nbsp; &nbsp; ...<BR>&nbsp; &nbsp; MyClass * p = new MyClass();<BR>&nbsp; &nbsp; bar(p);<BR>&nbsp; &nbsp; ...<BR>&nbsp; &nbsp; delete p;&nbsp; &nbsp;&nbsp; &nbsp; // 如果bar()中抛出异常，则不会运行到这里！<BR>}<BR><BR>void bar(MyClass * p)<BR>{<BR>&nbsp; &nbsp; throw MyException();<BR>}<BR><BR>对于这种情况，C++提供了std::auto_ptr这个模板来解决问题。这个常被称为“智能指针”的模板原理就是，将原来代码中的指针用一个栈上的模板实例保护起来，当发生异常unwinding stack的时候，这个模板实例会被析构，而在它的析构函数中，指针将被delete，例如：<BR><BR>void foo()<BR>{<BR>&nbsp; &nbsp; ...<BR>&nbsp; &nbsp; std::auto_ptr&lt;MyClass&gt;; p(new MyClass());<BR>&nbsp; &nbsp; bar(p.get());<BR>&nbsp; &nbsp; ...<BR>&nbsp; &nbsp; // delete p;&nbsp; &nbsp;&nbsp; &nbsp; // 这句不再需要了<BR>}<BR><BR>void bar(MyClass * p)<BR>{<BR>&nbsp; &nbsp; throw MyException();<BR>}<BR><BR>不论bar()是否抛出异常，只要p被析构，内存就会被释放。<BR><BR>不光对于内存，对于其他资源的管理也可以参照这个方法来完成。在ACE[2]中，这种方式被称为Guard，用来对锁进行保护。<BR><BR>3.5 构造函数和析构函数<BR>构造函数没有返回值，很多地方都推荐通过抛出异常来通知调用者构造失败。这是肯定是个好的办法，但是也不很完美。主要是因为在构造函数中抛出异常并不会引发析构函数的调用，例如：<BR><BR>class foo<BR>{<BR>public:<BR>&nbsp; &nbsp; ~foo() {} // 这个函数将被调用<BR>};<BR><BR>class bar<BR>{<BR>public:<BR>&nbsp; &nbsp; bar() { c_ = new char[10]; throw -1;}<BR>&nbsp; &nbsp; ~bar() { delete c_;}&nbsp;&nbsp;// 这个函数不会被调用！<BR>private:<BR>&nbsp; &nbsp; char * c_;<BR>&nbsp; &nbsp; foo f_;<BR>};<BR><BR>void baz()<BR>{<BR>&nbsp; &nbsp; try{<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;bar b;<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; catch(int){<BR>&nbsp; &nbsp; }<BR>}<BR><BR>在这个例子中，bar的析构函数不会被调用，但是尽管如此，foo的析构函数还是可以被调用。危险的是在构造函数中分配空间的c_，因为析构函数没有被调用而变成了leak。最好的解决办法还是auto_ptr，使用auto_ptr后，bar类的声明变成：<BR><BR>class bar<BR>{<BR>public:<BR>&nbsp; &nbsp; bar() { c_.reset(new char[10]); throw -1;}<BR>&nbsp; &nbsp; ~bar() { }&nbsp;&nbsp;// 不需要再delete c_了！<BR>private:<BR>&nbsp; &nbsp; auto_ptr&lt;char&gt;; c_;<BR>&nbsp; &nbsp; foo f_;<BR>};<BR><BR>析构函数中则不要抛出异常，这一点在Thinking In C++ Volume 2[3]中有明确表述。如果析构函数中调用了可能抛出异常的函数，则应该在析构函数内部catch它。<BR><BR>3.6 什么时候使用异常<BR>到现在为止，我们已经讨论完了异常的大部分问题，可以实际操作操作了。实际应用中遇到的最让我头疼的问题就是什么时候应该使用异常，是否应该用异常全面代替“简介”中提到的其它错误处理方式呢？<BR><BR>首先，不能用异常完全代替返回值，因为返回值的含义不一定只是成功或失败，有时候是一个可选择的状态，例如：<BR><BR>if(customer-&gt;;status() == active){<BR>&nbsp; &nbsp; ...<BR>}<BR>else{<BR>&nbsp; &nbsp; ...<BR>}<BR><BR>在这种情况下，不论返回值是什么，都是程序可以接受的正常的结果。而异常只能用来表达“异常”-- 也就是错误的状态。这好象是显而易见的事情，但是实际编程的过程中有很多更加模棱两可的时候，遇到这样的情况，首先要考虑的就是这个原则。<BR><BR>第二，看看在特定的情况下异常是否会发挥它的优点，而这个优点正好又不能使用其他技术达到（或者简单的达到）。比如，如果你正在为电信公司写一个复杂计费逻辑，那么你当然希望在整个计算费用的过程中集中精力去考虑业务逻辑方面的问题，而不是到处需要根据当前返回值判断是否释放前面步骤中申请的资源。这时候使用异常可以让你的代码非常清晰，即使你有100处申请资源的地方，只要一个地方集中释放他们就好了。例如：<BR><BR>bool bar1();<BR>bool bar2();<BR>bool bar3();<BR><BR>bool foo()<BR>{<BR>&nbsp; &nbsp; ...<BR>&nbsp; &nbsp; char * p1 = new char[10];<BR>&nbsp; &nbsp; ...<BR>&nbsp; &nbsp; if(!bar1()){<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;delete p1;<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return false;<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; ...<BR>&nbsp; &nbsp; char * p2 = new char[10];<BR>&nbsp; &nbsp; ...<BR>&nbsp; &nbsp; if(!bar2()){<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;delete p1;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; // 要释放前面申请的所有资源<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;delete p2;<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return false;<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; ...<BR>&nbsp; &nbsp; char * p3 = new char[10];<BR>&nbsp; &nbsp; ...<BR>&nbsp; &nbsp; if(!bar2()){<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;delete p1;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; // 要释放前面申请的所有资源<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;delete p2;<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;delete p3;<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return false;<BR>&nbsp; &nbsp; }<BR>}<BR><BR>这种流程显然不如：<BR><BR>void bar1() throw(int);<BR>void bar2() throw(int);<BR>void bar3() throw(int);<BR><BR>void foo() throw (int)<BR>{<BR>&nbsp; &nbsp; char * p1 = NULL;<BR>&nbsp; &nbsp; char * p2 = NULL;<BR>&nbsp; &nbsp; char * p3 = NULL;<BR>&nbsp; &nbsp; try{<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;char * p1 = new char[10];<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;bar1();<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;char * p2 = new char[10];<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;bar2();<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;char * p3 = new char[10];<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;bar3();<BR>&nbsp; &nbsp; }<BR>&nbsp; &nbsp; catch(int){<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;delete p1;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;// 集中释放资源<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;delete p2;<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;delete p3;<BR>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;throw;<BR>&nbsp; &nbsp; }<BR>}<BR><BR>第三，在Thinking In C++ Volume 2[3]中列了一个什么时候不应该用，什么时候应该用的表，大家可以参考一下。<BR><BR>最后，说一个与异常无关的东西，但也跟程序错误有关的，就是断言(assert)，我在开发中使用了异常后，很快发现有的人将应该使用assert处理的错误定义成了异常。这里稍微提醒一下assert的用法，非常简单的原则：只有对于那些可以通过改进程序纠正的错误，才可以用assert。返回值、异常显然与其不在一个层面上，这是C的入门知识。</SPAN> <BR><img src ="http://www.cnitblog.com/roguishangel/aggbug/6078.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/roguishangel/" target="_blank">牙刷</a> 2006-01-07 21:13 <a href="http://www.cnitblog.com/roguishangel/articles/6078.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用C语言进行CGI程序设计</title><link>http://www.cnitblog.com/roguishangel/articles/6076.html</link><dc:creator>牙刷</dc:creator><author>牙刷</author><pubDate>Sat, 07 Jan 2006 12:46:00 GMT</pubDate><guid>http://www.cnitblog.com/roguishangel/articles/6076.html</guid><wfw:comment>http://www.cnitblog.com/roguishangel/comments/6076.html</wfw:comment><comments>http://www.cnitblog.com/roguishangel/articles/6076.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/roguishangel/comments/commentRss/6076.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/roguishangel/services/trackbacks/6076.html</trackback:ping><description><![CDATA[<FONT size=2>一、CGI概述 <BR>　　CGI(公用网关接口)规定了Web服务器调用其他可执行程序(CGI程 序)的接口协议标准。Web服务器通过调用CGI程序实现和Web浏览器的交互,也就是CGI程序接受Web浏览器发送给Web服务器的信息,进行处理,将响应结果再回送给Web服务器及Web浏览器。CGI程序一般完成Web网页中表单(Form)数据的处理、数据库查询和实现与传统应用系统的集成等工作。CGI程序可以用任何程序设计语言编写,如Shell脚本语言、Perl、Fortran、Pascal、C语言等。但是用C语言编写的CGI程序具有执行速度快、安全性高(因为C语言程序是编译执行且不可被修改)等特点。 <BR><BR>　　CGI接口标准包括标准输入、环境变量、标准输出三部分。 <BR><BR>　　1.标准输入 <BR><BR>　　CGI程序像其他可执行程序一样,可通过标准输入(stdin)从Web服务器得到输入信息,如Form中的数据,这就是所谓的向CGI程序传递数据的POST方法。这意味着在操作系统命令行状态可执行CGI程序,对CGI程序进行调试。POST方法是常用的方法,本文将以此方法为例,分析CGI程序设计的方法、过程和技巧。 <BR><BR>　　2.环境变量 <BR><BR>　　操作系统提供了许多环境变量,它们定义了程序的执行环境,应用程序可以存取它们。Web服务器和CGI接口又另外设置了自己的一些环境变量,用来向CGI程序传递一些重要的参数。CGI的GET方法还通过 环境变量QUERY-STRING向CGI程序传递Form中的数据。 <BR><BR>　　3.标准输出 <BR><BR>　　CGI程序通过标准输出(stdout)将输出信息传送给Web服务器。传送给Web服务器的信息可以用各种格式,通常是以纯文本或者HTML文本的形式,这样我们就可以在命令行状态调试CGI程序,并且得到它们的输出。 <BR><BR>　　下面是一个简单的CGI程序,它将HTML中Form的信息直接输出到We b浏览器。 <BR>　　#include &lt;stdio.h&gt;;<BR>　　#include &lt;stdib.h&gt;;<BR>　　main()<BR>　　{<BR>　　　int,i,n;<BR>　　printf (″Contenttype:text/plain\n\n″);<BR>　　n=0;<BR>　　if(getenv(″CONTENT-LENGTH″))<BR>　　n=atoi(getenv(CONTENT-LENGTH″));<BR>　　for (i=0;i&lt;n;i++)<BR>　　putchar(getchar());<BR>　　putchar (′\n′);<BR>　　fflush(stdout);<BR>　　}<BR><BR><BR>　　下面对此程序作一下简要的分析。<BR>　　prinft (″Contenttype:text/plain\n\n″);<BR>　　此行通过标准输出将字符串″Contenttype:text/plain\n\n″传送给Web服务器。它是一个MIME头信息,它告诉Web服务器随后的输出是以纯ASCII文本的形式。请注意在这个头信息中有两个新行符,这是因为Web服务器需要在实际的文本信息开始之前先看见一个空行。<BR>　　if (getenv(″CONTENT-LENGTH″))<BR>　　n=atoi (getenv(″CONTENT-LENGTH″));<BR>　　此行首先检查环境变量CONTENT-LENGTH是否存在。Web服务器在调用使用POST方法的CGI程序时设置此环境变量,它的文本值表示Web服务器传送给CGI程序的输入中的字符数目,因此我们使用函数atoi() 将此环境变量的值转换成整数,并赋给变量n。请注意Web服务器并不以文件结束符来终止它的输出,所以如果不检查环境变量CONTENT-LENGTH,CGI程序就无法知道什么时候输入结束了。<BR><BR><BR>　　for (i=0;i&lt;n;i++)<BR>　　putchar(getchar());<BR>　　此行从0循环到(CONTENT-LENGTH-1)次将标准输入中读到的每一个字符直接拷贝到标准输出,也就是将所有的输入以ASCII的形式回送给Web服务器。 <BR>　　通过此例,我们可将CGI程序的一般工作过程总结为如下几点。<BR>　　1.通过检查环境变量CONTENT-LENGTH,确定有多少输入; <BR>　　2.循环使用getchar()或者其他文件读函数得到所有的输入; <BR>　　3.以相应的方法处理输入;<BR>　　4.通过″Contenttype:″头信息,将输出信息的格式告诉Web服务器; <BR>　　5.通过使用printf()或者putchar()或者其他的文件写函数,将输出传送给Web服务器。<BR>　　总之,CGI程序的主要任务就是从Web服务器得到输入信息,进行处理,然后将输出结果再送回给Web服务器。 <BR><BR>　　二、环境变量 <BR><BR>　　环境变量是文本串(名字/值对),可以被OS Shell或其他程序设置 ,也可以被其他程序访问。它们是Web服务器传递数据给CGI程序的简单手段,之所以称为环境变量是因为它们是全局变量,任何程序都可以存取它们。 <BR><BR>　　下面是CGI程序设计中常常要用到的一些环境变量。<BR>　　HTTP-REFERER:调用该CGI程序的网页的URL。<BR>　　REMOTE-HOST:调用该CGI程序的Web浏览器的机器名和域名。<BR>　　REQUEST-METHOD:指的是当Web服务器传递数据给CGI程序时所采用的方法,分为GET和POST两种方法。GET方法仅通过环境变量(如QUERY-STRING)传递数据给CGI程序,而POST方法通过环境变量和标准输入传递数据给CGI程序,因此POST方法可较方便地传递较多的数据给CGI程序。 <BR><BR>　　SCRIPT-NAME:该CGI程序的名称。<BR>　　QUERY-STRING:当使用POST方法时,Form中的数据最后放在QUERY-STRING中,传递给CGI程序。<BR>　　CONTENT-TYPE:传递给CGI程序数据的MIME类型,通常为″applica tion/x-www-form-url encodede″,它是从HTML Form中以POST方法传递数据给CGI程序的数据编码类型,称为URL编码类型。<BR>　　CONTENT-LENGTH:传递给CGI程序的数据字符数(字节数)。<BR>　　在C语言程序中,要访向环境变量,可使用getenv()库函数。例如:<BR>　　if (getenv (″CONTENT-LENGTH″))<BR>　　　n=atoi(getenv (″CONTENT-LENGTH″));<BR>　　请注意程序中最好调用两次getenv():第一次检查是否存在该环境变量,第二次再使用该环境变量。这是因为函数getenv()在给定的环境变量名不存在时,返回一个NULL(空)指针,如果你不首先检查而直接引用它,当该环境变量不存在时会引起CGI程序崩溃。<BR><BR>　　三、From输入的分析和解码 <BR><BR>　　1.分析名字/值对 <BR><BR>　　当用户提交一个HTML Form时,Web浏览器首先对Form中的数据以名字/值对的形式进行编码,并发送给Web服务器,然后由Web服务器传递给CGI程序。其格式如下:<BR>　　name1=value1&amp;name2=value2&amp;name3=value3&amp;name4=value4&amp;...<BR>　　其中名字是Form中定义的INPUT、SELECT或TEXTAREA等标置(Tag)名字,值是用户输入或选择的标置值。这种格式即为URL编码,程序中需要对其进行分析和解码。要分析这种数据流,CGI程序必须首先将数据流分解成一组组的名字/值对。这可以通过在输入流中查找下面的两个字符来完成。<BR>　　每当找到字符=,标志着一个Form变量名字的结束;每当找到字符&amp; ,标志着一个Form变量值的结束。请注意输入数据的最后一个变量的值不以&amp;结束。<BR>　　一旦名字/值对分解后,还必须将输入中的一些特殊字符转换成相应的ASCII字符。这些特殊字符是:<BR>　　+:将+转换成空格符;<BR>　　%xx:用其十六进制ASCII码值表示的特殊字符。根据值xx将其转换成相应的ASCII字符。<BR>　　对Form变量名和变量值都要进行这种转换。下面是一个对Form数据进行分析并将结果回送给Web服务器的CGI程序。<BR><BR><BR>　　#include &lt;stdio.h&gt;;<BR>　　#include &lt;stdlib.h&gt;;<BR>　　#include &lt;strings.h&gt;;<BR>　　int htoi(char *);<BR>　　main()<BR>　　{<BR>　　　int i,n;<BR>　　char c;<BR>　　printf (″Contenttype: text/plain\n\n″);<BR>　　n=0;<BR>　　if (getenv(″CONTENT-LENGTH″))<BR>　　　n=atoi(getenv(″CONTENT-LENGTH″));<BR>　　for (i=0; i&lt;n;i++){<BR>　　　int is-eq=0;<BR>　　c=getchar();<BR>　　switch ??{<BR>　　　case ′&amp;′:<BR>　　　　c=′\n′;<BR>　　　　break;<BR>　　　case ′+′:<BR>　　　　c=′　′;<BR>　　　　break;<BR>　　　case ′%′:{<BR>　　　　char s[3];<BR>　　　　s[0]=getchar();<BR>　　　　s[1]=getchar();<BR>　　　　s[2]=0;<BR>　　　　c=htoi(s);<BR>　　　　i+=2;<BR>　　　}<BR>　　　break;<BR>　　case ′=′:<BR>　　　c=′:′;<BR>　　　is-eq=1;<BR>　　　break;<BR>　　};<BR>　　putchar??;<BR>　　if (is-eq) putchar(′　′);<BR>　　}<BR>　　putchar (′\n′);<BR>　　fflush(stdout);<BR>　　}<BR>　　/* convert hex string to int */<BR>　　int htoi(char *s)<BR>　　{<BR>　　　char *digits=″0123456789ABCDEF″;<BR>　　if (islower (s[0])) s[0]=toupper(s[0]);<BR>　　if (islower (s[1])) s[1]=toupper(s[1]);<BR>　　return 16 * (strchr(digits, s[0]) -strchr (digits,′0′)<BR>)<BR>　　+(strchr(digits,s[1])-strchr(digits,′0′));<BR>　　}<BR><BR>　　上面的程序首先输出一个MIME头信息给Web服务器,检查输入中的字符数,并循环检查每一个字符。当发现字符为&amp;时,意味着一个名字/值对的结束,程序输出一个空行;当发现字符为+时,将它转换成空格; 当发现字符为%时,意味着一个两字符的十六进制值的开始,调用htoi()函数将随后的两个字符转换为相应的ASCII字符;当发现字符为=时,意味着一个名字/值对的名字部分的结束,并将它转换成字符:。最后将转换后的字符输出给Web服务器。 <BR>　　四、产生HTML输出 <BR><BR>　　CGI程序产生的输出由两部分组成:MIME头信息和实际的信息。两部分之间以一个空行分开。我们已经看到怎样使用MIME头信息″Cont enttype:text/plain\n\n″和printf()、put char()等函数调用来输 出纯ASCII文本给Web服务器。实际上,我们也可以使用MIME头信息″C ontenttype:text/html\n\n″来输出HTML源代码给Web服务器。请注意任何MIME头信息后必须有一个空行。一旦发送这个MIME头信息给We b服务器后,Web浏览器将认为随后的文本输出为HTML源代码,在HTML源代码中可以使用任何HTML结构,如超链、图像、Form,及对其他CGI程 序的调用。也就是说,我们可以在CGI程序中动态产生HTML源代码输出 ,下面是一个简单的例子。 <BR><BR>　　#include &lt;stdio.h&gt;;<BR>　　#include &lt;string.h&gt;;<BR>　　main()<BR>　　{<BR>　　　printf(″Contenttype:text/html\n\n″);<BR>　　printf(″&lt;html&gt;;\n″);<BR>　　printf(″&lt;head&gt;;&lt;title&gt;;An HTML Page From a CGI&lt;/title&gt;;&lt;/h ead&gt;;\n″);<BR>　　printf(″&lt;body&gt;;&lt;br&gt;;\n″);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(″&lt;h2&gt;; This is an HTML page generated from with i n a CGI program.. 　　.&lt;/h2&gt;;\n″);<BR>　　printf(″&lt;hr&gt;;&lt;p&gt;;\n″);<BR>　　printf(″&lt;a href="../output.html#two"&gt;;&lt;b&gt;; Go back to out put.html page &lt;<BR>　　/b&gt;;&lt;/a&gt;;\n″);<BR>　　printf(″&lt;/body&gt;;\n″);<BR>　　printf(″&lt;/html&gt;;\n″);<BR>　　fflush(stdout);<BR>　　}<BR><BR><BR>　　上面的CGI程序简单地用printf()函数来产生HTML源代码。请注意在输出的字符串中如果有双引号,在其前面必须有一个后斜字符\, 这是因为整个HTML代码串已经在双引号内,所以HTML代码串中的双引号符必须用一个后斜字符\来转义。<BR><BR><BR>　　五、结束语 <BR><BR>　　本文详细分析了用C语言进行CGI程序设计的方法、过程和技巧。C语言的CGI程序虽然执行速度快、可靠性高,但是相对于Perl语言来说,C语言缺乏强有力的字符串处理能力,因此在实际应用中,应根据需 要和个人爱好来选择合适的CGI程序设计语言。</FONT><img src ="http://www.cnitblog.com/roguishangel/aggbug/6076.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/roguishangel/" target="_blank">牙刷</a> 2006-01-07 20:46 <a href="http://www.cnitblog.com/roguishangel/articles/6076.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>玩转setjmp与longjmp</title><link>http://www.cnitblog.com/roguishangel/articles/5345.html</link><dc:creator>牙刷</dc:creator><author>牙刷</author><pubDate>Tue, 13 Dec 2005 12:21:00 GMT</pubDate><guid>http://www.cnitblog.com/roguishangel/articles/5345.html</guid><wfw:comment>http://www.cnitblog.com/roguishangel/comments/5345.html</wfw:comment><comments>http://www.cnitblog.com/roguishangel/articles/5345.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/roguishangel/comments/commentRss/5345.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/roguishangel/services/trackbacks/5345.html</trackback:ping><description><![CDATA[不要忘记，前面我们得出过结论，C语言中提供的这种异常处理机制，与C++中的异常处理模型很相似。例如，可以定义出类似的try block（受到监控的代码）；catch 
            block（异常错误的处理模块）；以及可以随时抛出的异常（throw语句）。所以说，我们可以通过一种非常有技巧的封装，来达到对setjmp和longjmp的使用方法（或者说语法规则），基本与C++中的语法一致。很有诱惑吧！
<p>首先展示阿愚封装的在C语言环境中异常处理框架<br>
            <br>
            　　1、首先是接口的头文件，主要采用“宏”技术！代码如下： 
            </p>
<p>/*************************************************<br>
              * author: 王胜祥 *<br>
              * email: &lt;mantx@21cn.com&gt; *<br>
              * date: 2005-03-07 *<br>
              * version: *<br>
              * filename: ceh.h *<br>
              *************************************************/</p>

            
<p><br>
              /********************************************************************</p>

            
<p> This file is part of CEH(Exception Handling in C Language).</p>

            
<p> CEH is free software; you can redistribute it and/or modify<br>
              it under the terms of the GNU General Public License as published 
              by<br>
              the Free Software Foundation; either version 2 of the License, or<br>
              (at your option) any later version.</p>

            
<p> CEH is distributed in the hope that it will be useful,<br>
              but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the<br>
              GNU General Public License for more details.</p>

            
<p> 　　注意：这个异常处理框架不支持线程安全，不能在多线程的程序环境下使用。<br>
              如果您想在多线程的程序中使用它，您可以自己试着来继续完善这个<br>
              框架模型。<br>
              *********************************************************************/</p>

            
<p>#include &lt;stdio.h&gt;<br>
              #include &lt;signal.h&gt;<br>
              #include &lt;setjmp.h&gt;<br>
              #include &lt;stdlib.h&gt;<br>
              #include &lt;float.h&gt;<br>
              #include &lt;math.h&gt;<br>
              #include &lt;string.h&gt;</p>

            
<p><br>
              ////////////////////////////////////////////////////<br>
              /* 与异常有关的结构体定义 */<br>
              typedef struct _CEH_EXCEPTION {<br>
              int err_type; /* 异常类型 */<br>
              int err_code; /* 错误代码 */<br>
              char err_msg[80]; /* 错误信息 */<br>
              }CEH_EXCEPTION; /* 异常对象 */</p>

            
<p>typedef struct _CEH_ELEMENT {<br>
              jmp_buf exec_status;<br>
              CEH_EXCEPTION ex_info;</p>

            
<p> struct _CEH_ELEMENT* next;<br>
              } CEH_ELEMENT; /* 存储异常对象的链表元素 */<br>
              ////////////////////////////////////////////////////</p>

            
<p><br>
              ////////////////////////////////////////////////////<br>
              /* 内部接口定义，操纵维护链表数据结构 */<br>
              extern void CEH_push(CEH_ELEMENT* ceh_element);<br>
              extern CEH_ELEMENT* CEH_pop();<br>
              extern CEH_ELEMENT* CEH_top();<br>
              extern int CEH_isEmpty();<br>
              ////////////////////////////////////////////////////</p>

            
<p><br>
              /* 以下是外部接口的定义 */<br>
              ////////////////////////////////////////////////////<br>
              /* 抛出异常 */<br>
              extern void thrower(CEH_EXCEPTION* e); </p>

            
<p>/* 抛出异常 (throw)<br>
              a表示err_type <br>
              b表示err_code <br>
              c表示err_msg <br>
              */<br>
              #define throw(a, b, c) \<br>
              { \<br>
              CEH_EXCEPTION ex; \<br>
              memset(&amp;ex, 0, sizeof(ex)); \<br>
              ex.err_type = a; \<br>
              ex.err_code = b; \<br>
              strncpy(ex.err_msg, c, sizeof(c)); \<br>
              thrower(&amp;ex); \<br>
              }</p>

            
<p>/* 重新抛出原来的异常 (rethrow)*/<br>
              #define rethrow thrower(ceh_ex_info)<br>
              ////////////////////////////////////////////////////</p>

            
<p><br>
              ////////////////////////////////////////////////////<br>
              /* 定义try block（受到监控的代码）*/<br>
              #define try \<br>
              { \<br>
              int ___ceh_b_catch_found, ___ceh_b_occur_exception; \<br>
              CEH_ELEMENT ___ceh_element; \<br>
              CEH_EXCEPTION* ceh_ex_info; \<br>
              memset(&amp;___ceh_element, 0, sizeof(___ceh_element)); \<br>
              CEH_push(&amp;___ceh_element); \<br>
              ceh_ex_info = &amp;___ceh_element.ex_info; \<br>
              ___ceh_b_catch_found = 0; \<br>
              if (!(___ceh_b_occur_exception=setjmp(___ceh_element.exec_status))) 
              \<br>
              {</p>

            
<p><br>
              /* 定义catch block（异常错误的处理模块）<br>
              catch表示捕获所有类型的异常<br>
              */<br>
              #define catch \<br>
              } \<br>
              else \<br>
              { \<br>
              CEH_pop(); \<br>
              ___ceh_b_catch_found = 1;</p>

            
<p><br>
              /* end_try表示前面定义的try block和catch block结束 */<br>
              #define end_try \<br>
              } \<br>
              { \<br>
              /* 没有执行到任何的catch块中 */ \<br>
              if(!___ceh_b_catch_found) \<br>
              { \<br>
              CEH_pop(); \<br>
              /* 出现了异常，但没有捕获到任何异常 */ \<br>
              if(___ceh_b_occur_exception) thrower(ceh_ex_info); \<br>
              } \<br>
              } \<br>
              } </p>

            
<p><br>
              /* 定义catch block（异常错误的处理模块）<br>
              catch_part表示捕获一定范围内的异常<br>
              */<br>
              #define catch_part(i, j) \<br>
              } \<br>
              else if(ceh_ex_info-&gt;err_type&gt;=i &amp;&amp; ceh_ex_info-&gt;err_type&lt;=j) 
              \<br>
              { \<br>
              CEH_pop(); \<br>
              ___ceh_b_catch_found = 1;</p>

            
<p><br>
              /* 定义catch block（异常错误的处理模块）<br>
              catch_one表示只捕获一种类型的异常<br>
              */<br>
              #define catch_one(i) \<br>
              } \<br>
              else if(ceh_ex_info-&gt;err_type==i) \<br>
              { \<br>
              CEH_pop(); \<br>
              ___ceh_b_catch_found = 1;<br>
              ////////////////////////////////////////////////////</p>

            
<p><br>
              ////////////////////////////////////////////////////<br>
              /* 其它可选的接口定义 */<br>
              extern void CEH_init();<br>
              ////////////////////////////////////////////////////</p>

            
<p><br>
              2、另外还有一个简单的实现文件，主要实现功能封装。代码如下：</p>

            
<p>/*************************************************<br>
              * author: 王胜祥 *<br>
              * email: &lt;mantx@21cn.com&gt; *<br>
              * date: 2005-03-07 *<br>
              * version: *<br>
              * filename: ceh.c * <br>
              *************************************************/</p>

            
<p><br>
              /********************************************************************</p>

            
<p> This file is part of CEH(Exception Handling in C Language).</p>

            
<p> CEH is free software; you can redistribute it and/or modify<br>
              it under the terms of the GNU General Public License as published 
              by<br>
              the Free Software Foundation; either version 2 of the License, or<br>
              (at your option) any later version.</p>

            
<p> CEH is distributed in the hope that it will be useful,<br>
              but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the<br>
              GNU General Public License for more details.</p>

            
<p> 注意：这个异常处理框架不支持线程安全，不能在多线程的程序环境下使用。<br>
              如果您想在多线程的程序中使用它，您可以自己试着来继续完善这个<br>
              框架模型。<br>
              *********************************************************************/</p>

            
<p>#include "ceh.h"</p>

            
<p>////////////////////////////////////////////////////<br>
              static CEH_ELEMENT* head = 0;</p>

            
<p>/* 把一个异常插入到链表头中 */<br>
              void CEH_push(CEH_ELEMENT* ceh_element)<br>
              {<br>
              if(head) ceh_element-&gt;next = head;<br>
              head = ceh_element;<br>
              }</p>

            
<p><br>
              /* 从链表头中，删除并返回一个异常 */<br>
              CEH_ELEMENT* CEH_pop()<br>
              {<br>
              CEH_ELEMENT* ret = 0;</p>

            
<p> ret = head;<br>
              head = head-&gt;next;</p>

            
<p> return ret;<br>
              }</p>

            
<p><br>
              /* 从链表头中，返回一个异常 */<br>
              CEH_ELEMENT* CEH_top()<br>
              {<br>
              return head;<br>
              }</p>

            
<p><br>
              /* 链表中是否有任何异常 */<br>
              int CEH_isEmpty()<br>
              {<br>
              return head==0;<br>
              }<br>
              ////////////////////////////////////////////////////</p>

            
<p><br>
              ////////////////////////////////////////////////////<br>
              /* 缺省的异常处理模块 */<br>
              static void CEH_uncaught_exception_handler(CEH_EXCEPTION *ceh_ex_info) 
              <br>
              {<br>
              printf("捕获到一个未处理的异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              fprintf(stderr, "程序终止!\n");<br>
              fflush(stderr);<br>
              exit(EXIT_FAILURE); <br>
              }<br>
              ////////////////////////////////////////////////////</p>

            
<p><br>
              ////////////////////////////////////////////////////<br>
              /* 抛出异常 */<br>
              void thrower(CEH_EXCEPTION* e) <br>
              {<br>
              CEH_ELEMENT *se;</p>

            
<p> if (CEH_isEmpty()) CEH_uncaught_exception_handler(e);</p>

            
<p> se = CEH_top();<br>
              se-&gt;ex_info.err_type = e-&gt;err_type;<br>
              se-&gt;ex_info.err_code = e-&gt;err_code;<br>
              strncpy(se-&gt;ex_info.err_msg, e-&gt;err_msg, sizeof(se-&gt;ex_info.err_msg));</p>

            
<p> longjmp(se-&gt;exec_status, 1);<br>
              }<br>
              ////////////////////////////////////////////////////</p>

            
<p> <br>
              ////////////////////////////////////////////////////<br>
              static void fphandler( int sig, int num )<br>
              {<br>
              _fpreset();</p>

            
<p> switch( num )<br>
              {<br>
              case _FPE_INVALID:<br>
              throw(-1, num, "Invalid number" );<br>
              case _FPE_OVERFLOW:<br>
              throw(-1, num, "Overflow" );<br>
              case _FPE_UNDERFLOW:<br>
              throw(-1, num, "Underflow" );<br>
              case _FPE_ZERODIVIDE:<br>
              throw(-1, num, "Divide by zero" );<br>
              default:<br>
              throw(-1, num, "Other floating point error" );<br>
              }<br>
              }</p>

            
<p>void CEH_init()<br>
              {<br>
              _control87( 0, _MCW_EM );<br>
              <br>
              if( signal( SIGFPE, fphandler ) == SIG_ERR )<br>
              {<br>
              fprintf( stderr, "Couldn't set SIGFPE\n" );<br>
              abort(); <br>
              }<br>
              }<br>
              ////////////////////////////////////////////////////<br>
              　　体验上面设计出的异常处理框架<br>
请花点时间仔细揣摩一下上面设计出的异常处理框架。呵呵！程序员朋友们，大家是不是发现它与C++提供的异常处理模型非常相似。例如，它提供的基本接口有
try、catch、以及throw等三条语句。还是先看个具体例子吧！以便验证一下这个C语言环境中异常处理框架是否真的比较好用。代码如下：</p>

            
<p>#include "ceh.h"</p>

            
<p>int main(void) <br>
              {<br>
              //定义try block块<br>
              try<br>
              {<br>
              int i,j;<br>
              printf("异常出现前\n\n");<br>
              <br>
              // 抛出一个异常<br>
              // 其中第一个参数，表示异常类型；第二个参数表示错误代码<br>
              // 第三个参数表示错误信息<br>
              throw(9, 15, "出现某某异常");</p>

            
<p> printf("异常出现后\n\n");<br>
              }<br>
              //定义catch block块<br>
              catch<br>
              {<br>
              printf("catch块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              // 这里稍有不同，需要定义一个表示当前的try block结束语句<br>
              // 它主要是清除相应的资源<br>
              end_try<br>
              }</p>

            
<p>　　注意，上面的测试程序可是C语言环境下的程序（文件的扩展名请使用.c结尾），虽然它看上去很像C++程序。请编译运行一下，发现它是不是运行结果如下：<br>
              异常出现前</p>

            
<p><strong>catch块，被执行到</strong><br>
              <br>
              　　捕获到一个异常，错误原因是：出现某某异常! err_type:9 err_code:15</p>

            
<p>
　　呵呵！程序的确是在按照我们预想的流程在执行。再次提醒，这可是C程序，但是它的异常处理却非常类似于C++中的风格，要知道，做到这一点其实非常地
不容易。当然，上面异常对象的传递只是在一个函数的内部，同样，它也适用于多个嵌套函数间的异常传递，还是用代码验证一下吧！在上面的代码基础下，小小修
改一点，代码如下：</p>

            
<p>#include "ceh.h"</p>

            
<p>void test1()<br>
              {<br>
              throw(0, 20, "hahaha");<br>
              }</p>

            
<p>void test()<br>
              {<br>
              test1();<br>
              }</p>

            
<p>int main(void) <br>
              {<br>
              try<br>
              {<br>
              int i,j;<br>
              printf("异常出现前\n\n");<br>
              <br>
              // 注意，这个函数的内部会抛出一个异常。<br>
              test();</p>

            
<p> throw(9, 15, "出现某某异常");</p>

            
<p> printf("异常出现后\n\n");<br>
              }<br>
              catch<br>
              {<br>
              printf("catch块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              end_try<br>
              }</p>

            
<p> 　　同样，在上面程序中，test1()函数内抛出的异常，可以被上层main()函数中的catch block中捕获到。运行结果就不再给出了，大家可以自己编译运行一把，看看运行结果。<br>
              另外这个异常处理框架，与C++中的异常处理模型类似，它也支持try catch块的多层嵌套。很厉害吧！还是看演示代码吧！，如下：</p>

            
<p>#include "ceh.h"</p>

            
<p>int main(void) <br>
              {<br>
              // 外层的try catch块<br>
              try<br>
              {<br>
              // 内层的try catch块<br>
              try<br>
              {<br>
              throw(1, 15, "嵌套在try块中");<br>
              }<br>
              catch<br>
              {<br>
              printf("内层的catch块被执行\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);</p>

            
<p> printf("外层的catch块被执行\n");<br>
              }<br>
              end_try</p>

            
<p> throw(2, 30, "再抛一个异常");<br>
              }<br>
              catch<br>
              {<br>
              printf("外层的catch块被执行\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              end_try<br>
              }</p>

            
<p>　　请编译运行一下，程序的运行结果如下：<br>
              　　内层的catch块被执行<br>
              　　捕获到一个异常，错误原因是：嵌套在try块中! err_type:1 err_code:15<br>
              　　外层的catch块被执行<br>
              　　捕获到一个异常，错误原因是：再抛一个异常! err_type:2 err_code:30</p>

            
<p>　　还有，这个异常处理框架也支持对异常的分类处理。这一点，也完全是模仿C++中的异常处理模型。不过，由于C语言中，不支持函数名重载，所以语法上略有不同，还是看演示代码吧！，如下：</p>

            
<p>#include "ceh.h"</p>

            
<p>int main(void) <br>
              {<br>
              try<br>
              {<br>
              int i,j;<br>
              printf("异常出现前\n\n");<br>
              <br>
              throw(9, 15, "出现某某异常");</p>

            
<p> printf("异常出现后\n\n");<br>
              }<br>
              // 这里表示捕获异常类型从4到6的异常<br>
              catch_part(4, 6)<br>
              {<br>
              printf("catch_part(4, 6)块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              // 这里表示捕获异常类型从9到10的异常<br>
              catch_part(9, 10)<br>
              {<br>
              printf("catch_part(9, 10)块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              // 这里表示只捕获异常类型为1的异常<br>
              catch_one(1)<br>
              {<br>
              printf("catch_one(1)块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              // 这里表示捕获所有类型的异常<br>
              catch<br>
              {<br>
              printf("catch块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              end_try<br>
              }</p>

            
<p>　　请编译运行一下，程序的运行结果如下：<br>
              　　异常出现前</p>

            
<p>catch_part(9, 10)块，被执行到<br>
              　　捕获到一个异常，错误原因是：出现某某异常! err_type:9 err_code:15</p>

            
<p>　　与C++中的异常处理模型相似，它这里的对异常的分类处理不仅支持一维线性的；同样，它也支持分层的，也即在当前的try catch块中找不到相应的catch 
              block，那么它将会到上一层的try catch块中继续寻找。演示代码如下：</p>

            
<p>#include "ceh.h"</p>

            
<p>int main(void) <br>
              {<br>
              try<br>
              {<br>
              try<br>
              {<br>
              throw(1, 15, "嵌套在try块中");<br>
              }<br>
              catch_part(4, 6)<br>
              {<br>
              printf("catch_part(4, 6)块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              end_try</p>

            
<p> printf("这里将不会被执行到\n");<br>
              }<br>
              catch_part(2, 3)<br>
              {<br>
              printf("catch_part(2, 3)块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              // 找到了对应的catch block<br>
              catch_one(1)<br>
              {<br>
              printf("catch_one(1)块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              catch<br>
              {<br>
              printf("catch块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              end_try</p>

            
<p>}</p>

            
<p>　　到目前为止，大家是不是已经觉得，这个主人公阿愚封装的在C语言环境中异常处理框架，已经与C++中的异常处理模型95%相似。无论是它的语法结构；还是所完成的功能；以及它使用上的灵活性等。下面我们来看一个各种情况综合的例子吧！代码如下：</p>

            
<p>#include "ceh.h"</p>

            
<p>void test1()<br>
              {<br>
              throw(0, 20, "hahaha");<br>
              }</p>

            
<p>void test()<br>
              {<br>
              test1();<br>
              }</p>

            
<p>int main(void) <br>
              {<br>
              try<br>
              {<br>
              test();<br>
              }<br>
              catch<br>
              {<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              end_try</p>

            
<p> try<br>
              {<br>
              try<br>
              {<br>
              throw(1, 15, "嵌套在try块中");<br>
              }<br>
              catch<br>
              {<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              end_try</p>

            
<p> throw(2, 30, "再抛一个异常");<br>
              }<br>
              catch<br>
              {<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);</p>

            
<p> try<br>
              {<br>
              throw(0, 20, "嵌套在catch块中");<br>
              }<br>
              catch<br>
              {<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              end_try<br>
              }<br>
              end_try<br>
              }</p>

            
<p>　　请编译运行一下，程序的运行结果如下：<br>
              　　捕获到一个异常，错误原因是：hahaha! err_type:0 err_code:20<br>
              　　捕获到一个异常，错误原因是：嵌套在try块中! err_type:1 err_code:15<br>
              　　捕获到一个异常，错误原因是：再抛一个异常! err_type:2 err_code:30<br>
              　　捕获到一个异常，错误原因是：嵌套在catch块中! err_type:0 err_code:20</p>

            
<p>　　最后，为了体会到这个异常处理框架，更进一步与C++中的异常处理模型相似。那就是它还支持异常的重新抛出，以及系统中能捕获并处理程序中没有catch到的异常。看代码吧！如下：</p>

            
<p>#include "ceh.h"</p>

            
<p>void test1()<br>
              {<br>
              throw(0, 20, "hahaha");<br>
              }</p>

            
<p>void test()<br>
              {<br>
              test1();<br>
              }</p>

            
<p>int main(void) <br>
              {<br>
              // 这里表示程序中将捕获浮点数计算异常<br>
              CEH_init();</p>

            
<p> try<br>
              {<br>
              try<br>
              {<br>
              try<br>
              {<br>
              double i,j;<br>
              j = 0;<br>
              // 这里出现浮点数计算异常<br>
              i = 1/j ;<br>
              <br>
              test();</p>

            
<p> throw(9, 15, "出现某某异常");<br>
              }<br>
              end_try<br>
              }<br>
              catch_part(4, 6)<br>
              {<br>
              printf("catch_part(4, 6)块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              catch_part(2, 3)<br>
              {<br>
              printf("catch_part(2, 3)块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);<br>
              }<br>
              // 捕获到上面的异常<br>
              catch<br>
              {<br>
              printf("内层的catch块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);</p>

            
<p> // 这里再次把上面的异常重新抛出<br>
              rethrow;</p>

            
<p> printf("这里将不会被执行到\n");<br>
              }<br>
              end_try<br>
              }<br>
              catch_part(7, 9)<br>
              {<br>
              printf("catch_part(7, 9)块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);</p>

            
<p> throw(2, 15, "出现某某异常");<br>
              }<br>
              // 再次捕获到上面的异常<br>
              catch<br>
              {<br>
              printf("外层的catch块，被执行到\n");<br>
              printf("捕获到一个异常，错误原因是：%s! err_type:%d err_code:%d\n",<br>
              ceh_ex_info-&gt;err_msg, ceh_ex_info-&gt;err_type, ceh_ex_info-&gt;err_code);</p>

            
<p> // 最后又抛出了一个异常，<br>
              // 但是这个异常没有对应的catch block处理，所以系统中处理了<br>
              throw(2, 15, "出现某某异常");<br>
              }<br>
              end_try<br>
              }</p>

            　　请编译运行一下，程序的运行结果如下：<br>

              　　内层的catch块，被执行到<br>

              　　捕获到一个异常，错误原因是：Divide by zero! err_type:-1 err_code:131<br>

              　　外层的catch块，被执行到<br>

              　　捕获到一个异常，错误原因是：Divide by zero! err_type:-1 err_code:131<br>

              　　捕获到一个未处理的异常，错误原因是：出现某某异常! err_type:2 err_code:15<br>

              　　程序终止!<img src ="http://www.cnitblog.com/roguishangel/aggbug/5345.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/roguishangel/" target="_blank">牙刷</a> 2005-12-13 20:21 <a href="http://www.cnitblog.com/roguishangel/articles/5345.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>CVS使用手册</title><link>http://www.cnitblog.com/roguishangel/articles/4536.html</link><dc:creator>牙刷</dc:creator><author>牙刷</author><pubDate>Tue, 15 Nov 2005 05:08:00 GMT</pubDate><guid>http://www.cnitblog.com/roguishangel/articles/4536.html</guid><wfw:comment>http://www.cnitblog.com/roguishangel/comments/4536.html</wfw:comment><comments>http://www.cnitblog.com/roguishangel/articles/4536.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/roguishangel/comments/commentRss/4536.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/roguishangel/services/trackbacks/4536.html</trackback:ping><description><![CDATA[<p>作者： 车东 Email: chedongATbigfoot.com/chedongATchedong.com</p>

<p>写于：2002/07/10 最后更新：
<script language="Javascript" src="http://www.chedong.com/referer.js"></script>Mon, 21 Feb 2005 08:46:12 GMT<br><a href="http://groups-beta.google.com/group/chedong">Feed Back &gt;&gt;</a>&nbsp;(<a href="http://www.linuxforum.net/doc/smartq-grand.html">Read this before you ask question</a>)<a href="http://www.creativecommons.cn/licenses/by-sa/1.0/"><br><img src="http://www.creativecommons.cn/images/public/somerights.gif" alt="Creative Commons License" border="0"></a><iframe name="google_ads_frame" src="http://pagead2.googlesyndication.com/pagead/ads?client=ca-pub-1309797784693300&amp;dt=1132077564268&amp;lmt=1108975572&amp;format=468x60_as&amp;output=html&amp;url=http%3A%2F%2Fwww.chedong.com%2Ftech%2Fcvs_card.html&amp;ref=http%3A%2F%2Fwww.linuxc.net%2Fviewthread.php%3Ftid%3D597%26pid%3D2963%26page%3D1%26sid%3DHv0E9W&amp;u_h=768&amp;u_w=1024&amp;u_ah=737&amp;u_aw=1024&amp;u_cd=24&amp;u_tz=-300&amp;u_his=1&amp;u_nplug=2&amp;u_nmime=3" marginwidth="0" marginheight="0" vspace="0" hspace="0" allowtransparency="true" frameborder="0" height="60" scrolling="no" width="468">&amp;lt;img&amp;gt;</iframe></p>

<p>版权声明：可以任意转载，转载时请务必以超链接形式标明文章原始出处和作者信息及本声明<br>
<a href="http://www.chedong.com/tech/cvs_card.html">
http://www.chedong.com/tech/cvs_card.html</a></p>

<p>关键词：CVS CVSWeb CVSTrac WinCVS CVSROOT </p>

<p>内容摘要： </p>

<p>CVS是一个C/S系统，多个开发人员通过一个中心版本控制系统来记录文件版本，从而达到保证文件同步的目的。工作模式如下： </p>

<pre>       CVS服务器（文件版本库）<br>     /     |       \<br>     （版 本 同 步）<br>   /       |         \<br>开发者1  开发者2   开发者3<br></pre>

<p>作为一般开发人员挑选2,6看就可以了，CVS的管理员则更需要懂的更多一些，最后还简单介绍了一些Windows下的cvs客户端使用，CVS远
程用户认证的选择及与BUG跟踪系统等开发环境的集成问题。</p>

<ol>
<li><a href="http://www.chedong.com/tech/cvs_card.html#init">CVS环境初始化</a>：CVS环境的搭建 管理员</li><li><a href="http://www.chedong.com/tech/cvs_card.html#daily">CVS的日常使用</a>：日常开发中最常用的CVS命令， 开发人员 管理员</li><li><a href="http://www.chedong.com/tech/cvs_card.html#branch">CVS的分支开发</a>：项目按照不同进度和目标并发进行 管理员</li><li><a href="http://www.chedong.com/tech/cvs_card.html#ssh">CVS的用户认证</a>：通过SSH的远程用户认证，安全，简单 管理员</li><li><a href="http://www.chedong.com/tech/cvs_card.html#cvsweb">CVSWEB</a>：CVS的WEB访问界面大大提高代码版本比较的效率 管理员</li><li><a href="http://www.chedong.com/tech/cvs_card.html#tag">CVS TAG</a>：将$<span style="font-weight: bold;">Id</span>$
加入代码注释中，方便开发过程的跟踪
开发人员</li><li><a href="http://www.chedong.com/tech/cvs_card.html#vss">CVS vs VSS</a>: CVS和Virsual SourceSafe的比较 开发人员 管理员</li><li><a href="http://www.chedong.com/tech/cvs_card.html#wincvs">WinCVS:</a> 通过SSH认证的WinCVS认证设置</li><li><a href="http://www.chedong.com/tech/cvs_card.html#cvstrac">基于CVSTrac的小组开发环境搭建</a>：通过CVSTrac实现web界面的CVS用户管
理,集成的BUG跟踪和WIKI交流</li><li><a href="http://www.chedong.com/tech/cvs_card.html#auth">CVS中的用户权限管理</a>：基于系统用户的CVS权限管理和基于CVSROOT/passwd的虚拟
用户管理<br>
  </li>
</ol>

<p>一个系统20%的功能往往能够满足80%的需求，CVS也不例外，以下是CVS最常用的功能，可能还不到它全部命令选项的20%，作为一般开发人员
平时会用cvs
update和cvs commit就够了，更多的需求在实际应用过程中自然会出现，不时回头看看相关文档经常有意外的收获。</p>

<p><br>
</p>

<h2><a name="init">CVS环境初始化</a></h2>

环境设置：指定CVS库的路径CVSROOT
<p> tcsh<br>
setenv CVSROOT /path/to/cvsroot<br>
bash<br>
CVSROOT=/path/to/cvsroot ; export CVSROOT</p>

<p>后面还提到远程CVS服务器的设置：<br>
CVSROOT=:ext:$USER@test.server.address#port:/path/to/cvsroot
CVS_RSH=ssh; export CVSROOT CVS_RSH<br>
<br>
初始化：CVS版本库的初始化。<br>
cvs init</p>

<p>一个项目的首次导入<br>
cvs import -m "write some comments here" project_name vendor_tag
release_tag<br>
执行后：会将所有源文件及目录导入到/path/to/cvsroot/project_name目录下<br>
<i>vender_tag: 开发商标记<br>
release_tag: 版本发布标记</i></p>

<p> 项目导出：将代码从CVS库里导出<br>
cvs checkout project_name<br>
<i>cvs 将创建project_name目录，并将最新版本的源代码导出到相应目录中。这个checkout和Virvual
SourceSafe中的check out不是一个概念，相对于Virvual SourceSafe的check out是cvs update，
check in是cvs commit。</i><br>
<br>
</p>

<h2><a name="daily" style="font-weight: normal;">CVS的日常使用</a><b> </b></h2>

<p> </p>

<p><b>注意：第一次导出以后，就不是通过cvs checkout来同步文件了，而是要进入刚才cvs checkout
project_name导出的project_name目录下进行具体文件的版本同步（添加，修改，删除）操作。</b></p>

<p><u>将文件同步到最新的版本<br>
</u> cvs update<br>
<i>不制定文件名，cvs将同步所有子目录下的文件，也可以制定某个文件名/目录进行同步<br>
</i>cvs update file_name<br>
<i>最好每天开始工作前或将自己的工作导入到CVS库里前都要做一次，并养成“先同步 后修改”的习惯，和Virvual
SourceSafe不同，CVS里没有文件锁定的概念，所有的冲突是在commit之前解决，如果你修改过程中，有其他人修改并commit到了CVS
库中，CVS会通知你文件冲突，并自动将冲突部分用<br>
&gt;&gt;&gt;&gt;&gt;&gt;<br>
content on cvs server<br>
&lt;&lt;&lt;&lt;&lt;&lt;<br>
content in your file<br>
&gt;&gt;&gt;&gt;&gt;&gt;<br>
标记出来，由你确认冲突内容的取舍。<br>
版本冲突一般是在多个人修改一个文件造成的，但这种项目管理上的问题不应该指望由CVS来解决。</i></p>

<p><u>确认修改写入到CVS库里</u><br>
cvs commit -m "write some comments here" file_name</p>

<p><i>注意：CVS的很多动作都是通过cvs
commit进行最后确认并修改的，最好每次只修改一个文件。在确认的前，还需要用户填写修改注释，以帮助其他开发人员了解修改的原因。如果不用写-m
"comments"而直接确认`cvs commit file_name`
的话，cvs会自动调用系统缺省的文字编辑器(一般是vi)要求你写入注释。<br>
注释的质量很重要：所以不仅必须要写，而且必须写一些比较有意义的内容：以方便其他开发人员能够很好的理解<br>
不好的注释，很难让其他的开发人员快速的理解：比如： -m "bug fixed" 甚至 -m ""<br>
好的注释，甚至可以用中文: -m "在用户注册过程中加入了Email地址校验"</i> <br>
<br>
修改某个版本注释：每次只确认一个文件到CVS库里是一个很好的习惯，但难免有时候忘了指定文件名，把多个文件以同样注释commit到CVS库里了，以
下命令可以允许你修改某个文件某个版本的注释：<br>
cvs admin -m 1.3:"write some comments here" file_name<br>
<br>
<u> 添加文件</u><br>
创建好新文件后，比如：touch new_file<br>
cvs add new_file<br>
<i>注意：对于图片，Word文档等非纯文本的项目，需要使用cvs add
-kb选项按2进制文件方式导入(k表示扩展选项，b表示binary)，否则有可能出现文件被破坏的情况<br>
比如：<br>
cvs add -kb new_file.gif<br>
cvs add -kb readme.doc</i></p>

<p><i>如果关键词替换属性在首次导入时设置错了怎么办？<br>
</i>cvs admin -kkv new_file.css <br>
<i><br>
</i><span style="text-decoration: underline;">然后确认修改并注释</span><br>
cvs ci -m "write some comments here"</p>

<p> <u>删除文件</u><br>
将某个源文件物理删除后，比如：rm file_name<br>
cvs rm file_name<br>
然后确认修改并注释<br>
cvs ci -m "write some comments here"<br>
以上面前2步合并的方法为：<br>
cvs rm -f file_name<br>
cvs ci -m "why delete file"<br>
<i>注意：很多cvs命令都有缩写形式：commit=&gt;ci; update=&gt;up; checkout=&gt;co/get;
remove=&gt;rm;</i> </p>

<p><u>添加目录</u><br>
cvs add dir_name<br>
<br>
<u>查看修改历史</u><br>
cvs log file_name<br>
cvs history file_name<br>
<br>
<u> 查看当前文件不同版本的区别</u><br>
cvs diff -r1.3 -r1.5 file_name<br>
查看当前文件（可能已经修改了）和库中相应文件的区别<br>
cvs diff file_name<br>
cvs的web界面提供了更方便的定位文件修改和比较版本区别的方法，具体安装设置请看后面的cvsweb使用</p>

<p><u>正确的通过CVS恢复旧版本的方法</u>：<br>
如果用cvs update -r1.2 file.name<br>
这个命令是给file.name加一个STICK TAG： "1.2" ，虽然你的本意只是想将它恢复到1.2版本<br>
正确的恢复版本的方法是：cvs update -p -r1.2 file_name &gt;file_name<br>
如果不小心已经加成STICK TAG的话：用cvs update -A 解决</p>

<p><u>移动文件/文件重命名</u><br>
cvs里没有cvs move或cvs rename，因为这两个操作是可以由先cvs remove old_file_name，然后cvs
add new_file_name实现的。</p>

<p> <u>删除/移动目录</u><br>
最方便的方法是让管理员直接移动，删除CVSROOT里相应目录（因为CVS一个项目下的子目录都是独立的，移动到$CVSROOT目录下都可以作为新的
独立项目：好比一颗树，其实砍下任意一枝都能独立存活），对目录进行了修改后，要求其开发人员重新导出项目cvs
checkout project_name 或者用cvs update -dP同步。</p>

<p><u>项目发布导出不带CVS目录的源文件</u><br>
做开发的时候你可能注意到了，每个开发目录下，CVS都创建了一个CVS/目录。里面有文件用于记录当前目录和CVS库之间的对应信息。但项目发布的时候
你一般不希望把文件目录还带着含有CVS信息的CVS目录吧，这个一次性的导出过程使用cvs
export命令，不过export只能针对一个TAG或者日期导出，比如：<br>
cvs export -r release1 project_name <br>
cvs export -D 20021023 project_name<br>
cvs export -D now project_name</p>

<h2><a name="branch">CVS Branch：项目多分支同步开发</a></h2>

确认版本里程碑：多个文件各自版本号不一样，项目到一定阶段，可以给所有文件统一指定一个阶段里程碑版本号，方便以后按照这个阶段里程碑版本号导出项目，
同时也是项目的多个分支开发的基础。<br>

<p> cvs tag release_1_0</p>

<p><u>开始一个新的里程碑</u>：<br>
cvs commit -r 2 标记所有文件开始进入2.x的开发</p>

<p><i>注意：CVS里的revsion和软件包的发布版本可以没有直接的关系。但所有文件使用和发布版本一致的版本号比较有助于维护。</i></p>

<p><u>版本分支的建立</u><br>
在开发项目的2.x版本的时候发现1.x有问题，但2.x又不敢用，则从先前标记的里程碑：release_1_0导出一个分支
release_1_0_patch<br>
cvs rtag -b -r release_1_0 release_1_0_patch proj_dir</p>

<p>一些人先在另外一个目录下导出release_1_0_patch这个分支：解决1.0中的紧急问题，<br>
cvs checkout -r release_1_0_patch<br>
而其他人员仍旧在项目的主干分支2.x上开发</p>

<p>在release_1_0_patch上修正错误后，标记一个1.0的错误修正版本号<br>
cvs tag release_1_0_patch_1</p>

<p>如果2.0认为这些错误修改在2.0里也需要，也可以在2.0的开发目录下合并release_1_0_patch_1中的修改到当前代码中：<br>
cvs update -j release_1_0_patch_1</p>

<h2><a name="ssh">CVS的远程认证通过SSH远程访问CVS</a></h2>

使用cvs本身基于pserver的远程认证很麻烦,需要定义服务器和用户组，用户名，设置密码等，<br>

<p> 常见的登陆格式如下：<br>
cvs -d :pserver:cvs_user_name@cvs.server.address:/path/to/cvsroot login<br>
例子：<br>
cvs -d :pserver:cvs@samba.org:/cvsroot login</p>

<p>不是很安全，因此一般是作为匿名只读CVS访问的方式。从安全考虑，通过系统本地帐号认证并通过SSH传输是比较好的办法，通过在客户机的
/etc/profile里设置一下内容：<br>
CVSROOT=:ext:$USER@cvs.server.address#port:/path/to/cvsroot
CVS_RSH=ssh; export CVSROOT CVS_RSH<br>
所有客户机所有本地用户都可以映射到CVS服务器相应同名帐号了。</p>

<p>比如:</p>

<p>CVS服务器是192.168.0.3，上面CVSROOT路径是/home/cvsroot，另外一台开发客户机是192.168.0.4，如果
tom在2台机器上都有同名的帐号，那么从192.168.0.4上设置了：<br>
export CVSROOT=:ext:tom@192.168.0.3:/home/cvsroot<br>
export CVS_RSH=ssh<br>
tom就可以直接在192.168.0.4上对192.168.0.3的cvsroot进行访问了（如果有权限的话）<br>
cvs checkout project_name<br>
cd project_name<br>
cvs update<br>
...<br>
cvs commit <br>
</p>

<p> 如果CVS所在服务器的SSH端口不在缺省的22，或者和客户端与CVS服务器端SSH缺省端口不一致，有时候设置了：<br>
:ext:$USER@test.server.address#port:/path/to/cvsroot <br>
<br>
仍然不行，比如有以下错误信息：<br>
ssh: test.server.address#port: Name or service not known<br>
cvs [checkout aborted]: end of file from server (consult above messages
if any)<br>
<br>
解决的方法是做一个脚本指定端口转向（不能使用alias，会出找不到文件错误）：<br>
创建一个/usr/bin/ssh_cvs文件，假设远程服务器的SSH端口是非缺省端口：34567<br>
#!/bin/sh<br>
/usr/bin/ssh -p 34567 "$@"<br>
然后：chmod +x /usr/bin/ssh_cvs<br>
并CVS_RSH=ssh_cvs; export CVS_RSH</p>

<p>注意：port是指相应服务器SSH的端口，不是指cvs专用的pserver的端口<br>
<br>
</p>

<h2><a name="cvsweb">CVSWEB：提高文件浏览效率</a></h2>

CVSWEB就是CVS的WEB界面，可以大大提高程序员定位修改的效率:<br>

<p> 使用的样例可以看：<a href="http://www.freebsd.org/cgi/cvsweb.cgi">http://www.freebsd.org/cgi/cvsweb.cgi</a></p>

<p>CVSWEB的下载：CVSWEB从最初的版本已经演化出很多功能界面更丰富的版本，这个是我个人感觉安装设置比较方便的：<br>
原先在：<s>http://www.spaghetti-code.de/software/linux/cvsweb/</s>，但目前已经删除，<a href="http://www.chedong.com/tech/cvsweb.tgz">目前仍可以在本站下载CVSWEB</a>，
其实最近2年<a href="http://www.freebsd.org/projects/cvsweb.html">FreeBSD的CVSWeb项目</a>已经有了更好的发展吧，而当初没有用FreeBSD那个版本
主要就是因为没有彩色的文件Diff功能。
<br>
下载解包：<br>
tar zxf cvsweb.tgz<br>
把配置文件cvsweb.conf放到安全的地方（比如和apache的配置放在同一个目录下），<br>
修改：cvsweb.cgi让CGI找到配置文件：<br>
$config = $ENV{'CVSWEB_CONFIG'} || '/path/to/apache/conf/cvsweb.conf';<br>
<br>
转到/path/to/apache/conf下并修改cvsweb.conf：</p>

<ol>
<li> 修改CVSROOT路径设置：<br>
%CVSROOT = (<br>
'Development' =&gt; '/path/to/cvsroot', #&lt;==修改指向本地的CVSROOT<br>
);</li><li> 缺省不显示已经删除的文档：<br>
"hideattic" =&gt; "1",#&lt;==缺省不显示已经删除的文档</li><li>在配置文件cvsweb.conf中还可以定制页头的描述信息，你可以修改$long_intro成你需要的文字</li>
</ol>

<p> CVSWEB可不能随便开放给所有用户，因此需要使用WEB用户认证：<br>
先生成 passwd:<br>
/path/to/apache/bin/htpasswd -c cvsweb.passwd user<br>
<br>
修改httpd.conf: 增加<br>
&lt;Directory "/path/to/apache/cgi-bin/cvsweb/"&gt;<br>
AuthName "CVS Authorization"<br>
AuthType Basic<br>
AuthUserFile /path/to/cvsweb.passwd<br>
require valid-user<br>
&lt;/Directory&gt;<br>
<br>
</p>

<h2><a name="tag">CVS TAGS: $Id: cvs_card.html,v 1.5 2003/03/09
08:41:46 chedong Exp $</a></h2>

将$Id: cvs_card.html,v 1.9 2003/11/09 07:57:11 chedong Exp $
加在程序文件开头的注释里是一个很好的习惯，cvs能够自动解释更新其中的内容成：file_name version time user_name
的格式，比如：cvs_card.txt,v 1.1 2002/04/05 04:24:12 chedong
Exp，可以这些信息了解文件的最后修改人和修改时间<br>

<p> <br>
</p>

<pre>几个常用的缺省文件：<br>default.php<br>&lt;?php<br>/*<br> * Copyright (c) 2002 Company Name.<br> * $Header: /home/cvsroot/tech/cvs_card.html,v 1.9 2003/11/09 07:57:11 chedong Exp $<br> */<br><br>?&gt;<br>====================================<br>Default.java: 注意文件头一般注释用 /* 开始 JAVADOC注释用 /** 开始的区别<br>/*<br> * Copyright (c) 2002 MyCompany Name.<br> * $Header: /home/cvsroot/tech/cvs_card.html,v 1.9 2003/11/09 07:57:11 chedong Exp $<br> */<br><br>package com.mycompany;<br><br>import java.;<br><br>/**<br> * comments here<br> */<br>public class Default {<br>    /**<br>     * Comments here<br>     * @param<br>     * @return<br>     */<br>    public toString() {<br><br>    }<br>}<br>====================================<br>default.pl:<br>#!/usr/bin/perl -w<br># Copyright (c) 2002 Company Name.<br># $Header: /home/cvsroot/tech/cvs_card.html,v 1.9 2003/11/09 07:57:11 chedong Exp $<br><br># file comments here<br><br>use strict;<br><br></pre>

<h2><a name="vss">CVS vs VSS</a></h2>

<p>CVS没有文件锁定模式，VSS在check out同时，同时记录了文件被导出者锁定。 </p>

<p>CVS的update和commit， VSS是get_lastest_version和check in </p>

<p>对应VSS的check out/undo check out的CVS里是edit和unedit </p>

<p>在CVS中，标记自动更新功能缺省是打开的，这样也带来一个潜在的问题，就是不用-kb方式添加binary文件的话在cvs自动更新时可能会导致
文件失效。</p>

<p>$Header: /home/cvsroot/tech/cvs_card.html,v 1.5 2003/03/09 08:41:46
chedong Exp $ $Date: 2003/11/09 07:57:11 $这样的标记在Virsual
SourceSafe中称之为Keyword
Explaination，缺省是关闭的，需要通过OPITION打开，并指定需要进行源文件关键词扫描的文件类型：*.txt,*.java,
*.html...</p>

<p>对于Virsual SourceSafe和CVS都通用的TAG有：<br>
$Header: /home/cvsroot/tech/cvs_card.html,v 1.5 2003/03/09 08:41:46
chedong Exp $<br>
$Author: chedong $<br>
$Date: 2003/11/09 07:57:11 $ <br>
$Revision: 1.9 $ </p>

<p>我建议尽量使用通用的关键词保证代码在CVS和VSS都能方便的跟踪。 </p>

<h2><a name="wincvs">WinCVS</a></h2>

下载：<br>

<p> cvs Windows客户端：目前稳定版本为1.2<br>
<a href="http://cvsgui.sourceforge.net/">http://cvsgui.sourceforge.net</a><br>
ssh Windows客户端<br>
<a href="http://www.networksimplicity.com/openssh/">http://www.networksimplicity.com/openssh/</a><br>
<br>
安装好以上2个软件以后：<br>
WinCVS客户端的admin==&gt;preference设置<br>
1 在general选单里<br>
设置CVSROOT： username@192.168.0.123:/home/cvsroot<br>
设置Authorization: 选择SSH server<br>
<br>
2 Port选单里<br>
钩上：check for alternate rsh name<br>
并设置ssh.exe的路径，缺省是装在 C:\Program Files\NetworkSimplicity\ssh\ssh.exe </p>

<p>然后就可以使用WinCVS进行cvs操作了，所有操作都会跳出命令行窗口要求你输入服务器端的认证密码。 </p>

<p>当然，如果你觉得这样很烦的话，还有一个办法就是生成一个没有密码的公钥/私钥对，并设置CVS使用基于公钥/私钥的SSH认证（在general
选单里）。</p>

<p>可以选择的diff工具：examdiff<br>
下载：<br>
<a href="http://www.prestosoft.com/examdiff/examdiff.htm">http://www.prestosoft.com/examdiff/examdiff.htm</a><br>
还是在WinCVS菜单admin==&gt;preference的WinCVS选单里<br>
选上：Externel diff program<br>
并设置diff工具的路径，比如：C:\Program Files\ed16i\ExamDiff.exe<br>
在对文件进行版本diff时，第一次需要将窗口右下角的use externel diff选上。 </p>

<h2><a name="cvstrac">基于CVSTrac的小组开发环境搭建</a></h2>

作为一个小组级的开发环境，版本控制系统和BUG跟踪系统等都涉及到用户认证部分。如何方便的将这些系统集成起来是一个非常困难的事情，毕竟我们不能指望
Linux下有像Source
Offsite那样集成度很高的版本控制/BUG跟踪集成系统。<br>

<br>

我个人是很反对使用pserver模式的远程用户认证的，但如果大部分组员使用WINDOWS客户端进行开发的话，总体来说使用
CVSROOT/passwd认证还是很难避免的，但CVS本身用户的管理比较麻烦。本来我打算自己用perl写一个管理界面的，直到我发现了
CVSTrac：
一个基于WEB界面的BUG跟踪系统，它外挂在CVS系统上的BUG跟踪系统，其中就包括了WEB界面的CVSROOT/passwd文件的管理，甚至还
集成了WIKIWIKI讨论组功能。
<p>这里首先说一下CVS的pserver模式下的用户认证，CVS的用户认证服务是基于inetd中的：<br>
cvspserver stream tcp nowait apache /usr/bin/cvs cvs
--allow-root=/home/cvsroot pserver<br>
一般在2401端口（这个端口号很好记：49的平方）<br>
<br>
CVS用户数据库是基于CVSROOT/passwd文件，文件格式：<br>
[username]:[crypt_password]:[mapping_system_user]<br>
由于密码都用的是UNIX标准的CRYPT加密，这个passwd文件的格式基本上是apache的htpasswd格式的扩展（比APACHE的
PASSWD文件多一个系统用户映射字段），所以这个文件最简单的方法可以用<br>
apache/bin/htpasswd -b myname mypassword <br>
创建。注意：通过htpasswd创建出来的文件会没有映射系统用户的字段<br>
例如：<br>
new:geBvosup/zKl2<br>
setup:aISQuNAAoY3qw<br>
test:hwEpz/BX.rEDU </p>

<p>映射系统用户的目的在于：你可以创建一个专门的CVS服务帐号，比如用apache的运行用户apache，并将/home/cvsroot目录下
的所有权限赋予这个用户，然后在passwd文件里创建不同的开发用户帐号，但开发用户帐号最后的文件读写权限都映射为apache用户，在SSH模式下
多个系统开发用户需要在同一个组中才可以相互读写CVS库中的文件。</p>

<p>进一步的，你可以将用户分别映射到apache这个系统用户上。<br>
new:geBvosup/zKl2:apache<br>
setup:aISQuNAAoY3qw:apache<br>
test:hwEpz/BX.rEDU:apache </p>

<p>CVSTrac很好的解决了CVSROOT/passwd的管理问题，而且包含了BUG跟踪报告系统和集成WIKIWIKI交流功能等，使用的
CGI方式的安装，并且基于<a href="http://www.gnu.org/copyleft/gpl.html">GNU Public
License</a>：<br>
</p>

<p align="left">在inetd里加入cvspserver服务：<br>
cvspserver stream tcp nowait apache /usr/bin/cvs cvs
--allow-root=/home/cvsroot pserver</p>

<p align="left">xietd的配置文件：%cat cvspserver <br>
service cvspserver<br>
{<br>
disable = no<br>
socket_type = stream<br>
wait = no<br>
user = apache<br>
server = /usr/bin/cvs<br>
server_args = -f --allow-root=/home/cvsroot pserver<br>
log_on_failure += USERID<br>
}</p>

<p>
注意：这里的用户设置成apache目的是和/home/cvsroot的所有用户一致，并且必须让这个这个用户对/home/cvsroot/下的
CVSROOT/passwd和cvstrac初始化生成的myproj.db有读取权限。</p>

<p> </p>

<p>安装过程 </p>

<ol>
<li>下载：可以从<a href="http://www.cvstrac.org/">http://www.cvstrac.org</a>
下载<br>
我用的是已经在Linux上编译好的应用程序包：cvstrac-1.1.2.bin.gz，<br>
%gzip -d cvstrac-1.1.2.bin.gz<br>
%chmod +x cvstrac-1.1.2.bin<br>
#mv cvstarc-1.1.1.bin /usr/bin/cvstrac</li><br>
如果是从源代码编译：<br>
从 http://www.sqlite.org/download.html 下载SQLITE的rpm包：<br>
rpm -i sqlite-devel-2.8.6-1.i386.rpm<br>
从 ftp://ftp.cvstrac.org/cvstrac/ 下载软件包<br>
解包，假设解包到/home/chedong/cvstrac-1.1.2下，并规划将cvstrac安装到/usr/local/bin目录下，
cd /home/chedong/cvstrac-1.1.2
编辑linux-gcc.mk:<br>
修改：<br>
SRCDIR = /home/chedong/cvstrac-1.1.2<br>
INSTALLDIR = /usr/local/bin<br>
然后<br>
mv linux-gcc.mk Makefile<br>
make<br>
#make install<br><br><li> 初始化cvstrac数据库：假设数据库名是 myproj<br>
在已经装好的CVS服务器上（CVS库这时候应该已经是初始化好了，比如：cvs init初始化在/home/cvsroot里），运行一下<br>
%cvstrac init /home/cvsroot myproj<br>
运行后，/home/cvsroot里会有一个的myproj.db库，使用CVSTRAC服务，/home/cvsroot/myproj.db
/home/cvsroot/CVSROOT/readers /home/cvsroot/CVSROOT/writers
/home/cvsroot/CVSROOT/passwd这几个文件对于web服务的运行用户应该是可写的，在RedHat8上，缺省就有一个叫
apache用户和一个apache组，所以在httpd.conf文件中设置了用apache用户运行web服务：<br>
User apache<br>
Group apache，<br>
然后设置属于apache用户和apache组<br>
#chown -R apache:apache /home/cvsroot<br>
-rw-r--r-- 1 apache apache 55296 Jan 5 19:40 myproj.db<br>
drwxrwxr-x 3 apache apache 4096 Oct 24 13:04 CVSROOT/<br>
drwxrwxr-x 2 apache apache 4096 Aug 30 19:47 some_proj/<br>
此外还在/home/cvsroot/CVSROOT中设置了：<br>
chmod 664 readers writers passwd<br>
  </li><li>在apche/cgi-bin目录中创建脚本cvstrac:<br>
#!/bin/sh<br>
/usr/bin/cvstrac cgi /home/cvsroot<br>
设置脚本可执行：<br>
chmod +x /home/apache/cgi-bin/cvstrac<br>
  </li><li>从 http://cvs.server.address/cgi-bin/cvstrac/myproj 进入管理界面<br>
缺省登录名：setup 密码 setup<br>
对于一般用户可以从：<br>
http://cvs.server.address/cgi-bin/cvstrac/myproj</li><li>在setup中重新设置了CVSROOT的路径后，/home/cvsroot<br>
如果是初次使用需要在/home/cvsroot/CVSROOT下创建passwd, readers, writers文件<br>
touch passwd readers writers<br>
然后设置属于apache用户，<br>
chown apache.apache passwd readers writers<br>
这样使用setup用户创建新用户后会同步更新CVSROOT/passwd下的帐号<br>
  </li>
</ol>

<p align="left">修改登录密码，进行BUG报告等，<br>
更多使用细节可以在使用中慢慢了解。<br>
</p>

<p align="left">对于前面提到的WinCVS在perference里设置：<br>
CVSROOT栏输入：username@ip.address.of.cvs:/home/cvsroot<br>
Authenitication选择：use passwd file on server side <br>
就可以了从服务器上进行CVS操作了。<br>
</p>

<h2><a name="auth">CVS的用户权限管理</a></h2>

<p align="left">CVS的权限管理分2种策略：<br>
</p>

<ul>
<li>基于系统文件权限的系统用户管理：适合多个在Linux上使用系统帐号的开发人员进行开发。</li><li>基于CVSROOT/passwd的虚拟用户管理：适合多个在Windows平台上的开发人员将帐号映射成系统帐号使用。</li>
</ul>

为什么使用apache/apache用户？首先RedHat8中缺省就有了，而且使用这个用户可以方便通过cvstrac进行WEB管理。<br>

chown -R apache.apache /home/cvsroot<br>

chmod 775 /home/cvsroot<br>

<p align="left">Linux上通过ssh连接CVS服务器的多个开发人员：通过都属于apache组实现文件的共享读写<br>
开发人员有开发服务器上的系统帐号：sysuser1
sysuser2，设置让他们都属于apache组，因为通过cvs新导入的项目都是对组开放的：664权限的，这样无论那个系统用户导入的项目文件，只
要文件的组宿主是apache，所有其他同组系统开发用户就都可以读写；基于ssh远程认证的也是一样。<br>
</p>

<p align="left">&nbsp; &nbsp;apache(system group)<br>
/ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; \<br>
sysuser1 &nbsp; sysuser2 &nbsp; &nbsp; sysuser3<br>
</p>

<p align="left">Windows上通过cvspserver连接CVS服务器的多个开发人员：通过在passwd文件种映射成
apache用户实现文件的共享读写<br>
他们的帐号通过CVSROOT/passwd和readers
writers这几个文件管理；通过cvstrac设置所有虚拟用户都映射到apache用户上即可。<br>
</p>

<p align="left">&nbsp; &nbsp;apache(system user)<br>
/ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp;\<br>
windev1 &nbsp; &nbsp; windev2 &nbsp; &nbsp; &nbsp;windev3&nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</p>



<p>利用CVS WinCVS/CVSWeb/CVSTrac 构成了一个相对完善的跨平台工作组开发版本控制环境。</p>

<p>相关资源： </p>

<p>CVS HOME：<br>
<a href="http://www.cvshome.org/">http://www.cvshome.org</a></p>

<p>CVS FAQ：<br>
<a href="http://www.loria.fr/%7Emolli/cvs-index.html">http://www.loria.fr/~molli/cvs-index.html</a><br>
<br>
相关网站:<br>
<a href="http://directory.google.com/Top/Computers/Software/Configuration_Management/Tools/Concurrent_Versions_System/">http://directory.google.com/Top/Computers/Software/Configuration_Management/Tools/Concurrent_Versions_System/</a><br>
</p>

<p>CVS--并行版本系统<br>
<a href="http://www.soforge.com/cvsdoc/zh_CN/book1.html">http://www.soforge.com/cvsdoc/zh_CN/book1.html</a></p>

<p>CVS 免费书:<br>
<a href="http://cvsbook.red-bean.com/">http://cvsbook.red-bean.com/</a></p>

<p><a href="http://refcards.com/refcards/cvs/index.html">CVS命令的速查卡片 refcards.com/refcards/cvs/</a></p>

<p>WinCVS:<br>
<a href="http://cvsgui.sourceforge.net/">http://cvsgui.sourceforge.net/</a></p>

<p>CVSTrac: A Web-Based Bug And Patch-Set Tracking System For CVS<br>
<a href="http://www.cvstrac.org/">http://www.cvstrac.org</a> </p>

<p>StatCVS：基于CVS的代码统计工具：按代码量，按开发者的统计表等<br>
<a href="http://sourceforge.net/projects/statcvs">http://sourceforge.net/projects/statcvs<br>
</a></p>

如何在WEB开发中规划CVS上：在Google上查 "cvs web development"<br>

<a href="http://ccm.redhat.com/bboard-archive/cvs_for_web_development/index.html">http://ccm.redhat.com/bboard-archive/cvs_for_web_development/index.html</a><br>

<br>

一些集成了CVS的IDE环境： <br>

<a href="http://www.eclipse.org/">Eclipse</a><br>

<a href="http://www.magicunix.com/product_ch.html">Magic C++</a><br>

<img src ="http://www.cnitblog.com/roguishangel/aggbug/4536.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/roguishangel/" target="_blank">牙刷</a> 2005-11-15 13:08 <a href="http://www.cnitblog.com/roguishangel/articles/4536.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Red Hat Linux 8.0和9.0上设置CVS服务器</title><link>http://www.cnitblog.com/roguishangel/articles/3756.html</link><dc:creator>牙刷</dc:creator><author>牙刷</author><pubDate>Mon, 31 Oct 2005 16:45:00 GMT</pubDate><guid>http://www.cnitblog.com/roguishangel/articles/3756.html</guid><wfw:comment>http://www.cnitblog.com/roguishangel/comments/3756.html</wfw:comment><comments>http://www.cnitblog.com/roguishangel/articles/3756.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/roguishangel/comments/commentRss/3756.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/roguishangel/services/trackbacks/3756.html</trackback:ping><description><![CDATA[<span class="postbody">原文:http://www.linuxfans.org/nuke/modules.php?name=Forums&file=viewtopic&t=92177<br>
<br>
服务器的安装略过不提，因为安装了开发工具的话默认就已经有了CVS。就算没有，更新软件包就可以搞定，除非你一定要安装最新版本。
<br>

<br>
1．首先创建用于CVS的组和用户：
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">#groupadd cvs
<br>
#useradd cvsroot -g cvs
<br>
#passwd cvsroot</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
OK，用户已经建立好了，cvsroot就是我们做CVS操作使用的。
<br>

<br>
2．修改配置文件
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">#more /etc/services | grep cvspserver</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
看看是否有
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">cvspserver 2401/tcp #CVS client/server operations
<br>
cvspserver 2401/udp #CVS client/server operations</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
这2行。系统自带了CVS时，这2行也已经有了，只需要确认一下。如果没有，请自己加上去。
<br>
然后必须创建启动脚本：
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">#vi /etc/xinet.d/cvspserver</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
内容如下
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">service cvspserver
<br>
{
<br>
disable = no
<br>
flags = REUSE
<br>
socket_type = stream
<br>
wait = no
<br>
user = root
<br>
server = /usr/bin/cvs
<br>
server_args = -f --allow-root=/home/cvsroot pserver
<br>
log_on_success += USERID
<br>
log_on_failure += USERID
<br>
}</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
其中server指定CVS可执行文件路径，默认安装就是/usr/bin/cvs。server_args指定源代码库路径及认证方式等，例子中把源代
码存放在cvsroot的主目录中，也可以另外指定路径，但必须注意权限设置，pserver是密码认证方式，这种方式的安全性要差一些，但操作起来比较
简单。请注意每行等号左右都有一个空格，否则无法启动服务。
<br>

<br>
3．初始化CVS
<br>
切换到cvsroot用户，然后进行初始化：
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">#cvs -d /home/cvsroot init</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
这个路径应该与cvspserver文件中指定的路径相同，初始化后会在此路径下面创建CVSROOT目录，存放用于CVS管理的一些文件。此时重新启动xinetd服务，CVS服务器应该能够启动了。
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">#service xinetd restart</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
当然，重新启动计算机也可以。确认是否启动：
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">#netstat -l | grep cvspserver</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
如果能看到
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">tcp 0 0 *:cvspserver *:* LISTEN</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
说明已经正常启动，没有的话请重新检查配置过程是否有错误或者遗漏。最后还必须检查防火墙的设置，把2401端口打开。
<br>

<br>
4．用户管理
<br>
CVS默认使用系统用户登录，为了系统安全性的考虑也可以使用独立的用户管理。CVS用户名和密码保存在CVSROOT目录下的passwd文件中，格式为：
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">用户名:密码:系统用户</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
也就是说，它把CVS用户映射到系统用户，这样我们就可以通过系统用户的权限设置来分配给用户不同的权限，而不需要让用户知道系统用户名和密码。
<br>
passwd文件默认并不存在，我们必须自己创建。文件中的密码字段使用MD5加密，不幸的是CVS没有提供添加用户名的命令，所以我们借用Apache的命令来完成这项工作：
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">#htpasswd passwd username</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>这个命令为username指定密码，并保存在passwd中，文件不存在时会自动创建。htpasswd命令不是为CVS而设，因此总有一些遗
憾，它不能自动添加映射到的用户名，不过没关系，我们设置好密码后，自己把这部分加上。我的做法是映射到cvsroot用户，如果需要映射其他的用户，请
注意给相应的目录设置好权限，否则CVS用户可能无法访问源代码仓库。
<br>
要彻底防止使用系统帐号登陆，可以编辑CVSROOT目录下的config文件，把
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">#SystemAuth=no</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
这一行前面的#去掉，CVS就不会验证系统用户了，否则当用户名不在passwd文件中时，CVS会进行系统用户的验证。
<br>此外还必须配置读写权限，使用CVSROOT目录下的readers和writers文件进行这个工作。这2个文件默认也是没有的，没关系，自己
创建就可以了。readers文件记录拥有只读权限的用户名，每行一个用户；writers文件记录拥有读写权限的用户名，也是每行一个用户。注意，
readers文件比writers优先，也就是说出现在readers中的用户将会是只读的，不管writers文件中是否存在该用户。
<br>

<br>
配置完毕，先测试一下：
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">#cvs -d “:pserver:username@127.0.0.1:/home/cvsroot” login</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
这里假设用户名是username，本机登陆。出现密码提示，输入正确的密码后，登陆成功。如果提示访问被拒绝，请检查用户权限、目录权限以及防火墙设置。建议设置环境变量CVSROOT：
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">#export CVSROOT=:pserver:username@127.0.0.1:/home/cvsroot</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
以后就不需要输入-d参数了，但-d参数会覆盖这个环境变量的设置。
<br>

<br>
5．源代码仓库的备份和移动
<br>基本上，CVS的源代码仓库没有什么特别之处，完全可以用文件备份的方式进行备份。需要注意的只是，应该确认备份的过程中没有用户提交修改，具体
的做法可以是停止CVS服务器或者使用锁等等。恢复时只需要把这些文件按原来的目录结构存放好，因为CVS的每一个模块都是单独的一个目录，与其他模块和
目录没有任何瓜葛，相当方便。甚至只需要在仓库中删除一个目录或者文件，便可以删除该模块的一些内容，不过并不建议这么做，使用CVS的删除功能将会有一
个历史记录，而对仓库的直接删除不留任何痕迹，这对项目管理是不利的。移动仓库与备份相似，只需要把该模块的目录移动到新的路径，便可以使用了。
<br>
如果不幸在备份之后有过一些修改并且执行了提交，当服务器出现问题需要恢复源代码仓库时，开发者提交新的修改就会出现版本不一致的错误。此时只需要把CVS相关的目录和文件删除，即可把新的修改提交。
<br>

<br>
6．更进一步的管理
<br>
CVSROOT目录下还有很多其他功能，其中最重要的就是modules文件。这个文件定义了源代码库的模块，下面是一个例子：
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">Linux    Linux
<br>
Kernel   Linux/kernel</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
这个文件的内容按行排列，每一行定义一个模块，首先是模块名，然后是模块路径，这是相对于CVS根目录的路径。它定义了两个模块，第一个是Linux模块，它位于Linux目录中，第二个是Kernel模块，这是Linux模块的子模块。
<br>
modules文件并非必须的，它的作用相当于一个索引，部分CVS客户端软件通过它可以快速找到相应的模块，比如WinCVS。
<br>

<br>
7．协同开发的问题
<br>默认方式下，CVS允许多个用户编辑同一个文件，这对一个协作良好的团队来说不会有什么问题，因为多个开发者同时修改同一个文件的同一部分是不正
常的，这在项目管理中就应该避免，出现这种情况说明项目组内部没有统一意见。而多个开发者修改文件的不同部分，CVS可以很好的管理。
<br>如果觉得这种方式难以控制，CVS也提供了解决办法，可以使用cvs admin
-l进行锁定，这样一个开发者正在做修改时CVS就不会允许其他用户checkout。这里顺便说明一下文件格式的问题，对于文本格式，CVS可以进行历
史记录比较、版本合并等工作，而二进制文件不支持这个操作，比如word文档、图片等就应该以二进制方式提交。对于二进制方式，由于无法进行合并，在无法
保证只有一个用户修改文件的情况下，建议使用加锁方式进行修改。必须注意的是，修改完毕记得解锁。
<br>
从1.6版本开始，CVS引入了监视的概念，这个功能可以让用户随时了解当前谁在修改文件，并且CVS可以自动发送邮件给每一个监视的用户告知最新的更新。
<br>

<br>
8．建立多个源代码仓库
<br>
如果需要管理多个开发组，而这些开发组之间不能互相访问，可以有2个办法：
<br>
a．共用一个端口，需要修改cvspserver文件，给server_args指定多个源代码路径，即多个—allow-root参数。由于xinetd的server_args长度有限制，可以在cvspserver文件中把服务器的设置重定向到另外一个文件，如：
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">server = /home/cvsroot/cvs.run</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
然后创建/home/cvsroot/cvs.run文件，该文件必须可执行，内容格式为：
<br>

<br>
</span>
<table align="center" border="0" cellpadding="3" cellspacing="1" width="90%">

<tbody><tr> 	  <td><span class="genmed"><b>代码:</b></span></td>
	</tr>
	<tr>
	  <td class="code">#!/bin/bash
<br>
/usr/bin/cvs -f \
<br>
--allow-root=/home/cvsroot/src1 \
<br>
--allow-root=/home/cvsroot/src2 \
<br>
pserver</td>
	</tr>
</tbody>
</table>

<span class="postbody">
<br>

<br>
注意此时源代码仓库不再是/home/cvsroot，进行初始化的时候要分别对这两个仓库路径进行初始化，而不再对/home/cvsroot路径进行初始化。
<br>
b．采用不同的端口提供服务
<br>
重复第2步和第3步，为不同的源代码仓库创建不同服务名的启动脚本，并为这些服务名指定不同的端口，初始化时也必须分别进行初始化。</span><img src ="http://www.cnitblog.com/roguishangel/aggbug/3756.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/roguishangel/" target="_blank">牙刷</a> 2005-11-01 00:45 <a href="http://www.cnitblog.com/roguishangel/articles/3756.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>对等网络(P2P)的研究与进展 改变因特网的技术</title><link>http://www.cnitblog.com/roguishangel/articles/3754.html</link><dc:creator>牙刷</dc:creator><author>牙刷</author><pubDate>Mon, 31 Oct 2005 15:40:00 GMT</pubDate><guid>http://www.cnitblog.com/roguishangel/articles/3754.html</guid><wfw:comment>http://www.cnitblog.com/roguishangel/comments/3754.html</wfw:comment><comments>http://www.cnitblog.com/roguishangel/articles/3754.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/roguishangel/comments/commentRss/3754.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/roguishangel/services/trackbacks/3754.html</trackback:ping><description><![CDATA[作者： 谢东亮 程时端 阙喜戎 | 日期： 2005-05-23<br>
<br>
&nbsp; 对等网络(P2P)被美国《财富》杂志称为改变因特网发展的四大新技术之一，甚至被认为是无线宽带互联网的未来技术。 <br>
 
<p>　P2P技术不仅为个人用户提供了前所未有的自由和便利，同时也试图有效地整合互联网的潜在资源，将基于网页的互联网转变成动态存取、自由交互的海量信息网络。 <br> </p>
<p>　P2P技术的发展以及P2P与网格技术的结合，将影响整个计算机网络的概念和人们的信息获取模式，真正实现“网络就是计算机，计算机就是网络”的梦想。 <br> </p>
<p>　　作为改变现有Internet应用模式的主要技术之一，计算机对等网络(P2P)是目前新一代互联网技术研究的热点之一。 <br> </p>
<p>　　自1999年以来，P2P的研究得到了国内外学术界和商业组织的广泛关注，同时，由于P2P本质特性不可避免地存在着许多社会、法律和技术上的问题，在学术界和<wbr>产业界也一直存在着一些怀疑的力量，这在很长一段时期使人们难以对P2P做出一个准确和公平的判断。 <br> </p>
<p>　　本文较为完整地分析了P2P网络的4种典型结构，并对P2P的主要应用模式、存在的问题以及可能的发展方向进行简要阐述。 <br> </p>
<p>1 P2P网络模型 <br> 　　P2P网络是一种具有较高扩展性的分布式系统结构，其对等概念是指网络中的物理节点在逻辑上具有相同的地位，而并非处理能力的对等。以Napster软件为代表<wbr>的P2P技术其实质在于将互联网的集中管理模式引向分散管理模式，将内容从中央单一节点引向网络的边缘，从而充分利用互联网中众多终端节点所蕴涵的处理能力和潜<wbr>在资源。相对于传统的集中式客户/服务器(C/S)模型，P2P弱化了服务器的概念，系统中的各个节点不再区分服务器和客户端的角色关系，每个节点既可请求服务<wbr>，也可提供服务，节点之间可以直接交换资源和服务而不必通过服务器。 <br> </p>
<p>　　P2P系统最大的特点就是用户之间直接共享资源，其核心技术就是分布式对象的定位机制，这也是提高网络可扩展性、解决网络带宽被吞噬的关键所在。迄今为止，P2<wbr>P网络已经历了三代不同网络模型，各种模型各有优缺点，有的还存在着本身难以克服的缺陷，因此在目前P2P技术还远未成熟的阶段，各种网络结构依然能够共存，甚<wbr>至呈现相互借鉴的形式。 <br> </p>
<p>1.1 集中目录式结构 <br> 　　集中目录式P2P结构是最早出现的P2P应用模式，因为仍然具有中心化的特点也被称为非纯粹的P2P结构。用于共享MP3音乐文件的Napster是其中最典型<wbr>的代表，其用户注册与文件检索过程类似于传统的C/S模式，区别在于所有资料并非存储在服务器上，而是存贮在各个节点中。查询节点根据网络流量和延迟<wbr>等信息选择合适的节点建立直接连接，而不必经过中央服务器进行。这种网络结构非常简单，但是它显示了P2P系统信息量巨大的优势和吸引力，同时也揭示了P2P系<wbr>统本质上所不可避免的两个问题：法律版权和资源浪费的问题。 <br> </p>
<p>1.2 纯P2P网络模型 <br> 　　纯P2P模式也被称作广播式的P2P模型。它取消了集中的中央服务器，每个用户随机接入网络，并与自己相邻的一组邻居节点通过端到端连接构成一个逻辑覆盖的网络<wbr>。对等节点之间的内容查询和内容共享都是直接通过相邻节点广播接力传递，同时每个节点还会记录搜索轨迹，以防止搜索环路的产生。 <br> </p>
<p>　　Gnutella模型是现在应用最广泛的纯P2P非结构化拓扑结构，它解决了网络结构中心化的问题，扩展性和容错性较好，但是Gnutella网络中<wbr>的搜索算法以泛洪的方式进行，控制信息的泛滥消耗了大量带宽并很快造成网络拥塞甚至网络的不稳定。同时，局部性能较差的节点可能会导致Gnutella网络被分<wbr>片，从而导致整个网络的可用性较差，另外这类系统更容易受到垃圾信息，甚至是病毒的恶意攻击。 <br> </p>
<p>1.3 混合式网络模型 <br> 　　Kazaa模型是P2P混合模型的典型代表，它在纯P2P分布式模型基础上引入了超级节点的概念，综合了集中式P2P快速查找和纯P2P去中心化的优<wbr>势。Kazaa模型将节点按能力不同(计算能力、内存大小、连接带宽、网络滞留时间等)区分为普通节点和搜索节点两类(也有的进一步分为三类节点，其思想本质相<wbr>同)。其中搜索节点与其临近的若干普通节点之间构成一个自治的簇，簇内采用基于集中目录式的P2P模式，而整个P2P网络中各个不同的簇之间再通过纯P2P的模<wbr>式将搜索节点相连起来，甚至也可以在各个搜索节点之间再次选取性能最优的节点，或者另外引入一新的性能最优的节点作为索引节点来保存整个网络中可以利用的搜索节<wbr>点信息，并且负责维护整个网络的结构。 <br> </p>
<p>　　由于普通节点的文件搜索先在本地所属的簇内进行，只有查询结果不充分的时候，再通过搜索节点之间进行有限的泛洪。这样就极为有效地消除纯P2P结构中使用泛洪算<wbr>法带来的网络拥塞、搜索迟缓等不利影响。同时，由于每个簇中的搜索节点监控着所有普通节点的行为，这也能确保一些恶意的攻击行为能在网络局部得到控制，并且超级<wbr>节点的存在也能在一定程度上提高整个网络的负载平衡。 <br> </p>
<p>　　总的来说，基于超级节点的混合式P2P网络结构比以往有较大程度的改进。 <br> 然而，由于超级节点本身的脆弱性也可能导致其簇内的结点处于孤立状态，因此这种局部索引的方法仍然存在一定的局限性。这导致了结构化的P2P网络模型的出现。 <br> </p>
<p>1.4 结构化网络模型 <br> 　　所谓结构化与非结构化模型的根本区别在于每个节点所维护的邻居是否能够按照某种全局方式组织起来以利于快速查找。结构化P2P模式是一种采用纯分布式的消息传递<wbr>机制和根据关键字进行查找的定位服务，目前的主流方法是采用分布式哈希表(DHT)技术，这也是目前扩展性最好的P2P路由方式之一。由于DHT各节点并不需要<wbr>维护整个网络的信息，只在节点中存储其临近的后继节点信息，因此较少的路由信息就可以有效地实现到达目标节点，同时又取消了泛洪算法。该模型有效地减少了节点信<wbr>息的发送数量，从而增强了P2P网络的扩展性。同时，出于冗余度以及延时的考虑，大部分DHT总是在节点的虚拟标识与关键字最接近的节点上复制备份冗余信息，这<wbr>样也避免了单一节点失效的问题。 <br> </p>
<p>　　目前基于DHT的代表性的研究项目主要包括加州大学伯克利分校的CAN项目和Tapestry项目，麻省理工学院的Chord项目、IRIS项目，以及微软研究<wbr>院的Pastry项目等。这些系统一般都假定节点具有相同的能力，这对于规模较小的系统较为有效。但这种假设并不适合大规模的Internet部署。同时基于D<wbr>HT的拓扑维护和修复算法也比Gnutella模型和Kazaa模型等无结构的系统要复杂得多，甚至在Chord项目中产生了“绕路”的问题。事实上，目前大量<wbr>实际应用还大都是基于无结构的拓扑和泛洪广播机制，现在大多采用DHT方式的P2P系统缺乏在Internet中大规模真实部署的实例，成功应用还比较少见。 <br> </p>
<p>2 P2P网络应用模式 <br> 　　Internet最初产生和发展的一个主动力就是资源共享，也正是文件交换的需求直接导致了P2P技术的兴起，这是P2P最初也是最成功的应用之一，也正是针对<wbr>这类应用的Napster使得人们在客户/服务器模式下开始重新认识P2P思想对人们使用网络习惯的影响。 <br> </p>
<p>　　随着人们对P2P思想的理解和技术的发展，作为一种软件架构，P2P还可以被开发出种类繁多的应用模式，除了最初的文件交换之外，还出现了一些分布式存储、深度<wbr>搜索、分布式计算、个人即时通信和协同工作等新颖应用。其中最著名的例子是基于分布式计算的搜索外星文明SETI@home科学实验，每个志愿参加者只需下载并<wbr>运行类似屏幕保护的方式，就可以贡献自己闲置的计算能力，参与分析Arecibo射电望远镜的无线电磁波数据并回送计算数据，截至2004年12月，已有528<wbr>万志愿者参加进来，获得了相当于216万年的CPU时间，仅一天的综合计算就相当于67.46Tflops运算。另外，随着SUN公司将其JXTA协议扩展到诸<wbr>如个人数字助理(PDA)和移动电话等手持终端上，并允许人们屏蔽具体的物理平台进行资料共享和文件交换等，P2P技术在移动通信和智能网领域也开始呈现出较大<wbr>应用前景。 <br> </p>
<p>3 P2P网络存在的问题 <br> 　　P2P最大的优点在于能够提供可靠的信息查询，但从社会和法律意义来说，绝大多数的P2P服务都将不可避免地遇到知识产权冲突，也可能成为一些非法内容传播的平<wbr>台。同时由于缺乏中心监管以及自由平等的动态特性，自组织的P2P网络在技术层面也有许多难以解决的问题。 <br> </p>
<p>　　从某种意义上来说，P2P网络和人际网络具有一定的相似性。一般来说，每个P2P网络都是众多参与者按照共同兴趣组建起来的一个虚拟组织，节点之间存在着一种假<wbr>定的相互信任关系，但随着P2P网络规模的扩大，这些P2P节点本质所特有的平等自由的动态特性往往与网络服务所需要的信任协作模型之间产生矛盾。激励作用的缺<wbr>失使节点间更多表现出“贪婪”、“抱怨”和“欺诈”的自私行为，因此P2P中预先假设的信任机制实际上非常脆弱，同时这种信任也难以在节点之间进行推理，导致了<wbr>全局性信任的缺乏，这直接影响了整个网络的稳定性与可用性。此外，相对于传统客户/服务器模式的服务器可以做主动和被动的防御，由于P2P节点安全防护手段的匮<wbr>乏以及P2P协议缺乏必要的认证机制和计算机操作系统的安全漏洞，安全问题在P2P网络中更为严重，这将直接影响P2P的大规模商用。另外，P2P网络中的节点<wbr>本身往往是计算能力相差较大的异构节点，每一个节点都被赋予了相同的职责而没有考虑其计算能力和网络带宽，局部性能较差的点将会导致整体网络性能的恶化，在这种<wbr>异构节点的环境中难以实现优化的资源管理和负载平衡。同时，由于用户加入离开P2P网络的随意性使得用户获得目标文件具有不确定性，导致许多并非必要的文件下载<wbr>，而造成大量带宽资源的滥用。特别是大多数P2P用户更喜欢传送音频、视频这些较大的媒体文件，这将使得带宽浪费问题更为突出，尤其在中国大量的用户还是拨号用<wbr>户，较窄的带宽也成为P2P应用难以逾越的障碍。 <br> </p>
<p>4 结论与展望 <br> 　　P2P技术在最近几年获得了高速的发展，也出现了较多应用，但截至目前，P2P中仍有很多的关键技术问题并没有得到解决，其中最典型的就是带宽吞噬、网络可扩展<wbr>性差和路由效率低下等问题。这导致P2P至少在目前的技术水平而言只能是一种小范围不可靠的应用或是满足特定任务需求的专门应用。并且，作为一种潜在的商业应用<wbr>，如何在P2P网络中有效地保护知识产权以及如何设计盈利模式将会面临更为严格的考验。 <br> </p>
<p>　　未来的网络将呈现大规模分布式、全球性计算和全球性存储的特征，从长远的趋势来看，对于访问和传输服务的需求必将远远大于对于计算功能的需要。尽管P2P技术现<wbr>在还不成熟，但是迄今为止，至少在理论上P2P仍是最有吸引力的个人通信技术。尤其是P2P与网格技术的结合将是分布式计算技术最有吸引力的发展趋势，虽然现在<wbr>还没有成熟的方案，但随着分布式系统经典问题的解决以及优化的资源动态分配和资源恢复技术的成熟，P2P与网格技术必将结合起来以影响整个计算机网络的概念和人<wbr>们的信息获取模式。 </p>
<br>
<img src ="http://www.cnitblog.com/roguishangel/aggbug/3754.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/roguishangel/" target="_blank">牙刷</a> 2005-10-31 23:40 <a href="http://www.cnitblog.com/roguishangel/articles/3754.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>