<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Boulevard Of Broken Dreams &#187; Develop</title>
	<atom:link href="http://www.ray77.com/category/develop/feed" rel="self" type="application/rss+xml" />
	<link>http://www.ray77.com</link>
	<description>Walking alone ... Waiting alone ...</description>
	<lastBuildDate>Mon, 10 May 2010 14:24:39 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
<xhtml:meta xmlns:xhtml="http://www.w3.org/1999/xhtml" name="robots" content="noindex" />
		<item>
		<title>VC++存储过程应用技巧</title>
		<link>http://www.ray77.com/visual-cpp-procedure-exert-skills.html</link>
		<comments>http://www.ray77.com/visual-cpp-procedure-exert-skills.html#comments</comments>
		<pubDate>Mon, 06 Apr 2009 16:07:36 +0000</pubDate>
		<dc:creator>Rock</dc:creator>
				<category><![CDATA[Develop]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[visual studio]]></category>

		<guid isPermaLink="false">http://www.ray77.com/%e5%ad%98%e5%82%a8%e8%bf%87%e7%a8%8b.html</guid>
		<description><![CDATA[　　很多朋友告诉我他数据库学得很好，对数据库的各类操作都相当熟练，没有难不到他们的SQL语句。然而他们写出的语句效率却并不是让人满意，一个读写就已经如此，要是成千上万频繁的数据库更新的话那个队列可能都要排到火星上去了。再者提到存储过程也是没人去做详细的了解，就更不提应用了。
　　存储过程（Stored Procedure）是一组为了完成特定功能的SQL语句集，经编译后存储在数据库中。用户通过指定存储过程的名字并给出参数（如果该存储过程带有参数）来执行它。存储过程是数据库中的一个重要对象，任何一个设计良好的数据库应用程序都应该用到存储过程。
　　平常对于一条SQL语句需要先编译然后再执行，然后才能返回用户所需要的结果，但如果对于相同的SQL语句进行频繁多次的使用的话，每一次都重新编译然后执行，效率显然是大打折扣的。而正如百科里存储过程的介绍所说，能够将编译好的SQL语句存储在数据库中，之后每次调用便略过了编译这个步骤，只需要向存储过程传递参数（输入或输出）就能够得到用户需要的数据结果。当然，这对小量的数据库读写感觉不出来，但如果是大量的数据库操作，存储过程的优势就能够相当明显的体现出来。
　　接下来我主要讲的是利用Visual C++对存储过程的调用。

　　首先我们得引入msado15.dll动态链接库，他包含了ADO编程的一系列函数。
　　#import &#8220;c:\program files\common files\system\ado\msado15.dll&#8221; no_namespace rename(&#8220;EOF&#8221;,&#8221;adoEOF&#8221;)　　//将EOF改为adoEOF是为了避免与文件的结束符号混淆
　　接下来就需要初始化Com库了，这里有两种方法，一种是
　　::CoInitialize(NULL);
　　另一种是
　　AfxOleInit();
　　这两种区别在于AfxOleInit附带一些其它资源的支持，如果不需要OLE的一些功能的话用CoInitialize就可以了。另外一点区别在于用CoInitialize最后需要释放（CoUninitialize），而AfxOleInit则会自动完成。
　　初始化完成COM库之后就可以开始建立数据库的连接了。
　　_ConnectionPtr m_pConn;//数据库的Com智能指针
　　m_pConn.CreateInstance(&#8220;ADODB.Connection&#8221;);
　　//打开本地（localhost）上的TestDb数据库。利用sa身份与口令123456登陆。
　　m_pConn-&#62;Open(&#8220;Driver={SQL Server};Server=localhost;Database=TestDB;UID=sa;PWD=123456&#8243;,&#8221;",&#8221;",adModeUnknown);
　　有人会疑惑了，为什么m_pConn可以用对象的“.”又可以用指针的“-&#62;”？其实在C++里有一个叫“智能指针”的另类，既拥有对象的属性，又拥有指针的功能。像_ConnectionPtr，_RecordsetPtr等等都归属于这一范畴。
　　到这里，我先用企业管理器创建一个存储过程，存储过程的名称为ProcTest。
　　CREATE PROCEDURE ProcTest 
　　　　@tusername    char(16),
　　　　@tpassword    char(16),
　　　　@retval        int        output
　　AS 
　　　　 if exists(select * from Account where username=@tusername)
　　　　　　BEGIN 
　　　　　　 if (select password from Account where username=@tusername)=@tpassword
　　　　　　　　set @retval=0
　　　　　　else
　　　　　　　　set @retval=1
　　　　　　END
　　　　else
　　　　　　set @retval=2
　　GO
　　这个存储过程的功能是通过传入用户名（tusername）与密码（tpassword），返回验证结果（retval）。如果帐号与密码都匹配便赋retval的值为0，密码错误赋retval的值为1，用户不存在赋retval的值为2。
　　然后就开始创建执行存储过程的命令对象了。
　　_CommandPtr m_pCmd;
　　m_pCmd.CreateInstance(&#8220;ADODB.Command&#8221;);
　　m_pCmd-&#62;ActiveConnection = m_pConn;
　　m_pCmd-&#62;CommandText = &#8220;ProcTest&#8221;;  //存储过程为ProcTest
　　接下来创建三个参数，分别是Input用户名，Input密码，Output验证结果。
　　_ParameterPtr m_pParam_username;
　　m_pParam_username.CreateInstance(&#8220;ADODB.Parameter&#8221;);
　　m_pParam_username = m_pCmd-&#62;CreateParameter(&#8220;tusername&#8221;,adChar,adParamInput,16,&#8221;rock&#8221;);
　　_ParameterPtr m_pParam_password;
　　m_pParam_password.CreateInstance(&#8220;ADODB.Parameter&#8221;);
　　m_pParam_password = m_pCmd-&#62;CreateParameter(&#8220;tpassword&#8221;,adChar,adParamInput,16,&#8221;123456&#8243;);
　　_ParameterPtr m_pParam_result;
　　m_pParam_result.CreateInstance(&#8220;ADODB.Parameter&#8221;);
　　m_pParam_result = m_pCmd-&#62;CreateParameter(&#8220;retval&#8221;,adChar,adParamOutput,30,&#8221;");
　　可以注意到两个是输入（adParamInput），一个是输出（adParamOutput），另外，如果是输入项第一个参数可以直接赋空，只需要在接下来的追加按存储过程里变量创建的顺序就可以了，但是做为良好的代码习惯，而且便于检查与阅读，最好还是赋上变量名。
　　接下来追加m_pCmd里面去吧。
　　m_pCmd-&#62;Parameters-&#62;Append(m_pParam_username);
　　m_pCmd-&#62;Parameters-&#62;Append(m_pParam_password);
　　m_pCmd-&#62;Parameters-&#62;Append(m_pParam_result);
　　最后就可以执行了。
　　m_pCmd-&#62;Execute(NULL,NULL,adCmdStoredProc);
　　之后通过
　　m_pCmd-&#62;Parameters-&#62;GetItem(_variant_t(“retval”))-&#62;GetValue();
　　就可以获得返回值了。简单吧？
　　这两天抽空写了两个类封装了下数据库的基本操作与存储过程，使用的时候只要在适当的地方加入头文件（DbOpt.h）然后建立对象就可以了，方法很简单。
点击下载
]]></description>
			<content:encoded><![CDATA[<p>　　很多朋友告诉我他数据库学得很好，对数据库的各类操作都相当熟练，没有难不到他们的SQL语句。然而他们写出的语句效率却并不是让人满意，一个读写就已经如此，要是成千上万频繁的数据库更新的话那个队列可能都要排到火星上去了。再者提到存储过程也是没人去做详细的了解，就更不提应用了。</p>
<blockquote><p>　　存储过程（Stored Procedure）是一组为了完成特定功能的SQL语句集，经编译后存储在数据库中。用户通过指定存储过程的名字并给出参数（如果该存储过程带有参数）来执行它。存储过程是数据库中的一个重要对象，任何一个设计良好的数据库应用程序都应该用到存储过程。</p></blockquote>
<p>　　平常对于一条SQL语句需要先编译然后再执行，然后才能返回用户所需要的结果，但如果对于相同的SQL语句进行频繁多次的使用的话，每一次都重新编译然后执行，效率显然是大打折扣的。而正如百科里存储过程的介绍所说，能够将编译好的SQL语句存储在数据库中，之后每次调用便略过了编译这个步骤，只需要向存储过程传递参数（输入或输出）就能够得到用户需要的数据结果。当然，这对小量的数据库读写感觉不出来，但如果是大量的数据库操作，存储过程的优势就能够相当明显的体现出来。</p>
<p>　　接下来我主要讲的是利用Visual C++对存储过程的调用。</p>
<p><span id="more-829"></span></p>
<p>　　首先我们得引入msado15.dll动态链接库，他包含了ADO编程的一系列函数。</p>
<blockquote><p>　　#import &#8220;c:\program files\common files\system\ado\msado15.dll&#8221; no_namespace rename(&#8220;EOF&#8221;,&#8221;adoEOF&#8221;)　　//将EOF改为adoEOF是为了避免与文件的结束符号混淆</p></blockquote>
<p>　　接下来就需要初始化Com库了，这里有两种方法，一种是</p>
<blockquote><p>　　::CoInitialize(NULL);</p></blockquote>
<p>　　另一种是</p>
<blockquote><p>　　AfxOleInit();</p></blockquote>
<p>　　这两种区别在于AfxOleInit附带一些其它资源的支持，如果不需要OLE的一些功能的话用CoInitialize就可以了。另外一点区别在于用CoInitialize最后需要释放（CoUninitialize），而AfxOleInit则会自动完成。</p>
<p>　　初始化完成COM库之后就可以开始建立数据库的连接了。</p>
<blockquote><p>　　_ConnectionPtr m_pConn;//数据库的Com智能指针</p>
<p>　　m_pConn.CreateInstance(&#8220;ADODB.Connection&#8221;);</p>
<p>　　//打开本地（localhost）上的TestDb数据库。利用sa身份与口令123456登陆。</p>
<p>　　m_pConn-&gt;Open(&#8220;Driver={SQL Server};Server=localhost;Database=TestDB;UID=sa;PWD=123456&#8243;,&#8221;",&#8221;",adModeUnknown);</p></blockquote>
<p>　　有人会疑惑了，为什么m_pConn可以用对象的“.”又可以用指针的“-&gt;”？其实在C++里有一个叫“智能指针”的另类，既拥有对象的属性，又拥有指针的功能。像_ConnectionPtr，_RecordsetPtr等等都归属于这一范畴。</p>
<p>　　到这里，我先用企业管理器创建一个存储过程，存储过程的名称为ProcTest。</p>
<blockquote><p>　　CREATE PROCEDURE ProcTest <br />
　　　　@tusername    char(16),<br />
　　　　@tpassword    char(16),<br />
　　　　@retval        int        output<br />
　　AS <br />
　　　　 if exists(select * from Account where username=@tusername)<br />
　　　　　　BEGIN <br />
　　　　　　 if (select password from Account where username=@tusername)=@tpassword<br />
　　　　　　　　set @retval=0<br />
　　　　　　else<br />
　　　　　　　　set @retval=1<br />
　　　　　　END<br />
　　　　else<br />
　　　　　　set @retval=2<br />
　　GO</p></blockquote>
<p>　　这个存储过程的功能是通过传入用户名（tusername）与密码（tpassword），返回验证结果（retval）。如果帐号与密码都匹配便赋retval的值为0，密码错误赋retval的值为1，用户不存在赋retval的值为2。</p>
<p>　　然后就开始创建执行存储过程的命令对象了。</p>
<blockquote><p>　　_CommandPtr m_pCmd;</p>
<p>　　m_pCmd.CreateInstance(&#8220;ADODB.Command&#8221;);</p>
<p>　　m_pCmd-&gt;ActiveConnection = m_pConn;</p>
<p>　　m_pCmd-&gt;CommandText = &#8220;ProcTest&#8221;;  //存储过程为ProcTest</p></blockquote>
<p>　　接下来创建三个参数，分别是Input用户名，Input密码，Output验证结果。</p>
<blockquote><p>　　_ParameterPtr m_pParam_username;</p>
<p>　　m_pParam_username.CreateInstance(&#8220;ADODB.Parameter&#8221;);</p>
<p>　　m_pParam_username = m_pCmd-&gt;CreateParameter(&#8220;tusername&#8221;,adChar,adParamInput,16,&#8221;rock&#8221;);</p>
<p>　　_ParameterPtr m_pParam_password;</p>
<p>　　m_pParam_password.CreateInstance(&#8220;ADODB.Parameter&#8221;);</p>
<p>　　m_pParam_password = m_pCmd-&gt;CreateParameter(&#8220;tpassword&#8221;,adChar,adParamInput,16,&#8221;123456&#8243;);</p>
<p>　　_ParameterPtr m_pParam_result;</p>
<p>　　m_pParam_result.CreateInstance(&#8220;ADODB.Parameter&#8221;);</p>
<p>　　m_pParam_result = m_pCmd-&gt;CreateParameter(&#8220;retval&#8221;,adChar,adParamOutput,30,&#8221;");</p></blockquote>
<p>　　可以注意到两个是输入（adParamInput），一个是输出（adParamOutput），另外，如果是输入项第一个参数可以直接赋空，只需要在接下来的追加按存储过程里变量创建的顺序就可以了，但是做为良好的代码习惯，而且便于检查与阅读，最好还是赋上变量名。</p>
<p>　　接下来追加m_pCmd里面去吧。</p>
<blockquote><p>　　m_pCmd-&gt;Parameters-&gt;Append(m_pParam_username);</p>
<p>　　m_pCmd-&gt;Parameters-&gt;Append(m_pParam_password);</p>
<p>　　m_pCmd-&gt;Parameters-&gt;Append(m_pParam_result);</p></blockquote>
<p>　　最后就可以执行了。</p>
<blockquote><p>　　m_pCmd-&gt;Execute(NULL,NULL,adCmdStoredProc);</p></blockquote>
<p>　　之后通过</p>
<blockquote><p>　　m_pCmd-&gt;Parameters-&gt;GetItem(_variant_t(“retval”))-&gt;GetValue();</p></blockquote>
<p>　　就可以获得返回值了。简单吧？</p>
<p>　　这两天抽空写了两个类封装了下数据库的基本操作与存储过程，使用的时候只要在适当的地方加入头文件（DbOpt.h）然后建立对象就可以了，方法很简单。</p>
<p><a href="http://www.ray77.com/wp-content/uploads/2009/04/dbopt.rar">点击下载</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.ray77.com/visual-cpp-procedure-exert-skills.html/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>利用SOCKET发送与接收结构体(C++)</title>
		<link>http://www.ray77.com/use-socket-send-and-receive-struct.html</link>
		<comments>http://www.ray77.com/use-socket-send-and-receive-struct.html#comments</comments>
		<pubDate>Thu, 02 Apr 2009 03:51:12 +0000</pubDate>
		<dc:creator>Rock</dc:creator>
				<category><![CDATA[Develop]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[SOCKET]]></category>
		<category><![CDATA[网络编程]]></category>

		<guid isPermaLink="false">http://www.ray77.com/use-socket-send-and-receive-struct.html</guid>
		<description><![CDATA[　　首先庆祝下Rock博客(ray77.com)的Google PR今天荣升到2了，对于Google的工程师们PR似乎只是个小游戏，但还是挺值得高兴的。以后Rock只发布原创的东西了，即使要转载也只转载一些相当有意义的文章，充数的杂闻就可以PASS了   。In my opinion , It is simply a weblog , a personal blog …
　　再唠叨下工作的事情。摩力游(moliyo)的人力资源部与程序部的高管似乎比较认可我的资质与技术水平，但学校还有些麻烦的事情需要请假处理这让他们比较难接受，不管怎么样，Rock一直在尽量协商，但愿不会影响到复试。对摩力游的中级客户端工程师还是相当憧憬的。  
　　嗯，接下来进入主题了，谈下C++中如何利用socket发送结构体，此教程只供网络编程菜鸟参考，高手可以绕路了。  

 　　在网络通讯过程中往往涉及一些有关联的参数传递，例如数组，结构体之类的。对于结构体其实方法挺简单，由于结构体对象在内存中分配的空间都是连续的，所以可以将整个结构体直接转化成字符串发送，到了接收方再将这个字符串还原成结构体就大功告成了。
　　首先，我们建立一个结构体。
　　struct UsrData{
　　char usr_id[16];
　　char usr_pwd[16];
　　char usr_nickname[16];
　　};
　　当然，这个结构体在发送方与接收方都必须声明。
　　接下来创建对象并初始化，然后发送。
　　UsrData sendUser;
　　memcpy( sendUser.usr_id, “100001”, sizeof(“100001”) );
　　memcpy( sendUser.usr_pwd, “123456”, sizeof(“123456”) );
　　memcpy( sendUser.usr_nickname, “Rock”, sizeof(“Rock”) );
　　send( m_socket, (char *)&#38;sendUser, sizeof(UsrData), 0 );
　　这样发送方就已经将这个mUser对象以字符串的形式发送出去了。
　　最后在接收方做接收。
　　char buffer[1024];
　　UsrData recvUser;
　　recv( m_socket, buffer, sizeof(buffer), ...]]></description>
			<content:encoded><![CDATA[<p>　　首先庆祝下Rock博客(ray77.com)的Google PR今天荣升到2了，对于Google的工程师们PR似乎只是个小游戏，但还是挺值得高兴的。以后Rock只发布原创的东西了，即使要转载也只转载一些相当有意义的文章，充数的杂闻就可以PASS了 <img src='http://www.ray77.com/wp-includes/images/smilies/icon_wink.gif' alt=':wink:' class='wp-smiley' />  。In my opinion , It is simply a weblog , a personal blog …</p>
<p>　　再唠叨下工作的事情。摩力游(moliyo)的人力资源部与程序部的高管似乎比较认可我的资质与技术水平，但学校还有些麻烦的事情需要请假处理这让他们比较难接受，不管怎么样，Rock一直在尽量协商，但愿不会影响到复试。对摩力游的中级客户端工程师还是相当憧憬的。 <img src='http://www.ray77.com/wp-includes/images/smilies/icon_neutral.gif' alt=':neutral:' class='wp-smiley' /> </p>
<p>　　嗯，接下来进入主题了，谈下C++中如何利用socket发送结构体，此教程只供网络编程菜鸟参考，高手可以绕路了。 <img src='http://www.ray77.com/wp-includes/images/smilies/icon_exclaim.gif' alt=':!:' class='wp-smiley' /> </p>
<p><span id="more-819"></span><br />
 　　在网络通讯过程中往往涉及一些有关联的参数传递，例如数组，结构体之类的。对于结构体其实方法挺简单，由于结构体对象在内存中分配的空间都是连续的，所以可以将整个结构体直接转化成字符串发送，到了接收方再将这个字符串还原成结构体就大功告成了。</p>
<p>　　首先，我们建立一个结构体。</p>
<blockquote><p>　　struct UsrData{</p>
<p>　　char usr_id[16];</p>
<p>　　char usr_pwd[16];</p>
<p>　　char usr_nickname[16];</p>
<p>　　};</p></blockquote>
<p>　　当然，这个结构体在发送方与接收方都必须声明。</p>
<p>　　接下来创建对象并初始化，然后发送。</p>
<blockquote><p>　　UsrData sendUser;</p>
<p>　　memcpy( sendUser.usr_id, “100001”, sizeof(“100001”) );</p>
<p>　　memcpy( sendUser.usr_pwd, “123456”, sizeof(“123456”) );</p>
<p>　　memcpy( sendUser.usr_nickname, “Rock”, sizeof(“Rock”) );</p>
<p>　　send( m_socket, (char *)&amp;sendUser, sizeof(UsrData), 0 );</p></blockquote>
<p>　　这样发送方就已经将这个mUser对象以字符串的形式发送出去了。</p>
<p>　　最后在接收方做接收。</p>
<blockquote><p>　　char buffer[1024];</p>
<p>　　UsrData recvUser;</p>
<p>　　recv( m_socket, buffer, sizeof(buffer), 0 );</p>
<p>　　memcpy( &amp;recvUser, buffer, sizeof(buffer) );</p></blockquote>
<p>　　这样得到的recvUser对象里的数据与sendUser相同了。具体原因其实很简单，就是因为结构体对象的内存区域连续，同时每个成员的区块大小都分配好了，当接收完自己的区块，其实自己的数据已经接收完成。挺简单的，但还是挺有用的。 <img src='http://www.ray77.com/wp-includes/images/smilies/icon_smile.gif' alt=':smile:' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.ray77.com/use-socket-send-and-receive-struct.html/feed</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>09年2月编程语言排行榜：商业编程语言的王者之争</title>
		<link>http://www.ray77.com/program-language-200902-rank.html</link>
		<comments>http://www.ray77.com/program-language-200902-rank.html#comments</comments>
		<pubDate>Sun, 22 Feb 2009 15:07:40 +0000</pubDate>
		<dc:creator>Rock</dc:creator>
				<category><![CDATA[Develop]]></category>
		<category><![CDATA[2009]]></category>
		<category><![CDATA[排行]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[语言]]></category>

		<guid isPermaLink="false">http://www.ray77.com/09%e5%b9%b42%e6%9c%88%e7%bc%96%e7%a8%8b%e8%af%ad%e8%a8%80%e6%8e%92%e8%a1%8c%e6%a6%9c%ef%bc%9a%e5%95%86%e4%b8%9a%e7%bc%96%e7%a8%8b%e8%af%ad%e8%a8%80%e7%9a%84%e7%8e%8b%e8%80%85%e4%b9%8b%e4%ba%89.html</guid>
		<description><![CDATA[商业编程语言的王者之争
　　2009年2月Tiobe编程语言排行榜发布，前十位的排名没有太大变化，C语言在取得了08年年度编程语言后增势迅猛，相比去年同期增长了0.98%。本期前20名的榜单中，两种同样以商业数据处理擅长的编程语言COBOL和RPG的排名变化值得我们关注。COBOL本期跌出前20名，本期排在22位（上期排名17）；RPG语言进入前20名，排在第19位（上期排名21）。
 
　　2009年2月榜单

　　COBOL语言
　　COBOL于1960年正式发布，是一种面向数据处理的、面向文件的、面向过程(POL)的高级编程语言，是一种功能很强而又极为冗长的语言。 COBOL适合于商业及数据处理的类似英语的程序设计语言。这种语言可使商业数据处理过程精确表达。经过40多年的不断修改、丰富完善和标准化，COBOL已发展为多种版本的庞大语言，在财会工作、统计报表、计划编制、情报检索、人事管理等数据管理及商业数据处理领域，都有着广泛的应用。世界上70％的数据是用COBOL语言处理的，并且90％的ATM事务处理用的都是COBOL语言。每天在线处理的COBOL事务有300亿次，500强中有 492家（包括全部的100强）使用了COBOL语言。
　　RPG语言
　　RPG是Report Program Generator的缩写，是一种起源于用在DEC及IBM的小型机操作系统中编制报表程序的编程语言。RPG是一种完全过程化程序设计语言。其最新版本 RPG IV由IBM主要的小型机系统AS/400来支持。对中型机上的商业应用程序而言，RPG极有可能成为继COBOL后的第二个使用最多的商业程序设计语言。从本期榜单中，我们也可以看到RPG对COBOL商业编程语言王者地位的挑战。
　　前十名长期趋势

 
　　以下是排名21到50的语言

　　英文原文：http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html
]]></description>
			<content:encoded><![CDATA[<div id="attachment_715" class="wp-caption aligncenter" style="width: 604px"><img class="size-full wp-image-715" title="09年2月编程语言排行榜：商业编程语言的王者之争" src="http://www.ray77.com/wp-content/uploads/2009/02/da058.jpg" alt="da058" width="594" height="164" /><p class="wp-caption-text">商业编程语言的王者之争</p></div>
<p>　　2009年2月Tiobe编程语言排行榜发布，前十位的排名没有太大变化，C语言在取得了08年年度编程语言后增势迅猛，相比去年同期增长了0.98%。本期前20名的榜单中，两种同样以商业数据处理擅长的编程语言COBOL和RPG的排名变化值得我们关注。COBOL本期跌出前20名，本期排在22位（上期排名17）；RPG语言进入前20名，排在第19位（上期排名21）。</p>
<p><span id="more-711"></span> </p>
<p><strong>　　2009年2月榜单</strong></p>
<p style="text-align: center;"><img class="aligncenter" style="display: inline; border-width: 0px;" title="09年2月编程语言排行榜：商业编程语言的王者之争" src="http://www.ray77.com/wp-content/uploads/2009/02/tiobe090201809.jpg" border="0" alt="09年2月编程语言排行榜：商业编程语言的王者之争" width="570" height="596" /></p>
<p><strong>　　COBOL语言</strong></p>
<p>　　COBOL于1960年正式发布，是一种面向数据处理的、面向文件的、面向过程(POL)的高级编程语言，是一种功能很强而又极为冗长的语言。 COBOL适合于商业及数据处理的类似英语的程序设计语言。这种语言可使商业数据处理过程精确表达。经过40多年的不断修改、丰富完善和标准化，COBOL已发展为多种版本的庞大语言，在财会工作、统计报表、计划编制、情报检索、人事管理等数据管理及商业数据处理领域，都有着广泛的应用。世界上70％的数据是用COBOL语言处理的，并且90％的ATM事务处理用的都是COBOL语言。每天在线处理的COBOL事务有300亿次，500强中有 492家（包括全部的100强）使用了COBOL语言。</p>
<p><strong>　　RPG语言</strong></p>
<p>　　RPG是Report Program Generator的缩写，是一种起源于用在DEC及IBM的小型机操作系统中编制报表程序的编程语言。RPG是一种完全过程化程序设计语言。其最新版本 RPG IV由IBM主要的小型机系统AS/400来支持。对中型机上的商业应用程序而言，RPG极有可能成为继COBOL后的第二个使用最多的商业程序设计语言。从本期榜单中，我们也可以看到RPG对COBOL商业编程语言王者地位的挑战。</p>
<p><strong>　　前十名长期趋势</strong></p>
<p style="text-align: center;"><img class="aligncenter" style="display: block; border: 0px;" title="09年2月编程语言排行榜：商业编程语言的王者之争" src="http://www.ray77.com/wp-content/uploads/2009/02/tiobe090202.jpg" border="0" alt="09年2月编程语言排行榜：商业编程语言的王者之争" width="594" height="449" /></p>
<p> </p>
<p><strong>　　以下是排名21到50的语言</strong></p>
<p style="text-align: center;"><img class="aligncenter" style="display: block; border: 0px;" title="09年2月编程语言排行榜：商业编程语言的王者之争" src="http://www.ray77.com/wp-content/uploads/2009/02/tiobe090203.jpg" border="0" alt="09年2月编程语言排行榜：商业编程语言的王者之争" width="270" height="778" /></p>
<p><strong>　　英文原文：<a href="http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html" target="_blank">http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html</a></strong></p>
]]></content:encoded>
			<wfw:commentRss>http://www.ray77.com/program-language-200902-rank.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Windows 2003 搭建虚拟主机服务器</title>
		<link>http://www.ray77.com/windows-2003-build-virtual-host-server.html</link>
		<comments>http://www.ray77.com/windows-2003-build-virtual-host-server.html#comments</comments>
		<pubDate>Sun, 22 Feb 2009 14:47:59 +0000</pubDate>
		<dc:creator>Rock</dc:creator>
				<category><![CDATA[Develop]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[服务器]]></category>
		<category><![CDATA[虚拟主机]]></category>

		<guid isPermaLink="false">http://www.ray77.com/windows-2003-%e6%90%ad%e5%bb%ba%e8%99%9a%e6%8b%9f%e4%b8%bb%e6%9c%ba%e6%9c%8d%e5%8a%a1%e5%99%a8.html</guid>
		<description><![CDATA[Windows 2003 Build Virtual Host Server
　　一直想找一份详细的windows server 2003搭建Web服务器的教程，这篇文章讲得比较基础，也比较详细。同时希望能有份教程把服务器的配置与机房的管理的专业教程，Rock很有兴趣自己购买台服务器搭建Web、FTP、DNS以及邮件的服务器然后放在机房里。同时还希望能够搭建自己的LAMP服务器主机&#8230;哇&#8230;那种感觉绝对是超级无敌爽的&#8230;废话不多说了，对win服务器感兴趣的朋友可以看下这份教程。
　　随着Internet越来越普及，各种各样的虚拟主机也越来越多，由于虚拟主机需要同时向大量不同的用户提供安全的网络应用环境，因此搭建一个虚拟主机平台除了需要架设WEB和FTP服务器， 还涉及到一些虚拟主机环境所特有的问题。以下就对这些特有的问题进行归纳和分析。我个人认为在建立虚拟主机时主要需要注意以下三个大方面:
　　1.用户与磁盘空间和域名的绑定;
　　2.用户使用磁盘空间的容量限制(磁盘配额);
　　3.IIS和用户环境的安全问题(我使用的是Windows Server系统);
　　下面我就以这三个方面在Windows 2003 Enterprise Server具体如何实现做一个详细的介绍。

　　一、用户与磁盘空间和域名的绑定
　　先打开计算机管理(要是服务器已经升成为AD模式，就打开Active Directory用户与计算机)，点击展开本地用户和组，右键单击用户模块分别创建两个用户帐号。我这里建立的用户帐号依次为test1、test2。注意，为了安全考虑，请在组的模块中新建一个组。把用于虚拟主机的用户归纳到里面，并把用户原来隶属于的User组删除。(作为一个称职的管理员，需要注意的小细节是很多的，不然一个错误的细节就会引起灾难)如图1：
图1：建立虚拟主机的用户组
　　建立好帐号后，就在FTP空间目录下先建立一个LocalUser文件夹。然后对应所使用的帐号建立子文件夹。如:要是FTP空间指定的是D: WEB_Space文件夹，那就在这个文件夹下创建一个LocalUser文件夹。接着建立两个子文件夹:test1、test2，如图2：
图2：建立LocalUser文件夹
　　由于刚才我们建立用户帐号时，把它们单独的归纳到一个组中，这时就需要在FTP空间根目录属性的安全选项卡中把这个组添加进去，用户将无法通过FTP访问。如图3：
图3：设置用户组权限
　　接着我们需要建立FTP 用户隔离站点。这个功能是以前版本的IIS中FTP站点所没有的，FTP 用户隔离为 Internet 服务提供商 (ISP) 和应用服务提供商提供了解决方案，使他们可以为客户提供上载文件和 Web 内容的个人 FTP 目录。FTP 用户隔离通过将用户限制在自己的目录中，来防止用户查看或覆盖其他用户的 Web 内容。
　　因为顶层目录就是 FTP 服务的根目录，用户无法浏览目录树的上一层。在特定的站点内，用户能创建、修改或删除文件和文件夹。FTP 用户隔离是站点属性，而不是服务器属性。无法为每个 FTP 站点启动或关闭该属性。所以在建立FTP站点的时候就应该选择好，不然站点建立以后，将无法修改。
　　FTP 用户隔离共有三种模式:
　　不隔离用户:该模式不启用 FTP 用户隔离。该模式的工作方式与以前版本的 IIS 类似。由于在登录到 FTP 站点的不同用户间的隔离尚未实施，该模式最适合于只提供共享内容下载功能的站点或不需要在用户间进行数据访问保护的站点。
　　隔离用户:该模式在用户访问与其用户名匹配的主目录前，根据本机或域帐户验证用户。所有用户的主目录都在单一 FTP 主目录下，每个用户均被安放和限制在自己的主目录中。不允许用户浏览自己主目录外的内容。如果用户需要访问特定的共享文件夹，您可以再建立一个虚拟根目录。该模式不使用 Active Directory 目录服务进行验证。 注意 当使用该模式创建了上百个主目录时，服务器性能会下降。
　　用 Active Directory 隔离用户:该模式根据相应的 ...]]></description>
			<content:encoded><![CDATA[<div class="wp-caption aligncenter" style="width: 610px"><img style="display: inline; border: 0px;" title="Windows 2003搭建虚拟主机服务器" src="http://www.ray77.com/wp-content/uploads/2009/02/post4305441140486644.jpg" border="0" alt="Windows 2003搭建虚拟主机服务器" width="600" height="163" /><p class="wp-caption-text">Windows 2003 Build Virtual Host Server</p></div>
<p>　　一直想找一份详细的windows server 2003搭建Web服务器的教程，这篇文章讲得比较基础，也比较详细。同时希望能有份教程把服务器的配置与机房的管理的专业教程，Rock很有兴趣自己购买台服务器搭建Web、FTP、DNS以及邮件的服务器然后放在机房里。同时还希望能够搭建自己的LAMP服务器主机&#8230;哇&#8230;那种感觉绝对是超级无敌爽的&#8230;废话不多说了，对win服务器感兴趣的朋友可以看下这份教程。</p>
<p>　　随着Internet越来越普及，各种各样的虚拟主机也越来越多，由于虚拟主机需要同时向大量不同的用户提供安全的网络应用环境，因此搭建一个虚拟主机平台除了需要架设WEB和FTP服务器， 还涉及到一些虚拟主机环境所特有的问题。以下就对这些特有的问题进行归纳和分析。我个人认为在建立虚拟主机时主要需要注意以下三个大方面:</p>
<p>　　1.用户与磁盘空间和域名的绑定;<br />
　　2.用户使用磁盘空间的容量限制(磁盘配额);<br />
　　3.IIS和用户环境的安全问题(我使用的是Windows Server系统);</p>
<p>　　下面我就以这三个方面在Windows 2003 Enterprise Server具体如何实现做一个详细的介绍。</p>
<p><span id="more-706"></span></p>
<p><strong>　　一、用户与磁盘空间和域名的绑定</strong></p>
<p>　　先打开计算机管理(要是服务器已经升成为AD模式，就打开Active Directory用户与计算机)，点击展开本地用户和组，右键单击用户模块分别创建两个用户帐号。我这里建立的用户帐号依次为test1、test2。注意，为了安全考虑，请在组的模块中新建一个组。把用于虚拟主机的用户归纳到里面，并把用户原来隶属于的User组删除。(作为一个称职的管理员，需要注意的小细节是很多的，不然一个错误的细节就会引起灾难)如图1：</p>
<div class="wp-caption aligncenter" style="width: 336px"><img style="display: inline; border: 0px;" title="建立虚拟主机的用户组" src="http://www.ray77.com/wp-content/uploads/2009/02/t-1.jpg" border="0" alt="建立虚拟主机的用户组" width="326" height="232" /><p class="wp-caption-text">图1：建立虚拟主机的用户组</p></div>
<p>　　建立好帐号后，就在FTP空间目录下先建立一个LocalUser文件夹。然后对应所使用的帐号建立子文件夹。如:要是FTP空间指定的是D: WEB_Space文件夹，那就在这个文件夹下创建一个LocalUser文件夹。接着建立两个子文件夹:test1、test2，如图2：</p>
<div class="wp-caption aligncenter" style="width: 336px"><img style="display: inline; border: 0px;" title="建立LocalUser文件夹" src="http://www.ray77.com/wp-content/uploads/2009/02/t-2.jpg" border="0" alt="建立LocalUser文件夹" width="326" height="232" /><p class="wp-caption-text">图2：建立LocalUser文件夹</p></div>
<p>　　由于刚才我们建立用户帐号时，把它们单独的归纳到一个组中，这时就需要在FTP空间根目录属性的安全选项卡中把这个组添加进去，用户将无法通过FTP访问。如图3：</p>
<div class="wp-caption aligncenter" style="width: 336px"><img style="display: inline; border: 0px;" title="设置用户组权限" src="http://www.ray77.com/wp-content/uploads/2009/02/t3.jpg" border="0" alt="设置用户组权限" width="326" height="232" /><p class="wp-caption-text">图3：设置用户组权限</p></div>
<p>　　接着我们需要建立FTP 用户隔离站点。这个功能是以前版本的IIS中FTP站点所没有的，FTP 用户隔离为 Internet 服务提供商 (ISP) 和应用服务提供商提供了解决方案，使他们可以为客户提供上载文件和 Web 内容的个人 FTP 目录。FTP 用户隔离通过将用户限制在自己的目录中，来防止用户查看或覆盖其他用户的 Web 内容。</p>
<p>　　因为顶层目录就是 FTP 服务的根目录，用户无法浏览目录树的上一层。在特定的站点内，用户能创建、修改或删除文件和文件夹。FTP 用户隔离是站点属性，而不是服务器属性。无法为每个 FTP 站点启动或关闭该属性。所以在建立FTP站点的时候就应该选择好，不然站点建立以后，将无法修改。</p>
<p>　　FTP 用户隔离共有三种模式:</p>
<p>　　不隔离用户:该模式不启用 FTP 用户隔离。该模式的工作方式与以前版本的 IIS 类似。由于在登录到 FTP 站点的不同用户间的隔离尚未实施，该模式最适合于只提供共享内容下载功能的站点或不需要在用户间进行数据访问保护的站点。</p>
<p>　　隔离用户:该模式在用户访问与其用户名匹配的主目录前，根据本机或域帐户验证用户。所有用户的主目录都在单一 FTP 主目录下，每个用户均被安放和限制在自己的主目录中。不允许用户浏览自己主目录外的内容。如果用户需要访问特定的共享文件夹，您可以再建立一个虚拟根目录。该模式不使用 Active Directory 目录服务进行验证。 注意 当使用该模式创建了上百个主目录时，服务器性能会下降。</p>
<p>　　用 Active Directory 隔离用户:该模式根据相应的 Active Directory 容器验证用户凭据，而不是搜索整个 Active Directory，那样做需要大量的处理时间。将为每个客户指定特定的 FTP 服务器实例，以确保数据完整性及隔离性。当用户对象在 Active Directory 容器内时，可以将 FTPRoot 和 FTPDir 属性提取出来，为用户主目录提供完整路径。</p>
<p>　　如果FTP 服务能成功地访问该路径，则用户被放在代表 FTP 根位置的该主目录中。用户只能看见自己的 FTP 根位置，因此受限制而无法向上浏览目录树。如果 FTPRoot 或 FTPDir 属性不存在，或它们无法共同构成有效、可访问的路径，用户将无法访问。</p>
<p>　　打开Internet信息服务(IIS)管理器，点击展开服务器，右键单击FTP站点，新建一个FTP站点，在建立向导中，因为我的服务器不是Active Directory模式，所以我们选择隔离用户这个选项。如图4：</p>
<div class="wp-caption aligncenter" style="width: 336px"><img style="display: inline; border: 0px;" title="FTP用户隔离选项" src="http://www.ray77.com/wp-content/uploads/2009/02/t-4.jpg" border="0" alt="FTP用户隔离选项" width="326" height="232" /><p class="wp-caption-text">图4：FTP用户隔离选项</p></div>
<p>　　完成建立向导后，开打新建立的FTP站点属性，点击属性中的安全用户选项卡，把允许匿名连接关闭。确定后退出。</p>
<p>　　最后是域名的绑定，域名绑定有两种定义，第一种是直接使用完整域名绑定WEB服务，如在服务器上提供<a href="http://www.test1.com">www.test1.com</a>和www.test2.com空间服务，第二种是开设子域名服务，如提供test1.yesky.com和test2.yesky.com空间服务。这两种在工作原理上是一样的，但第二种在操作上有一些不同。需要自己服务器的DSN支持。</p>
<p>　　首先我们让我们来了解一下关于域名(DNS)的基本知识，在我们要了解DNS，首先要了解几个名词:域名空间;根域;顶级域;二级域;主机名;区域。</p>
<p>　　域名空间:是DNS名的结构统称，他的结构主要是由根域，顶级域，二级域和主机名组成。</p>
<p>　　根域:是处在整个结构的顶级，是用点(.)表示的。由国外的几个公司管理的。55555没有我们的份~郁闷!!)</p>
<p>　　顶级域:是由2-3个英文字母组成，并且有着一定的意义，一般采用相对意义的英文单词缩写或相对代码。如:COM是指商业机构，GOV是指政府机构，CN是指中国……</p>
<p>　　二级域:是由一些域名提供商出租给个人或企业、机构的服务。如<a href="http://www.yesky.com">www.yesky.com</a>这个域名中yesky就属于二级域。</p>
<p>　　主机名:是用来表示Internet或内部网的计算机名称，但大家注意一点，在Internet上，有的时候主机名指代表一台服务器的IP地址，而不是服务器的名称，这一点是和内部网有区别的!</p>
<p>　　区域:是域名空间中的一个离散部分。区域主要是用来把一个域名变成可管理的几个部分，如:www.yesky.com这个域名我们可以把他划分成www和yesky,com这两个部分。这样，我们就可以利用yesky.com这部分生成不同的主机名。如mail.yesky.com;bbs.yesky.com等等。这一点比较重要。</p>
<p>　　DSN服务工作的流程我们可以把它称作名字解析过程，它共分为两种:正向搜索和反向搜索。正向搜索是把一个域名解析成一个IP，我们这里就用Internet上的<a href="http://www.yesky.com">www.yesky.com</a>域名做一个案例。</p>
<p>　　我们先在IE浏览器中输入<a href="http://www.yesky.com">www.yesky.com</a>这个域名，然后计算机将自动把这个域名传递给本地DNS服务器(也就是指在本机网卡属性中TCP/IP协议的DSN服务器输入框里输入的IP地址所对应的服务器)，DNS服务器收到信息后，将在自己的区域表中搜索有没有该域名所对应的IP!有则返回，若没有，它则会把搜索的信息传递给国外的几个根域DSN服务器之一，请求解析该域名。</p>
<p>　　根域DSN服务器则返回一条对COM域DNS服务器的IP地址给本地DNS服务器引用(由于yesky.com的顶级域是COM所以返回COM域DNS服务器的IP地址)，本地DNS服务器在根据IP地址给COM域DNS服务器发送一条<a href="http://www.yesky.com">www.yesky.com</a>域名解析请求的信息，COM域DNS服务器返回一条对yeskyDSN服务器的IP地址指引，然后本地DNS服务器再根据收到的IP地址给yeskyDSN服务器，发送一条www.yesky.com域名解析请求的信息，yeskyDSN服务器根据请求反馈给www的IP地址，本地服务器再把这个IP反馈给我们。这时解析完成，我们也就打开了www.yesky.com的网页。</p>
<p>　　反向搜索正好相反，它是把一个IP地址解析成一个域名，常看见的诸如Windows 2003下的Nslookup命令工具。由于DNS服务是按域名而不是按IP地址索引的，反向搜索一搜索就会搜索所有的信息，很消耗资源。为了避免这种情况，DNS服务创建了一个叫in-addr.arpa的特殊二级域，它使用的是与其他域名空间结构相同的方法，但它不采用域名，而是采用IP地址。</p>
<p>　　注意，要想做Internet上第二种虚拟主机，最好向域名提供商申请DNS转移权，要求提供商把DNS解析权指定到你的DNS服务器上，这样你就能自己利用Windwos Server的DSN服务随意开设子域名了，不然你每次要用一个新的子域名都要向域名提供商申请，那是很麻烦的，而且DSN在自己的服务器上还有很多好处，如开设MAIL服务等等。</p>
<p>　　先来打开DNS管理器，点击展开服务器，在正向查找区域新建一个区域。选择创建主要区域，输入你申请的域名，注意，只要输入我上面所说的区域就可以了，如yesky.com、sina.com.cn。完成。如图5：</p>
<div class="wp-caption aligncenter" style="width: 336px"><img style="display: inline; border: 0px;" title="创建主要区域" src="http://www.ray77.com/wp-content/uploads/2009/02/t-5.jpg" border="0" alt="创建主要区域" width="326" height="232" /><p class="wp-caption-text">图5：创建主要区域</p></div>
<p>　　创建好区域后，就在你创建的区域里创建主机。最好创建的主机名与你的用户名相同，这样方便管理。当然，如果你的系统已经升级到AD模式，那这步可以跳过，因为在AD模式下，你每创建一个帐户，就会自动的生成对应的主机名称。如图6：</p>
<div class="wp-caption aligncenter" style="width: 336px"><img style="display: inline; border: 0px;" title="创建主机" src="http://www.ray77.com/wp-content/uploads/2009/02/t-6.jpg" border="0" alt="创建主机" width="326" height="232" /><p class="wp-caption-text">图6：创建主机</p></div>
<p>　　所有的主机都创建好了后，就打开Internet信息服务(IIS)管理器，在WEB站点下创建新的WEB站点。在站点创建向导的IP地址和端口设置对话框中输入需要绑定的完整域名，如<a href="http://www.test.com">www.test.com</a>、test1.test.com、test12.test.com，创建完成后，域名就和空间绑定了。如图7：</p>
<div class="wp-caption aligncenter" style="width: 336px"><img style="display: inline; border: 0px;" title="域名和空间的绑定" src="http://www.ray77.com/wp-content/uploads/2009/02/t-7.jpg" border="0" alt="域名和空间的绑定" width="326" height="232" /><p class="wp-caption-text">图7：域名和空间的绑定</p></div>
<p>　　在DNS中有多少个主机，就可以建立多少个子域名WEB站点。也可以用域名提供商提供的直接指定IP的域名。但这里需要注意的是，一旦服务器中Internet信息服务(IIS)管理器里出现了带主机头的站点后，有时将会导致没有主机头的站点出错，这个问题很奇怪，可是能DNS映射引起的。</p>
<p><strong>　　二、用户使用磁盘空间的容量限制(磁盘配额)</strong></p>
<p>　　在配置之前，让我们先了解一下磁盘配额管理的一些基础知识，以便我们更容易理解它的工作方法。Windows 2003磁盘配额会跟踪每个用户在每个盘符中的使用情况。并根据用户的磁盘配额进行控制。因为配额是以每个用户做为单位进行跟踪的，所以不管用户在这个盘符下的任何地方储存文件都会被记录。磁盘配额共有二个比较显著的特点:</p>
<p>　　1.根据每个用户所拥有的文件和文件夹来计算使用磁盘的空间量。当一个新建、复制、保存文件到开启了磁盘配额的盘符上，或获得盘符上某文件所有权时，系统就自动从磁盘配额管理所限定的空间中扣减该用户的空间容量。</p>
<p>　　2.系统在计算用户使用磁盘空间时是忽略压缩的，它按照未压缩的字节计算用户使用的硬盘空间，而不管用户实际使用了多少磁盘空间。这样做是因为现在很多不同的文件类型在压缩的时候所压缩的比例不同。会造成文件长度的很大差异，给磁盘管理带来很大的工作负担。</p>
<p>　　注意，磁盘配额必须建立在NTFS格式的盘符上。不然无法使用。</p>
<p>　　选择你FTP空间所在的盘符，打开盘符的属性对话框，单击配额选项卡，选择启用配额管理复选框，开启磁盘配额管理。这是原本一些灰色不能使用的属性开始能使用了。请根据情况修改它们，如图8：</p>
<div class="wp-caption aligncenter" style="width: 199px"><img style="display: inline; border: 0px;" title="修改磁盘配额" src="http://www.ray77.com/wp-content/uploads/2009/02/t-8.jpg" border="0" alt="修改磁盘配额" width="189" height="240" /><p class="wp-caption-text">图8：修改磁盘配额</p></div>
<p>　　拒绝将磁盘空间给超过配额限制的用户:选择这个复选框，当用户超过了分配的磁盘空间时，他们就会收到一个空间已用完的消息，而且不能在往空间里面写任何东西</p>
<p>　　不限制磁盘使用:当你不打算限制用户磁盘空间时，单击这个选项。</p>
<p>　　将磁盘空间限制为:配置用户可以使用的磁盘空间容量</p>
<p>　　将警告等级设置为:配置在用户登陆空间时，如果空间使用已经达到警告空间等级，将会发送一个信息给该用户，提醒用户空间快使用完了</p>
<p>　　配额项:单击这个按钮可以打开配额项对话框，在这个对话框中，可以通过配置菜单分别定制每个用户磁盘空间。也可以删除老用户空间配额限制。另外它的主界面就是一个用户配额监控器。如图9：</p>
<div class="wp-caption aligncenter" style="width: 447px"><img style="display: inline; border: 0px;" title="用户配额监控器" src="http://www.ray77.com/wp-content/uploads/2009/02/t-9.jpg" border="0" alt="用户配额监控器" width="437" height="280" /><p class="wp-caption-text">图9：用户配额监控器</p></div>
<p>　　设置好后确定-关闭属性窗口，这时用户的磁盘配额就已经设置好了。还是比较简单的。</p>
<p>　　<strong>三、IIS和用户环境的安全问题</strong></p>
<p>　　记得一位资深的安全人士曾经说过，从Windows 2000 Server开始，本身的系统漏洞和网络漏洞已经不是那么多了，甚至可以说Windows本身安全系数已经超越了LINUX系统本身，(这里我只是引用语句，希望不会引发Windows和LINUX向来的争论)但由于Windows周边产品漏洞和不安全因数太多，尤其是IIS服务。才导致人人说它不安全。</p>
<p>　　由于系统的安全问题实在是太过于庞大，所分的体系也非常多，不可能面面具到，所以我根据个人经验说几个和虚拟主机密切相关的安全问题，但由于这些问题要想得到彻底的解决说明的话，那需要太多的篇幅，这里我也只能指出这些问题，和解决这些问题的思路。</p>
<p>　　支持ASP系统的IIS安全问题:</p>
<p>　　在支持ASP的IIS系统中，主要问题有两个，一个是由于ASP页面的输入框引起的安全攻击。在ASP页面中，我们经常可以看见输入框，如登录帐号、密码、查询、手机等等..这些控件其实是很危险的，因为这些控件的后台往往是连接着数据库，(SQL、Oracle等)有经验的程序员可以通过这些输入框输入数据库命令，在通过系统本身的数据库执行后，得到数据库帐号和密码。那也意味着离得到SYSTEM或Administrator帐号不远了。</p>
<p>　　所以，一般在做ASP页面时，我们在有这些输入框的窗口上都做一些限制，如最多只能输入多少个字符、哪些特殊字符不允许输入等等。但现在很少有网站能做到滴水不漏的，可能也是因为工作量太大的缘故吧:( 第二个问题是FileSystemObject这个组件为 ASP 提供了对默认的Windows Server服务器硬盘上的任何文件进行读、写、复制、删除、改名等操作。权限实在是太大。也导致一些恶意虚拟主机租借用户利用这个ASP组件来攻击和控制系统。所以在不是必要的情况下，请不要启用它。太危险了。</p>
<p>　　基本系统设置安全问题:</p>
<p>　　一、端口设置。端口是计算机和外部网络相连的逻辑接口，也是计算机的第一道屏障，由于做虚拟主机的系统一般不需要开太多的端口，所以可以在网卡属性、Internet协议(TCP/IP)属性、高级、选项、TCP/IP筛选属性里进行添加。不过Windows Server TCP/IP筛选设置做的有点问题:只允许开哪些端口，不允许关哪些端口，郁闷吧。</p>
<p>　　二、IIS设置:首先，把C盘中的默认的IIS目录Inetpub彻底删掉，在Internet信息服务(IIS)管理器中将默认WEB网站给停止，从新建立你所需要的WEB发布站点，但注意，请把你要发布站点的目录建立在其他盘符，并不要给其命名一些相对简单或容易猜中的名字，如WEB、IIS、Inetpub等。其次，在Internet信息服务(IIS)管理器中删除在你虚拟主机上不想用到的文件名映射，如，你的系统只需要支持ASP和HTML的话，那只要保留这个两个文件名映射就可以了，其他的都可以删除，步骤为在Internet信息服务(IIS)管理器中右击主机、属性、WWW服务、编辑、主目录配置、应用程序映射，然后就开始一个个删吧。最后在的应用程序调试书签内将脚本错误消息改为发送文本就基本可以了。</p>
<p>　　三、预防DoS: DOS:即Denial Of Service，拒绝服务的缩写，原理就是生产出大量的数据包让服务器不停的处理，导致服务器不能对其他的数据产生响应。在注册表HKLMSYSTEMCurrentControlSetServicesTcpipParameters中更改以下值可以帮助你防御一定强度的DoS攻击 SynAttackProtect REG_DWORD 2 EnablePMTUDiscovery REG_DWORD 0 NoNameReleaseOnDemand REG_DWORD 1 EnableDeadGWDetect REG_DWORD 0 KeepAliveTime REG_DWORD 300,000 PerFORMRouterDiscovery REG_DWORD 0 EnableICMPRedirects REG_DWORD 0</p>
<p>　　四、工作进程隔离:在虚拟主机上经常会出现由于某个用户WEB站点的应用程序出错，进入无限循环，导致服务器资源大量消耗，最终死机的情况，这个问题在以往的IIS中是很难解决的，有时还得借助第三方软件。</p>
<p>　　但现在，IIS 6.0 引入了工作进程隔离模式，该模式可以在隔离环境中运行所有的 Web 应用程序。当在工作进程隔离模式下运行 IIS 时，可以将应用程序配置成在单独的应用程序池中运行。每个应用程序池在逻辑上表示一个可配置的工作进程，并且链接到池中的应用程序。工作进程彼此独立运行;它们可能失败，但不会影响其他工作进程。应用程序池保护其中的应用程序免受支持其他应用程序池的工作进程的影响。这样，就可以避免应用程序相互影响。</p>
<p>　　在工作进程隔离模式下，超文本传输协议 (HTTP) 请求被直接路由到服务于已配置的应用程序的内核应用程序池队列。服务于应用程序池的工作进程会将请求直接从该队列中拉出，避免了进程切换的开销。为进一步保护 WWW 服务，IIS 6.0 会隔离关键的万维网发布服务组件，如 HTTP 协议堆栈和 WWW 服务管理和监控，避免其受到在工作进程中运行的第三方代码的影响。</p>
<p>　　HTTP 协议堆栈接受 WWW 服务请求并将其排入队列。当工作进程处于不正常的状态并因此中断处理请求时，HTTP 协议堆栈会继续处理请求。同时，WWW 服务将检测不正常的工作进程并将其关闭。如果要求新工作进程为请求提供服务，WWW 服务会启动一个新工作进程以便从 HTTP 协议堆栈中获取队列中的请求。即使工作进程失败，WWW 服务仍会继续处理请求并保护用户免于丢失服务。打开 Internet信息服务(IIS)管理器，展开服务器，右键点击应用程序池，选择属性。就可以在弹出的对话框中根据自己的系统环境进行进程隔离设置了。如图10：</p>
<div class="wp-caption aligncenter" style="width: 347px"><img style="display: inline; border: 0px;" title="Internet信息服务(IIS)管理器" src="http://www.ray77.com/wp-content/uploads/2009/02/t-10.jpg" border="0" alt="Internet信息服务(IIS)管理器" width="337" height="238" /><p class="wp-caption-text">图10：Internet信息服务(IIS)管理器</p></div>
<p>　　由于系统环境比较多样化，所以很难有一个设置的标准，所以具体的设置请参考Windows 2003的帮助。</p>
<p>　　五、查看事件查看器:作为一个网管就应该时时刻刻的观察服务器的安全性，而事件查看器可以反映系统80%的安全情况，这需要我们要养成每天分析事件查看器的习惯。一般情况是每天早晚各查看一次，并保留一个星期的日志。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ray77.com/windows-2003-build-virtual-host-server.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CentOS 5.0 编译安装 Apache+PHP+Mysql+Zend Optimizer+Eaccelerator+phpMyAdmin</title>
		<link>http://www.ray77.com/centos-apache-php-mysql.html</link>
		<comments>http://www.ray77.com/centos-apache-php-mysql.html#comments</comments>
		<pubDate>Wed, 11 Feb 2009 12:50:33 +0000</pubDate>
		<dc:creator>Rock</dc:creator>
				<category><![CDATA[Develop]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[CentOS]]></category>
		<category><![CDATA[Mysql]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[phpmyadmin]]></category>
		<category><![CDATA[服务器]]></category>

		<guid isPermaLink="false">http://www.ray77.com/centos-apache-php-mysql.html</guid>
		<description><![CDATA[CentOS Linux
　　CentOS是Community ENTerprise Operating System的简称，我们有很多人叫它社区企业操作系统，不管你怎么叫它，它都是Linux操作系统的一个发行版本。
　　CentOS并不是全新的Linux发行版，倘若一说到Red Hat这个大名，大家似乎都听过。在Red Hat家族中有企业版的产品，它是Red Hat Enterprise Linux（以下称之为RHEL），CentOS正是这个RHEL的克隆版本。RHEL是很多企业采用的Linux发行版本，需要向Red Hat付费才可以使用，并能得到付过费用的服务和技术支持和版本升级。CentOS可以像RHEL一样的构筑Linux系统环境，但不需要向Red Hat付任何的产品和服务费用，同时也得不到任何有偿技术支持和升级服务。

　　Red Hat公司的产品中，有Red Hat Linux（如Redhat8,9）和针对企业发行的版本Red Hat Enterprise Linux，都能够通过网络FTP免费的获得并使用，但是在2003年的时候，Red Hat Linux停止了发布，它的项目由Fedora Project这个项目所取代，并以Fedora Core这个名字发行并提供给普通用户免费使用。Fedora Core这个Linux发行版更新很快，大约半年左右就有新的版本发布。目前的版本是Fedora Core 6，这个Fedora Core试验的韵味比较浓厚，每次发行都有新的功能被加入到其中，得到的成功结果将被采用道RHEL的发布中。虽说这样，频繁的被改进更新的不安定产品对于企业来说并不是最好的选择，大多数企业还是会选择有偿的RHEL产品（这里面有很深的含义，比如说企业用Linux赚钱，赚到的钱回报给企业，资金在企业间流通，回报社会，提高服务水准等）。
　　在构成RHEL的大多数软件包中，都是基于GPL协议发布的，也就是我们常说的开源软件。正因为是这样，Red Hat公司也遵循这个协议，将构成RHEL的软件包公开发布，只要是遵循GPL协议，任何人都可以在原有的软件构成的基础上再开发和发布。CentOS就是这样在RHEL发布的基础上将RHEL的构成克隆再现的一个Linux发行版本。RHEL的克隆版本不只CentOS一个，还有White Box Enterprise Linux和TAO Linux 和Scientific Linux（其他的这些都没听说过，是吧？）。
　　虽然说是RHEL的克隆，但并不是一模一样，所说的克隆是具有100%的互换性（真的么？）。但并不保障对应RHEL的软件在CentOS上面也能够100%的正常工作。并且安全漏洞的修正和软件包的升级对应RHEL的有偿服务和技术支持来说，数日数星期数个月的延迟情况也有（其实也没看出来多慢）。
　　CentOS的特点
　　在CentOS的全称里面我们可以看到Enterprise OS，也就是说企业系统，这个企业系统并不是企业级别的系统，而是它可以提供企业级应用所需要的要素。
　　例如：
　　稳定的环境
　　长期的升级更新支持
　　保守性强
　　大规模的系统也能够发挥很好的性能
　　CentOS满足以上的要素，满足上面要素的发行版还有Fedora 。Fedora和CentOS非常的相像，但是对CentOS来说，Fedora提供更多的新的功能和软件，发布更新快等特点，这样在稳定性和管理方面就增加了很多工作。企业所需要的系统环境应该是，高效稳定的系统环境，一次构建后能够长期使用的系统环境，所以Fedora那样的频繁更新发布的系统环境并不对应企业的应用。另一方面，CentOS却能够满足以上企业的需要，在众多的RHEL的克隆版本中，CentOS是很出众很优秀的。
　　官方网站：http://www.centos.org
　　中文社区：http://www.centospub.com/bbs
1、下载centos 5
我是下载的DVD版本，大家也可以下载服务器CD安装版本，其实都差不多。大家可以到这儿下载，速度很快的。
http://ftp.iasi.roedu.net/mirrors/ce&#8230;86-bin-DVD.iso
当然也可以在windows下用BT或迅雷下载，速度也很不错的。
下载后当然就刻录成光盘。我建议你刻录DVD啦，如果是菜鸟，也可以在图形界面进行学习，不会这么抓不住头脑。
2、（1）安装CentOS 5
作为服务器，不安装不需要的组件，所以在选择组件的时候，除了选择FTP SERVER外取消所有组件的选择。也不要选web服务器。因为我们后面要手动编译安装。
系统约定RPM包和源码包存放位置
RPM包和源码包存放位置 /usr/local/src
源码包编译安装位置(prefix) /usr/local/xxx
脚本以及维护程序存放位置 /usr/local/sbin
MySQL 数据库位置 /var/lib/mysql
Apache 网站根目录 /home/www
Apache 虚拟主机日志根目录 /data/logs/www
yum RPM包信息文件 /etc/yum.list
3、系统环境部署及调整
（1）. 检查系统是否正常
# ...]]></description>
			<content:encoded><![CDATA[<div class="wp-caption aligncenter" style="width: 610px"><img style="display: block; border: 0px;" title="CentOS 5.0 编译安装 Apache+PHP+Mysql+Zend Optimizer+Eaccelerator+phpMyAdmin" src="http://www.ray77.com/wp-content/uploads/2009/02/centos.jpg" border="0" alt="centos" width="600" height="182" /><p class="wp-caption-text">CentOS Linux</p></div>
<p>　　CentOS是Community ENTerprise Operating System的简称，我们有很多人叫它社区企业操作系统，不管你怎么叫它，它都是Linux操作系统的一个发行版本。<br />
　　CentOS并不是全新的Linux发行版，倘若一说到Red Hat这个大名，大家似乎都听过。在Red Hat家族中有企业版的产品，它是Red Hat Enterprise Linux（以下称之为RHEL），CentOS正是这个RHEL的克隆版本。RHEL是很多企业采用的Linux发行版本，需要向Red Hat付费才可以使用，并能得到付过费用的服务和技术支持和版本升级。CentOS可以像RHEL一样的构筑Linux系统环境，但不需要向Red Hat付任何的产品和服务费用，同时也得不到任何有偿技术支持和升级服务。</p>
<p><span id="more-668"></span></p>
<p>　　Red Hat公司的产品中，有Red Hat Linux（如Redhat8,9）和针对企业发行的版本Red Hat Enterprise Linux，都能够通过网络FTP免费的获得并使用，但是在2003年的时候，Red Hat Linux停止了发布，它的项目由Fedora Project这个项目所取代，并以Fedora Core这个名字发行并提供给普通用户免费使用。Fedora Core这个Linux发行版更新很快，大约半年左右就有新的版本发布。目前的版本是Fedora Core 6，这个Fedora Core试验的韵味比较浓厚，每次发行都有新的功能被加入到其中，得到的成功结果将被采用道RHEL的发布中。虽说这样，频繁的被改进更新的不安定产品对于企业来说并不是最好的选择，大多数企业还是会选择有偿的RHEL产品（这里面有很深的含义，比如说企业用Linux赚钱，赚到的钱回报给企业，资金在企业间流通，回报社会，提高服务水准等）。<br />
　　在构成RHEL的大多数软件包中，都是基于GPL协议发布的，也就是我们常说的开源软件。正因为是这样，Red Hat公司也遵循这个协议，将构成RHEL的软件包公开发布，只要是遵循GPL协议，任何人都可以在原有的软件构成的基础上再开发和发布。CentOS就是这样在RHEL发布的基础上将RHEL的构成克隆再现的一个Linux发行版本。RHEL的克隆版本不只CentOS一个，还有White Box Enterprise Linux和TAO Linux 和Scientific Linux（其他的这些都没听说过，是吧？）。<br />
　　虽然说是RHEL的克隆，但并不是一模一样，所说的克隆是具有100%的互换性（真的么？）。但并不保障对应RHEL的软件在CentOS上面也能够100%的正常工作。并且安全漏洞的修正和软件包的升级对应RHEL的有偿服务和技术支持来说，数日数星期数个月的延迟情况也有（其实也没看出来多慢）。<br />
　　<strong>CentOS的特点</strong><br />
　　在CentOS的全称里面我们可以看到Enterprise OS，也就是说企业系统，这个企业系统并不是企业级别的系统，而是它可以提供企业级应用所需要的要素。<br />
　　例如：<br />
　　稳定的环境<br />
　　长期的升级更新支持<br />
　　保守性强<br />
　　大规模的系统也能够发挥很好的性能<br />
　　CentOS满足以上的要素，满足上面要素的发行版还有Fedora 。Fedora和CentOS非常的相像，但是对CentOS来说，Fedora提供更多的新的功能和软件，发布更新快等特点，这样在稳定性和管理方面就增加了很多工作。企业所需要的系统环境应该是，高效稳定的系统环境，一次构建后能够长期使用的系统环境，所以Fedora那样的频繁更新发布的系统环境并不对应企业的应用。另一方面，CentOS却能够满足以上企业的需要，在众多的RHEL的克隆版本中，CentOS是很出众很优秀的。<br />
　　官方网站：<a href="http://www.centos.org">http://www.centos.org</a><br />
　　中文社区：<a href="http://www.centospub.com/bbs">http://www.centospub.com/bbs</a></p>
<p>1、下载centos 5<br />
我是下载的DVD版本，大家也可以下载服务器CD安装版本，其实都差不多。大家可以到这儿下载，速度很快的。<br />
<a href="http://ftp.iasi.roedu.net/mirrors/ce...86-bin-DVD.iso">http://ftp.iasi.roedu.net/mirrors/ce&#8230;86-bin-DVD.iso</a></p>
<p>当然也可以在windows下用BT或迅雷下载，速度也很不错的。</p>
<p>下载后当然就刻录成光盘。我建议你刻录DVD啦，如果是菜鸟，也可以在图形界面进行学习，不会这么抓不住头脑。</p>
<p>2、（1）安装CentOS 5<br />
作为服务器，不安装不需要的组件，所以在选择组件的时候，除了选择FTP SERVER外取消所有组件的选择。也不要选web服务器。因为我们后面要手动编译安装。</p>
<p>系统约定RPM包和源码包存放位置</p>
<p>RPM包和源码包存放位置 /usr/local/src<br />
源码包编译安装位置(prefix) /usr/local/xxx<br />
脚本以及维护程序存放位置 /usr/local/sbin<br />
MySQL 数据库位置 /var/lib/mysql<br />
Apache 网站根目录 /home/www<br />
Apache 虚拟主机日志根目录 /data/logs/www<br />
yum RPM包信息文件 /etc/yum.list</p>
<p>3、系统环境部署及调整</p>
<p>（1）. 检查系统是否正常<br />
# more /var/log/messages //检查有无系统内核级错误信息<br />
# demesg //检查硬件设备是否有错误信息<br />
# ifconfig //检查网卡设置是否正确<br />
# ping www.163.com // 检查网络是否正常</p>
<p>（2）. 关闭不需要的服务<br />
# export LANG=&#8217;en_US&#8217; //设置语言<br />
# setup //选择启动的服务<br />
进入system service 选项。<br />
以space 键选定所需服务。<br />
以下仅列出需要启动的服务，未列出的服务一律关闭：<br />
crond<br />
irqbalance 仅当服务器CPU为S.M.P架构或支持双核心、HT技术时，才需开启，否则关闭。<br />
microcode_ctl<br />
network<br />
vsftpd<br />
sshd<br />
syslog</p>
<p>（3）、修改/etc/yum.repos.d/CentOS-Base.repo，将镜象站点地址改为在中国的镜象站点地址。不然我们通过yum安装软件速度会极慢。修改如下：</p>
<p># CentOS-Base.repo<br />
#<br />
# This file uses a new mirrorlist system developed by Lance Davis for CentOS.<br />
# The mirror system uses the connecting IP address of the client and the<br />
# update status of each mirror to pick mirrors that are updated to and<br />
# geographically close to the client. You should use this for CentOS updates<br />
# unless you are manually picking other mirrors.<br />
#<br />
# If the mirrorlist= does not work for you, as a fall back you can try the<br />
# remarked out baseurl= line instead.<br />
#<br />
#</p>
<p>[base]<br />
name=CentOS-$releasever &#8211; Base<br />
baseurl=<a href="http://mirror.be10.com/centos/">http://mirror.be10.com/centos/</a>$releasever/os/$basearch/<br />
gpgcheck=1<br />
gpgkey=<a href="http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5">http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5</a><br />
protect=1</p>
<p>#released updates<br />
[updates]<br />
name=CentOS-$releasever &#8211; Updates<br />
baseurl=<a href="http://mirror.be10.com/centos/">http://mirror.be10.com/centos/</a>$releasever/updates/$basearch/<br />
gpgcheck=1<br />
gpgkey=<a href="http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5">http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5</a><br />
protect=1</p>
<p>#packages used/produced in the build but not released<br />
[addons]<br />
name=CentOS-$releasever &#8211; Addons<br />
baseurl=<a href="http://mirror.be10.com/centos/">http://mirror.be10.com/centos/</a>$releasever/addons/$basearch/<br />
gpgcheck=1<br />
gpgkey=<a href="http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5">http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5</a><br />
protect=0</p>
<p>#additional packages that may be useful<br />
[extras]<br />
name=CentOS-$releasever &#8211; Extras<br />
baseurl=<a href="http://mirror.be10.com/centos/">http://mirror.be10.com/centos/</a>$releasever/extras/$basearch/<br />
gpgcheck=1<br />
gpgkey=<a href="http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5">http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5</a><br />
protect=0</p>
<p>#additional packages that extend functionality of existing packages<br />
[centosplus]<br />
name=CentOS-$releasever &#8211; Plus<br />
baseurl=<a href="http://mirror.be10.com/centos/">http://mirror.be10.com/centos/</a>$releasever/centosplus/$basearch/<br />
gpgcheck=1<br />
enabled=0<br />
gpgkey=<a href="http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5">http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5</a><br />
protect=1</p>
<p>#contrib &#8211; packages by Centos Users<br />
[contrib]<br />
name=CentOS-$releasever &#8211; Contrib<br />
baseurl=<a href="http://mirror.be10.com/centos/">http://mirror.be10.com/centos/</a>$releasever/contrib/$basearch/<br />
gpgcheck=1<br />
enabled=0<br />
protect=0<br />
gpgkey=<a href="http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5">http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-5</a></p>
<p>#packages in testing<br />
[testing]<br />
name=CentOS-5 &#8211; Testing<br />
baseurl=<a href="http://mirror.be10.com/centos/5/testing/i386/">http://mirror.be10.com/centos/5/testing/i386/</a></p>
<p>gpgcheck=1<br />
enabled=0<br />
protect=0</p>
<p>保存。</p>
<p>（2）更新系统，我们使用yum,</p>
<p>然后执行：<br />
# yum upgrade<br />
建议更新所有列出的程序，rhel 5.X的稳定性还要继续努力呢。</p>
<p>（4）、定时校正服务器时间<br />
# yum install ntp<br />
# crontab -e<br />
0 23 * * * root /usr/sbin/ntpdate 210.72.145.44 &gt; /dev/null 2&gt;&amp;1</p>
<p>以上命令设置好后存盘。您的机器将在每天的23:00根据中国国家授时中心的NTP服务器时间自动校准时间。</p>
<p>(5). 对TCP/IP网络参数进行调整，加强抗SYN Flood能力<br />
# echo &#8216;net.ipv4.tcp_syncookies = 1&#8242; &gt;&gt; /etc/sysctl.conf //将net.ipv4.tcp_syncookies = 1写入sysctl.conf 文件<br />
# sysctl -p //查看</p>
<p>（6）、FTP服务器的配置<br />
vi /etc/vsftpd/vsftpd.conf<br />
把anonymous_enable=YES注释掉不允许匿名登录。<br />
把chroot_list_enable=YES<br />
chroot_list_file=/etc/vsftpd.chroot_list<br />
前的注释去掉。<br />
把ftpd_banner=*前的注释去掉。后面改成你的欢迎信息(这样设置可以避免显示ftp服务器的版本信息)<br />
然后保存，service vsftpd start就可以了。</p>
<p>这时应当添加用户，因为root默认不能通过FTP方式登录。</p>
<p># adduser username<br />
# passwd userpassword</p>
<p>这样对于我们上传一些文件到系统中很方便。</p>
<p>4. 重新启动系统<br />
# init 6</p>
<p>5. 使用 yum 程序安装所需开发包（以下为标准的 RPM 包名称）<br />
# yum install gcc gcc-c++ gcc-g77 flex bison autoconf automake bzip2-devel zlib-devel ncurses-devel libjpeg-devel libpng-devel libtiff-devel freetype-devel pam-devel openssl-devel libxml2-devel</p>
<p>#这里我们将编译GD所必须的一些小软件比如libpng,libtiff,freetype,libjpeg,等先用RPM的方式一并安装好，避免手动编译浪费时间，同时也能避免很多错误，这几个小软件的编译很麻烦。这几个小软件编译错误了，GD当然安装不了，php5的编译当然也没戏了。所以我们抓大放小，对这些小牛鬼蛇神采取快速简洁的方式进行安装。并且对服务器的性能也不能产生什么影响。<br />
另外libxml2系统已经默认安装了，所以我们不需要手工编译了，直接安装它的开发包就行了。</p>
<p>6. 源码编译安装所需包 (Source)<br />
(1) GD2<br />
# cd /usr/local/src<br />
# wget <a href="http://www.boutell.com/gd/http/gd-2.0.34.tar.gz">http://www.boutell.com/gd/http/gd-2.0.34.tar.gz</a><br />
# tar xzvf gd-2.0.34.tar.gz<br />
# cd gd-2.0.34<br />
# CHOST=&#8221;i686-pc-linux-gnu&#8221; CFLAGS=&#8221;-O3 -msse2 -mmmx -Wall -W -mfpmath=sse -mcpu=pentium4 -march=pentium4 -pipe -fomit-frame-pointer&#8221; CXXFLAGS=&#8221;-O3 -msse2 -mmmx -Wall -W -mfpmath=sse -funroll-loops -mcpu=pentium4 -march=pentium4 -pipe -fomit-frame-pointer&#8221; ./configure &#8211;prefix=/usr/local/gd2 &#8211;mandir=/usr/share/man //./configure 配置。</p>
<p># make //make 是用来编译的，它从 Makefile 中读取指令，然后编译。<br />
# make install //make install 是用来安装的，它也从 Makefile 中读取指令，安装到指定的位置。</p>
<p>(2) Apache 日志截断程序<br />
# cd /usr/local/src<br />
# wget <a href="http://cronolog.org/download/cronolog-1.6.2.tar.gz">http://cronolog.org/download/cronolog-1.6.2.tar.gz</a><br />
# tar xzvf cronolog-1.6.2.tar.gz<br />
# cd cronolog-1.6.2<br />
# ./configure &#8211;prefix=/usr/local/cronolog<br />
# make<br />
# make install</p>
<p>7、编译mysql 5.0.46<br />
mysql 5.0.46是企业版本，貌似双数版本都是企业版本了。个人觉得代码质量要比社区版本要好一些。大家可以下载，免费使用。并不需要向mysql公司交钱。</p>
<p>cd /usr/local/src<br />
# wget <a href="http://mirror.provenscaling.com/mysq...-5.0.46.tar.gz">http://mirror.provenscaling.com/mysq&#8230;-5.0.46.tar.gz</a><br />
# tar xzvf mysql-5.0.46.tar.gz<br />
# cd mysql-5.0.46</p>
<p>修改mysql 客户端最大连接数， 默认的只有100,远远达不到我们的要求。</p>
<p># vi sql/mysqld.cc</p>
<p>搜索找到下面一行：<br />
{&#8220;max_connections&#8221;, OPT_MAX_CONNECTIONS,<br />
&#8220;The number of simultaneous clients allowed.&#8221;, (gptr*) &amp;max_connections,<br />
(gptr*) &amp;max_connections, 0, GET_ULONG, REQUIRED_ARG, 100, 1, 16384, 0, 1,<br />
0},</p>
<p>将其中的100改为1500, 当然小点也可以，根据你的需要来，不建议改的太大。</p>
<p>{&#8220;max_connections&#8221;, OPT_MAX_CONNECTIONS,<br />
&#8220;The number of simultaneous clients allowed.&#8221;, (gptr*) &amp;max_connections,<br />
(gptr*) &amp;max_connections, 0, GET_ULONG, REQUIRED_ARG, 1500, 1, 16384, 0, 1,<br />
0},</p>
<p>保存。</p>
<p># CHOST=&#8221;i686-pc-linux-gnu&#8221; CFLAGS=&#8221;-O3 -msse2 -mmmx -Wall -W -mfpmath=sse -mcpu=pentium4 -march=pentium4 -pipe -fomit-frame-pointer&#8221; CXXFLAGS=&#8221;-O3 -msse2 -mmmx -Wall -W -mfpmath=sse -funroll-loops -mcpu=pentium4 -march=pentium4 -pipe -fomit-frame-pointer&#8221; ./configure &#8211;prefix=/usr/local/mysql &#8211;localstatedir=/var/lib/mysql &#8211;with-comment=Source &#8211;with-server-suffix=-enterprise-gpl &#8211;with-mysqld-user=mysql &#8211;without-debug &#8211;with-big-tables &#8211;with-charset=utf8 &#8211;with-collation=utf8_general_ci &#8211;with-extra-charsets=all &#8211;with-pthread &#8211;enable-static &#8211;enable-thread-safe-client &#8211;with-client-ldflags=-all-static &#8211;with-mysqld-ldflags=-all-static &#8211;enable-assembler &#8211;without-innodb &#8211;without-ndb-debug &#8211;without-isam</p>
<p>配置成功会提示：</p>
<p>MySQL has a Web site at <a href="http://www.mysql.com/">http://www.mysql.com/</a> which carries details on the<br />
latest release, upcoming features, and other information to make your<br />
work or play with MySQL more productive. There you can also find<br />
information about mailing lists for MySQL discussion.</p>
<p>Remember to check the platform specific part of the reference manual for<br />
hints about installing MySQL on your platform. Also have a look at the<br />
files in the Docs directory.</p>
<p>Thank you for choosing MySQL!</p>
<p>// 注意 ，CHOST=&#8221;i686-pc-linux-gnu&#8221; CFLAGS=&#8221;-O3 -msse2 -mmmx -Wall -W -mfpmath=sse -mcpu=pentium4 -march=pentium4 -pipe -fomit-frame-pointer&#8221; CXXFLAGS=&#8221;-O3 -msse2 -mmmx -Wall -W -mfpmath=sse -funroll-loops -mcpu=pentium4 -march=pentium4 -pipe -fomit-frame-pointer&#8221; 这个环境参数只针对intel P4 芯片，如果你的CPU是AMD的，注意不能使用。请查看相应的编译优化参数。否则程序会无法编译，即使编译成功也无法运行，嘿嘿。</p>
<p>关于其他CPU的优化请看我的BLOG的一篇转贴：<br />
<a href="http://www.cnprint.org/bbs/blogs/1/blog43.html">http://www.cnprint.org/bbs/blogs/1/blog43.html</a></p>
<p># make<br />
编译的时间可能会比较长，毕竟优化的比较厉害。</p>
<p># make install</p>
<p>编译安装完成后执行后续操作：<br />
# useradd mysql //添加 mysql 用户<br />
# cd /usr/local/mysql<br />
# bin/mysql_install_db &#8211;user=mysql<br />
# chown -R root:mysql . //设置权限，注意后面有一个 &#8220;.&#8221;<br />
# chown -R mysql /var/lib/mysql //设置 mysql 目录权限<br />
# chgrp -R mysql . //注意后面有一个 &#8220;.&#8221;<br />
# cp share/mysql/my-medium.cnf /etc/my.cnf<br />
# cp share/mysql/mysql.server /etc/rc.d/init.d/mysqld //开机自动启动 mysql。<br />
# chmod 755 /etc/rc.d/init.d/mysqld<br />
# chkconfig &#8211;add mysqld<br />
# /etc/rc.d/init.d/mysqld start //启动 MySQL<br />
# bin/mysqladmin -u root password &#8220;password_for_root&#8221;<br />
# service mysqld stop //关闭 MySQL</p>
<p>8. 编译安装 Apache<br />
# cd /usr/local/src<br />
# wget <a href="http://www.ip97.com/apache.org/httpd/httpd-2.2.6.tar.gz">http://www.ip97.com/apache.org/httpd/httpd-2.2.6.tar.gz</a><br />
# tar zxvf httpd-2.2.6.tar.gz<br />
# cd httpd-2.2.6</p>
<p>依次安装apr和apr-util</p>
<p># cd srclib/apr<br />
# CHOST=&#8221;i686-pc-linux-gnu&#8221; CFLAGS=&#8221;-O3 -msse2 -mmmx -Wall -W -mfpmath=sse -mcpu=pentium4 -march=pentium4 -pipe -fomit-frame-pointer&#8221; CXXFLAGS=&#8221;-O3 -msse2 -mmmx -Wall -W -mfpmath=sse -funroll-loops -mcpu=pentium4 -march=pentium4 -pipe -fomit-frame-pointer&#8221; ./configure &#8211;prefix=/usr/local/apr &#8211;enable-threads &#8211;enable-other-child &#8211;enable-static<br />
# make &amp;&amp; make install</p>
<p># cd ../apr-util<br />
# CHOST=&#8221;i686-pc-linux-gnu&#8221; CFLAGS=&#8221;-O3 -msse2 -mmmx -Wall -W -mfpmath=sse -mcpu=pentium4 -march=pentium4 -pipe -fomit-frame-pointer&#8221; CXXFLAGS=&#8221;-O3 -msse2 -mmmx -Wall -W -mfpmath=sse -funroll-loops -mcpu=pentium4 -march=pentium4 -pipe -fomit-frame-pointer&#8221; ./configure &#8211;prefix=/usr/local/apr-util &#8211;with-apr=/usr/local/apr/ &#8211;with-mysql=/usr/local/mysql<br />
# make &amp;&amp; make install</p>
<p>cd /usr/local/src/httpd-2.2.6<br />
# CHOST=&#8221;i686-pc-linux-gnu&#8221; CFLAGS=&#8221;-O3 -msse2 -mmmx -Wall -W -mfpmath=sse -mcpu=pentium4 -march=pentium4 -pipe -fomit-frame-pointer&#8221; CXXFLAGS=&#8221;-O3 -msse2 -mmmx -Wall -W -mfpmath=sse -funroll-loops -mcpu=pentium4 -march=pentium4 -pipe -fomit-frame-pointer&#8221; ./configure &#8211;prefix=/usr/local/apache2 &#8211;enable-mods-shared=all &#8211;with-mysql=/usr/local/mysql &#8211;enable-cache &#8211;enable-file-cache &#8211;enable-mem-cache &#8211;enable-disk-cache &#8211;enable-static-support &#8211;enable-static-htpasswd &#8211;enable-static-htdigest &#8211;enable-static-rotatelogs &#8211;enable-static-logresolve &#8211;enable-static-htdbm &#8211;enable-static-ab &#8211;enable-static-checkgid &#8211;disable-cgid &#8211;disable-cgi &#8211;with-apr=/usr/local/apr/ &#8211;with-apr-util=/usr/local/apr-util/ &#8211;enable-ssl &#8211;with-ssl=/usr/include/openssl</p>
<p># make<br />
# make install<br />
# echo &#8216;/usr/local/apache2/bin/apachectl start &#8216; &gt;&gt; /etc/rc.local //将 apachectl 的调用加入到你的系统启动文件中。</p>
<p>注解：<br />
./configure //配置源代码树<br />
&#8211;prefix=/usr/local/apache2 //体系无关文件的顶级安装目录PREFIX ，也就Apache的安装目录。<br />
&#8211;enable-module=so //打开 so 模块，so 模块是用来提 DSO 支持的 apache 核心模块<br />
&#8211;enable-mods-shared=all //编译全部的模板，对于不需要我们可以在httpd.conf去掉。<br />
&#8211;enable-cache //支持缓存<br />
&#8211;enable-file-cache //支持文件缓存<br />
&#8211;enable-mem-cache //支持记忆缓存<br />
&#8211;enable-disk-cache //支持磁盘缓存<br />
&#8211;enable-static-support //支持静态连接(默认为动态连接)<br />
&#8211;enable-static-htpasswd //使用静态连接编译 htpasswd &#8211; 管理用于基本认证的用户文件<br />
&#8211;enable-static-htdigest //使用静态连接编译 htdigest &#8211; 管理用于摘要认证的用户文件<br />
&#8211;enable-static-rotatelogs //使用静态连接编译 rotatelogs &#8211; 滚动 Apache 日志的管道日志程序<br />
&#8211;enable-static-logresolve //使用静态连接编译 logresolve &#8211; 解析 Apache 日志中的IP地址为主机名<br />
&#8211;enable-static-htdbm //使用静态连接编译 htdbm &#8211; 操作 DBM 密码数据库<br />
&#8211;enable-static-ab //使用静态连接编译 ab &#8211; Apache HTTP 服务器性能测试工具<br />
&#8211;enable-static-checkgid //使用静态连接编译 checkgid<br />
&#8211;disable-cgid //禁止用一个外部 CGI 守护进程执行CGI脚本<br />
&#8211;disable-cgi //禁止编译 CGI 版本的 PHP<br />
&#8211;enable-ssl // 编译 ssl模块。</p>
<p>我们不再使用worker模式编译apache，worker模式和php貌似有一些不协调不稳定之处。所以使用了默认的perfork模式。</p>
<p>将apache设置成开机自启动:</p>
<p>在/etc/rc.d/rc.local文件中加入一行<br />
/usr/local/apache2/bin/apachectl start<br />
这样每次重新启动系统以后,apache也会随系统一起启动.</p>
<p>或者<br />
# cp /usr/local/apache2/bin/apachectl /etc/rc.d/init.d/httpd<br />
然后 vi /etc/rc.d/init.d/httpd 添加(#!/bin/sh下面)<br />
# chkconfig: 2345 10 90<br />
# description: Activates/Deactivates Apache Web Server<br />
最后，运行chkconfig把Apache添加到系统的启动服务组里面：<br />
# chkconfig &#8211;add httpd<br />
# chkconfig httpd on</p>
<p>9、编译php 5.2.5-devel。<br />
php 5.2.4有点小BUG，无法在centos 5上正常编译，官方已经在php5.2.5中修复了。所以我们使用php 5.2.5进行编译。<br />
Suhosin是php增强型安全补丁，可以编译到静态内核中，也可以编译成php动态扩展。我个人强烈你建议安装成静态内核。Suhosin已经进入freebsd和gentoo的ports。下面的以下先说静态安装步骤。当然你也可以在安装php后将它编译成php的动态扩展。</p>
<p># cd /usr/local/src<br />
# wget <a href="http://cn.php.net/get/php-5.2.5.tar.gz/from/this/mirror">http://cn.php.net/get/php-5.2.5.tar.gz/from/this/mirror</a><br />
wget <a href="http://www.hardened-php.net/suhosin/...9.6.2.patch.gz">http://www.hardened-php.net/suhosin/&#8230;9.6.2.patch.gz</a><br />
# tar zxvf php-5.2.5.tar.gz<br />
# gunzip suhosin-patch-5.2.1-0.9.6.2.patch.gz<br />
# cd php-5.2.5</p>
<p># CHOST=&#8221;i686-pc-linux-gnu&#8221; CFLAGS=&#8221;-O3 -msse2 -mmmx -Wall -W -mfpmath=sse -mcpu=pentium4 -march=pentium4 -pipe -fomit-frame-pointer&#8221; CXXFLAGS=&#8221;-O3 -msse2 -mmmx -Wall -W -mfpmath=sse -funroll-loops -mcpu=pentium4 -march=pentium4 -pipe -fomit-frame-pointer&#8221; ./configure &#8211;prefix=/usr/local/php &#8211;with-apxs2=/usr/local/apache2/bin/apxs &#8211;with-pear=/usr/share/php &#8211;with-zlib-dir &#8211;with-bz2 &#8211;with-libxml-dir=/usr &#8211;with-gd=/usr/local/gd2 &#8211;enable-gd-native-ttf &#8211;enable-gd-jis-conv &#8211;with-freetype-dir &#8211;with-jpeg-dir &#8211;with-png-dir &#8211;with-ttf=shared,/usr &#8211;enable-mbstring &#8211;with-mysql=/usr/local/mysql &#8211;with-mysqli=/usr/local/mysql/bin/mysql_config &#8211;with-config-file-path=/etc &#8211;with-iconv &#8211;disable-ipv6 &#8211;enable-static &#8211;enable-maintainer-zts &#8211;enable-zend-multibyte &#8211;enable-inline-optimization &#8211;enable-zend-multibyte &#8211;enable-sockets &#8211;enable-soap &#8211;with-openssl</p>
<p>配置成功会提示：</p>
<p>+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;+<br />
| License: |<br />
| This software is subject to the PHP License, available in this |<br />
| distribution in the file LICENSE. By continuing this installation |<br />
| process, you are bound by the terms of this license agreement. |<br />
| If you do not agree with the terms of this license, you must abort |<br />
| the installation process at this point. |<br />
+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;+</p>
<p>Thank you for using PHP.</p>
<p># make</p>
<p># make install</p>
<p># cp php.ini-recommended /etc/php.ini</p>
<p>在这里也顺便说一下将suhosin安装成为php的动态扩展的方法。毕竟网上根本不见它的中文安装教程。</p>
<p>虽然我个人不推荐这种方式。</p>
<p>wget <a href="http://www.hardened-php.net/suhosin/...sin-0.9.16.tgz">http://www.hardened-php.net/suhosin/&#8230;sin-0.9.16.tgz</a><br />
tar zxvf suhosin-0.9.16.tgz<br />
cd suhosin-0.9.16<br />
./configure &#8211;with-php-config=/usr/local/php/bin/php-config<br />
make<br />
make install</p>
<p>会提示编译的模块存在的目录，记住它。<br />
Installing shared extensions: /usr/local/php/lib/php/extensions/no-debug-non-zts-20060613/</p>
<p>然后在php.ini中增加一行下列语句。<br />
extension=/usr/local/php/lib/php/extensions/no-debug-non-zts-20060613/suhosin.so</p>
<p>10 、整合apache 与php<br />
# vi /usr/local/apache2/conf/httpd.conf<br />
在最后一行加上：<br />
AddType application/x-httpd-php .php</p>
<p>查找：(设置 WEB 默认文件)<br />
DirectoryIndex index.html<br />
替换为：<br />
DirectoryIndex index.php index.html index.htm //在 WEB 目录不到默认文件，httpd 就会执行 /var/www/error/noindex.html</p>
<p>找到这一段：<br />
# AllowOverride controls what directives may be placed in .htaccess files.<br />
# It can be &#8220;All&#8221;, &#8220;None&#8221;, or any combination of the keywords:<br />
# Options FileInfo AuthConfig Limit<br />
#<br />
AllowOverride none</p>
<p>更改为AllowOverride all<br />
允许apache rewrite</p>
<p># 监听443端口,支持https连接<br />
取消注释 httpd.conf 中的 Include conf/extra/httpd-ssl.conf</p>
<p>保存httpd.conf，退出。</p>
<p># /usr/local/apache2/bin/apachectl restart //重启 Apache</p>
<p>这时会出现错误：</p>
<p>/usr/local/apache2/bin/apachectl start<br />
httpd: Syntax error on line 107 of /usr/local/apache2/conf/httpd.conf: Cannot load /usr/local/apache2/modules/libphp5.so into server: /usr/local/apache2/modules/libphp5.so: cannot restore segment prot after reloc: Permission denied</p>
<p>不急，我们慢慢解决。</p>
<p>先重启下机器：<br />
reboot<br />
这个Permission denied问题，在centos 5下面一般是Selinux引起的，作为生产用服务器，我建议你千万别草率地关掉Selinux一了百了。就像家里的防盗网，阻碍了你的猫自由进出窗户，你不能为了猫方便，就把防盗网简单拆除是同样的道理。我看见网上许多人建议把Selinux简单关闭来解决这个问题，很不以为然。<br />
我们可以这样操作：</p>
<p># audit2allow -d<br />
allow initrc_t usr_t:file execmod;<br />
allow mount_t default_t:file execute;</p>
<p># cd /etc/selinux/targeted/modules/<br />
# audit2allow -M local -d</p>
<p>屏幕产生如下提示：</p>
<p>产生类型强制文件：local.te<br />
正在编译策略<br />
checkmodule -M -m -o local.mod local.te<br />
semodule_package -o local.pp -m local.mod</p>
<p>********************重要 ***********************</p>
<p>为了在内核中加载这个新创建的策略软件包，<br />
您需要执行</p>
<p>semodule -i local.pp</p>
<p>我们运行</p>
<p># semodule -i local.pp</p>
<p>这样就让Selinux加载了新的规则。</p>
<p>更详细的内容请看我在BLOG上的转贴：</p>
<p><a href="http://www.cnprint.org/bbs/blogs/1/blog48.html">http://www.cnprint.org/bbs/blogs/1/blog48.html</a></p>
<p>reboot</p>
<p>哈哈，apache不会再报错了吧？<br />
这样我保留了selinux的功能，同时apache也能正常运行。</p>
<p>11. 查看确认 L.A.M.P 环境信息<br />
vi /usr/local/apache2/htdocs/phpinfo.php</p>
<p>新增加下面一行，并保存。</p>
<p>&lt;?php phpinfo(); ?&gt;</p>
<p># chmod 755 /usr/local/apache2/htdocs/phpinfo.php</p>
<p>用浏览器打开 <a href="http://127.0.0.1/phpinfo.php">http://127.0.0.1/phpinfo.php</a></p>
<p># echo &#8216; &#8216; &gt; /usr/local/apache2/htdocs/testdb.php<br />
# chmod 755 /usr/local/apache2/htdocs/testdb.php<br />
# service mysqld start<br />
用浏览器打开 <a href="http://127.0.0.1/testdb.php">http://127.0.0.1/testdb.php</a><br />
检查 phpinfo 中的各项信息是否正确。</p>
<p>12、设置SSL并创建自己的CA</p>
<p># cd /usr/share/ssl/misc</p>
<p># ./CA -newca</p>
<p>屏幕上出现如下的提示:CA certificate filename (or enter to create)</p>
<p>这是要求输入要创建的CA的证书文件名, 可以直接回车或输入证书文件名。</p>
<p>Making CA certificate &#8230;<br />
Generating a 1024 bit RSA private key<br />
&#8230;&#8230;&#8230;++++++<br />
&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;..++++++<br />
writing new private key to &#8216;./demoCA/private/./cakey.pem&#8217;<br />
Enter PEM pass phrase:</p>
<p>Verifying password &#8211; Enter PEM pass phrase:-</p>
<p>此时要求输入和验证CA的私钥口令、国家代码（中国是CN）、省份、城市或地区、组织或企业名称、部门名称、CA的名称或服务器的主机名称、管理员电子邮件地址。</p>
<p>至此,在当前目录下生成了demoCA的目录,CA的证书就在该目录下,文件名为cacert.pem</p>
<p>生成服务器的证书请求</p>
<p># ./CA -newreq</p>
<p>屏幕上出现如下的提示:</p>
<p>Generating a 1024 bit RSA private key<br />
&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;..++++++<br />
&#8230;..++++++<br />
writing new private key to &#8216;newreq.pem&#8217;<br />
Enter PEM pass phrase:<br />
Verifying password &#8211; Enter PEM pass phrase:</p>
<p>此时要求输入和验证服务器的私钥口令、国家代码（中国是CN）、省份、城市或地区、组织或企业名称、部门名称、CA的名称或服务器的主机名称、管理员电子邮件地址。</p>
<p>Please enter the following &#8216;extra&#8217; attributes</p>
<p>to be sent with your certificaterequest</p>
<p>A challenge password []:</p>
<p>An optional company name []:</p>
<p>.Request (and private key) is in newreq.pem</p>
<p>这是要求输入服务器的相关信息。</p>
<p>此时,在当前目录下生成了一个名为newreq.pem的文件,包含了要生成服务器数字证书的请求。</p>
<p>签署证书</p>
<p># ./CA -sign</p>
<p>屏幕上出现如下的提示:</p>
<p>Using configuration from /usr/share/ssl/openssl.cnf</p>
<p>Enter PEM pass phrase:</p>
<p>此时一样需要输入CA的私钥口令、国家代码（中国是CN）、省份、城市或地区、组织或企业名称、部门名称、CA的名称或服务器的主机名称、管理员电子邮件地址。</p>
<p>Certificate is to be certified until Nov 19 13:46:19 2002 GMT (365 days)</p>
<p>Sign the certificate? [y/n]:y</p>
<p>这时显示证书请求文件中的各项信息,并询问是否要签署证书，回答y，进行签署。</p>
<p>1 out of 1 certificate requests certified, commit? [y/n]y</p>
<p>回答y,会显示已经签署的证书的信息,并在当前目录下生成服务器的证书文件newcert.pem。</p>
<p># mkdir /usr/local/apache2/conf/ssl.crt/<br />
# mkdir /usr/local/apache2/conf/ssl.key/<br />
# cp newcert.pem /usr/local/apache2/conf/ssl.crt/server.pem<br />
# cp newreq.pem /usr/local/apache2/conf/ssl.key/server.pem</p>
<p>更改服务器的证书文件的相关配置<br />
# vi /usr/local/apache2/conf/extra/httpd-ssl.conf</p>
<p>查找并修改</p>
<p># Server Certificate:<br />
# Point SSLCertificateFile at a PEM encoded certificate. If<br />
# the certificate is encrypted, then you will be prompted for a<br />
# pass phrase. Note that a kill -HUP will prompt again. Keep<br />
# in mind that if you have both an RSA and a DSA certificate you<br />
# can configure both in parallel (to also allow the use of DSA<br />
# ciphers, etc.)<br />
SSLCertificateFile /usr/local/apache2/conf/ssl.crt/server.pem<br />
#SSLCertificateFile /usr/local/apache2/conf/server-dsa.crt</p>
<p># Server Private Key:<br />
# If the key is not combined with the certificate, use this<br />
# directive to point at the key file. Keep in mind that if<br />
# you&#8217;ve both a RSA and a DSA private key you can configure<br />
# both in parallel (to also allow the use of DSA ciphers, etc.)<br />
SSLCertificateKeyFile /usr/local/apache2/conf/ssl.key/server.pem<br />
#SSLCertificateKeyFile /usr/local/apache2/conf/server-dsa.key</p>
<p>示例文件</p>
<p>在SSL的根目录中生成一个index.html,它是如下所示:</p>
<p>＜html＞</p>
<p>这是SSL示例!</p>
<p>＜/html＞</p>
<p>测试</p>
<p>假如Web服务器的DNS名称是www.cnprint.org.</p>
<p>在浏览器的URL地址栏里输入 <a href="http://www.cnprint.org/，浏览器便会显示APACHE安装时确省的Test">http://www.cnprint.org/，浏览器便会显示APACHE安装时确省的Test</a> Page.</p>
<p>在浏览器的URL地址栏里输入 <a href="https://www.cnprint.org/，注意:是">https://www.cnprint.org/，注意:是</a> https 而不是http !</p>
<p>浏览器会提示站点已经采用了SSL进行数据的加密传输.由于我们的CA证书不是浏览器缺省的信任的根证书,所以,浏览器会说无法确认服务器的证书可信。暂时不管,一直NEXT,最后,浏览器会显示:这是SSL示例!</p>
<p>可以把CA的证书放在非SSL的站点上,让浏览器下载并安装CA证书,并将其设置成可信任的根证书,便可解决上面的问题.8 解除HTTPD起动时的口令输入。</p>
<p>由于安全的原因,Web服务器的私钥是口令加密了的，每次重新起动HTTPD或Linux时,都会要求输入Web服务器的私钥的口令。</p>
<p>如果要解除HTTPD起动时的口令输入,可以这样:</p>
<p># cd /usr/local/apache2/conf/ssl.key/<br />
# cp server.pem server.pem.org<br />
# openssl rsa -in server.pem.org -out server.pem<br />
# chmod 400 server.pem</p>
<p>另外在网上看到一个方法，我没有试。有兴趣的可以试下。</p>
<p>创建SSL密码自动应答文件，否则每次Apache启动的时候，都会要求你输入SSL的密码.<br />
创建 /usr/local/apache2/conf/ssl.key/sendsslpwd ,内容如下.<br />
#!/bin/bash<br />
SSLpasswd=&#8221;YOUR PASSPHRASE&#8221;<br />
echo $SSLpasswd<br />
chmod 755 /usr/local/apache2/conf/ssl.key/sendsslpwd</p>
<p>此时，Web服务器的私钥已经没有口令加密,一定要确保server.pem文件除root外,任何用户均无权读取它。</p>
<p>13、安装 Zend Optimizer<br />
# cd /usr/local/src<br />
# wget <a href="http://downloads.zend.com/optimizer/...21-i386.tar.gz">http://downloads.zend.com/optimizer/&#8230;21-i386.tar.gz</a><br />
# tar xzvf ZendOptimizer-3.3.0-linux-glibc21-i386.tar.gz<br />
# ./ZendOptimizer-3.3.0-linux-glibc21-i386/install.sh<br />
按照它的提示一步步进行就行了。<br />
总之一句话。如果你的服务器环境不需要ZendOptimizer，那么能不安就不安装这个。避免和eaccelerator冲突。</p>
<p>14、安装eaccelerator // eaccelerator是php的加速软件，使用后php的执行效率会有较大幅度的提升。目前eaccelerator 0.9.5.2已经和ZendOptimizer-3.3.0能够基本上兼容啦。不过我个人觉得，ZendOptimizer-3.3.0没有加速的功能，反而使php运行变慢，只是起到了运行zend加密文件的作用而已。闲话不多说了，大家有兴趣的，可以去google下。</p>
<p># cd /usr/local/src<br />
# wget <a href="http://bart.eaccelerator.net/source/....9.5.2.tar.bz2">http://bart.eaccelerator.net/source/&#8230;.9.5.2.tar.bz2</a><br />
# tar -jxvf eaccelerator-0.9.5.2.tar.bz2<br />
# cd eaccelerator-0.9.5.2</p>
<p>export PHP_PREFIX=&#8221;/usr/local/php&#8221;<br />
$PHP_PREFIX/bin/phpize //指定一下php的目录</p>
<p># ./configure &#8211;enable-eaccelerator=shared &#8211;with-php-config=$PHP_PREFIX/bin/php-config // 设置</p>
<p># make &amp; make install</p>
<p>编译安装后我们会看到屏幕提示的eaccelerator.so所在的目录，比如我得到的是/usr/local/php/lib/php/extensions/no-debug-zts-20060613/eaccelerator.so，记住这个路径，待会要用到。</p>
<p>修改php.ini（安装完zend之后，php.ini存放于/usr/local/Zend/etc）<br />
在文件最后，zend之前，注意，这部分内容务必放在zend之前，不然可能会出现不可预期的服务器问题。添加下列信息：</p>
<p>[eaccelerator]<br />
extension=&#8221;/usr/local/php/lib/php/extensions/no-debug-zts-20060613/eaccelerator.so&#8221;<br />
eaccelerator.shm_size=&#8221;32&#8243;<br />
eaccelerator.cache_dir=&#8221;/tmp/eaccelerator&#8221;<br />
eaccelerator.enable=&#8221;1&#8243;<br />
eaccelerator.optimizer=&#8221;1&#8243;<br />
eaccelerator.check_mtime=&#8221;1&#8243;<br />
eaccelerator.debug=&#8221;0&#8243;<br />
eaccelerator.filter=&#8221;"<br />
eaccelerator.shm_max=&#8221;0&#8243;<br />
eaccelerator.shm_ttl=&#8221;0&#8243;<br />
eaccelerator.shm_prune_period=&#8221;0&#8243;<br />
eaccelerator.shm_only=&#8221;0&#8243;<br />
eaccelerator.compress=&#8221;1&#8243;<br />
eaccelerator.compress_level=&#8221;9&#8243;</p>
<p>解释:<br />
zend_extension 是安装完程序自动指示给我们的<br />
eaccelerator.shm_size=&#8221;32&#8243; 缓存大小单位MB<br />
eaccelerator.cache_dir=&#8221;/tmp/eaccelerator&#8221; 缓存路径</p>
<p># mkdir /tmp/eaccelerator // 建立目录<br />
# chmod 0777 /tmp/eaccelerator // 修改目录属性</p>
<p>最后重新启动apachectl</p>
<p>重启apache，phpinfo显示：<br />
This program makes use of the Zend Scripting Language Engine:<br />
Zend Engine v2.2.0, Copyright (c) 1998-2006 Zend Technologies<br />
with eAccelerator v0.9.5.2, Copyright (c) 2004-2006 eAccelerator, by eAccelerator<br />
with Zend Extension Manager v1.0.11, Copyright (c) 2003-2006, by Zend Technologies<br />
with Zend Optimizer v3.3.0, Copyright (c) 1998-2006, by Zend Technologies</p>
<p>也会有eAccelerator的具体信息。</p>
<p>15、安装phpmyadmin，管理mysql数据库</p>
<p># cd /usr/local/apache2/htdocs/<br />
# wget <a href="http://nchc.dl.sourceforge.net/sourc...-8-only.tar.gz">http://nchc.dl.sourceforge.net/sourc&#8230;-8-only.tar.gz</a></p>
<p># tar zxvf phpMyAdmin-2.11.1-all-languages-utf-8-only.tar.gz<br />
# mv phpMyAdmin-2.11.1-all-languages-utf-8-only phpmyadmin</p>
<p># cd phpmyadmin/libraries</p>
<p>修改配置文件<br />
# vi config.default.php</p>
<p>找到这几行进行修改：<br />
$cfg['Servers'][$i]['auth_type'] = &#8216;http&#8217;; // Authentication method (valid choices: config, http, HTTP, signon or cookie)<br />
$cfg['Servers'][$i]['user'] = &#8216;root&#8217;; // MySQL user<br />
$cfg['Servers'][$i]['password'] = &#8216;PASSWORD&#8217;; // MySQL password (only needed</p>
<p>经过这几个步骤，我们一个比较安全的LAMP服务器就环境基本建立成功啦。感觉上也不是很难，是吧？</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ray77.com/centos-apache-php-mysql.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>程序员的十层楼(测测你的技术层级)</title>
		<link>http://www.ray77.com/programer-ten-floors.html</link>
		<comments>http://www.ray77.com/programer-ten-floors.html#comments</comments>
		<pubDate>Thu, 05 Feb 2009 04:45:45 +0000</pubDate>
		<dc:creator>Rock</dc:creator>
				<category><![CDATA[Develop]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://www.ray77.com/%e7%a8%8b%e5%ba%8f%e5%91%98%e7%9a%84%e5%8d%81%e5%b1%82%e6%a5%bc%e6%b5%8b%e6%b5%8b%e4%bd%a0%e7%9a%84%e6%8a%80%e6%9c%af%e5%b1%82%e7%ba%a7.html</guid>
		<description><![CDATA[Programmer Ten Floors
　　自西方文艺复兴以来，中国在自然科学方面落后西方很多，软件领域也不例外。当然现在中国的许多程序员们对此可能有许多不同的意见，有些人认为中国的程序员水平远落后于西方，有些则认为中国的程序员个人能力并不比西方的程序员差，只是整个软件产业落后而已。
　　那么，到底中国的程序员水平比西方程序员水平差，还是中国有许多优秀的程序员达到或超过了西方程序员同等水平呢？要解决这个问题，必须先知道程序员有多少种技术层级，每个层级需要什么样的技术水平，然后再比较中国和西方在各个技术层级的人数，就可以知道到底有没有差距，差距有多大。
　　当然，对于如何划分程序员的技术层级，不同公司或不同人会有不同的划分标准，下面的划分仅代表个人的观点，如有不当之处，还请砸板砖予以纠正。 
第1层 菜鸟
　　第1层楼属于地板层，迈进这层楼的门槛是很低的。基本上懂计算机的基本操作，了解计算机专业的一些基础知识，掌握一门基本的编程语言如C/C++，或者Java，或者JavaScript，&#8230;，均可入门迈进这层。
　　在这层上，中国有着绝对的优势，除了从计算机专业毕业的众多人数外，还有大量的通信、自动化、数学等相关专业的人士进入这一行，此外还有众多的其他专业转行的人士，人数绝对比西方多出甚多。并且还有一个优势就是我们这层人员的平均智商比西方肯定高。
　　没有多少人愿意一辈子做菜鸟，因为做&#8221;菜鸟&#8221;的滋味实在是不咋的，整天被老大们吆喝着去装装机器，搭建一下测试环境，或者对照着别人写好的测试用例做一些黑盒测试，好一点的可以被安排去写一点测试代码。当然如果运气&#8221;好&#8221;的话，碰到了国内的一些作坊式的公司，也有机会去写一些正式的代码。
　　所以，菜鸟们总是在努力学习，希望爬更高的一层楼去。
第2层 大虾
　　从第1层爬到第2层相对容易一些，以C/C++程序员为例，只要熟练掌握C/C++编程语言，掌握C标准库和常用的各种数据结构算法，掌握STL的基本实现和使用方法，掌握多线程编程基础知识，掌握一种开发环境，再对各种操作系统的API都去使用一下，搞网络编程的当然对socket编程要好好掌握一下，然后再学习一些面向对象的设计知识和设计模式等，学习一些测试、软件工程和质量控制的基本知识，大部分人经过2～3年的努力，都可以爬到第2层，晋升为&#8221;大虾&#8221;。
　　中国的&#8221;大虾&#8221;数量和&#8221;菜鸟&#8221;数量估计不会少多少，所以这层上仍然远领先于西方。
　　大虾们通常还是有些自知之明，知道自己只能实现一些简单的功能，做不了大的东西，有时候还会遇到一些疑难问题给卡住，所以他们对那些大牛级的人物通常是非常崇拜的，国外的如Robert C. Martin、Linus Torvalds，国内的如求伯君、王志东等通常是他们崇拜的对象。其中的有些人希望有一天也能达到这些大牛级人物的水平，所以他们继续往楼上爬去。
第3层 牛人
　　由于&#8221;大虾&#8221;们经常被一些疑难问题给卡住，所以有了&#8221;大虾&#8221;们只好继续学习，他们需要将原来所学的知识进一步熟练掌握，比如以熟练掌握C++编程语言为例，除了学一些基础性的C++书籍如《C++ Primer》，《Effective C++》，《Think in C++》，《Exception C++》等之外，更重要的是需要了解C++编译器的原理和实现机制，了解操作系统中的内部机制如内存管理、进程和线程的管理机制，了解处理器的基础知识和代码优化的方法，此外还需要更深入地学习更多的数据结构与算法，掌握更深入的测试和调试知识以及质量管理和控制方法，对各种设计方法有更好的理解等。
　　学习上面说的这些知识不是一挥而就的，不看个三五十本书并掌握它是做不到的。以数据结构算法来说，至少要看个5～10本这方面的著作；以软件设计来说，光懂结构化设计、面向对象设计和一些设计模式是不够的，还要了解软件架构设计、交互设计、面向方面的设计、面向使用的设计、面向数据结构算法的设计、情感化设计等，否则是很难进到这个楼层的。
　　当然除了上面说的知识外，大虾们还需要去学习各种经验和技巧。当然这点难不倒他们，现在出版的书籍众多，网络上的技术文章更是不胜数，然后再去各种专业论坛里泡一泡，把这些书籍和文章中的各种经验、技能、技巧掌握下来，再去学习一些知名的开源项目如Apache或Linux操作系统的源代码实现等。此时对付一般的疑难问题通常都不在话下，菜鸟和大虾们会觉得你很&#8221;牛&#8221;，你也就爬到了第3层，晋升为&#8221;牛人&#8221;了。
　　看了上面所讲的要求，可能有些大虾要晕过去了，成为牛人要学这么多东西啊！要求是不是太高了？其实要求一点也不高，这么点东西都掌握不了的话，怎么能让别人觉得你&#8221;牛&#8221;呢？
　　需要提一下的是，进入多核时代后，从第2层爬到第3层增加了一道多核编程的门槛。当然要迈过这道门槛并不难，已经有很多前辈高人迈进了这道门槛，只要循着他们的足迹前进就可以了。想迈进这道门槛者不妨去学习一下TBB开源项目的源代码(链接：http://www.threadingbuildingblocks.org/)，然后上Intel的博客（http://softwareblogs-zho.intel.com/）和多核论坛（http://forum.csdn.net/Intel/IntelMulti-core/）去看看相关文章，再买上几本相关的书籍学习一下。
　　在国内， 一旦成为&#8221;牛人&#8221;，通常可以到许多知名的公司里去，运气好者可以挂上一个架构师的头衔，甚至挂上一个&#8221;首席架构师&#8221;或者&#8221;首席xx学家&#8221;的头衔也不足为奇。有不少爬到这层的人就以为到了楼顶了，可以眼睛往天上看了，开始目空一切起来，以为自己什么都可以做了，什么都懂了，经常在网络上乱砸板砖是这个群体的最好写照。由此也看出，国内的牛人数量仍然众多，远多于西方的牛人数量，在这层上仍然是领先的。
　　也有不少谦虚的&#8221;牛人&#8221;，知道自己现在还不到半桶水阶段。他们深知爬楼的游戏就像猴子上树一样，往下看是笑脸，往上看是屁股。为了多看笑脸，少看屁股，他们并没有在此停步不前，而是继续寻找到更上一层的楼梯，以便继续往上爬。
第4层 大牛
　　从第3层爬到第4层可不像上面说过的那几层一样容易，要成为大牛的话，你必须要能做牛人们做不了的事情，解决牛人们解决不了问题。比如牛人们通常都不懂写操作系统，不会写编译器，不懂得TCP/IP协议的底层实现，如果你有能力将其中的任何一个实现得象模象样的话，那么你就从牛人升级为&#8221;大牛&#8221;了。
　　当然，由于各个专业领域的差别，这里举操作系统、编译器、TCP/IP协议只是作为例子，并不代表成为&#8221;大牛&#8221;一定需要掌握这些知识，以时下热门的多核编程来说，如果你能比牛人们更深入地掌握其中的各种思想原理，能更加自如的运用，并有能力去实现一个象开源项目TBB库一样的东西，也可以成为&#8221;大牛&#8221;，又或者你能写出一个类似Apache一样的服务器，或者写出一个数据库，都可以成为&#8221;大牛&#8221;。
　　要成为&#8221;大牛&#8221;并不是一件简单的事情，需要付出比牛人们多得多的努力，一般来说，至少要看过200~400本左右的专业书籍并好好掌握它，除此之外，还得经常关注网络和期刊杂志上的各种最新信息。
　　当&#8221;牛人&#8221;晋升为&#8221;大牛&#8221;，让&#8221;牛人们&#8221;发现有比他们更牛的人时，对&#8221;牛人&#8221;们的心灵的震撼是可想而知的。由于牛人们的数量庞大，并且牛人对大虾和菜鸟阶层有言传身教的影响，所以大牛们通常能获得非常高的社会知名度，几乎可以用&#8221;引无数菜鸟、大虾、牛人竞折腰&#8221;来形容，看看前面提过的Linus Torvalds等大牛，应该知道此言不虚。
　　虽然成为&#8221;大牛&#8221;的条件看起来似乎很高似的，但是这层楼并不是很难爬的一层，只要通过一定的努力，素质不是很差，还是有许多&#8221;牛人&#8221;可以爬到这一层的。由此可知，&#8221;大牛&#8221;这个楼层的人数其实并不像想像的那么少，例如比尔·盖茨之类的人好像也是属于这一层的。
　　由于&#8221;大牛&#8221;这层的人数不少，所以也很难统计除到底是中国的&#8221;大牛&#8221;数量多还是西方的大牛数量多？我估计应该是个旗鼓相当的数量，或者中国的&#8221;大牛&#8221;们会更多一些。
　　看到这里，可能会有很多人会以为我在这里说瞎话，Linus Torvalds写出了著名的Linux操作系统，我国并没有人写出过类似的东西啊，我国的&#8221;大牛&#8221;怎么能和西方的比呢? 不知大家注意到没有，Linus Torvalds只是写出了一个&#8221;象模象样&#8221;的操作系统雏形，Linux后来真正发展成闻名全球的开源操作系统期间，完全是因为许多支持开源的商业公司如IBM等，派出了许多比Linus Torvalds更高楼层的幕后英雄在里面把它开发出来的。
　　可能有些菜鸟认为Linus Torvalds是程序员中的上帝，不妨说个小故事：
　　Linus，Richard Stallman和Don Knuth（高德纳）一同参加一个会议。
　　Linus 说：&#8221;上帝说我创造了世界上最优秀的操作系统。&#8221;
　　Richard Stallman自然不甘示弱地说：&#8221;上帝说我创造了世界上最好用的编译器。&#8221;
　　Don Knuth一脸疑惑的说：&#8221;等等，等等，我什么时候说过这些话？&#8221;
　　由此可以看出，Linus Torvalds的技术水平并不像想像中那么高，只是&#8221;牛人&#8221;和&#8221;大虾&#8221;觉得&#8221;大牛&#8221;比他们更牛吧了。在我国，有一些当时还处于&#8221;大虾&#8221;层的人物，也能写出介绍如何写操作系统的书，并且书写得非常出色，而且写出了一个有那么一点点象模象样的操作系统来。我想中国的&#8221;大牛&#8221;们是不会比西方差的，之所以没有人写出类似的商业产品来，完全是社会环境的原因，并不是技术能力达不到的原因。
　　&#8221;大牛&#8221;们之所以成为大牛，主要的原因是因为把&#8221;牛人&#8221;给盖了下去，并不是他们自己觉得如何牛。也许有很多菜鸟、大虾甚至牛人觉得&#8221;大牛&#8221;这层已经到顶了，但大多数&#8221;大牛&#8221;估计应该是有自知之明的，他们知道自己现在还没有爬到半山腰，也就勉强能算个半桶水的水平，其中有些爬到这层没有累趴下，仍然能量充沛，并且又有志者，还是会继续往更上一层楼爬的。
　　看到这里，也许有些菜鸟、大虾、牛人想不明白了，还有比&#8221;大牛&#8221;们更高的楼层，那会是什么样的楼层？下面就来看看第5层楼的奥妙。
第5层 专家
　　当大牛们真正动手做一个操作系统或者类似的其他软件时，他们就会发现自己的基本功仍然有很多的不足。以内存管理为例，如果直接抄袭Linux或者其他开源操作系统的内存管理算法，会被人看不起的，如果自动动手实现一个内存管理算法，他会发现现在有关内存管理方法的算法数量众多，自己并没有全部学过和实践过，不知道到底该用那种内存管理算法。
　　看到这里，可能有些人已经明白第5层楼的奥妙了，那就是需要做基础研究，当然在计算机里，最重要的就是&#8221;计算&#8221;二字，程序员要做基础研究，主要的内容就是研究非数值&#8221;计算&#8221;。
　　非数值计算可是一个非常庞大的领域，不仅时下热门的&#8221;多核计算&#8221;与&#8221;云计算&#8221;属于非数值计算范畴，就是软件需求、设计、测试、调试、评估、质量控制、软件工程等本质上也属于非数值计算的范畴，甚至芯片硬件设计也同样牵涉到非数值计算。如果你还没有真正领悟&#8221;计算&#8221;二字的含义，那么你就没有机会进到这层楼来。
　　可能有人仍然没有明白为什么比尔·盖茨被划在了大牛层，没有进到这层来。虽然比尔·盖茨大学未毕业，学历不够，但是家有藏书2万余册，进入软件这个行业比绝大部分人都早，撇开他的商业才能不谈，即使只看他的技术水平，也可以算得上是学富五车，顶上几个普通的计算机软件博士之和是没有问题的，比起Linus Torvalds之类的&#8221;大牛&#8221;们应该技高一筹才对，怎么还进不了这层楼呢？
　　非常遗憾的是，从Windows操作系统的实现来看，其对计算的理解是很肤浅的，如果把Google对计算方面的理解比做大学生，比尔·盖茨只能算做一个初中生，所以比尔·盖茨永远只能做个大牛人，成不了&#8221;专家&#8221;。
　　看到这里，也许国内的大牛们要高兴起来了，原来比尔·盖茨也只和我等在同一个层次，只要再升一层就可以超越比尔·盖茨了。不过爬到这层可没有从&#8221;牛人&#8221;升为&#8221;大牛&#8221;那么简单，人家比尔·盖茨都家有2万多册书，让你看个500~1000本以上的专业书籍并掌握好它应该要求不高吧。当然，这并不是主要的条件，更重要的是，需要到专业的学术站点去学习了，到ACM，IEEE，Elsevier，SpringerLink，SIAM等地方去下载论文应该成为你的定期功课，使用Google搜索引擎中的学术搜索更是应该成为你的日常必修课。此外，你还得经常关注是否有与你研究相关的开源项目冒出来，例如当听到有TBB这样针对多核的开源项目时，你应该第一时间到Google里输入&#8221;TBB&#8221;搜索一下，将其源代码下载下来好好研究一番，这样也许你的一只脚已经快迈进了这层楼的门槛。
　　当你象我上面说的那样去做了以后，随着时间的推移，总会有某天，你发现，在很多小的领域里，你已经学不到什么新东西了，所有最新出来的研究成果你几乎都知道。此时你会发现你比在做&#8221;牛人&#8221;和&#8221;大牛&#8221;时的水平不知高出了多少，但是你一点也&#8221;牛&#8221;不起来，因为你学的知识和思想都是别人提出来的，你自己并没有多少自己的知识和思想分享给别人，所以你还得继续往楼上爬才行。
　　我不知道国内的&#8221;专家&#8221;到底有多少，不过有一点可以肯定的是，如果把那些专门蒙大家的&#8221;砖家&#8221;也算上的话，我们的砖家比西方的要多得多。
第6层 学者
　　当&#8221;专家&#8221;们想继续往上一层楼爬时，他们几乎一眼就可以看到楼梯的入口，不过令他们吃惊的是，楼梯入口处竖了一道高高的门槛，上面写着&#8221;创新&#8221;二字。不幸的是，大多数人在爬到第5层楼时已经体能消耗过度，无力翻过这道门槛。
　　有少数体能充足者，可以轻易翻越这道门槛，但是并不意味着体力消耗过度者就无法翻越，因为你只是暂时还没有掌握恢复体能的方法而已，当掌握了恢复体能的方法，将体能恢复后，你就可以轻易地翻越这道门槛了。
　　怎么才能将体能恢复呢？我们的老祖宗&#8221;孔子&#8221;早就教导过我们&#8221;温故而知新&#8221;，在英文里，研究的单词是&#8221;research&#8221;，其前缀&#8221;re&#8221;和&#8221;search&#8221;分别是什么意思不用我解释吧。或许有些人觉得&#8221;温故而知新&#8221;和&#8221;research&#8221;有些抽象，不好理解，我再给打个简单的比方，比如你在爬一座高山，爬了半天，中途体力不支，怎么恢复体力呢？自然是休息一下，重新进食一些食物，体力很快就可以得到恢复。
　　由此可知，对体能消耗过度者，休息＋重新进食通常是恢复体能的最佳选择。可惜的是，国内的老板们并不懂得这点，他们的公司里不仅连正常国家规定的休息时间都不给足，有些公司甚至有员工&#8221;过劳死&#8221;出现。所以国内能翻越&#8221;创新&#8221;这道门槛的人是&#8221;少之又少&#8221;，和西方比起来估计是数量级的差别。
　　再说说重新进食的问题，这个重新进食是有讲究的，需要进食一些基础性易消化的简单食物，不能进食山珍海味级的复杂食物，否则很难快速吸收。以查找为例，并不是去天天盯着那些复杂的查找结构和算法进行研究，你需要做的是将二分查找、哈希查找、普通二叉树查找等基础性的知识好好地复习几遍。
　　以哈希查找为例，首先你需要去将各种冲突解决方法如链式结构、二次哈希等编写一遍，再试试不同种类的哈希函数，然后还需要试试在硬盘中如何实现哈希查找，并考虑数据从硬盘读到内存后，如何组织硬盘中的数据才能快速地在内存中构建出哈希表来，&#8230;，这样你可能需要将一个哈希表写上十几个不同的版本，并比较各个版本的性能、功能方面的区别和适用范围。
　　总之，对任何一种简单的东西，你需要考虑各种各样的需求，以需求来驱动研究。最后你将各种最基础性的查找结构和算法都了然于胸后，或许某天你再看其他更复杂的查找算法，或者你在散步时，脑袋里灵光一现，突然间就发现了更好的方法，也就从专家晋升为&#8221;学者&#8221;了。
　　学者所做的事情，通常都是在前人的基础上，进行一些小的优化和改进，例如别人发明了链式基数排序的方法，你第1个发现使用一定的方法，可以用数组替代链表进行基数排序，性能还能得到进一步提高。
　　由于学者需要的只是一些小的优化改进，因此中国还是有一定数量的学者。不过和国外的数量比起来，估计少了一个数量级而已。
　　也许有人会觉得现在中国许多公司申请专利的数量达到甚至超过西方发达国家了，我们的学者数量应该不会比他们少多少。因此，有必要把专利和这里说的创新的区别解释一下。
　　所谓专利者，只要是以前没有的，新的东西，都可以申请专利；甚至是以前有的东西，你把他用到了一个新的领域的产品里去，也可以申请专利。比如你在房子里造一个水泥柱子，只要以前没有人就这件事申请专利，那么你就可以申请专利，并且下次你把水泥柱子挪一个位置，又可以申请一个新的专利；或者你在一个柜子上打上几个孔，下次又把孔的位置改一改，&#8230;，均可申请专利。
　　这层楼里所说的创新，是指学术层面的创新，是基础研究方面的创新，和专利的概念是完全不同的，难度也是完全不同的。你即使申请了一万个象那种打孔一类的专利，加起来也够不到这层楼里的一个创新。
　　当你爬到第6层楼时，你也许会有一种突破极限的快感，因为你终于把那道高高的写着&#8221;创新&#8221;二字的门槛给翻过去了，实现了&#8221;0&#8243;的突破。这时，你也许有一种&#8221;独上高楼，欲望尽天涯路&#8221;的感觉，但是很快你会发现看到的都是比较近的路，远处的路根本看不清楚。如果你还有足够的体力的话，你会想爬到更高一层的楼层去。
第7层 大师
　　从第6层楼爬到第7层楼，并没有多少捷径可走，主要看你有没有足够的能量。你如果能象Hoare一样设计出一个快速排序的算法；或者象Eugene W. Myers一样设计出了一个用编辑图的最短路径模型来解决diff问题的算法；或者象M.J.D. Powell一样提出了一个能够处理非线性规划问题的SQP方法；或者你发现基于比较的排序算法，它的复杂度下界为O(NLogN)；或者你发现用栈可以将递归的算法变成非递归的；或者你设计出一个红黑树或者AVL树之类的查找结构；或者你设计出一个象C++或Java一样的语言；或者你发明了UML；&#8230;，你就爬到了第7层，晋升为&#8221;大师&#8221;了。
　　上面举的这些例子中，其中有些人站的楼层比这层高，这里只是为了形象说明而举例他们的某个成就。从上面列出的一些大师的贡献可以看出，成为大师必须要有较大的贡献。首先解决问题必须是比较重要的，其次你要比前辈们在某方面有一个较大的提高，或者你解决的是一个全新的以前没有解决过的问题；最重要的是，主要的思路和方法必须是你自己提供的，不再是在别人的思路基础上进行的优化和改进。
　　看了上面这些要求，如果能量不够的话，你也许会觉得有些困难，所以不是每个人都能成为&#8221;大师&#8221;的。中国软件业里能称得上是&#8221;大师&#8221;的人，用屈指可数来形容，估计是绰绰有余。值得一提得是，国外的&#8221;大师&#8221;就象我们的&#8221;大牛&#8221;一样满天飞的多。
　　我把我猜测本国有可能进到这层楼的大师列一下，以起个抛砖引玉的作用。汉王的&#8221;手写识别&#8221;技术由于是完全保密的，不知道它里面用了什么思想，原创思想占的比重有多少，因此不知道该把它划到这层楼还是更高一层楼去。原山东大学王小云教授破解DES和MD5算法时，用到的方法不知道是不是完全原创的，如果是的话也可进到这层楼来。
　　陈景润虽然没有彻底解决哥德巴赫猜想，但他在解决问题时所用的方法是创新的，因此也可以进到这层楼来。当然，如果能彻底解决哥德巴赫猜想，那么可以算到更高的楼层去。
　　求伯君和王志东等大牛们，他们在做WPS和表格处理之类的软件时，不知是否有较大的原创算法在里面，如果有的话就算我错把他们划到了大牛层。由于所学有限，不知道国内还有那些人能够得上&#8221;大师&#8221;的级别，或许有少量做研究的教授、院士们，可以达到这个级别，有知道的不妨回个帖子晾一晾。
　　鉴于&#8221;大师&#8221;这个称号的光环效应，相信有不少人梦想着成为&#8221;大师&#8221;。或许你看了前面举的一些大师的例子，你会觉得要成为大师非常困难。不妨说一下，现在有一条通往&#8221;大师&#8221;之路的捷径打开了，那就是多核计算领域，有大量的处女地等待大家去挖掘。
　　以前在单核时代开发的各种算法，现在都需要改写成并行的。数据结构与算法、图像处理、数值计算、操作系统、编译器、测试调试等各个领域，都存在大量的机会，可以让你进到这层楼来，甚至有可能让你进到更高一层楼去。
第8层 科学家
　　科学家向来都是一个神圣的称号，因此我把他放在了“大师”之上。要成为科学家，你的贡献必须超越大师，不妨随便举一些例子。
　　如果你象Dijkstra一样设计了ALGOL语言，提出了程序设计的三种基本结构：顺序、选择、循环，那么你可以爬到第8层楼来。顺便说一下，即使抛开这个成果，Dijkstra凭他的PV操作和信号量概念的提出，同样可以进到这层楼。
　　如果你象Don Knuth一样，是数据结构与算法这门学科的重要奠基者，你也可以进到这层楼来。当然，数据结构和算法这门学科不是某个人开创的，是许多大师和科学家集体开创的。
　　如果你象巴科斯一样发明了Fortran语言，并提出了巴科斯范式，对高级程序语言的发展起了重要作用，你也可以进到这层楼来。
　　或者你象Ken Thompson、Dennis Ritchie一样发明了Unix操作系统和功能强大、高效、灵活、表达力强的C语言，对操作系统理论和高级编程语言均作出重大贡献，那么你也可以进到这层楼来。
　　或者你有Frederick P. Brooks一样机会，可以去领导开发IBM的大型计算机System/360和OS/360操作系统，并在失败后反思总结，写出《人月神话》，对软件工程作出里程碑式的贡献，你也可以进到这层来。
　　或者你提出了面向对象设计的基本思想，或者你设计了互联网的TCP/IP协议，或者你象Steven A.Cook一样奠定NP完全性的理论基础，或者你象Frances Allen一样专注于并行计算来实现编译技术，在编译优化理论和技术取得基础性的成就，…，均可进入这层。
　　当然，如果你发明了C++语言或者Java语言，你进不到这层来，因为你用到的主要思想都是这层楼中的科学家提出的，你自己并没有没有多少原创思想在里面。
　　看了上面列出的科学家的成就，你会发现，要成为“科学家”，通常要开创一门分支学科，或者是这个分支学科的奠基者，或者在某个分支学科里作出里程碑式的重大贡献。如果做不到这些的话，那么你能象Andrew C. Yao（姚期智）一样在对计算理论的多个方向如伪随机数生成，密码学与通信复杂度等各个方向上作出重要贡献，成为集大成者，也可以进入这层楼。
　　成为“科学家”后，如果你有幸象Dijkstra一样，出现在一个非常重视科学的国度。当你去世时，你家乡满城的人都会自动地去为你送葬。不过如果不幸生错地方的话，能不挨“板砖”估计就算万幸了。
　　从上面随便举的一些例子中，你可能能猜到，西方科学家的数量是非常多的，于是你会想中国应该也有少量的科学家吧？我可以很负责任地告诉你一个不幸的结果，中国本土产生的科学家的数量为0。目前在国内，软件领域的唯一的科学家就是上面提过的姚期智，还是国外请回来的，并不是本土产生的。
　　可能你不同意我说的本土科学家数量为0的结论，因为你经常看到有许多公司里都有所谓“首席XX科学家”的头衔。我想说的是，这些所谓的“首席XX科学家”都是远远够不到这层楼的级别的，有些人的水平估计也就是一个“牛人”或“大牛”的级别，好一点的最多也就一个“学者”的级别。尤其是那些被称作“首席经X学家”的，基本上可以把称号改为“首席坑大家”。
　　虽然我国没有人能爬到这层楼上来，但是西方国家仍然有许多人爬到了比这层更高的楼上。如果要问我们比西方落后多少？那么可以简单地回答为：“落后了三层楼”。下面就来看看我们做梦都没有到过的更高一层楼的秘密。
第9层 大科学家
　　进入这层楼的门槛通常需要一些运气，比如某天有个苹果砸到你头上时，你碰巧发现了万有引力，那么你可以进到这层楼来。当然，万有引力几百年前就被人发现了，如果你现在到处嚷嚷着说你发现了万有引力，恐怕马上会有人打110，然后警察会把你送到不正常人类的聚集地去。因此，这里举万有引力的例子，只是说你要有类似的成就才能进到这层楼来。
　　牛顿发现万有引力定律开创了经典物理运动力学这门学科，如果你也能开创一门大的学科，那么你就从科学家晋升为“大科学家”。比如爱因斯坦创建了相对论，从一个小职员变成了大科学家。当然大科学家可远不止这两人，数学界里比物理学界更是多得多，如欧几里得创建了平面几何，笛卡尔开创解析几何，还有欧拉、高斯、莱布尼茨等数不清的人物，跟计算相关的大科学家则有图灵等人。
　　从上面列出的一些大科学家可以发现，他们的成就不仅是开创了一个大的学科，更重要的是他们的成就上升到了“公理”的层面。发现公理通常是需要一点运气的，如果你的运气不够好的话，另外还有一个笨办法也可以进到这层楼来，那就是成为集大成者。例如冯·诺伊曼，对数学的所有分支都非常了解，许多领域都有较大的贡献，即使撇开他对计算机的开创贡献，成为大科学家照样绰绰有余。
　　当然，程序员们最关心的是自己有没有机会变成大科学家。既然计算机这门大学科的开创性成果早就被冯·诺伊曼、图灵等人摘走了，那么程序员们是不是没有机会变成大科学家了呢？我们的古人说得好：“江山代有才人出，各领风骚数百年”，现在在计算机这门学科下面诞生了许多非常重要的大的分支，所以你还是有足够的机会进到这层楼的。
　　如果你能够彻底解决自然语言理解（机器翻译）这门学科中的核心问题， 或者你在人工智能或者机器视觉（图像识别）方面有突破性的发现，那么你同样可以轻易地晋升为“大科学家”。这样当某天你老了去世时，或许那天国人已经觉醒，你也能享受到如Dijkstra一样的待遇，有满城甚至全国的人去为你送葬。
　　现在还剩下另外一个大家感兴趣的问题没有讨论，那就是这层中已经出现了牛顿、爱因斯坦、高斯等我们平常人都认为是顶级的科学家，是不是这层已经是楼顶了呢？相信还记得本文标题的人应该知道现在仅仅是第9层，还有第10层没有到达呢。可能不少人现在要感到困惑了，难道还有人站在比牛顿、爱因斯坦、高斯等人更高的楼层上？
　　这个世界上确实存在可以用一只手的手指数得清的那么几个人，他们爬到了第10层楼上。因此，第10层楼不是虚构的，而是确实存在的。如果对此有疑惑或者认为我在胡诌一番的话，那么不妨继续往下看下去，窥一下第10层楼的秘密。
]]></description>
			<content:encoded><![CDATA[<div class="wp-caption aligncenter" style="width: 610px"><img style="border: 0px;" title="程序员的十层楼" src="http://www.ray77.com/wp-content/uploads/2009/02/vs2008-home-banner.jpg" border="0" alt="vs2008_home_banner" width="600" height="142" /><p class="wp-caption-text">Programmer Ten Floors</p></div>
<p>　　自西方文艺复兴以来，中国在自然科学方面落后西方很多，软件领域也不例外。当然现在中国的许多程序员们对此可能有许多不同的意见，有些人认为中国的程序员水平远落后于西方，有些则认为中国的程序员个人能力并不比西方的程序员差，只是整个软件产业落后而已。</p>
<p>　　那么，到底中国的程序员水平比西方程序员水平差，还是中国有许多优秀的程序员达到或超过了西方程序员同等水平呢？要解决这个问题，必须先知道程序员有多少种技术层级，每个层级需要什么样的技术水平，然后再比较中国和西方在各个技术层级的人数，就可以知道到底有没有差距，差距有多大。</p>
<p>　　当然，对于如何划分程序员的技术层级，不同公司或不同人会有不同的划分标准，下面的划分仅代表个人的观点，如有不当之处，还请砸板砖予以纠正。 <span id="more-621"></span></p>
<p><strong>第1层 菜鸟</strong></p>
<p>　　第1层楼属于地板层，迈进这层楼的门槛是很低的。基本上懂计算机的基本操作，了解计算机专业的一些基础知识，掌握一门基本的编程语言如C/C++，或者Java，或者JavaScript，&#8230;，均可入门迈进这层。</p>
<p>　　在这层上，中国有着绝对的优势，除了从计算机专业毕业的众多人数外，还有大量的通信、自动化、数学等相关专业的人士进入这一行，此外还有众多的其他专业转行的人士，人数绝对比西方多出甚多。并且还有一个优势就是我们这层人员的平均智商比西方肯定高。</p>
<p>　　没有多少人愿意一辈子做菜鸟，因为做&#8221;菜鸟&#8221;的滋味实在是不咋的，整天被老大们吆喝着去装装机器，搭建一下测试环境，或者对照着别人写好的测试用例做一些黑盒测试，好一点的可以被安排去写一点测试代码。当然如果运气&#8221;好&#8221;的话，碰到了国内的一些作坊式的公司，也有机会去写一些正式的代码。</p>
<p>　　所以，菜鸟们总是在努力学习，希望爬更高的一层楼去。</p>
<p><strong>第2层 大虾</strong></p>
<p>　　从第1层爬到第2层相对容易一些，以C/C++程序员为例，只要熟练掌握C/C++编程语言，掌握C标准库和常用的各种数据结构算法，掌握STL的基本实现和使用方法，掌握多线程编程基础知识，掌握一种开发环境，再对各种操作系统的API都去使用一下，搞网络编程的当然对socket编程要好好掌握一下，然后再学习一些面向对象的设计知识和设计模式等，学习一些测试、软件工程和质量控制的基本知识，大部分人经过2～3年的努力，都可以爬到第2层，晋升为&#8221;大虾&#8221;。</p>
<p>　　中国的&#8221;大虾&#8221;数量和&#8221;菜鸟&#8221;数量估计不会少多少，所以这层上仍然远领先于西方。</p>
<p>　　大虾们通常还是有些自知之明，知道自己只能实现一些简单的功能，做不了大的东西，有时候还会遇到一些疑难问题给卡住，所以他们对那些大牛级的人物通常是非常崇拜的，国外的如Robert C. Martin、Linus Torvalds，国内的如求伯君、王志东等通常是他们崇拜的对象。其中的有些人希望有一天也能达到这些大牛级人物的水平，所以他们继续往楼上爬去。</p>
<p><strong>第3层 牛人</strong></p>
<p>　　由于&#8221;大虾&#8221;们经常被一些疑难问题给卡住，所以有了&#8221;大虾&#8221;们只好继续学习，他们需要将原来所学的知识进一步熟练掌握，比如以熟练掌握C++编程语言为例，除了学一些基础性的C++书籍如《C++ Primer》，《Effective C++》，《Think in C++》，《Exception C++》等之外，更重要的是需要了解C++编译器的原理和实现机制，了解操作系统中的内部机制如内存管理、进程和线程的管理机制，了解处理器的基础知识和代码优化的方法，此外还需要更深入地学习更多的数据结构与算法，掌握更深入的测试和调试知识以及质量管理和控制方法，对各种设计方法有更好的理解等。</p>
<p>　　学习上面说的这些知识不是一挥而就的，不看个三五十本书并掌握它是做不到的。以数据结构算法来说，至少要看个5～10本这方面的著作；以软件设计来说，光懂结构化设计、面向对象设计和一些设计模式是不够的，还要了解软件架构设计、交互设计、面向方面的设计、面向使用的设计、面向数据结构算法的设计、情感化设计等，否则是很难进到这个楼层的。</p>
<p>　　当然除了上面说的知识外，大虾们还需要去学习各种经验和技巧。当然这点难不倒他们，现在出版的书籍众多，网络上的技术文章更是不胜数，然后再去各种专业论坛里泡一泡，把这些书籍和文章中的各种经验、技能、技巧掌握下来，再去学习一些知名的开源项目如Apache或Linux操作系统的源代码实现等。此时对付一般的疑难问题通常都不在话下，菜鸟和大虾们会觉得你很&#8221;牛&#8221;，你也就爬到了第3层，晋升为&#8221;牛人&#8221;了。</p>
<p>　　看了上面所讲的要求，可能有些大虾要晕过去了，成为牛人要学这么多东西啊！要求是不是太高了？其实要求一点也不高，这么点东西都掌握不了的话，怎么能让别人觉得你&#8221;牛&#8221;呢？</p>
<p>　　需要提一下的是，进入多核时代后，从第2层爬到第3层增加了一道多核编程的门槛。当然要迈过这道门槛并不难，已经有很多前辈高人迈进了这道门槛，只要循着他们的足迹前进就可以了。想迈进这道门槛者不妨去学习一下TBB开源项目的源代码(链接：<a href="http://www.threadingbuildingblocks.org/" target="_blank">http://www.threadingbuildingblocks.org/</a>)，然后上Intel的博客（<a href="http://softwareblogs-zho.intel.com/" target="_blank">http://softwareblogs-zho.intel.com/</a>）和多核论坛（<a href="http://forum.csdn.net/Intel/IntelMulti-core/" target="_blank">http://forum.csdn.net/Intel/IntelMulti-core/</a>）去看看相关文章，再买上几本相关的书籍学习一下。</p>
<p>　　在国内， 一旦成为&#8221;牛人&#8221;，通常可以到许多知名的公司里去，运气好者可以挂上一个架构师的头衔，甚至挂上一个&#8221;首席架构师&#8221;或者&#8221;首席xx学家&#8221;的头衔也不足为奇。有不少爬到这层的人就以为到了楼顶了，可以眼睛往天上看了，开始目空一切起来，以为自己什么都可以做了，什么都懂了，经常在网络上乱砸板砖是这个群体的最好写照。由此也看出，国内的牛人数量仍然众多，远多于西方的牛人数量，在这层上仍然是领先的。</p>
<p>　　也有不少谦虚的&#8221;牛人&#8221;，知道自己现在还不到半桶水阶段。他们深知爬楼的游戏就像猴子上树一样，往下看是笑脸，往上看是屁股。为了多看笑脸，少看屁股，他们并没有在此停步不前，而是继续寻找到更上一层的楼梯，以便继续往上爬。</p>
<p><strong>第4层 大牛</strong></p>
<p>　　从第3层爬到第4层可不像上面说过的那几层一样容易，要成为大牛的话，你必须要能做牛人们做不了的事情，解决牛人们解决不了问题。比如牛人们通常都不懂写操作系统，不会写编译器，不懂得TCP/IP协议的底层实现，如果你有能力将其中的任何一个实现得象模象样的话，那么你就从牛人升级为&#8221;大牛&#8221;了。</p>
<p>　　当然，由于各个专业领域的差别，这里举操作系统、编译器、TCP/IP协议只是作为例子，并不代表成为&#8221;大牛&#8221;一定需要掌握这些知识，以时下热门的多核编程来说，如果你能比牛人们更深入地掌握其中的各种思想原理，能更加自如的运用，并有能力去实现一个象开源项目TBB库一样的东西，也可以成为&#8221;大牛&#8221;，又或者你能写出一个类似Apache一样的服务器，或者写出一个数据库，都可以成为&#8221;大牛&#8221;。</p>
<p>　　要成为&#8221;大牛&#8221;并不是一件简单的事情，需要付出比牛人们多得多的努力，一般来说，至少要看过200~400本左右的专业书籍并好好掌握它，除此之外，还得经常关注网络和期刊杂志上的各种最新信息。</p>
<p>　　当&#8221;牛人&#8221;晋升为&#8221;大牛&#8221;，让&#8221;牛人们&#8221;发现有比他们更牛的人时，对&#8221;牛人&#8221;们的心灵的震撼是可想而知的。由于牛人们的数量庞大，并且牛人对大虾和菜鸟阶层有言传身教的影响，所以大牛们通常能获得非常高的社会知名度，几乎可以用&#8221;引无数菜鸟、大虾、牛人竞折腰&#8221;来形容，看看前面提过的Linus Torvalds等大牛，应该知道此言不虚。</p>
<p>　　虽然成为&#8221;大牛&#8221;的条件看起来似乎很高似的，但是这层楼并不是很难爬的一层，只要通过一定的努力，素质不是很差，还是有许多&#8221;牛人&#8221;可以爬到这一层的。由此可知，&#8221;大牛&#8221;这个楼层的人数其实并不像想像的那么少，例如比尔·盖茨之类的人好像也是属于这一层的。</p>
<p>　　由于&#8221;大牛&#8221;这层的人数不少，所以也很难统计除到底是中国的&#8221;大牛&#8221;数量多还是西方的大牛数量多？我估计应该是个旗鼓相当的数量，或者中国的&#8221;大牛&#8221;们会更多一些。</p>
<p>　　看到这里，可能会有很多人会以为我在这里说瞎话，Linus Torvalds写出了著名的Linux操作系统，我国并没有人写出过类似的东西啊，我国的&#8221;大牛&#8221;怎么能和西方的比呢? 不知大家注意到没有，Linus Torvalds只是写出了一个&#8221;象模象样&#8221;的操作系统雏形，Linux后来真正发展成闻名全球的开源操作系统期间，完全是因为许多支持开源的商业公司如IBM等，派出了许多比Linus Torvalds更高楼层的幕后英雄在里面把它开发出来的。</p>
<p>　　可能有些菜鸟认为Linus Torvalds是程序员中的上帝，不妨说个小故事：</p>
<p>　　Linus，Richard Stallman和Don Knuth（高德纳）一同参加一个会议。</p>
<p>　　Linus 说：&#8221;上帝说我创造了世界上最优秀的操作系统。&#8221;</p>
<p>　　Richard Stallman自然不甘示弱地说：&#8221;上帝说我创造了世界上最好用的编译器。&#8221;</p>
<p>　　Don Knuth一脸疑惑的说：&#8221;等等，等等，我什么时候说过这些话？&#8221;</p>
<p>　　由此可以看出，Linus Torvalds的技术水平并不像想像中那么高，只是&#8221;牛人&#8221;和&#8221;大虾&#8221;觉得&#8221;大牛&#8221;比他们更牛吧了。在我国，有一些当时还处于&#8221;大虾&#8221;层的人物，也能写出介绍如何写操作系统的书，并且书写得非常出色，而且写出了一个有那么一点点象模象样的操作系统来。我想中国的&#8221;大牛&#8221;们是不会比西方差的，之所以没有人写出类似的商业产品来，完全是社会环境的原因，并不是技术能力达不到的原因。</p>
<p>　　&#8221;大牛&#8221;们之所以成为大牛，主要的原因是因为把&#8221;牛人&#8221;给盖了下去，并不是他们自己觉得如何牛。也许有很多菜鸟、大虾甚至牛人觉得&#8221;大牛&#8221;这层已经到顶了，但大多数&#8221;大牛&#8221;估计应该是有自知之明的，他们知道自己现在还没有爬到半山腰，也就勉强能算个半桶水的水平，其中有些爬到这层没有累趴下，仍然能量充沛，并且又有志者，还是会继续往更上一层楼爬的。</p>
<p>　　看到这里，也许有些菜鸟、大虾、牛人想不明白了，还有比&#8221;大牛&#8221;们更高的楼层，那会是什么样的楼层？下面就来看看第5层楼的奥妙。</p>
<p><strong>第5层 专家</strong></p>
<p>　　当大牛们真正动手做一个操作系统或者类似的其他软件时，他们就会发现自己的基本功仍然有很多的不足。以内存管理为例，如果直接抄袭Linux或者其他开源操作系统的内存管理算法，会被人看不起的，如果自动动手实现一个内存管理算法，他会发现现在有关内存管理方法的算法数量众多，自己并没有全部学过和实践过，不知道到底该用那种内存管理算法。</p>
<p>　　看到这里，可能有些人已经明白第5层楼的奥妙了，那就是需要做基础研究，当然在计算机里，最重要的就是&#8221;计算&#8221;二字，程序员要做基础研究，主要的内容就是研究非数值&#8221;计算&#8221;。</p>
<p>　　非数值计算可是一个非常庞大的领域，不仅时下热门的&#8221;多核计算&#8221;与&#8221;云计算&#8221;属于非数值计算范畴，就是软件需求、设计、测试、调试、评估、质量控制、软件工程等本质上也属于非数值计算的范畴，甚至芯片硬件设计也同样牵涉到非数值计算。如果你还没有真正领悟&#8221;计算&#8221;二字的含义，那么你就没有机会进到这层楼来。</p>
<p>　　可能有人仍然没有明白为什么比尔·盖茨被划在了大牛层，没有进到这层来。虽然比尔·盖茨大学未毕业，学历不够，但是家有藏书2万余册，进入软件这个行业比绝大部分人都早，撇开他的商业才能不谈，即使只看他的技术水平，也可以算得上是学富五车，顶上几个普通的计算机软件博士之和是没有问题的，比起Linus Torvalds之类的&#8221;大牛&#8221;们应该技高一筹才对，怎么还进不了这层楼呢？</p>
<p>　　非常遗憾的是，从Windows操作系统的实现来看，其对计算的理解是很肤浅的，如果把Google对计算方面的理解比做大学生，比尔·盖茨只能算做一个初中生，所以比尔·盖茨永远只能做个大牛人，成不了&#8221;专家&#8221;。</p>
<p>　　看到这里，也许国内的大牛们要高兴起来了，原来比尔·盖茨也只和我等在同一个层次，只要再升一层就可以超越比尔·盖茨了。不过爬到这层可没有从&#8221;牛人&#8221;升为&#8221;大牛&#8221;那么简单，人家比尔·盖茨都家有2万多册书，让你看个500~1000本以上的专业书籍并掌握好它应该要求不高吧。当然，这并不是主要的条件，更重要的是，需要到专业的学术站点去学习了，到ACM，IEEE，Elsevier，SpringerLink，SIAM等地方去下载论文应该成为你的定期功课，使用Google搜索引擎中的学术搜索更是应该成为你的日常必修课。此外，你还得经常关注是否有与你研究相关的开源项目冒出来，例如当听到有TBB这样针对多核的开源项目时，你应该第一时间到Google里输入&#8221;TBB&#8221;搜索一下，将其源代码下载下来好好研究一番，这样也许你的一只脚已经快迈进了这层楼的门槛。</p>
<p>　　当你象我上面说的那样去做了以后，随着时间的推移，总会有某天，你发现，在很多小的领域里，你已经学不到什么新东西了，所有最新出来的研究成果你几乎都知道。此时你会发现你比在做&#8221;牛人&#8221;和&#8221;大牛&#8221;时的水平不知高出了多少，但是你一点也&#8221;牛&#8221;不起来，因为你学的知识和思想都是别人提出来的，你自己并没有多少自己的知识和思想分享给别人，所以你还得继续往楼上爬才行。</p>
<p>　　我不知道国内的&#8221;专家&#8221;到底有多少，不过有一点可以肯定的是，如果把那些专门蒙大家的&#8221;砖家&#8221;也算上的话，我们的砖家比西方的要多得多。</p>
<p><strong>第6层 学者</strong></p>
<p>　　当&#8221;专家&#8221;们想继续往上一层楼爬时，他们几乎一眼就可以看到楼梯的入口，不过令他们吃惊的是，楼梯入口处竖了一道高高的门槛，上面写着&#8221;创新&#8221;二字。不幸的是，大多数人在爬到第5层楼时已经体能消耗过度，无力翻过这道门槛。</p>
<p>　　有少数体能充足者，可以轻易翻越这道门槛，但是并不意味着体力消耗过度者就无法翻越，因为你只是暂时还没有掌握恢复体能的方法而已，当掌握了恢复体能的方法，将体能恢复后，你就可以轻易地翻越这道门槛了。</p>
<p>　　怎么才能将体能恢复呢？我们的老祖宗&#8221;孔子&#8221;早就教导过我们&#8221;温故而知新&#8221;，在英文里，研究的单词是&#8221;research&#8221;，其前缀&#8221;re&#8221;和&#8221;search&#8221;分别是什么意思不用我解释吧。或许有些人觉得&#8221;温故而知新&#8221;和&#8221;research&#8221;有些抽象，不好理解，我再给打个简单的比方，比如你在爬一座高山，爬了半天，中途体力不支，怎么恢复体力呢？自然是休息一下，重新进食一些食物，体力很快就可以得到恢复。</p>
<p>　　由此可知，对体能消耗过度者，休息＋重新进食通常是恢复体能的最佳选择。可惜的是，国内的老板们并不懂得这点，他们的公司里不仅连正常国家规定的休息时间都不给足，有些公司甚至有员工&#8221;过劳死&#8221;出现。所以国内能翻越&#8221;创新&#8221;这道门槛的人是&#8221;少之又少&#8221;，和西方比起来估计是数量级的差别。</p>
<p>　　再说说重新进食的问题，这个重新进食是有讲究的，需要进食一些基础性易消化的简单食物，不能进食山珍海味级的复杂食物，否则很难快速吸收。以查找为例，并不是去天天盯着那些复杂的查找结构和算法进行研究，你需要做的是将二分查找、哈希查找、普通二叉树查找等基础性的知识好好地复习几遍。</p>
<p>　　以哈希查找为例，首先你需要去将各种冲突解决方法如链式结构、二次哈希等编写一遍，再试试不同种类的哈希函数，然后还需要试试在硬盘中如何实现哈希查找，并考虑数据从硬盘读到内存后，如何组织硬盘中的数据才能快速地在内存中构建出哈希表来，&#8230;，这样你可能需要将一个哈希表写上十几个不同的版本，并比较各个版本的性能、功能方面的区别和适用范围。</p>
<p>　　总之，对任何一种简单的东西，你需要考虑各种各样的需求，以需求来驱动研究。最后你将各种最基础性的查找结构和算法都了然于胸后，或许某天你再看其他更复杂的查找算法，或者你在散步时，脑袋里灵光一现，突然间就发现了更好的方法，也就从专家晋升为&#8221;学者&#8221;了。</p>
<p>　　学者所做的事情，通常都是在前人的基础上，进行一些小的优化和改进，例如别人发明了链式基数排序的方法，你第1个发现使用一定的方法，可以用数组替代链表进行基数排序，性能还能得到进一步提高。</p>
<p>　　由于学者需要的只是一些小的优化改进，因此中国还是有一定数量的学者。不过和国外的数量比起来，估计少了一个数量级而已。</p>
<p>　　也许有人会觉得现在中国许多公司申请专利的数量达到甚至超过西方发达国家了，我们的学者数量应该不会比他们少多少。因此，有必要把专利和这里说的创新的区别解释一下。</p>
<p>　　所谓专利者，只要是以前没有的，新的东西，都可以申请专利；甚至是以前有的东西，你把他用到了一个新的领域的产品里去，也可以申请专利。比如你在房子里造一个水泥柱子，只要以前没有人就这件事申请专利，那么你就可以申请专利，并且下次你把水泥柱子挪一个位置，又可以申请一个新的专利；或者你在一个柜子上打上几个孔，下次又把孔的位置改一改，&#8230;，均可申请专利。</p>
<p>　　这层楼里所说的创新，是指学术层面的创新，是基础研究方面的创新，和专利的概念是完全不同的，难度也是完全不同的。你即使申请了一万个象那种打孔一类的专利，加起来也够不到这层楼里的一个创新。</p>
<p>　　当你爬到第6层楼时，你也许会有一种突破极限的快感，因为你终于把那道高高的写着&#8221;创新&#8221;二字的门槛给翻过去了，实现了&#8221;0&#8243;的突破。这时，你也许有一种&#8221;独上高楼，欲望尽天涯路&#8221;的感觉，但是很快你会发现看到的都是比较近的路，远处的路根本看不清楚。如果你还有足够的体力的话，你会想爬到更高一层的楼层去。</p>
<p><strong>第7层 大师</strong></p>
<p>　　从第6层楼爬到第7层楼，并没有多少捷径可走，主要看你有没有足够的能量。你如果能象Hoare一样设计出一个快速排序的算法；或者象Eugene W. Myers一样设计出了一个用编辑图的最短路径模型来解决diff问题的算法；或者象M.J.D. Powell一样提出了一个能够处理非线性规划问题的SQP方法；或者你发现基于比较的排序算法，它的复杂度下界为O(NLogN)；或者你发现用栈可以将递归的算法变成非递归的；或者你设计出一个红黑树或者AVL树之类的查找结构；或者你设计出一个象C++或Java一样的语言；或者你发明了UML；&#8230;，你就爬到了第7层，晋升为&#8221;大师&#8221;了。</p>
<p>　　上面举的这些例子中，其中有些人站的楼层比这层高，这里只是为了形象说明而举例他们的某个成就。从上面列出的一些大师的贡献可以看出，成为大师必须要有较大的贡献。首先解决问题必须是比较重要的，其次你要比前辈们在某方面有一个较大的提高，或者你解决的是一个全新的以前没有解决过的问题；最重要的是，主要的思路和方法必须是你自己提供的，不再是在别人的思路基础上进行的优化和改进。</p>
<p>　　看了上面这些要求，如果能量不够的话，你也许会觉得有些困难，所以不是每个人都能成为&#8221;大师&#8221;的。中国软件业里能称得上是&#8221;大师&#8221;的人，用屈指可数来形容，估计是绰绰有余。值得一提得是，国外的&#8221;大师&#8221;就象我们的&#8221;大牛&#8221;一样满天飞的多。</p>
<p>　　我把我猜测本国有可能进到这层楼的大师列一下，以起个抛砖引玉的作用。汉王的&#8221;手写识别&#8221;技术由于是完全保密的，不知道它里面用了什么思想，原创思想占的比重有多少，因此不知道该把它划到这层楼还是更高一层楼去。原山东大学王小云教授破解DES和MD5算法时，用到的方法不知道是不是完全原创的，如果是的话也可进到这层楼来。</p>
<p>　　陈景润虽然没有彻底解决哥德巴赫猜想，但他在解决问题时所用的方法是创新的，因此也可以进到这层楼来。当然，如果能彻底解决哥德巴赫猜想，那么可以算到更高的楼层去。</p>
<p>　　求伯君和王志东等大牛们，他们在做WPS和表格处理之类的软件时，不知是否有较大的原创算法在里面，如果有的话就算我错把他们划到了大牛层。由于所学有限，不知道国内还有那些人能够得上&#8221;大师&#8221;的级别，或许有少量做研究的教授、院士们，可以达到这个级别，有知道的不妨回个帖子晾一晾。</p>
<p>　　鉴于&#8221;大师&#8221;这个称号的光环效应，相信有不少人梦想着成为&#8221;大师&#8221;。或许你看了前面举的一些大师的例子，你会觉得要成为大师非常困难。不妨说一下，现在有一条通往&#8221;大师&#8221;之路的捷径打开了，那就是多核计算领域，有大量的处女地等待大家去挖掘。</p>
<p>　　以前在单核时代开发的各种算法，现在都需要改写成并行的。数据结构与算法、图像处理、数值计算、操作系统、编译器、测试调试等各个领域，都存在大量的机会，可以让你进到这层楼来，甚至有可能让你进到更高一层楼去。</p>
<p><strong>第8层 科学家</strong></p>
<p>　　科学家向来都是一个神圣的称号，因此我把他放在了“大师”之上。要成为科学家，你的贡献必须超越大师，不妨随便举一些例子。</p>
<p>　　如果你象Dijkstra一样设计了ALGOL语言，提出了程序设计的三种基本结构：顺序、选择、循环，那么你可以爬到第8层楼来。顺便说一下，即使抛开这个成果，Dijkstra凭他的PV操作和信号量概念的提出，同样可以进到这层楼。</p>
<p>　　如果你象Don Knuth一样，是数据结构与算法这门学科的重要奠基者，你也可以进到这层楼来。当然，数据结构和算法这门学科不是某个人开创的，是许多大师和科学家集体开创的。</p>
<p>　　如果你象巴科斯一样发明了Fortran语言，并提出了巴科斯范式，对高级程序语言的发展起了重要作用，你也可以进到这层楼来。</p>
<p>　　或者你象Ken Thompson、Dennis Ritchie一样发明了Unix操作系统和功能强大、高效、灵活、表达力强的C语言，对操作系统理论和高级编程语言均作出重大贡献，那么你也可以进到这层楼来。</p>
<p>　　或者你有Frederick P. Brooks一样机会，可以去领导开发IBM的大型计算机System/360和OS/360操作系统，并在失败后反思总结，写出《人月神话》，对软件工程作出里程碑式的贡献，你也可以进到这层来。</p>
<p>　　或者你提出了面向对象设计的基本思想，或者你设计了互联网的TCP/IP协议，或者你象Steven A.Cook一样奠定NP完全性的理论基础，或者你象Frances Allen一样专注于并行计算来实现编译技术，在编译优化理论和技术取得基础性的成就，…，均可进入这层。</p>
<p>　　当然，如果你发明了C++语言或者Java语言，你进不到这层来，因为你用到的主要思想都是这层楼中的科学家提出的，你自己并没有没有多少原创思想在里面。</p>
<p>　　看了上面列出的科学家的成就，你会发现，要成为“科学家”，通常要开创一门分支学科，或者是这个分支学科的奠基者，或者在某个分支学科里作出里程碑式的重大贡献。如果做不到这些的话，那么你能象Andrew C. Yao（姚期智）一样在对计算理论的多个方向如伪随机数生成，密码学与通信复杂度等各个方向上作出重要贡献，成为集大成者，也可以进入这层楼。</p>
<p>　　成为“科学家”后，如果你有幸象Dijkstra一样，出现在一个非常重视科学的国度。当你去世时，你家乡满城的人都会自动地去为你送葬。不过如果不幸生错地方的话，能不挨“板砖”估计就算万幸了。</p>
<p>　　从上面随便举的一些例子中，你可能能猜到，西方科学家的数量是非常多的，于是你会想中国应该也有少量的科学家吧？我可以很负责任地告诉你一个不幸的结果，中国本土产生的科学家的数量为0。目前在国内，软件领域的唯一的科学家就是上面提过的姚期智，还是国外请回来的，并不是本土产生的。</p>
<p>　　可能你不同意我说的本土科学家数量为0的结论，因为你经常看到有许多公司里都有所谓“首席XX科学家”的头衔。我想说的是，这些所谓的“首席XX科学家”都是远远够不到这层楼的级别的，有些人的水平估计也就是一个“牛人”或“大牛”的级别，好一点的最多也就一个“学者”的级别。尤其是那些被称作“首席经X学家”的，基本上可以把称号改为“首席坑大家”。</p>
<p>　　虽然我国没有人能爬到这层楼上来，但是西方国家仍然有许多人爬到了比这层更高的楼上。如果要问我们比西方落后多少？那么可以简单地回答为：“落后了三层楼”。下面就来看看我们做梦都没有到过的更高一层楼的秘密。</p>
<p><strong>第9层 大科学家</strong></p>
<p>　　进入这层楼的门槛通常需要一些运气，比如某天有个苹果砸到你头上时，你碰巧发现了万有引力，那么你可以进到这层楼来。当然，万有引力几百年前就被人发现了，如果你现在到处嚷嚷着说你发现了万有引力，恐怕马上会有人打110，然后警察会把你送到不正常人类的聚集地去。因此，这里举万有引力的例子，只是说你要有类似的成就才能进到这层楼来。</p>
<p>　　牛顿发现万有引力定律开创了经典物理运动力学这门学科，如果你也能开创一门大的学科，那么你就从科学家晋升为“大科学家”。比如爱因斯坦创建了相对论，从一个小职员变成了大科学家。当然大科学家可远不止这两人，数学界里比物理学界更是多得多，如欧几里得创建了平面几何，笛卡尔开创解析几何，还有欧拉、高斯、莱布尼茨等数不清的人物，跟计算相关的大科学家则有图灵等人。</p>
<p>　　从上面列出的一些大科学家可以发现，他们的成就不仅是开创了一个大的学科，更重要的是他们的成就上升到了“公理”的层面。发现公理通常是需要一点运气的，如果你的运气不够好的话，另外还有一个笨办法也可以进到这层楼来，那就是成为集大成者。例如冯·诺伊曼，对数学的所有分支都非常了解，许多领域都有较大的贡献，即使撇开他对计算机的开创贡献，成为大科学家照样绰绰有余。</p>
<p>　　当然，程序员们最关心的是自己有没有机会变成大科学家。既然计算机这门大学科的开创性成果早就被冯·诺伊曼、图灵等人摘走了，那么程序员们是不是没有机会变成大科学家了呢？我们的古人说得好：“江山代有才人出，各领风骚数百年”，现在在计算机这门学科下面诞生了许多非常重要的大的分支，所以你还是有足够的机会进到这层楼的。</p>
<p>　　如果你能够彻底解决自然语言理解（机器翻译）这门学科中的核心问题， 或者你在人工智能或者机器视觉（图像识别）方面有突破性的发现，那么你同样可以轻易地晋升为“大科学家”。这样当某天你老了去世时，或许那天国人已经觉醒，你也能享受到如Dijkstra一样的待遇，有满城甚至全国的人去为你送葬。</p>
<p>　　现在还剩下另外一个大家感兴趣的问题没有讨论，那就是这层中已经出现了牛顿、爱因斯坦、高斯等我们平常人都认为是顶级的科学家，是不是这层已经是楼顶了呢？相信还记得本文标题的人应该知道现在仅仅是第9层，还有第10层没有到达呢。可能不少人现在要感到困惑了，难道还有人站在比牛顿、爱因斯坦、高斯等人更高的楼层上？</p>
<p>　　这个世界上确实存在可以用一只手的手指数得清的那么几个人，他们爬到了第10层楼上。因此，第10层楼不是虚构的，而是确实存在的。如果对此有疑惑或者认为我在胡诌一番的话，那么不妨继续往下看下去，窥一下第10层楼的秘密。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ray77.com/programer-ten-floors.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>给C++初学者的50个忠告</title>
		<link>http://www.ray77.com/c-beginners-50-advices.html</link>
		<comments>http://www.ray77.com/c-beginners-50-advices.html#comments</comments>
		<pubDate>Fri, 02 Jan 2009 13:42:53 +0000</pubDate>
		<dc:creator>Rock</dc:creator>
				<category><![CDATA[Develop]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[经典]]></category>

		<guid isPermaLink="false">http://www.ray77.com/?p=573</guid>
		<description><![CDATA[
　　1.把C++当成一门新的语言学习(和C没啥关系!真的。);　
　　2.看《Thinking　In　C++》,不要看《C++变成死相》;　
　　3.看《The　C++　Programming　Language》和《Inside　The　C++　Object　Model》,不要因为他们很难而我们自己是初学者所以就不看;　
　　4.不要被VC、BCB、BC、MC、TC等词汇所迷惑——他们都是集成开发环境,而我们要学的是一门语言;　
　　5.不要放过任何一个看上去很简单的小编程问题——他们往往并不那么简单,或者可以引伸出很多知识点;　
　　6.会用Visual　C++,并不说明你会C++;　
　　7.学class并不难,template、STL、generic　programming也不过如此——难的是长期坚持实践和不遗余力的博览群书;　
　　8.如果不是天才的话,想学编程就不要想玩游戏——你以为你做到了,其实你的C++水平并没有和你通关的能力一起变高——其实可以时刻记住:学C++是为了编游戏的;　
　　9.看Visual　C++的书,是学不了C++语言的;　
　　10.浮躁的人容易说:XX语言不行了,应该学YY;——是你自己不行了吧!?　
　　11.浮躁的人容易问:我到底该学什么;——别问,学就对了;　
　　12.浮躁的人容易问:XX有钱途吗;——建议你去抢银行;　
　　13.浮躁的人容易说:我要中文版!我英文不行!——不行?学呀!　
　　14.浮躁的人容易问:XX和YY哪个好;——告诉你吧,都好——只要你学就行;　
　　15.浮躁的人分两种:a)只观望而不学的人;b)只学而不坚持的人;　
　　16.把时髦的技术挂在嘴边,还不如把过时的技术记在心里;　
　　17.C++不仅仅是支持面向对象的程序设计语言;　
　　18.学习编程最好的方法之一就是阅读源代码;　
　　19.在任何时刻都不要认为自己手中的书已经足够了;　
　　20.请阅读《The　Standard　C++　Bible》(中文版:标准C++宝典),掌握C++标准;　
　　21.看得懂的书,请仔细看;看不懂的书,请硬着头皮看;　
　　22.别指望看第一遍书就能记住和掌握什么——请看第二遍、第三遍;　
　　23.请看《Effective　C++》和《More　Effective　C++》以及《Exceptional　C++》;　
　　24.不要停留在集成开发环境的摇篮上,要学会控制集成开发环境,还要学会用命令行方式处理程序;　
　　25.和别人一起讨论有意义的C++知识点,而不是争吵XX行不行或者YY与ZZ哪个好;　
　　26.请看《程序设计实践》,并严格的按照其要求去做;　
　　27.不要因为C和C++中有一些语法和关键字看上去相同,就认为它们的意义和作用完全一样;　
　　28.C++绝不是所谓的C的“扩充”——如果C++一开始就起名叫Z语言,你一定不会把C和Z语言联系得那么紧密;　
　　29.请不要认为学过XX语言再改学C++会有什么问题——你只不过又在学一门全新的语言而已;　
　　30.读完了《Inside　The　C++　Object　Model》以后再来认定自己是不是已经学会了C++;　
　　31.学习编程的秘诀是:编程,编程,再编程;　
　　32.请留意下列书籍:《C++面向对象高效编程(C++　Effective　Object-Oriented　Software　Construction)》《面向对象软件构造(Object-Oriented　Software　Construction)》《设计模式(Design　Patterns)》《The　Art　of　Computer　Programming》;　
　　33.记住:面向对象技术不只是C++专有的;　
　　34.请把书上的程序例子亲手输入到电脑上实践,即使配套光盘中有源代码;　
　　35.把在书中看到的有意义的例子扩充;　
　　36.请重视C++中的异常处理技术,并将其切实的运用到自己的程序中;　
　　37.经常回顾自己以前写过的程序,并尝试重写,把自己学到的新知识运用进去;　
　　38.不要漏掉书中任何一个练习题——请全部做完并记录下解题思路;　
　　39.C++语言和C++的集成开发环境要同时学习和掌握;　
　　40.既然决定了学C++,就请坚持学下去,因为学习程序设计语言的目的是掌握程序设计技术,而程序设计技术是跨语言的;　
　　41.就让C++语言的各种平台和开发环境去激烈的竞争吧,我们要以学习C++语言本身为主;　
　　42.当你写C++程序写到一半却发现自己用的方法很拙劣时,请不要马上停手;请尽快将余下的部分粗略的完成以保证这个设计的完整性,然后分析自己的错误并重新设计和编写(参见43);　
　　43.别心急,设计C++的class确实不容易;自己程序中的class和自己的class设计水平是在不断的编程实践中完善和发展的;　
　　44.决不要因为程序“很小”就不遵循某些你不熟练的规则——好习惯是培养出来的,而不是一次记住的;
　　45.每学到一个C++难点的时候,尝试着对别人讲解这个知识点并让他理解——你能讲清楚才说明你真的理解了;　
　　46.记录下在和别人交流时发现的自己忽视或不理解的知识点;　
　　47.请不断的对自己写的程序提出更高的要求,哪怕你的程序版本号会变成Version　100.XX;　
　　48.保存好你写过的所有的程序——那是你最好的积累之一;　
　　49.请不要做浮躁的人;　
　　50.请热爱C++!
　　PS: 一年前就看过这个贴子，写得确实很到位，现在贴上来，让更多的C++初学者以及爱好者揣磨下。《Effective　C++》与《Inside　The　C++　Object　Model》实在是经典，Rock现在还在研读中，而且我相信能把这两本书吃透并且加以运用的人兴许就是真真的高手。  
]]></description>
			<content:encoded><![CDATA[<p style="text-align: center;"><img class="size-full wp-image-632 aligncenter" title="给C++初学者的50个忠告" src="http://www.ray77.com/wp-content/uploads/2009/01/post-156085-1132655351.jpg" alt="post-156085-1132655351" width="594" height="134" /></p>
<p>　　1.把C++当成一门新的语言学习(和C没啥关系!真的。);　<br />
　　2.看《Thinking　In　C++》,不要看《C++变成死相》;　<br />
　　3.看《The　C++　Programming　Language》和《Inside　The　C++　Object　Model》,不要因为他们很难而我们自己是初学者所以就不看;　<br />
　　4.不要被VC、BCB、BC、MC、TC等词汇所迷惑——他们都是集成开发环境,而我们要学的是一门语言;　<br />
　　5.不要放过任何一个看上去很简单的小编程问题——他们往往并不那么简单,或者可以引伸出很多知识点;　<br />
　　6.会用Visual　C++,并不说明你会C++;　<br />
　　7.学class并不难,template、STL、generic　programming也不过如此——难的是长期坚持实践和不遗余力的博览群书;　<br />
　　8.如果不是天才的话,想学编程就不要想玩游戏——你以为你做到了,其实你的C++水平并没有和你通关的能力一起变高——其实可以时刻记住:学C++是为了编游戏的;　<span id="more-573"></span><br />
　　9.看Visual　C++的书,是学不了C++语言的;　<br />
　　10.浮躁的人容易说:XX语言不行了,应该学YY;——是你自己不行了吧!?　<br />
　　11.浮躁的人容易问:我到底该学什么;——别问,学就对了;　<br />
　　12.浮躁的人容易问:XX有钱途吗;——建议你去抢银行;　<br />
　　13.浮躁的人容易说:我要中文版!我英文不行!——不行?学呀!　<br />
　　14.浮躁的人容易问:XX和YY哪个好;——告诉你吧,都好——只要你学就行;　<br />
　　15.浮躁的人分两种:a)只观望而不学的人;b)只学而不坚持的人;　<br />
　　16.把时髦的技术挂在嘴边,还不如把过时的技术记在心里;　<br />
　　17.C++不仅仅是支持面向对象的程序设计语言;　<br />
　　18.学习编程最好的方法之一就是阅读源代码;　<br />
　　19.在任何时刻都不要认为自己手中的书已经足够了;　<br />
　　20.请阅读《The　Standard　C++　Bible》(中文版:标准C++宝典),掌握C++标准;　<br />
　　21.看得懂的书,请仔细看;看不懂的书,请硬着头皮看;　<br />
　　22.别指望看第一遍书就能记住和掌握什么——请看第二遍、第三遍;　<br />
　　23.请看《Effective　C++》和《More　Effective　C++》以及《Exceptional　C++》;　<br />
　　24.不要停留在集成开发环境的摇篮上,要学会控制集成开发环境,还要学会用命令行方式处理程序;　<br />
　　25.和别人一起讨论有意义的C++知识点,而不是争吵XX行不行或者YY与ZZ哪个好;　<br />
　　26.请看《程序设计实践》,并严格的按照其要求去做;　<br />
　　27.不要因为C和C++中有一些语法和关键字看上去相同,就认为它们的意义和作用完全一样;　<br />
　　28.C++绝不是所谓的C的“扩充”——如果C++一开始就起名叫Z语言,你一定不会把C和Z语言联系得那么紧密;　<br />
　　29.请不要认为学过XX语言再改学C++会有什么问题——你只不过又在学一门全新的语言而已;　<br />
　　30.读完了《Inside　The　C++　Object　Model》以后再来认定自己是不是已经学会了C++;　<br />
　　31.学习编程的秘诀是:编程,编程,再编程;　<br />
　　32.请留意下列书籍:《C++面向对象高效编程(C++　Effective　Object-Oriented　Software　Construction)》《面向对象软件构造(Object-Oriented　Software　Construction)》《设计模式(Design　Patterns)》《The　Art　of　Computer　Programming》;　<br />
　　33.记住:面向对象技术不只是C++专有的;　<br />
　　34.请把书上的程序例子亲手输入到电脑上实践,即使配套光盘中有源代码;　<br />
　　35.把在书中看到的有意义的例子扩充;　<br />
　　36.请重视C++中的异常处理技术,并将其切实的运用到自己的程序中;　<br />
　　37.经常回顾自己以前写过的程序,并尝试重写,把自己学到的新知识运用进去;　<br />
　　38.不要漏掉书中任何一个练习题——请全部做完并记录下解题思路;　<br />
　　39.C++语言和C++的集成开发环境要同时学习和掌握;　<br />
　　40.既然决定了学C++,就请坚持学下去,因为学习程序设计语言的目的是掌握程序设计技术,而程序设计技术是跨语言的;　<br />
　　41.就让C++语言的各种平台和开发环境去激烈的竞争吧,我们要以学习C++语言本身为主;　<br />
　　42.当你写C++程序写到一半却发现自己用的方法很拙劣时,请不要马上停手;请尽快将余下的部分粗略的完成以保证这个设计的完整性,然后分析自己的错误并重新设计和编写(参见43);　<br />
　　43.别心急,设计C++的class确实不容易;自己程序中的class和自己的class设计水平是在不断的编程实践中完善和发展的;　<br />
　　44.决不要因为程序“很小”就不遵循某些你不熟练的规则——好习惯是培养出来的,而不是一次记住的;<br />
　　45.每学到一个C++难点的时候,尝试着对别人讲解这个知识点并让他理解——你能讲清楚才说明你真的理解了;　<br />
　　46.记录下在和别人交流时发现的自己忽视或不理解的知识点;　<br />
　　47.请不断的对自己写的程序提出更高的要求,哪怕你的程序版本号会变成Version　100.XX;　<br />
　　48.保存好你写过的所有的程序——那是你最好的积累之一;　<br />
　　49.请不要做浮躁的人;　<br />
　　50.请热爱C++!</p>
<p>　　PS: 一年前就看过这个贴子，写得确实很到位，现在贴上来，让更多的C++初学者以及爱好者揣磨下。《Effective　C++》与《Inside　The　C++　Object　Model》实在是经典，Rock现在还在研读中，而且我相信能把这两本书吃透并且加以运用的人兴许就是真真的高手。 <img src='http://www.ray77.com/wp-includes/images/smilies/icon_cool.gif' alt=':cool:' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.ray77.com/c-beginners-50-advices.html/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>用完成端口开发大响应规模的Winsock应用程序</title>
		<link>http://www.ray77.com/use-finish-port-answer-winsoc.html</link>
		<comments>http://www.ray77.com/use-finish-port-answer-winsoc.html#comments</comments>
		<pubDate>Fri, 02 Jan 2009 13:17:07 +0000</pubDate>
		<dc:creator>Rock</dc:creator>
				<category><![CDATA[Develop]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Winsock]]></category>
		<category><![CDATA[网络编程]]></category>

		<guid isPermaLink="false">http://www.ray77.com/?p=570</guid>
		<description><![CDATA[　　通常要开发网络应用程序并不是一件轻松的事情，不过，实际上只要掌握几个关键的原则也就可以了——创建和连接一个套接字，尝试进行连接，然后收发数据。真正难的是要写出一个可以接纳少则一个，多则数千个连接的网络应用程序。本文将讨论如何通过Winsock2在Windows NT 和 Windows 2000上开发高扩展能力的Winsock应用程序。文章主要的焦点在客户机/服务器模型的服务器这一方，当然，其中的许多要点对模型的双方都适用。
API与响应规模
　　通过Win32的重叠I/O机制，应用程序可以提请一项I/O操作，重叠的操作请求在后台完成，而同一时间提请操作的线程去做其他的事情。等重叠操作完成后线程收到有关的通知。这种机制对那些耗时的操作而言特别有用。不过，像Windows 3.1上的WSAAsyncSelect()及Unix下的select()那样的函数虽然易于使用，但是它们不能满足响应规模的需要。而完成端口机制是针对操作系统内部进行了优化，在Windows NT 和 Windows 2000上，使用了完成端口的重叠I/O机制才能够真正扩大系统的响应规模。
完成端口
　　一个完成端口其实就是一个通知队列，由操作系统把已经完成的重叠I/O请求的通知放入其中。当某项I/O操作一旦完成，某个可以对该操作结果进行处理的工作者线程就会收到一则通知。而套接字在被创建后，可以在任何时候与某个完成端口进行关联。
　　通常情况下，我们会在应用程序中创建一定数量的工作者线程来处理这些通知。线程数量取决于应用程序的特定需要。理想的情况是，线程数量等于处理器的数量，不过这也要求任何线程都不应该执行诸如同步读写、等待事件通知等阻塞型的操作，以免线程阻塞。每个线程都将分到一定的CPU时间，在此期间该线程可以运行，然后另一个线程将分到一个时间片并开始执行。如果某个线程执行了阻塞型的操作，操作系统将剥夺其未使用的剩余时间片并让其它线程开始执行。也就是说，前一个线程没有充分使用其时间片，当发生这样的情况时，应用程序应该准备其它线程来充分利用这些时间片。
　　完成端口的使用分为两步。首先创建完成端口，如以下代码所示：

HANDLE    hIocp;
hIocp = CreateIoCompletionPort(
    INVALID_HANDLE_VALUE,
    NULL,
    (ULONG_PTR)0,
    0);
if (hIocp == NULL) {
    // Error
}
　　完成端口创建后，要把将使用该完成端口的套接字与之关联起来。方法是再次调用CreateIoCompletionPort ()函数，第一个参数FileHandle设为套接字的句柄，第二个参数ExistingCompletionPort 设为刚刚创建的那个完成端口的句柄。
　　以下代码创建了一个套接字，并把它和前面创建的完成端口关联起来：

SOCKET    s;

s = socket(AF_INET, SOCK_STREAM, ...]]></description>
			<content:encoded><![CDATA[<p>　　通常要开发网络应用程序并不是一件轻松的事情，不过，实际上只要掌握几个关键的原则也就可以了——创建和连接一个套接字，尝试进行连接，然后收发数据。真正难的是要写出一个可以接纳少则一个，多则数千个连接的网络应用程序。本文将讨论如何通过Winsock2在Windows NT 和 Windows 2000上开发高扩展能力的Winsock应用程序。文章主要的焦点在客户机/服务器模型的服务器这一方，当然，其中的许多要点对模型的双方都适用。</p>
<p><span style="color: #009999;"><strong>API与响应规模</strong></span></p>
<p>　　通过Win32的重叠I/O机制，应用程序可以提请一项I/O操作，重叠的操作请求在后台完成，而同一时间提请操作的线程去做其他的事情。等重叠操作完成后线程收到有关的通知。这种机制对那些耗时的操作而言特别有用。不过，像Windows 3.1上的WSAAsyncSelect()及Unix下的select()那样的函数虽然易于使用，但是它们不能满足响应规模的需要。而完成端口机制是针对操作系统内部进行了优化，在Windows NT 和 Windows 2000上，使用了完成端口的重叠I/O机制才能够真正扩大系统的响应规模。</p>
<p><span style="color: #009999;"><strong>完成端口</strong></span></p>
<p>　　一个完成端口其实就是一个通知队列，由操作系统把已经完成的重叠I/O请求的通知放入其中。当某项I/O操作一旦完成，某个可以对该操作结果进行处理的工作者线程就会收到一则通知。而套接字在被创建后，可以在任何时候与某个完成端口进行关联。<span id="more-570"></span></p>
<p>　　通常情况下，我们会在应用程序中创建一定数量的工作者线程来处理这些通知。线程数量取决于应用程序的特定需要。理想的情况是，线程数量等于处理器的数量，不过这也要求任何线程都不应该执行诸如同步读写、等待事件通知等阻塞型的操作，以免线程阻塞。每个线程都将分到一定的CPU时间，在此期间该线程可以运行，然后另一个线程将分到一个时间片并开始执行。如果某个线程执行了阻塞型的操作，操作系统将剥夺其未使用的剩余时间片并让其它线程开始执行。也就是说，前一个线程没有充分使用其时间片，当发生这样的情况时，应用程序应该准备其它线程来充分利用这些时间片。</p>
<p>　　完成端口的使用分为两步。首先创建完成端口，如以下代码所示：</p>
<blockquote><p>
HANDLE    hIocp;<br />
hIocp = CreateIoCompletionPort(<br />
    INVALID_HANDLE_VALUE,<br />
    NULL,<br />
    (ULONG_PTR)0,<br />
    0);<br />
if (hIocp == NULL) {<br />
    // Error<br />
}</p></blockquote>
<p>　　完成端口创建后，要把将使用该完成端口的套接字与之关联起来。方法是再次调用CreateIoCompletionPort ()函数，第一个参数FileHandle设为套接字的句柄，第二个参数ExistingCompletionPort 设为刚刚创建的那个完成端口的句柄。<br />
　　以下代码创建了一个套接字，并把它和前面创建的完成端口关联起来：</p>
<blockquote>
<pre>SOCKET    s;

s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) {
    // Error
if (CreateIoCompletionPort((HANDLE)s,
                           hIocp,
                           (ULONG_PTR)0,
                           0) == NULL)
{
// Error
}
...
}</pre>
</blockquote>
<p>　　这时就完成了套接字与完成端口的关联操作。在这个套接字上进行的任何重叠操作都将通过完成端口发出完成通知。注意，CreateIoCompletionPort()函数中的第三个参数用来设置一个与该套接字相关的“完成键(completion key)”(译者注：完成键可以是任何数据类型)。每当完成通知到来时，应用程序可以读取相应的完成键，因此，完成键可用来给套接字传递一些背景信息。</p>
<p>　　在创建了完成端口、将一个或多个套接字与之相关联之后，我们就要创建若干个线程来处理完成通知。这些线程不断循环调用GetQueuedCompletionStatus ()函数并返回完成通知。</p>
<p>　　下面，我们先来看看应用程序如何跟踪这些重叠操作。当应用程序调用一个重叠操作函数时，要把指向一个overlapped结构的指针包括在其参数中。当操作完成后，我们可以通过GetQueuedCompletionStatus()函数中拿回这个指针。不过，单是根据这个指针所指向的overlapped结构，应用程序并不能分辨究竟完成的是哪个操作。要实现对操作的跟踪，你可以自己定义一个OVERLAPPED结构，在其中加入所需的跟踪信息。</p>
<p>　　无论何时调用重叠操作函数时，总是会通过其lpOverlapped参数传递一个OVERLAPPEDPLUS结构(例如WSASend、 WSARecv等函数)。这就允许你为每一个重叠调用操作设置某些操作状态信息，当操作结束后，你可以通过GetQueuedCompletionStatus()函数获得你自定义结构的指针。注意OVERLAPPED字段不要求一定是这个扩展后的结构的第一个字段。当得到了指向OVERLAPPED结构的指针以后，可以用CONTAINING_RECORD宏取出其中指向扩展结构的指针。</p>
<p>　　OVERLAPPED 结构的定义如下：</p>
<blockquote>
<pre>typedef struct _OVERLAPPEDPLUS {
    OVERLAPPED        ol;
    SOCKET            s, sclient;
    int               OpCode;
    WSABUF            wbuf;
    DWORD             dwBytes, dwFlags;
    // 其它有用的信息
} OVERLAPPEDPLUS;

#define OP_READ     0
#define OP_WRITE    1
#define OP_ACCEPT   2</pre>
</blockquote>
<p>　　下面让我们来看看工作者线程的情况。</p>
<p>　　工作线程WorkerThread代码：</p>
<blockquote>
<pre>DWORD WINAPI WorkerThread(LPVOID lpParam)
{
    ULONG_PTR       *PerHandleKey;
    OVERLAPPED      *Overlap;
    OVERLAPPEDPLUS  *OverlapPlus,
                    *newolp;
    DWORD           dwBytesXfered;

    while (1)
    {
        ret = GetQueuedCompletionStatus(
            hIocp,
            &amp;dwBytesXfered,
            (PULONG_PTR)&amp;PerHandleKey,
            &amp;Overlap,
            INFINITE);
        if (ret == 0)
        {
            // Operation failed
            continue;
        }
        OverlapPlus = CONTAINING_RECORD(Overlap, OVERLAPPEDPLUS, ol);

    switch (OverlapPlus-&gt;OpCode)
    {
    case OP_ACCEPT:
        // Client socket is contained in OverlapPlus.sclient
        // Add client to completion port
            CreateIoCompletionPort(
                (HANDLE)OverlapPlus-&gt;sclient,
                hIocp,
                (ULONG_PTR)0,
                0);

        //  Need a new OVERLAPPEDPLUS structure
        //  for the newly accepted socket. Perhaps
        //  keep a look aside list of free structures.
        newolp = AllocateOverlappedPlus();
        if (!newolp)
        {
            // Error
        }
        newolp-&gt;s = OverlapPlus-&gt;sclient;
        newolp-&gt;OpCode = OP_READ;

        // This function prepares the data to be sent
        PrepareSendBuffer(&amp;newolp-&gt;wbuf);

        ret = WSASend(
                newolp-&gt;s,
                &amp;newolp-&gt;wbuf,
                1,
                &amp;newolp-&gt;dwBytes,
                0,
                &amp;newolp.ol,
                NULL);

        if (ret == SOCKET_ERROR)
        {
            if (WSAGetLastError() != WSA_IO_PENDING)
            {
            // Error
            }
        }

        // Put structure in look aside list for later use
        FreeOverlappedPlus(OverlapPlus);

        // Signal accept thread to issue another AcceptEx
        SetEvent(hAcceptThread);
        break;

    case OP_READ:
        // Process the data read
        // ...

        // Repost the read if necessary, reusing the same
        // receive buffer as before
        memset(&amp;OverlapPlus-&gt;ol, 0, sizeof(OVERLAPPED));
        ret = WSARecv(
              OverlapPlus-&gt;s,
              &amp;OverlapPlus-&gt;wbuf,
              1,
              &amp;OverlapPlus-&gt;dwBytes,
              &amp;OverlapPlus-&gt;dwFlags,
              &amp;OverlapPlus-&gt;ol,
              NULL);

        if (ret == SOCKET_ERROR)
        {
            if (WSAGetLastError() != WSA_IO_PENDING)
            {
                // Error
            }
        }
        break;

    case OP_WRITE:
        // Process the data sent, etc.
        break;
    } // switch
    } // while
}  // WorkerThread</pre>
</blockquote>
<p>　　其中每句柄键(PerHandleKey)变量的内容，是在把完成端口与套接字进行关联时所设置的完成键参数；Overlap参数返回的是一个指向发出重叠操作时所使用的那个OVERLAPPEDPLUS结构的指针。</p>
<p>　　要记住，如果重叠操作调用失败时(也就是说，返回值是SOCKET_ERROR，并且错误原因不是WSA_IO_PENDING)，那么完成端口将不会收到任何完成通知。如果重叠操作调用成功，或者发生原因是WSA_IO_PENDING的错误时，完成端口将总是能够收到完成通知。</p>
<p><strong><span style="color: #009999;">Windows NT和Windows 2000的套接字架构</span></strong></p>
<p>　　对于开发大响应规模的Winsock应用程序而言，对Windows NT和Windows 2000的套接字架构有基本的了解是很有帮助的。下图是Windows 2000中的Winsock架构：<br />
<img src="http://www.vckbase.com/document/journal/vckbase50/images/winsockfig03.gif" alt="" width="200" height="231" /></p>
<p>　　与其它类型操作系统不同，Windows NT和Windows 2000的传输协议没有一种风格像套接字那样的、可以和应用程序直接交谈的界面，而是采用了一种更为底层的API，叫做传输驱动程序界面(Transport Driver Interface,TDI)。Winsock的核心模式驱动程序负责连接和缓冲区管理，以便向应用程序提供套接字仿真(在AFD.SYS文件中实现)，同时负责与底层传输驱动程序对话。</p>
<p><strong><span style="color: #009999;">谁来负责管理缓冲区？</span></strong></p>
<p>　　正如上面所说的，应用程序通过Winsock来和传输协议驱动程序交谈，而AFD.SYS负责为应用程序进行缓冲区管理。也就是说，当应用程序调用send()或WSASend()函数来发送数据时，AFD.SYS将把数据拷贝进它自己的内部缓冲区(取决于SO_SNDBUF设定值)，然后send()或WSASend()函数立即返回。也可以这么说，AFD.SYS在后台负责把数据发送出去。不过，如果应用程序要求发出的数据超过了SO_SNDBUF设定的缓冲区大小，那么WSASend()函数会阻塞，直至所有数据发送完毕。</p>
<p>　　从远程客户端接收数据的情况也类似。只要不用从应用程序那里接收大量的数据，而且没有超出SO_RCVBUF设定的值，AFD.SYS将把数据先拷贝到其内部缓冲区中。当应用程序调用recv()或WSARecv()函数时，数据将从内部缓冲拷贝到应用程序提供的缓冲区。</p>
<p>　　多数情况下，这样的架构运行良好，特别在是应用程序采用传统的套接字下非重叠的send()和receive()模式编写的时候。不过程序员要小心的是，尽管可以通过setsockopt()这个API来把SO_SNDBUF和SO_RCVBUF选项值设成0(关闭内部缓冲区)，但是程序员必须十分清楚把AFD.SYS的内部缓冲区关掉会造成什么后果，避免收发数据时有关的缓冲区拷贝可能引起的系统崩溃。</p>
<p>　　举例来说，一个应用程序通过设定SO_SNDBUF为0把缓冲区关闭，然后发出一个阻塞send()调用。在这样的情况下，系统内核会把应用程序的缓冲区锁定，直到接收方确认收到了整个缓冲区后send()调用才返回。似乎这是一种判定你的数据是否已经为对方全部收到的简洁的方法，实际上却并非如此。想想看，即使远端TCP通知数据已经收到，其实也根本不代表数据已经成功送给客户端应用程序，比如对方可能发生资源不足的情况，导致AFD.SYS不能把数据拷贝给应用程序。另一个更要紧的问题是，在每个线程中每次只能进行一次发送调用，效率极其低下。</p>
<p>　　把SO_RCVBUF设为0，关闭AFD.SYS的接收缓冲区也不能让性能得到提升，这只会迫使接收到的数据在比Winsock更低的层次进行缓冲，当你发出receive调用时，同样要进行缓冲区拷贝，因此你本来想避免缓冲区拷贝的阴谋不会得逞。</p>
<p>　　现在我们应该清楚了，关闭缓冲区对于多数应用程序而言并不是什么好主意。只要要应用程序注意随时在某个连接上保持几个WSARecvs重叠调用，那么通常没有必要关闭接收缓冲区。如果AFD.SYS总是有由应用程序提供的缓冲区可用，那么它将没有必要使用内部缓冲区。</p>
<p>　　高性能的服务器应用程序可以关闭发送缓冲区，同时不会损失性能。不过，这样的应用程序必须十分小心，保证它总是发出多个重叠发送调用，而不是等待某个重叠发送结束了才发出下一个。如果应用程序是按一个发完再发下一个的顺序来操作，那浪费掉两次发送中间的空档时间，总之是要保证传输驱动程序在发送完一个缓冲区后，立刻可以转向另一个缓冲区。</p>
<p><span style="color: #009999;"><strong>资源的限制条件</strong></span></p>
<p>　　在设计任何服务器应用程序时，其强健性是主要的目标。也就是说，</p>
<p>　　你的应用程序要能够应对任何突发的问题，例如并发客户请求数达到峰值、可用内存临时出现不足、以及其它短时间的现象。这就要求程序的设计者注意Windows NT和2000系统下的资源限制条件的问题，从容地处理突发性事件。</p>
<p>　　你可以直接控制的、最基本的资源就是网络带宽。通常，使用用户数据报协议(UDP)的应用程序都可能会比较注意带宽方面的限制，以最大限度地减少包的丢失。然而，在使用TCP连接时，服务器必须十分小心地控制好，防止网络带宽过载超过一定的时间，否则将需要重发大量的包或造成大量连接中断。关于带宽管理的方法应根据不同的应用程序而定，这超出了本文讨论的范围。</p>
<p>　　虚拟内存的使用也必须很小心地管理。通过谨慎地申请和释放内存，或者应用lookaside lists(一种高速缓存)技术来重新使用已分配的内存，将有助于控制服务器应用程序的内存开销(原文为“让服务器应用程序留下的脚印小一点”)，避免操作系统频繁地将应用程序申请的物理内存交换到虚拟内存中(原文为“让操作系统能够总是把更多的应用程序地址空间更多地保留在内存中”)。你也可以通过SetWorkingSetSize()这个Win32 API让操作系统分配给你的应用程序更多的物理内存。</p>
<p>　　在使用Winsock时还可能碰到另外两个非直接的资源不足情况。一个是被锁定的内存页面的极限。如果你把AFD.SYS的缓冲关闭，当应用程序收发数据时，应用程序缓冲区的所有页面将被锁定到物理内存中。这是因为内核驱动程序需要访问这些内存，在此期间这些页面不能交换出去。如果操作系统需要给其它应用程序分配一些可分页的物理内存，而又没有足够的内存时就会发生问题。我们的目标是要防止写出一个病态的、锁定所有物理内存、让系统崩溃的程序。也就是说，你的程序锁定内存时，不要超出系统规定的内存分页极限。</p>
<p>　　在Windows NT和2000系统上，所有应用程序总共可以锁定的内存大约是物理内存的1/8(不过这只是一个大概的估计，不是你计算内存的依据)。如果你的应用程序不注意这一点，当你的发出太多的重叠收发调用，而且I/O没来得及完成时，就可能偶尔发生ERROR_INSUFFICIENT_RESOURCES的错误。在这种情况下你要避免过度锁定内存。同时要注意，系统会锁定包含你的缓冲区所在的整个内存页面，因此缓冲区靠近页边界时是有代价的(译者理解，缓冲区如果正好超过页面边界，那怕是1个字节，超出的这个字节所在的页面也会被锁定)。</p>
<p>　　另外一个限制是你的程序可能会遇到系统未分页池资源不足的情况。所谓未分页池是一块永远不被交换出去的内存区域，这块内存用来存储一些供各种内核组件访问的数据，其中有的内核组件是不能访问那些被交换出去的页面空间的。Windows NT和2000的驱动程序能够从这个特定的未分页池分配内存。</p>
<p>　　当应用程序创建一个套接字(或者是类似的打开某个文件)时，内核会从未分页池中分配一定数量的内存，而且在绑定、连接套接字时，内核又会从未分页池中再分配一些内存。当你注意观察这种行为时你将发现，如果你发出某些I/O请求时(例如收发数据)，你会从未分页池里再分配多一些内存(比如要追踪某个待决的I/O操作，你可能需要给这个操作添加一个自定义结构，如前文所提及的)。最后这就可能会造成一定的问题，操作系统会限制未分页内存的用量。</p>
<p>　　在Windows NT和2000这两种操作系统上，给每个连接分配的未分页内存的具体数量是不同的，未来版本的Windows很可能也不同。为了使应用程序的生命期更长，你就不应该计算对未分页池内存的具体需求量。</p>
<p>　　你的程序必须防止消耗到未分页池的极限。当系统中未分页池剩余空间太小时，某些与你的应用程序毫无关系的内核驱动就会发疯，甚至造成系统崩溃，特别是当系统中有第三方设备或驱动程序时，更容易发生这样的惨剧(而且无法预测)。同时你还要记住，同一台电脑上还可能运行有其它同样消耗未分页池的其它应用程序，因此在设计你的应用程序时，对资源量的预估要特别保守和谨慎。</p>
<p>　　处理资源不足的问题是十分复杂的，因为发生上述情况时你不会收到特别的错误代码，通常你只能收到一般性的WSAENOBUFS或者ERROR_INSUFFICIENT_RESOURCES 错误。要处理这些错误，首先，把你的应用程序工作配置调整到合理的最大值(译者注：所谓工作配置，是指应用程序各部分运行中所需的内存用量，请参考 <a href="http://msdn.microsoft.com/msdnmag/issues/1000/Bugslayer/Bugslayer1000.asp" target="_blank">http://msdn.microsoft.com/msdnmag/issues/1000/Bugslayer/Bugslayer1000.asp</a> ，关于内存优化，译者另有译文)，如果错误继续出现，那么注意检查是否是网络带宽不足的问题。之后，请确认你没有同时发出太多的收发调用。最后，如果还是收到资源不足的错误，那就很可能是遇到了未分页内存池不足的问题了。要释放未分页内存池空间，请关闭应用程序中相当部分的连接，等待系统自行渡过和修正这个瞬时的错误。</p>
<p><strong><span style="color: #009999;">接受连接请求</span></strong></p>
<p>　　服务器要做的最普通的事情之一就是接受来自客户端的连接请求。在套接字上使用重叠I/O接受连接的惟一API就是AcceptEx()函数。有趣的是，通常的同步接受函数accept()的返回值是一个新的套接字，而AcceptEx()函数则需要另外一个套接字作为它的参数之一。这是因为AcceptEx()是一个重叠操作，所以你需要事先创建一个套接字(但不要绑定或连接它)，并把这个套接字通过参数传给AcceptEx()。以下是一小段典型的使用AcceptEx()的伪代码：</p>
<blockquote>
<pre>do {
    -等待上一个 AcceptEx 完成
    -创建一个新套接字并与完成端口进行关联
    -设置背景结构等等
    -发出一个 AcceptEx 请求
}while(TRUE);</pre>
</blockquote>
<p>　　作为一个高响应能力的服务器，它必须发出足够的AcceptEx调用，守候着，一旦出现客户端连接请求就立刻响应。至于发出多少个AcceptEx才够，就取决于你的服务器程序所期待的通信交通类型。比如，如果进入连接率高的情况(因为连接持续时间较短，或者出现交通高峰)，那么所需要守候的AcceptEx当然要比那些偶尔进入的客户端连接的情况要多。聪明的做法是，由应用程序来分析交通状况，并调整AcceptEx守候的数量，而不是固定在某个数量上。</p>
<p>　　对于Windows2000，Winsock提供了一些机制，帮助你判定AcceptEx的数量是否足够。这就是，在创建监听套接字时创建一个事件，通过WSAEventSelect()这个API并注册FD_ACCEPT事件通知来把套接字和这个事件关联起来。一旦系统收到一个连接请求，如果系统中没有AcceptEx()正在等待接受连接，那么上面的事件将收到一个信号。通过这个事件，你就可以判断你有没有发出足够的AcceptEx()，或者检测出一个非正常的客户请求(下文述)。这种机制对Windows NT 4.0不适用。</p>
<p>　　使用AcceptEx()的一大好处是，你可以通过一次调用就完成接受客户端连接请求和接受数据(通过传送lpOutputBuffer参数)两件事情。也就是说，如果客户端在发出连接的同时传输数据，你的AcceptEx()调用在连接创建并接收了客户端数据后就可以立刻返回。这样可能是很有用的，但是也可能会引发问题，因为AcceptEx()必须等全部客户端数据都收到了才返回。具体来说，如果你在发出AcceptEx()调用的同时传递了lpOutputBuffer参数，那么AcceptEx()不再是一项原子型的操作，而是分成了两步：接受客户连接，等待接收数据。当缺少一种机制来通知你的应用程序所发生的这种情况：“连接已经建立了，正在等待客户端数据”，这将意味着有可能出现客户端只发出连接请求，但是不发送数据。如果你的服务器收到太多这种类型的连接时，它将拒绝连接更多的合法客户端请求。这就是黑客进行“拒绝服务”攻击的常见手法。</p>
<p>　　要预防此类攻击，接受连接的线程应该不时地通过调用getsockopt()函数(选项参数为SO_CONNECT_TIME)来检查AcceptEx()里守候的套接字。getsockopt()函数的选项值将被设置为套接字被连接的时间，或者设置为-1(代表套接字尚未建立连接)。这时，WSAEventSelect()的特性就可以很好地利用来做这种检查。如果发现连接已经建立，但是很久都没有收到数据的情况，那么就应该终止连接，方法就是关闭作为参数提供给AcceptEx()的那个套接字。注意，在多数非紧急情况下，如果套接字已经传递给AcceptEx()并开始守候，但还未建立连接，那么你的应用程序不应该关闭它们。这是因为即使关闭了这些套接字，出于提高系统性能的考虑，在连接进入之前，或者监听套接字自身被关闭之前，相应的内核模式的数据结构也不会被干净地清除。</p>
<p>　　发出AcceptEx()调用的线程，似乎与那个进行完成端口关联操作、处理其它I/O完成通知的线程是同一个，但是，别忘记线程里应该尽力避免执行阻塞型的操作。Winsock2分层结构的一个副作用是调用socket()或WSASocket() API的上层架构可能很重要(译者不太明白原文意思，抱歉)。每个AcceptEx()调用都需要创建一个新套接字，所以最好有一个独立的线程专门调用AcceptEx()，而不参与其它I/O处理。你也可以利用这个线程来执行其它任务，比如事件记录。</p>
<p>　　有关AcceptEx()的最后一个注意事项：要实现这些API，并不需要其它提供商提供的Winsock2实现。这一点对微软特有的其它API也同样适用，比如TransmitFile()和GetAcceptExSockAddrs()，以及其它可能会被加入到新版Windows的API. 在Windows NT和2000上，这些API是在微软的底层提供者DLL(mswsock.dll)中实现的，可通过与mswsock.lib编译连接进行调用，或者通过WSAIoctl() (选项参数为SIO_GET_EXTENSION_FUNCTION_POINTER)动态获得函数的指针。</p>
<p>　　如果在没有事先获得函数指针的情况下直接调用函数(也就是说，编译时静态连接mswsock.lib，在程序中直接调用函数)，那么性能将很受影响。因为AcceptEx()被置于Winsock2架构之外，每次调用时它都被迫通过WSAIoctl()取得函数指针。要避免这种性能损失，需要使用这些API的应用程序应该通过调用WSAIoctl()直接从底层的提供者那里取得函数的指针。</p>
<p>参见下图套接字架构：<br />
<img src="http://www.vckbase.com/document/journal/vckbase50/images/winsockfig03.gif" alt="" width="200" height="231" /></p>
<p><strong><span style="color: #009999;">TransmitFile 和 TransmitPackets </span></strong></p>
<p>　　Winsock 提供两个专门为文件和内存数据传输进行了优化的函数。其中TransmitFile()这个API函数在Windows NT 4.0 和 Windows 2000上都可以使用，而TransmitPackets()则将在未来版本的Windows中实现。</p>
<p>　　TransmitFile()用来把文件内容通过Winsock进行传输。通常发送文件的做法是，先调用CreateFile()打开一个文件，然后不断循环调用ReadFile() 和WSASend ()直至数据发送完毕。但是这种方法很没有效率，因为每次调用ReadFile() 和 WSASend ()都会涉及一次从用户模式到内核模式的转换。如果换成TransmitFile()，那么只需要给它一个已打开文件的句柄和要发送的字节数，而所涉及的模式转换操作将只在调用CreateFile()打开文件时发生一次，然后TransmitFile()时再发生一次。这样效率就高多了。</p>
<p>　　TransmitPackets()比TransmitFile()更进一步，它允许用户只调用一次就可以发送指定的多个文件和内存缓冲区。函数原型如下：</p>
<blockquote>
<pre>BOOL TransmitPackets(
  SOCKET hSocket,
  LPTRANSMIT_PACKET_ELEMENT lpPacketArray,
  DWORD nElementCount,
  DWORD nSendSize,
  LPOVERLAPPED lpOverlapped,
  DWORD dwFlags
);</pre>
</blockquote>
<p>　　其中，lpPacketArray是一个结构的数组，其中的每个元素既可以是一个文件句柄或者内存缓冲区，该结构定义如下：</p>
<blockquote>
<pre>typedef struct _TRANSMIT_PACKETS_ELEMENT {
    DWORD dwElFlags;
    DWORD cLength;
    union {
        struct {
            LARGE_INTEGER     nFileOffset;
            HANDLE            hFile;
            };
            PVOID             pBuffer;
    };
} TRANSMIT_FILE_BUFFERS;</pre>
</blockquote>
<p>　　其中各字段是自描述型的(self explanatory)。<br />
　　dwElFlags字段：指定当前元素是一个文件句柄还是内存缓冲区(分别通过常量TF_ELEMENT_FILE 和TF_ELEMENT_MEMORY指定)；<br />
　　cLength字段：指定将从数据源发送的字节数(如果是文件，这个字段值为0表示发送整个文件)；<br />
　　结构中的无名联合体：包含文件句柄的内存缓冲区(以及可能的偏移量)。</p>
<p>　　使用这两个API的另一个好处，是可以通过指定TF_REUSE_SOCKET和TF_DISCONNECT标志来重用套接字句柄。每当API完成数据的传输工作后，就会在传输层级别断开连接，这样这个套接字就又可以重新提供给AcceptEx()使用。采用这种优化的方法编程，将减轻那个专门做接受操作的线程创建套接字的压力(前文述及)。</p>
<p>　　这两个API也都有一个共同的弱点：Windows NT Workstation 或 Windows 2000 专业版中，函数每次只能处理两个调用请求，只有在Windows NT、Windows 2000服务器版、Windows 2000高级服务器版或 Windows 2000 Data Center中才获得完全支持。</p>
<p><span style="color: #009999;"><strong>放在一起看看</strong></span></p>
<p>　　以上各节中，我们讨论了开发高性能的、大响应规模的应用程序所需的函数、方法和可能遇到的资源瓶颈问题。这些对你意味着什么呢？其实，这取决于你如何构造你的服务器和客户端。当你能够在服务器和客户端设计上进行更好地控制时，那么你越能够避开瓶颈问题。</p>
<p>　　来看一个示范的环境。我们要设计一个服务器来响应客户端的连接、发送请求、接收数据以及断开连接。那么，服务器将需要创建一个监听套接字，把它与某个完成端口进行关联，为每颗CPU创建一个工作线程。再创建一个线程专门用来发出AcceptEx()。我们知道客户端会在发出连接请求后立刻传送数据，所以如果我们准备好接收缓冲区会使事情变得更为容易。当然，不要忘记不时地轮询AcceptEx()调用中使用的套接字(使用SO_CONNECT_TIME选项参数)来确保没有恶意超时的连接。</p>
<p>　　该设计中有一个重要的问题要考虑，我们应该允许多少个AcceptEx()进行守候。这是因为，每发出一个AcceptEx()时我们都同时需要为它提供一个接收缓冲区，那么内存中将会出现很多被锁定的页面(前文说过了，每个重叠操作都会消耗一小部分未分页内存池，同时还会锁定所有涉及的缓冲区)。这个问题很难回答，没有一个确切的答案。最好的方法是把这个值做成可以调整的，通过反复做性能测试，你就可以得出在典型应用环境中最佳的值。</p>
<p>　　好了，当你测算清楚后，下面就是发送数据的问题了，考虑的重点是你希望服务器同时处理多少个并发的连接。通常情况下，服务器应该限制并发连接的数量以及等候处理的发送调用。因为并发连接数量越多，所消耗的未分页内存池也越多；等候处理的发送调用越多，被锁定的内存页面也越多(小心别超过了极限)。这同样也需要反复测试才知道答案。</p>
<p>　　对于上述环境，通常不需要关闭单个套接字的缓冲区，因为只在AcceptEx()中有一次接收数据的操作，而要保证给每个到来的连接提供接收缓冲区并不是太难的事情。但是，如果客户机与服务器交互的方式变一变，客户机在发送了一次数据之后，还需要发送更多的数据，在这种情况下关闭接收缓冲就不太妙了，除非你想办法保证在每个连接上都发出了重叠接收调用来接收更多的数据。</p>
<p><span style="color: #009999;"><strong>结论</strong></span></p>
<p>　　开发大响应规模的Winsock服务器并不是很可怕，其实也就是设置一个监听套接字、接受连接请求和进行重叠收发调用。通过设置合理的进行守候的重叠调用的数量，防止出现未分页内存池被耗尽，这才是最主要的挑战。按照我们前面讨论的一些原则，你就可以开发出大响应规模的服务器应用程序。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ray77.com/use-finish-port-answer-winsoc.html/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>走近 STL</title>
		<link>http://www.ray77.com/into-the-stl.html</link>
		<comments>http://www.ray77.com/into-the-stl.html#comments</comments>
		<pubDate>Fri, 02 Jan 2009 12:53:21 +0000</pubDate>
		<dc:creator>Rock</dc:creator>
				<category><![CDATA[Develop]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[STL]]></category>
		<category><![CDATA[编程]]></category>

		<guid isPermaLink="false">http://www.ray77.com/?p=567</guid>
		<description><![CDATA[　　本文面向的读者：学习过C++程序设计语言（也就是说学习过Template），但是还没有接触过STL的STL的初学者。这实际上是我学习STL的一篇笔记，老鸟就不用看了。
什么是泛型程序设计
　　我们可以简单的理解为：使用模板的程序设计就是泛型程序设计。就像我们我们可以简单的理解面向对象程序设计就是使用虚函数的程序设计一样。
STL是什么
　　作为一个C++程序设计者，STL是一种不可忽视的技术。Sandard Template Library (STL)：
标准模板库,更准确的说是 C++ 程序设计语言标准模板库。学习过MFC的人知道，MFC是微软公司创建的 C++ 类库。而与之类似的是 STL 是模板库，只不过 STL 是 ANSI/ISO 标准的一部分，而 MFC 只不过是微软的一个产品而已。也就是说STL是所有C++编译器和所有操作系统平台都支持的一种库，说它是一种库是因为，虽然STL是一种标准，也就是说对所有的编译器来说，提供给C++程序设计者的接口都是一样的。也就是说同一段STL代码在不同编译器和操作系统平台上运行的结果都是相同的，但是底层实现可以是不同的。 令人兴奋的是，STL的使用者并不需要了解它的底层实现。 试想一下，如果我们有一把能打开所有锁的钥匙，那将是多么令人疯狂啊。嘎嘎。这个歪梦我做了20多年鸟。
　　STL的目的是标准化组件，这样你就不用重新开发它们了。你可以仅仅使用这些现成的组件。STL现在是C++的一部分，因此不用额外安装什么。它被内建在你的编译器之内。
为什么我们需要学习STL

STL是 C++的ANSI/ISO 标准的一部分,可以用于所有C++语言编译器和所有平台(Windows/Unix/Linux..)。STL的同一版本在任意硬件配置下都是可用的；
STL 提供了大量的可复用软件组织。例如，程序员再也不用自己设计排序，搜索算法了，这些都已经是STL的一部分了。嘎嘎，有意思吧；
使用STL 的应用程序保证了得到的实现在处理速度和内存利用方面都是高效的，因为STL设计者们已经为我们考虑好了；
使用STL编写的代码更容易修改和阅读，这是当然的鸟。因为代码更短了，很多基础工作代码已经被组件化了；
使用简单，虽然内部实现很复杂；

　　虽然，STL的优点甚多，但是STL的语法实在令初学者人头疼，许多人望而却步。可是STL是每个C++程序设计者迟早都要啃的一块骨头。因为越来越多的C++代码是用STL编写的，看不懂麻烦就大鸟。越来越多的人在用STL，不懂就无法和别人一起合作了。好事多磨嘛，早点学习早点解脱。
下面让我们来看几段代码吧：（你觉得头疼就不要看了）
//stl_cpp_1.cpp
#include &#60;iostream&#62;
double mean(double *array, size_t n)
{
    double m=0;
    for(size_t i=0; i&#60;n; ++i){
        m += array[i];
  ...]]></description>
			<content:encoded><![CDATA[<p>　　本文面向的读者：学习过C++程序设计语言（也就是说学习过Template），但是还没有接触过STL的STL的初学者。这实际上是我学习STL的一篇笔记，老鸟就不用看了。</p>
<p><strong>什么是泛型程序设计</strong><br />
　　我们可以简单的理解为：使用模板的程序设计就是泛型程序设计。就像我们我们可以简单的理解面向对象程序设计就是使用虚函数的程序设计一样。</p>
<p><strong>STL是什么</strong><br />
　　作为一个C++程序设计者，STL是一种不可忽视的技术。Sandard Template Library (STL)：<br />
标准模板库,更准确的说是 C++ 程序设计语言标准模板库。学习过MFC的人知道，MFC是微软公司创建的 C++ 类库。而与之类似的是 STL 是模板库，只不过 STL 是 ANSI/ISO 标准的一部分，而 MFC 只不过是微软的一个产品而已。也就是说STL是所有C++编译器和所有操作系统平台都支持的一种库，说它是一种库是因为，虽然STL是一种标准，也就是说对所有的编译器来说，提供给C++程序设计者的接口都是一样的。也就是说同一段STL代码在不同编译器和操作系统平台上运行的结果都是相同的，但是底层实现可以是不同的。 令人兴奋的是，STL的使用者并不需要了解它的底层实现。 试想一下，如果我们有一把能打开所有锁的钥匙，那将是多么令人疯狂啊。嘎嘎。这个歪梦我做了20多年鸟。<br />
　　STL的目的是标准化组件，这样你就不用重新开发它们了。你可以仅仅使用这些现成的组件。STL现在是C++的一部分，因此不用额外安装什么。它被内建在你的编译器之内。<span id="more-567"></span></p>
<p><strong>为什么我们需要学习STL</strong></p>
<ul>
<li>STL是 C++的ANSI/ISO 标准的一部分,可以用于所有C++语言编译器和所有平台(Windows/Unix/Linux..)。STL的同一版本在任意硬件配置下都是可用的；</li>
<li>STL 提供了大量的可复用软件组织。例如，程序员再也不用自己设计排序，搜索算法了，这些都已经是STL的一部分了。嘎嘎，有意思吧；</li>
<li>使用STL 的应用程序保证了得到的实现在处理速度和内存利用方面都是高效的，因为STL设计者们已经为我们考虑好了；</li>
<li>使用STL编写的代码更容易修改和阅读，这是当然的鸟。因为代码更短了，很多基础工作代码已经被组件化了；</li>
<li>使用简单，虽然内部实现很复杂；</li>
</ul>
<p>　　虽然，STL的优点甚多，但是STL的语法实在令初学者人头疼，许多人望而却步。可是STL是每个C++程序设计者迟早都要啃的一块骨头。因为越来越多的C++代码是用STL编写的，看不懂麻烦就大鸟。越来越多的人在用STL，不懂就无法和别人一起合作了。好事多磨嘛，早点学习早点解脱。</p>
<p>下面让我们来看几段代码吧：（你觉得头疼就不要看了）</p>
<pre>//stl_cpp_1.cpp
#include &lt;iostream&gt;
double mean(double *array, size_t n)
{
    double m=0;
    for(size_t i=0; i&lt;n; ++i){
        m += array[i];
    }
    return m/n;
}

int main(void)
{
    double a[] = {1, 2, 3, 4, 5};
    std::cout&lt;&lt;mean(a, 5)&lt;&lt;std::endl;    // will print 3
    return 0;
} </pre>
<p>好懂吧，除了那个std有点让人不舒服以外？这是一段普通的没有使用STL的C++代码。再看下面一段：</p>
<pre>//stl_cpp_2.cpp
#include &lt;vector&gt;
#include &lt;iostream&gt;

int main(void)
{
    std::vector&lt;double&gt; a;
    std::vector&lt;double&gt;::const_iterator i;
    a.push_back(1);
    a.push_back(2);
    a.push_back(3);
    a.push_back(4);
    a.push_back(5);
    for(i=a.begin(); i!=a.end(); ++i){
        std::cout&lt;&lt;(*i)&lt;&lt;std::endl;
    }
    return 0;
}</pre>
<p>　　如果你真的没有接触过STL的话，你会问，呀，vector 是啥呀？我会告诉你，那是一排美女。嘎嘎。这可不是个比喻，表想歪鸟。这是一段纯种的STL代码，看到尖括号了吧，知道那是模板了吧。看到a.push_back(5),a.begin(),a.end()你不感觉奇怪么？可是我们并没有定义这些函数啊。</p>
<pre>//stl_cpp_3.cpp
#include &lt;vector&gt;
#include &lt;iostream&gt;
int main(void)
{
    std::vector&lt;int&gt; q;
    q.push_back(10);
    q.push_back(11);
    q.push_back(12);

    std::vector&lt;int&gt; v;
    for(int i=0; i&lt;5; ++i){
        v.push_back(i);
    }
    std::vector&lt;int&gt;::iterator it = v.begin() + 1;
    it = v.insert(it, 33);
    v.insert(it, q.begin(), q.end());
    it = v.begin() + 3;
    v.insert(it, 3, -1);
    it = v.begin() + 4;
    v.erase(it);
    it = v.begin() + 1;
    v.erase(it, it + 4);
    v.clear();

    return 0;
}</pre>
<p>　　这一段你又看到了新东西了吧，iterator？？？不罗嗦了，等你看完这篇文章，回头再看就简单了。在正式介绍STL之前，我们需要花点时间来了解一下模板和命名空间。<br />
　　关于模板的其他细节，读者可以参阅《C++ Templates 中文版》（有点费脑子哦）。在这里，我只简单的介绍一下模板类和函数模板的概念。<br />
　　模板是C++中实现代码重用机制的一种工具，可以实现类型参数化，把类型定义为参数。函数模板和类模板允许用户构造模板函数和模板类。</p>
<p><img style="border: 0px;" src="http://www.vckbase.com/document/journal/vckbase42/images/stlnotesimg1.jpg" border="0" alt="" width="476" height="210" /><br />
图1</p>
<p>下面我们来看一段函数模板的例子：</p>
<pre>//stl_cpp_4.cpp
#include&lt;iostream.h&gt;
#include&lt;string.h&gt;
//定义函数模板
template&lt;class T&gt;  //template 是关键字，T 表示一种待实例化的类型
//template&lt;typename T&gt;  也是对的
T max(T a, T b)//函数模板，函数名为 max，此函数有2个T类型的参数，返回类型为T
{
  return (a&gt;b)?a:b;
}
//在此例实例化的时候，T可以是多种类型的，int,char,string…
int main(void)
{
  int x=2,y=6;
  double x1=9.123,y1=12.6543;
  cout&lt;&lt;"把T实例化为int:"&lt;&lt;max(x,y)&lt;&lt;endl;//实例化函数模板，把T实例化为int
  cout&lt;&lt;"把T实例化为double:"&lt;&lt;max(x1,y1)&lt;&lt;endl;
　//实例化函数模板，把T实例化为double
　getchar(); //这一行代码用来在dos下查看结果，也可以用cin.get();
}</pre>
<p>下面再看看，类模板：</p>
<pre>//stl_cpp_5.cpp
#include&lt;iostream.h&gt;
//定义名为ex_class的类模板
template &lt; typename T&gt;  class ex_class
{
    T value;
public:
    ex_class(T v) { value=v; }
    void set_value(T v) { value=v; }
    T get_value(void) {return value;}
};
//main()函数中测试ex_class类模板
int main(void)
{
    //测试int类型数据
    ex_class &lt;int&gt; a(5),b(10);
    cout&lt;&lt;"a.value:"&lt;&lt;a.get_value()&lt;&lt;endl;
    cout&lt;&lt;"b.value:"&lt;&lt;b.get_value()&lt;&lt;endl;
    //测试char类型数据
    ex_class &lt;char&gt; ch(''A'');
    cout&lt;&lt;"ch.value:"&lt;&lt;ch.get_value()&lt;&lt;endl;
    ch.set_value(''a'');
    cout&lt;&lt;"ch.value:"&lt;&lt;ch.get_value()&lt;&lt;endl;
    //测试double类型数据
    ex_class &lt;double&gt; x(5.5);
    cout&lt;&lt;"x.value:"&lt;&lt;x.get_value()&lt;&lt;endl;
    x.set_value(7.5);
    cout&lt;&lt;"x.value:"&lt;&lt;x.get_value()&lt;&lt;endl;
}</pre>
<p><strong>命名空间（名字空间）</strong><br />
　　命名空间是C++的一种机制，用来把单个标识符下的大量有逻辑联系的程序实体组合到一起。此标识符作为此组群的名字。命名空间用关键字namespace 来定义：</p>
<pre>//stl_cpp_6.cpp
#include &lt;iostream&gt;
using namespace std;
namespace printA
{
   print()  {cout&lt;&lt;"using namespace printA….."&lt;&lt;endl; };
}
namespace printB
{
   print()  {cout&lt;&lt;"using namespace printB….."&lt;&lt;endl; };
}
int main(void)
{
   printA::print();    //测试命名空间printA， ：：是作用域解析运算符
   printB::print();
 }
命名空间可以嵌套定义：
  namespace A
{
   functiong1(){};
   namespace B
   { }
}</pre>
<p>　　一个namespace是指一个具名的范围（named scope）。namespace被用来将相关的声明划归在一起，将不相关的代码部分隔开。命名空间只是命名了一个特殊的作用域，当程序很大，而且需要多人合作的时候，命名空间就显得特别的重要。比如2个程序员A,B 在同一个程序中定义了函数 pop(),如果没有使用命名空间，则会出错，而且这种错误难以检测出来。为了安全起见，他们可以定义不同的命名空间 A和B，在用的时候可以使用A::pop()和B::pop()来区分。<br />
　　在STL中，标准库的全部成员在预先定义的命名空间std中。如果要用类模板vector ，有两种方法：一是在程序的前面添加预处理指令：</p>
<pre>   #include &lt;vector&gt;
   using namespace std;</pre>
<p>第二种方法是：</p>
<pre>   #include &lt;vector&gt;
   using std::vector;</pre>
<p><strong>动态绑定和静态绑定</strong><br />
　　所谓绑定是指，对于参与多态行为的类型，他们具有多态行为的接口是在公共基类的设计中就预先确定的。而非绑定则对于参与多态行为的类型，他们的接口没有预先定义。<br />
　　在C++中通过继承实现的多态是动态绑定，通过模板实现的多态是静态绑定。动态绑定的接口是在运行期间（动态）完成的，静态绑定的接口是在编译期间（静态）完成的。好了，有了以上的知识我们可以来学习STL 了。</p>
<p><strong>STL 的组成</strong><br />
　　STL有三大核心部分：容器（Container）、算法（Algorithms）、迭代器（Iterator），容器适配器（container adaptor），函数对象(functor)，除此之外还有STL其他标准组件。</p>
<ul>
<li>容器：装东西的东西，装水的杯子，装咸水的大海，装人的教室……STL里的容器是可容纳一些数据的模板类；</li>
<li>算法：就是往杯子里倒水，往大海里排污，从教室里撵人……STL里的算法，就是处理容器里面数据的方法，操作；</li>
<li>迭代器：往杯子里倒水的水壶，排污的管道，撵人的那个物业管理人员……STL里的迭代器：遍历容器中数据的对象；</li>
</ul>
<p>　　对存储于容器中的数据进行处理时，迭代器能从一个成员移向另一个成员。他能按预先定义的顺序在某些容器中的成员间移动。对普通的一维数组、向量、双端队列和列表来说，迭代器是一种指针。<br />
知道了吧？嘎嘎，当然了，你猜到了，那是我在瞎扯蛋。</p>
<p>下面让我们来看看专家是怎么说的：</p>
<ul>
<li>容器（container）：容器是数据在内存中组织的方法，例如，数组、堆栈、队列、链表或二叉树（不过这些都不是STL标准容器）。STL中的容器是一种存储T（Template）类型值的有限集合的数据结构,容器的内部实现一般是类。这些值可以是对象本身，如果数据类型T代表的是Class的话。</li>
<li>算法（algorithm）：算法是应用在容器上以各种方法处理其内容的行为或功能。例如，有对容器内容排序、复制、检索和合并的算法。在STL中，算法是由模板函数表现的。这些函数不是容器类的成员函数。相反，它们是独立的函数。令人吃惊的特点之一就是其算法如此通用。不仅可以将其用于STL容器，而且可以用于普通的C＋＋数组或任何其他应用程序指定的容器。</li>
<li>迭代器(iterator)：一旦选定一种容器类型和数据行为(算法)，那么剩下唯一要他做的就是用迭代器使其相互作用。可以把达代器看作一个指向容器中元素的普通指针。可以如递增一个指针那样递增迭代器，使其依次指向容器中每一个后继的元素。迭代器是STL的一个关键部分，因为它将算法和容器连在一起。</li>
</ul>
<p>下面我将依次介绍STL的这三个主要组件。</p>
<p><strong>容器</strong><br />
　　STL中的容器有队列容器和关联容器，容器适配器（congtainer adapters：stack,queue，priority queue），位集（bit_set），串包(string_package)等等。<br />
　　在本文中，我将介绍list,vector，deque等队列容器，和set和multisets,map和multimaps等关联容器，一共7种基本容器类。<br />
　　队列容器（顺序容器）：队列容器按照线性排列来存储T类型值的集合，队列的每个成员都有自己的特有的位置。顺序容器有向量类型、双端队列类型、列表类型三种。<br />
基本容器——顺序容器<br />
　　向量（vector容器类）：＃include &lt;vector&gt;，vector是一种动态数组，是基本数组的类模板。其内部定义了很多基本操作。既然这是一个类，那么它就会有自己的构造函数。vector 类中定义了4中种构造函数：</p>
<li>默认构造函数，构造一个初始长度为0的空向量，<br />
如：vector&lt;int&gt; v1;</li>
<li>带有单个整形参数的构造函数，此参数描述了向量的初始大小。这个构造函数还有一个可选的参数，这是一个类型为T的实例，描述了各个向量种各成员的初始值；<br />
如：vector&lt;int&gt; v2(init_size,0); 如果预先定义了：int init_size;他的成员值都被初始化为0；</li>
<li>复制构造函数，构造一个新的向量，作为已存在的向量的完全复制，<br />
如：vector&lt;int&gt; v3(v2);</li>
<li>带两个常量参数的构造函数，产生初始值为一个区间的向量。区间由一个半开区间[first,last](MS word的显示可能会有问题，first前是一个左方括号，last后面是一个右圆括号)来指定。<br />
如：vector&lt;int&gt; v4（first,last）</li>
<p>下面一个例子用的是第四种构造方法，其它的方法读者可以自己试试。</p>
<pre>//stl_cpp_7.cpp
//程序：初始化演示
#include &lt;cstring&gt;
#include &lt;vector&gt;
#include &lt;iostream&gt;
using namespace std;
int ar[10] = {  12, 45, 234, 64, 12, 35, 63, 23, 12, 55  };
char* str = "Hello World";
int main(void)
{
  vector &lt;int&gt; vec1(ar, ar+10);          //first=ar,last=ar+10,不包括ar+10
  vector &lt;char&gt; vec2(str, str+strlen(str));  //first=str,last= str+strlen(str),不包括最后一个
  cout&lt;&lt;"vec1:"&lt;&lt;endl;
//打印vec1和vec2，const_iterator是迭代器，后面会讲到
//当然，也可以用for (int i=0; i&lt;vec1.size(); i++)cout &lt;&lt; vec[i];输出
//size()是vector的一个成员函数
  for(vector&lt;int&gt;::const_iterator p=vec1.begin();p!=vec1.end(); ++p)
     cout&lt;&lt;*p;
  cout&lt;&lt;''\n''&lt;&lt;"vec2:"&lt;&lt;endl;
  for(vector&lt;char&gt;::const_iterator p1=vec2.begin();p1!=vec2.end(); ++p1)
      cout&lt;&lt;*p1;
  getchar();
  return 0;
}</pre>
<p>　　为了帮助理解向量的概念，这里写了一个小例子，其中用到了vector的成员函数：begin()，end()，push_back()，assign()，front()，back()，erase()，empty()，at()，size()。</p>
<pre>//stl_cpp_8.cpp
#include &lt;iostream&gt;
#include &lt;vector&gt;
using namespace std;
typedef vector&lt;int&gt; INTVECTOR;//自定义类型INTVECTOR
//测试vector容器的功能
void main(void)
{
    //vec1对象初始为空
    INTVECTOR vec1;
    //vec2对象最初有10个值为6的元素
    INTVECTOR vec2(10,6);
    //vec3对象最初有3个值为6的元素，拷贝构造
    INTVECTOR vec3(vec2.begin(),vec2.begin()+3);
    //声明一个名为i的双向迭代器
    INTVECTOR::iterator i;
    //从前向后显示vec1中的数据
    cout&lt;&lt;"vec1.begin()--vec1.end():"&lt;&lt;endl;
    for (i =vec1.begin(); i !=vec1.end(); ++i)
        cout &lt;&lt; *i &lt;&lt; " ";
    cout &lt;&lt; endl;
    //从前向后显示vec2中的数据
    cout&lt;&lt;"vec2.begin()--vec2.end():"&lt;&lt;endl;
    for (i =vec2.begin(); i !=vec2.end(); ++i)
        cout &lt;&lt; *i &lt;&lt; " ";
    cout &lt;&lt; endl;
    //从前向后显示vec3中的数据
    cout&lt;&lt;"vec3.begin()--vec3.end():"&lt;&lt;endl;
    for (i =vec3.begin(); i !=vec3.end(); ++i)
        cout &lt;&lt; *i &lt;&lt; " ";
    cout &lt;&lt; endl;
    //测试添加和插入成员函数，vector不支持从前插入
    vec1.push_back(2);//从后面添加一个成员
    vec1.push_back(4);
vec1.insert(vec1.begin()+1,5);//在vec1第一个的位置上插入成员5
//从vec1第一的位置开始插入vec3的所有成员
vec1.insert(vec1.begin()+1,vec3.begin(),vec3.end());
cout&lt;&lt;"after push() and insert() now the vec1 is:" &lt;&lt;endl;
    for (i =vec1.begin(); i !=vec1.end(); ++i)
        cout &lt;&lt; *i &lt;&lt; " ";
    cout &lt;&lt; endl;
    //测试赋值成员函数
    vec2.assign(8,1);   // 重新给vec2赋值，8个成员的初始值都为1
    cout&lt;&lt;"vec2.assign(8,1):" &lt;&lt;endl;
    for (i =vec2.begin(); i !=vec2.end(); ++i)
        cout &lt;&lt; *i &lt;&lt; " ";
    cout &lt;&lt; endl;
    //测试引用类函数
    cout&lt;&lt;"vec1.front()="&lt;&lt;vec1.front()&lt;&lt;endl;//vec1第零个成员
    cout&lt;&lt;"vec1.back()="&lt;&lt;vec1.back()&lt;&lt;endl;//vec1的最后一个成员
    cout&lt;&lt;"vec1.at(4)="&lt;&lt;vec1.at(4)&lt;&lt;endl;//vec1的第五个成员
    cout&lt;&lt;"vec1[4]="&lt;&lt;vec1[4]&lt;&lt;endl;
    //测试移出和删除
    vec1.pop_back();//将最后一个成员移出vec1
    vec1.erase(vec1.begin()+1,vec1.end()-2);//删除成员
    cout&lt;&lt;"vec1.pop_back() and vec1.erase():" &lt;&lt;endl;
    for (i =vec1.begin(); i !=vec1.end(); ++i)
        cout &lt;&lt; *i &lt;&lt; " ";
    cout &lt;&lt; endl;
    //显示序列的状态信息
    cout&lt;&lt;"vec1.size(): "&lt;&lt;vec1.size()&lt;&lt;endl;//打印成员个数
    cout&lt;&lt;"vec1.empty(): "&lt;&lt;vec1.empty()&lt;&lt;endl;//清空
}</pre>
<p>　　push_back()是将数据放入vector（向量）或deque（双端队列）的标准函数。Insert()是一个与之类似的函数，然而它在所有容器中都可以使用，但是用法更加复杂。end()实际上是取末尾加一，以便让循环正确运行&#8211;它返回的指针指向最靠近数组界限的数据。<br />
　　在Java里面也有向量的概念。Java中的向量是对象的集合。其中，各元素可以不必同类型，元素可以增加和删除，不能直接加入原始数据类型。</p>
<p><strong>双端队列（qeque容器类）：#include &lt;deque&gt;</strong><br />
　　deque（读音：deck，意即：double queue）容器类与vector类似，支持随机访问和快速插入删除，它在容器中某一位置上的操作所花费的是线性时间。与vector不同的是，deque还支持从开始端插入数据：<br />
push_front()。此外deque也不支持与vector的capacity()、reserve()类似的操作。</p>
<pre>//stl_cpp_9.cpp
#include &lt;iostream&gt;
#include &lt;deque&gt;
using namespace std;
typedef deque&lt;int&gt; INTDEQUE;//有些人很讨厌这种定义法，呵呵
//从前向后显示deque队列的全部元素
void put_deque(INTDEQUE deque, char *name)
{
    INTDEQUE::iterator pdeque;//仍然使用迭代器输出
    cout &lt;&lt; "The contents of " &lt;&lt; name &lt;&lt; " : ";
    for(pdeque = deque.begin(); pdeque != deque.end(); pdeque++)
        cout &lt;&lt; *pdeque &lt;&lt; " ";//注意有 "*"号哦，没有"*"号的话会报错
    cout&lt;&lt;endl;
}
//测试deqtor容器的功能
void main(void)
{
	//deq1对象初始为空
    INTDEQUE deq1;
    //deq2对象最初有10个值为6的元素
    INTDEQUE deq2(10,6);
    //deq3对象最初有3个值为6的元素
    //声明一个名为i的双向迭代器变量
    INTDEQUE::iterator i;
    //从前向后显示deq1中的数据
    put_deque(deq1,"deq1");
    //从前向后显示deq2中的数据
    put_deque(deq2,"deq2");
	//从deq1序列后面添加两个元素
	deq1.push_back(2);
	deq1.push_back(4);
	cout&lt;&lt;"deq1.push_back(2) and deq1.push_back(4):"&lt;&lt;endl;
    put_deque(deq1,"deq1");
	//从deq1序列前面添加两个元素
	deq1.push_front(5);
	deq1.push_front(7);
	cout&lt;&lt;"deq1.push_front(5) and deq1.push_front(7):"&lt;&lt;endl;
    put_deque(deq1,"deq1");
	//在deq1序列中间插入数据
	deq1.insert(deq1.begin()+1,3,9);
	cout&lt;&lt;"deq1.insert(deq1.begin()+1,3,9):"&lt;&lt;endl;
    put_deque(deq1,"deq1");
	//测试引用类函数
	cout&lt;&lt;"deq1.at(4)="&lt;&lt;deq1.at(4)&lt;&lt;endl;
	cout&lt;&lt;"deq1[4]="&lt;&lt;deq1[4]&lt;&lt;endl;
	deq1.at(1)=10;
	deq1[2]=12;
	cout&lt;&lt;"deq1.at(1)=10 and deq1[2]=12 :"&lt;&lt;endl;
    put_deque(deq1,"deq1");
	//从deq1序列的前后各移去一个元素
	deq1.pop_front();
	deq1.pop_back();
	cout&lt;&lt;"deq1.pop_front() and deq1.pop_back():"&lt;&lt;endl;
    put_deque(deq1,"deq1");
	//清除deq1中的第2个元素
	deq1.erase(deq1.begin()+1);
	cout&lt;&lt;"deq1.erase(deq1.begin()+1):"&lt;&lt;endl;
    put_deque(deq1,"deq1");
	//对deq2赋值并显示
	deq2.assign(8,1);
	cout&lt;&lt;"deq2.assign(8,1):"&lt;&lt;endl;
    put_deque(deq2,"deq2");
}</pre>
<p>　　上面我们演示了deque如何进行插入删除等操作，像erase(),assign()是大多数容器都有的操作。关于deque的其他操作请参阅附录。</p>
<p><strong>表（List容器类）：#include &lt;list&gt;</strong><br />
　　 List又叫链表，是一种双线性列表，只能顺序访问（从前向后或者从后向前），图2是list的数据组织形式。与　　前面两种容器类有一个明显的区别就是：它不支持随机访问。要访问表中某个下标处的项需要从表头或表尾处（接近该下标的一端）开始循环。而且缺少下标预算符：operator[]。</p>
<p><img style="border: 0px;" src="http://www.vckbase.com/document/journal/vckbase42/images/stlnotesimg2.jpg" border="0" alt="" width="517" height="89" /><br />
图2</p>
<p>　　同时，list仍然包涵了erase(),begin(),end(),insert(),push_back(),push_front()这些基本函数，下面我们来演示一下list的其他函数功能。</p>
<p>merge()：合并两个排序列表；<br />
splice()：拼接两个列表；<br />
sort()：列表的排序；</p>
<pre>//stl_cpp_10.cpp
#include &lt;iostream&gt;
#include &lt;string&gt;
#include &lt;list&gt;
using namespace std;
void PrintIt(list&lt;int&gt; n)
{
    for(list&lt;int&gt;::iterator iter=n.begin(); iter!=n.end(); ++iter)
      cout&lt;&lt;*iter&lt;&lt;" ";//用迭代器进行输出循环
    }
int main(void)
{
    list&lt;int&gt; listn1,listn2;
    //给listn1,listn2初始化
    listn1.push_back(123);
    listn1.push_back(0);
    listn1.push_back(34);
    listn1.push_back(1123);
    //now listn1:123,0,34,1123
    listn2.push_back(100);
    listn2.push_back(12);
    //now listn2:12,100
    listn1.sort();
    listn2.sort();
    //给listn1和listn2排序
    //now listn1:0,34,123,1123         listn2:12,100
    PrintIt(listn1);
    cout&lt;&lt;endl;
    PrintIt(listn2);
    listn1.merge(listn2);
    //合并两个排序列表后,listn1:0，12，34，100，123，1123
    cout&lt;&lt;endl;
    PrintIt(listn1);
    cin.get();
}</pre>
<p>　　上面并没有演示splice()函数的用法，这是一个拗口的函数。用起来有点麻烦。图3所示是splice函数的功能。将一个列表插入到另一个列表当中。list容器类定义了splice()函数的3个版本：</p>
<pre>splice(position,list_value);
splice(position,list_value,ptr);
splice(position,list_value,first,last);</pre>
<p>　　list_value是一个已存在的列表，它将被插入到源列表中，position是一个迭代参数，他当前指向的是要进行拼接的列表中的特定位置。</p>
<p><img style="border: 0px;" src="http://www.vckbase.com/document/journal/vckbase42/images/stlnotesimg3.jpg" border="0" alt="" width="503" height="138" /><br />
图3</p>
<pre>listn1:123,0,34,1123   listn2:12,100</pre>
<p>　　执行listn1.splice(find(listn1.begin(),listn1.end(),0),listn2);之后，listn1将变为：123，12，100，34，1123。即把listn2插入到listn1的0这个元素之前。其中，find()函数找到0这个元素在listn1中的位置。值得注意的是，在执行splice之后，list_value将不复存在了。这个例子中是listn2将不再存在。<br />
　　第二个版本当中的ptr是一个迭代器参数，执行的结果是把ptr所指向的值直接插入到position当前指向的位置之前.这将只向源列表中插入一个元素。<br />
　　第三个版本的first和last也是迭代器参数，并不等于list_value.begin(),list_value.end()。First指的是要插入的列的第一个元素，last指的是要插入的列的最后一个元素。</p>
<p>如果listn1:123,0,34,1123 listn2:12,43，87，100 执行完以下函数之后</p>
<pre>listn1.splice(find(listn1.begin(),listn1.end(),0),++listn2.begin(),--listn2.end());
listn1:123,43,87,0,34,1123  listn2:12,100</pre>
<p>　　以上，我们学习了vector,deque,list三种基本顺序容器，其他的顺序容器还有：slist,bit_vector等等。</p>
<p><strong>另一种容器——关联容器（有点费解哦，出去让脑子清醒一下再回来看）</strong><br />
　　与前面讲到的顺序容器相比，关联容器更注重快速和高效地检索数据的能力。这些容器是根据键值（key）来检索数据的，键可以是值也可以是容器中的某一成员。这一类中的成员在初始化后都是按一定顺序排好序的。</p>
<p><strong>集和多集（set 和multiset 容器类）：#include &lt;set&gt;</strong><br />
　　一个集合（set）是一个容器，它其中所包含的元素的值是唯一的。这在收集一个数据的具体值的时候是有用的。集合中的元素按一定的顺序排列，并被作为集合中的实例。如果你需要一个键/值对（pair）来存储数据，map（也是一个关联容器，后面将马上要讲到）是一个更好的选择。一个集合通过一个链表来组织，在插入操作和删除操作上比向量（vector）快，但查找或添加末尾的元素时会有些慢。<br />
　　在集中，所有的成员都是排列好的。如果先后往一个集中插入：12，2，3，123，5，65<br />
　　则输出该集时为：2，3，5，12，65，123<br />
　　集和多集的区别是：set支持唯一键值，set中的值都是特定的，而且只出现一次；而multiset中可以出现副本键，同一值可以出现多次。</p>
<p>Set和multiset的模板参数：</p>
<pre>template&lt;class key, class compare, class Allocator=allocator&gt;</pre>
<p>　　第一个参数key是所存储的键的类型，第二个参数是为排序值而定义的比较函数的类型，第三个参数是被实现的存储分配符的类型。在有些编译器的具体实现中，第三个参数可以省略。第二个参数使用了合适形式的迭代器为键定义了特定的关系操作符，并用来在容器中遍历值时建立顺序。集的迭代器是双向，同时也是常量的，所以迭代器在使用的时候不能修改元素的值。</p>
<p>Set定义了三个构造函数：<br />
默认构造函数：</p>
<pre>explicit set(const Compare&amp;=compare());
如：set&lt;int,less&lt;int&gt; &gt; set1;</pre>
<p>　　less&lt;int&gt;是一个标准类，用于形成降序排列函数对象。升序排列是用greater&lt;int&gt;。通过指定某一预先定义的区间来初始化set对象的构造函数：</p>
<pre>template&lt;class InputIterator&gt; set(InputIterator, InputIterator,\ const Compare&amp;=compare());
如：set&lt;int ,less&lt;int&gt; &gt;set2(vector1.begin(),vector1.end());</pre>
<p>复制构造函数：</p>
<pre>set（const set&lt;Key,Compare&amp;&gt;）;
如：set&lt;int ,less&lt;int&gt; &gt;set3(set2);</pre>
<p>下面我们来看一个简单的集和多集的插入例程：</p>
<pre>//stl_cpp_11.cpp
#include &lt;iostream&gt;
#include &lt;set&gt;
using namespace std;
int main(void)
{
    set&lt;int&gt; set1;
   for(int i=0; i&lt;10; ++i)
     set1.insert(i);
    for(set&lt;int&gt;::iterator p=set1.begin();p!=set1.end();++p)
      cout&lt;&lt;*p&lt;&lt;"";
     if(set1.insert(3).second)//把3插入到set1中
//插入成功则set1.insert(3).second返回1，否则返回0
//此例中，集中已经有3这个元素了，所以插入将失败
       cout&lt;&lt;"set insert success";
     else
       cout&lt;&lt;"set insert failed";
    int a[] = {4, 1, 1, 1, 1, 1, 0, 5, 1, 0};
    multiset&lt;int&gt; A;
A.insert(set1.begin(),set1.end());
    A.insert(a,a+10);
    cout&lt;&lt;endl;
    for(multiset&lt;int&gt;::iterator p=A.begin();p!=A.end();++p)
    cout&lt;&lt;*p&lt;&lt;" ";
   cin.get();
    return 0;
}</pre>
<p>　　在集之间可以进行并集（set_union()）、交集(set_intersection())、差集(set_diffrence())d等操作，功能强大。</p>
<p><strong>映射和多重映射（map 和multimap）：#include &lt;map&gt;</strong><br />
　　映射和多重映射基于某一类型Key的键集的存在，提供对T类型的数据进行快速和高效的检索。对map而言，键只是指存储在容器中的某一成员。Map不支持副本键，multimap支持副本键。Map和multimap对象包涵了键和各个键有关的值，键和值的数据类型是不相同的，这与set不同。set中的key和value是Key类型的，而map中的key和value是一个pair结构中的两个分量。Map支持下表运算符operator[],用访问普通数组的方式访问map，不过下标为map的键。在multimap中一个键可以对应多个不同的值。</p>
<p>下面的例程说明了map中键与值的关系。</p>
<pre> //stl_cpp_12.cpp
#include &lt;iostream&gt;
#include &lt;map&gt;
using namespace std;
int main(void)
{
   map&lt;char,int,less&lt;char&gt; &gt; map1;
   map&lt;char,int,less&lt;char&gt; &gt;::iterator mapIter;
    //char 是键的类型，int是值的类型
    //下面是初始化，与数组类似
//也可以用map1.insert(map&lt;char,int,less&lt;char&gt; &gt;::value_type(''c'',3));
map1[''c'']=3;
    map1[''d'']=4;
    map1[''a'']=1;
    map1[''b'']=2;
    for(mapIter=map1.begin();mapIter!=map1.end();++mapIter)
      cout&lt;&lt;" "&lt;&lt;(*mapIter).first&lt;&lt;": "&lt;&lt;(*mapIter).second;
   //first对应定义中的char键，second对应定义中的int值
   //检索对应于d键的值是这样做的：
   map&lt;char,int,less&lt;char&gt; &gt;::const_iterator ptr;
   ptr=map1.find(''d'');
   cout&lt;&lt;''\n''&lt;&lt;" "&lt;&lt;(*ptr).first&lt;&lt;" 键对应于值："&lt;&lt;(*ptr).second;
   cin.get();
      return 0;
}</pre>
<p>　　从以上例程中，我们可以看到map对象的行为和一般数组的行为类似。Map允许两个或多个值使用比较操作符。下面我们再看看multimap:</p>
<pre>//stl_cpp_13.cpp
#include &lt;iostream&gt;
#include &lt;map&gt;
#include &lt;string&gt;
using namespace std;
int main(void)
{
    multimap&lt;string,string,less&lt;string&gt; &gt;mulmap;
    multimap&lt;string,string,less&lt;string&gt; &gt;::iterator p;
    //初始化多重映射mulmap:
    typedef multimap&lt;string,string,less&lt;string&gt; &gt;::value_type vt;
    typedef string s;
    mulmap.insert(vt(s("Tom "),s("is a student")));
    mulmap.insert(vt(s("Tom "),s("is a boy")));
    mulmap.insert(vt(s("Tom "),s("is a bad boy of blue!")));
    mulmap.insert(vt(s("Jerry "),s("is a student")));
    mulmap.insert(vt(s("Jerry "),s("is a beatutiful girl")));
    mulmap.insert(vt(s("DJ "),s("is a student")));
    //输出初始化以后的多重映射mulmap:
    for(p=mulmap.begin();p!=mulmap.end();++p)
       cout&lt;&lt;(*p).first&lt;&lt;(*p).second&lt;&lt;endl;
    //检索并输出Jerry键所对应的所有的值
    cout&lt;&lt;"find Jerry :"&lt;&lt;endl;
    p=mulmap.find(s("Jerry "));
    while((*p).first=="Jerry ")
    {
        cout&lt;&lt;(*p).first&lt;&lt;(*p).second&lt;&lt;endl;
        ++p;
    }
    cin.get();
    return 0;
}</pre>
<p>　　在map中是不允许一个键对应多个值的，在multimap中，不支持operator[],也就是说不支持map中允许的下标操作。</p>
<p><strong>算法（algorithm）：#inlcude &lt;algorithm&gt;</strong><br />
　　STL中算法的大部分都不作为某些特定容器类的成员函数，他们是泛型的，每个算法都有处理大量不同容器类中数据的使用。值得注意的是，STL中的算法大多有多种版本，用户可以依照具体的情况选择合适版本。中在STL的泛型算法中有4类基本的算法：</p>
<ul>
<li>变序型队列算法，可以改变容器内的数据；</li>
<li>非变序型队列算法，处理容器内的数据而不改变他们；</li>
<li>排序值算法，包涵对容器中的值进行排序和合并的算法，还有二叉搜索算法 $$通用数值算法；</li>
</ul>
<p>注：STL的算法并不只是针对STL容器，对一般容器也是适用的。</p>
<p><strong>变序型队列算法（mutating algorithms）：</strong><br />
　　又叫可修改的序列算法。这类算法有复制（copy）算法、交换（swap）算法、替代（replace）算法、删除（remove）算法，移动（transfer）算法、翻转（reverse）算法等等。这些算法可以改变容器中的数据（数据值和值在容器中的位置）。下面介绍2个比较常用的算法reverse()和copy()。</p>
<pre>//stl_cpp_14.cpp
#include &lt;iostream&gt;
#include &lt;algorithm&gt;
#include &lt;iterator&gt;//下面用到了输出迭代器ostream_iterator
using namespace std;
int main(void)
{
    int arr[6]={1,12,3,2,1215,90};
	 int arr1[7];
    int arr2[6]={2,5,6,9,0,-56};
    copy(arr,(arr+6),arr1);//将数组aar复制到arr1
    cout&lt;&lt;"arr[6] copy to arr1[7],now arr1: "&lt;&lt;endl;
	for(int i=0;i&lt;7;i++)
		cout&lt;&lt;" "&lt;&lt;arr1[i];
   reverse(arr,arr+6);//将排好序的arr翻转
   cout&lt;&lt;''\n''&lt;&lt;"arr reversed ,now arr:"&lt;&lt;endl;
	copy(arr,arr+6,ostream_iterator&lt;int&gt;(cout, " "));//复制到输出迭代器
swap_ranges(arr,arr+6,arr2);//交换arr和arr2序列
	cout&lt;&lt;''\n''&lt;&lt;"arr swaped to arr2,now arr:"&lt;&lt;endl;
	copy(arr,arr+6,ostream_iterator&lt;int&gt;(cout, " "));
	cout&lt;&lt;''\n''&lt;&lt;"arr2:"&lt;&lt;endl;
	copy(arr2,arr2+6,ostream_iterator&lt;int&gt;(cout, " "));
	cin.get();
   return 0;
}</pre>
<p>revese()的功能是将一个容器内的数据顺序翻转过来，它的原型是：</p>
<pre>  template&lt;class Bidirectional &gt;
    void reverse(Bidirectional first, Bidirectional last);</pre>
<p>将first和last之间的元素翻转过来，上例中你也可以只将arr中的一部分进行翻转：</p>
<pre>   reverse(arr+3,arr+6);这也是有效的。First和last需要指定一个操作区间。</pre>
<p>Copy()是要将一个容器内的数据复制到另一个容器内，它的原型是：</p>
<pre>   Template&lt;class InputIterator ，class OutputIterator&gt;
  OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result);</pre>
<p>　　它把[first,last－1]内的队列成员复制到区间[result,result+(last-first)-1]中。泛型交换算法：Swap()操作的是单值交换，它的原型是：</p>
<pre>template&lt;class T&gt;
void swap(T&amp; a,T&amp; b);</pre>
<p>swap_ranges()操作的是两个相等大小区间中的值，它的原型是：</p>
<pre>  template&lt;class ForwardIterator1, class ForwardIterator2&gt;
  ForwardIterator2 swap_ranges(ForwardIterator1 first1,ForwardIterator1 last1, \
ForwardIterator1 first2);</pre>
<p>　　交换区间[first1,last1-1]和[first2, first2+(last1-first1)-1]之间的值，并假设这两个区间是不重叠的。</p>
<p><strong>非变序型队列算法（Non-mutating algorithm）</strong>：<br />
　　又叫不可修改的序列算法。这一类算法操作不影响其操作的容器的内容，包括搜索队列成员算法，等价性检查算法，计算队列成员个数的算法。我将用下面的例子介绍其中的find(),search(),count()：</p>
<pre>//stl_cpp_15.cpp
#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
using namespace std;
int main(void)
{
    int a[10]={12,31,5,2,23,121,0,89,34,66};
    vector&lt;int&gt; v1(a,a+10);
    vector&lt;int&gt;::iterator result1,result2;//result1和result2是随机访问迭代器
    result1=find(v1.begin(),v1.end(),2);
    //在v1中找到2，result1指向v1中的2
    result2=find(v1.begin(),v1.end(),8);
    //在v1中没有找到8，result2指向的是v1.end()
    cout&lt;&lt;result1-v1.begin()&lt;&lt;endl; //3－0＝3或4－1＝3，屏幕结果是3
    cout&lt;&lt;result2-v1.end()&lt;&lt;endl;
    int b[9]={5,2,23,54,5,5,5,2,2};
    vector&lt;int&gt; v2(a+2,a+8);
    vector&lt;int&gt; v3(b,b+4);
    result1=search(v1.begin(),v1.end(),v2.begin(),v2.end());
    cout&lt;&lt;*result1&lt;&lt;endl;
    //在v1中找到了序列v2，result1指向v2在v1中开始的位置
     result1=search(v1.begin(),v1.end(),v3.begin(),v3.end());
     cout&lt;&lt;*(result1-1)&lt;&lt;endl;
    //在v1中没有找到序列v3，result指向v1.end(),屏幕打印出v1的最后一个元素66
     vector&lt;int&gt; v4(b,b+9);
     int i=count(v4.begin(),v4.end(),5);
     int j=count(v4.begin(),v4.end(),2);
     cout&lt;&lt;"there are "&lt;&lt;i&lt;&lt;" members in v4 equel to 5"&lt;&lt;endl;
     cout&lt;&lt;"there are "&lt;&lt;j&lt;&lt;" members in v4 equel to 2"&lt;&lt;endl;
     //计算v4中有多少个成员等于 5,2
    cin.get();
    return 0;
}</pre>
<p>find()的原型是：</p>
<pre>template&lt;class InputIterator，class EqualityComparable&gt;
InputIterator find(InputIterator first, InputIterator last,\
                   const EqualityComparable&amp; value);</pre>
<p>　　其功能是在序列[first,last-1]中查找value值，如果找到，就返回一个指向value在序列中第一次出现的迭代，如果没有找到，就返回一个指向last的迭代（last并不属于序列）。 search()的原型是：</p>
<pre>template &lt;class ForwardIterator1, class ForwardIterator2&gt;
ForwardIterator1 search(ForwardIterator1 first1, ForwardIterator1 last1,\
                        ForwardIterator2 first2, ForwardIterator2 last2);</pre>
<p>　　其功能是在源序列[first1,last1-1]查找目标序列[first2，last2-1]如果查找成功，就返回一个指向源序列中目标序列出现的首位置的迭代。查找失败则返回一个指向last的迭代。 Count()的原型是：</p>
<pre>template &lt;class InputIterator, class EqualityComparable&gt;
iterator_traits&lt;InputIterator&gt;::difference_type count(InputIterator first,\
InputIterator last, const EqualityComparable&amp; value);</pre>
<p>　　其功能是在序列[first,last-1]中查找出等于value的成员，返回等于value得成员的个数。</p>
<p><strong>排序算法（sort algorithm）</strong>：<br />
　　这一类算法很多，功能强大同时也相对复杂一些。这些算法依赖的是关系运算。在这里我只介绍其中比较简单的几种排序算法：sort(),merge(),includes()</p>
<pre>//stl_cpp_16.cpp
  #include &lt;iostream&gt;
#include &lt;algorithm&gt;
using namespace std;
int main(void)
{
    int a[10]={12,0,5,3,6,8,9,34,32,18};
    int b[5]={5,3,6,8,9};
    int d[15];
    sort(a,a+10);
    for(int i=0;i&lt;10;i++)
      cout&lt;&lt;" "&lt;&lt;a[i];
    sort(b,b+5);
    if(includes(a,a+10,b,b+5))
       cout&lt;&lt;''\n''&lt;&lt;"sorted b members are included in a."&lt;&lt;endl;
    else
       cout&lt;&lt;"sorted a dosn`t contain sorted b!";
     merge(a,a+10,b,b+5,d);
    for(int j=0;j&lt;15;j++)
       cout&lt;&lt;" "&lt;&lt;d[j];
    cin.get();
    return 0;
}</pre>
<p>sort()的原型是：</p>
<pre>template &lt;class RandomAccessIterator&gt;
void sort(RandomAccessIterator first, RandomAccessIterator last);</pre>
<p>　　功能是对[first,last-1]区间内的元素进行排序操作。与之类似的操作还有：partial_sort(), stable_sort()，partial_sort_copy()等等。 merge()的原型是：</p>
<pre>template &lt;class InputIterator1, class InputIterator2, class OutputIterator&gt;
OutputIterator merge(InputIterator1 first1, InputIterator1 last1,\
               InputIterator2 first2, InputIterator2 last2,OutputIterator result);</pre>
<p>　　将有序区间[first1,last1-1]和[first2,last2-1]合并到[result, result + (last1 - first1) + (last2 - first2)-1]区间内。</p>
<p>Includes()的原型是：</p>
<pre>template &lt;class InputIterator1, class InputIterator2&gt;
bool includes(InputIterator1 first1, InputIterator1 last1,\
                  InputIterator2 first2, InputIterator2 last2);</pre>
<p>　　其功能是检查有序区间[first2,last2-1]内元素是否都在[first1,last1-1]区间内，返回一个bool值。</p>
<p><strong>通用数值算法（generalized numeric algorithms）</strong><br />
　　这一类算法还不多，涉及到专业领域中有用的算术操作，独立包涵于头文件&lt;numeric&gt;中（HP版本的STL中是&lt;algo.h&gt;）。这里不作介绍。<br />
　　STL中的算法大都有多种版本，常见的版本有以下4中：</p>
<ul>
<li>默认版本，假设给出了特定操作符；</li>
<li>一般版本，使用了成员提供的操作符；</li>
<li>复制版本，对原队列的副本进行操作，常带有 _copy 后缀；</li>
<li>谓词版本，只应用于满足给定谓词的队列成员，常带有 _if 后缀；</li>
</ul>
<p>　　以上我们学习了STL容器和算法的概念，以及一些简单的STL容器和算法。在使用算法处理容器内的数据时，需要从一个数据成员移向另一个数据成员，迭代器恰好实现了这一功能。下面我们来学习STL迭代器 。</p>
<p><strong>迭代器（itertor）：#include&lt;iterator&gt;</strong><br />
　　迭代器实际上是一种泛化指针，如果一个迭代器指向了容器中的某一成员，那么迭代器将可以通过自增自减来遍历容器中的所有成员。迭代器是联系容器和算法的媒介，是算法操作容器的接口。在运用算法操作容器的时候，我们常常在不知不觉中已经使用了迭代器。<br />
STL中定义了6种迭代器：</p>
<ul>
<li>输入迭代器，在容器的连续区间内向前移动，可以读取容器内任意值；</li>
<li>输出迭代器，把值写进它所指向的队列成员中；</li>
<li>前向迭代器，读取队列中的值，并可以向前移动到下一位置（++p,p++）；</li>
<li>双向迭代器，读取队列中的值，并可以向前向后遍历容器；</li>
<li>随机访问迭代器, vector&lt;T&gt;::iterator，list&lt;T&gt;::iterator等都是这种迭代器 ；</li>
<li>流迭代器，可以直接输出、输入流中的值；</li>
</ul>
<p>　　实际上，在前面的例子中，我们不停的在用迭代器。下面我们用几个例子来帮助理解这些迭代器的用法。<br />
下面的例子用到了输入输出迭代器：</p>
<pre>// stl_cpp_17.cpp
#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;iterator&gt;
#include &lt;vector&gt;
#include &lt;string&gt;
using namespace std;
int main(void)
{
    vector&lt;string&gt; v1;
    ifstream file("Text1.txt");
    if(file.fail())
    {
      cout&lt;&lt;"open file Text1.txt failed"&lt;&lt;endl;
      return 1;
    }
  copy(istream_iterator&lt;string&gt;(file),istream_iterator&lt;string&gt;(),inserter(v1,\
v1.begin()));
    copy(v1.begin(),v1.end(),ostream_iterator&lt;string&gt;(cout," "));
    cout&lt;&lt;endl;
    cin.get();
    return 0;
}</pre>
<p>　　这里用到了输入迭代器istream_iterator，输出迭代器ostream_iterator。程序完成了将一个文件输出到屏幕的功能，先将文件读入，然后通过输入迭代器把文件内容复制到类型为字符串的向量容器内，最后由输出迭代器输出。Inserter是一个输入迭代器的一个函数(迭代器适配器)，它的使用方法是：</p>
<pre>inserter (container ,pos);</pre>
<p>　　congtainer是将要用来存入数据的容器，pos是容器存入数据的开始位置。上例中，是把文件内容存入（copy()）到向量v1中。</p>
<p>现在我们已经对STL的三大基本组件有了一个大概的了解，下面让我们一起来看看STL的其他标准组件。</p>
<p><strong>函数对象（functor或者funtion objects）：#include &lt;function&gt;</strong><br />
　　函数对象又称之为仿函数。函数对象将函数封装在一个对象中，使得它可作为参数传递给合适的STL算法，从而使算法的功能得以扩展。可以把它当作函数来使用。用户也可以定义自己的函数对象。下面让我们来定义一个自己的函数对象。</p>
<pre>// stl_cpp_18.cpp
#include &lt;iostream&gt;
using namespace std;
struct int_max{
    int operator()(int x,int y){return x&gt;y?x:y; }
    };//operator() 重载了"（）"， (int x,int y)是参数列表
int main(void)
{
    cout&lt;&lt;int_max()(3,4)&lt;&lt;endl;
    cin.get();
    return 0;
}</pre>
<p>　　这里的int_max（）就是一个函数对象，struct关键字也可以用class来代替，只不过struct默认情况下是公有访问权限，而class定义的是默认私有访问权限。下面我们来定义一个STL风格的函数对象：</p>
<pre>// stl_cpp_19.cpp
#include &lt;iostream&gt;
#include &lt;vector&gt;
using namespace std;
struct adder : public unary_function&lt;double, void&gt;
    {
      adder() : sum(0) {}
      double sum;
      void operator()(double x) { sum += x; }
    };
int main(void)
{
    double a[5]={0.5644,1.1,6.6,8.8,9.9};
    vector&lt;double&gt; V(a,a+5);
    adder result = for_each(V.begin(), V.end(), adder());
    cout &lt;&lt; "The sum is " &lt;&lt; result.sum &lt;&lt; endl;
    cin.get();
    return 0;
}</pre>
<p>　　在这里，我们定义了一个函数对象adder()，这也是一个类，它的基类是unary_function函数对象。unary_function是一个空基类，不包涵任何操作或变量。只是一种格式说明，它有两个参数，第一个参数是函数对象的使用数据类型，第二个参数是它的返回类型。基于它所定义的函数对象是一元函数对象。（注：用关键字struct或者class定义的类型实际上都是&#8221;类&#8221;）<br />
　　STL内定义了各种函数对象，否定器、约束器、一元谓词、二元谓词都是常用的函数对象。函数对象对于编程来说很重要，因为他如同对象类型的抽象一样作用于操作。</p>
<p><strong>适配器（adapter）</strong>：<br />
　　适配器是用来修改其他组件接口的STL组件，是带有一个参数的类模板（这个参数是操作的值的数据类型）。STL定义了3种形式的适配器：容器适配器，迭代器适配器，函数适配器。</p>
<ul>
<li>容器适配器：包括栈（stack）、队列(queue)、优先(priority_queue)。使用容器适配器，stack就可以被实现为基本容器类型（vector,dequeue,list）的适配。可以把stack看作是某种特殊的vctor,deque或者list容器，只是其操作仍然受到stack本身属性的限制。queue和priority_queue与之类似。容器适配器的接口更为简单，只是受限比一般容器要多；</li>
<li>迭代器适配器：修改为某些基本容器定义的迭代器的接口的一种STL组件。反向迭代器和插入迭代器都属于迭代器适配器，迭代器适配器扩展了迭代器的功能；</li>
<li>函数适配器：通过转换或者修改其他函数对象使其功能得到扩展。这一类适配器有否定器（相当于&#8221;非&#8221;操作）、帮定器、函数指针适配器。</li>
</ul>
<p><strong>结束语<br />
</strong>　　如果你理解了算法、迭代器、容器，那么你几乎就了解了 STL。关于STL的其他方面，新手都是不常用的，可以暂时以理解STL的组成的编程思想为主。这篇文章里用到了19个cpp代码，每个代码都在Windows 2000+ Dev-C++ 4.9.9.0和windows 2000＋VC环境下通过编译运行。读者可以通过copy/paste到任何一款C++编译器中运行。无论你想不想学STL，先运行一下STL代码吧。编程快乐，好好学习，天天向上。</p>
<p>我是新手，欢迎高手批评，欢迎STL学习者交流： Email：taohanjunjiang@yahoo.com.cn QQ:370679790</p>
<hr />【附录】</p>
<p><strong>附录一：Dev-C++ 和VC</strong><br />
　　在Dev-C++ 4.9.9.0 +windows 2000下，不允许出现,void main(){} 而必须为 int main() {return 0;} ,或者 int main(){},奇怪的是VC对 int main(){} 的写法会提出警告，而必须为 int main() {return 0;}。建议使用标准格式 int main(void) {……return 0;}<br />
<span lang="en">　　</span>Dev-C++而且不支持头文件方式，#include &lt;iostream.h&gt;是不对的，而必须为#include &lt;iostream&gt; using namespace std; VC两种格式都支持。建议使用#include &lt;iostream&gt; using namespace std;<br />
　　如果用纯C/C++编程，建议使用比较简单的C++编译器，甚至是命令行下的编译方式。功能强大的IDE甚至会误导编程者。还要花费不少的时间来学习使用IDE开发环境。Dev比VC要简单一些，只是不适合做大型工程，而且编译时间比VC稍慢一点，纯C++编程建议使用。他有插入时间和头文件注释模板的功能，作者可以方便地插入编程、程序更改时间，方便地填写程序说明信息。<br />
　　在Dev下可以导入VC工程，而且保持原来的VC工程文件，但是Dev对VC的支持还不够，在编译的时候会遇到错误。<br />
　　在编写dos程序的时候，建议在程序的末尾return之前加上：getchar();cin.get();之类的代码，以方便看运行结果。</p>
<p><strong>附录二：中小型程序段的编辑工具</strong><br />
　　在编辑程序过程中并不一定非要在特定的IDE环境中，在有些专业化的文本编辑工具中编辑代码会更有利于代码的修改和编辑。这里介绍几种更能比较强大的文本编辑器。</p>
<ul>
<li>UltraEdit-32</li>
<li>EditPlus</li>
<li>SourceInsight</li>
<li>Vim</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.ray77.com/into-the-stl.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A* Pathfinding for Beginners</title>
		<link>http://www.ray77.com/a-pathfinding-for-beginners.html</link>
		<comments>http://www.ray77.com/a-pathfinding-for-beginners.html#comments</comments>
		<pubDate>Sun, 09 Nov 2008 13:36:55 +0000</pubDate>
		<dc:creator>Rock</dc:creator>
				<category><![CDATA[Develop]]></category>
		<category><![CDATA[A星]]></category>
		<category><![CDATA[人工智能]]></category>
		<category><![CDATA[启发]]></category>
		<category><![CDATA[寻路]]></category>
		<category><![CDATA[搜索]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[迷宫]]></category>

		<guid isPermaLink="false">http://www.ray77.com/?p=223</guid>
		<description><![CDATA[　　虽然掌握了A*（读作A-star）算法就认为它很容易，对于初学者来说，它却是复杂的。网上有很多解释A*的文章，不过大多数是写给理解了基础知识的人。本文是给初学者的。
　　本文并不想成为关于这个主题的权威论文。实际上它讨论了基础知识并为你做一些准备，以便进一步阅读其他资料和理解它们讨论的内容。本文的后面列出了几个最好的文章，在进阶阅读中。
　　最后，本文不是编程规范的。你应该能够改写这里的东西到任何计算机语言上。如你所期望的，同时，我包含了一个示例程序的链接，在本文后面结束的地方。这个程序包有两个版本：一个是C++，另一个用Blitz Basic语言编写。如果你只是想看看A*的行为，里面也含有可执行exe文件。
　　但我们要超越自己。让我们从头开始 &#8230;
　　介绍：搜索区域
　　我们假设某人想从A点到达B点，一堵墙把它们分开了。如下图所示，绿色是开始点A，红色是结束点B，而蓝色填充的方块是中间的墙。

[图 1][Figure 1]
　　你应该注意的第一件事是，我们把搜索区域分割成了方块的格子。简化搜索区域，如你目前完成的那样，这是寻路的第一步。这个特殊方法把搜索区域简化成了一个二维数组。数组的每一个项目代表了格子里的一个方块，它的状态记录成可行走和不可行走。通过计算出从A到达B应该走哪些方块，就找到了路径。一旦路径找到，我们的人从一个方块的中心移动到下一个方块的中心，直到抵达目标。
　　这些中心点称作“节点”。当你在其它地方阅读关于寻路时，你将经常发现人们讨论节点。为什么不直接把它们认为是方块呢？因为有可能你要把你的寻路区域以非方块的东西来分割。它们可能是矩形，六角形，或任何形状，真的。而节点可以放到形状内的任何位置。在中心，或者沿着边缘，或其它地方。然而我们使用这个系统，因为它最简单。
　　开始搜索
一旦我们把搜索区域简化成了可以管理的大量节点，就象我们上面所做的那样采用格子的布局，下一步就是引导一个搜索来找出最短路径。在A*寻路的做法，我们从开始点A做起，检查它周围的方块，并且向外普通的搜索，直到找到目标。
　　我们这样开始搜索：
　　从开始点A起，添加它到待考虑的方块的“开放列表”。开放列表有点象购物列表。此时只有一个项目在里面，但很快我们会得到更多。它包含了你可能取用的沿途的方块，也可能不用它。基本上，这是需要检查的方块的列表。
　　观察开始点邻近的所有可到达或可行走的方块，忽略有墙，水或其他非法地形的方块。也把它们添加到开放列表。对每一个方块，保存A 点作为它们的“父亲”。这个父亲方块在跟踪路径时非常重要。后面会更多的解释。
　　把开始方块A从开放列表中取出，并放到“封闭列表”内，它是所有现在不需要再关注的方块的列表。
　　在此，你应该有了类似下图的东西。在这个图中，中间的深绿色的方块就是开始方块。它有浅蓝色的外框，表示它被添加到封闭列表了。所有的相邻方块现在都进入要检查的方块的开放列表中了，它们有浅绿的外框。每一个都有灰色的指针指回它的父亲，它就是开始方块。

[图 2][Figure 2]
　　下一步，我们从开放列表中，选出一个相邻的方块，然后多多少少重复早先的过程，下面会说到。但是我们选择哪一个呢？具有最小F值的那个。
　　路径排序
　　找到形成路径的方块的关键是下面的等式：
　　F = G + H
　　这里
　　G = 从开始 点A到格子中给定方块的移动代价，沿着到达该方块而生成的那个路径。
　　H = 从格子中给定 的方块到最终目标 B点的评估移动代价。这种方式通常称作试探法，有点让人混乱。因为这是一个猜测，所以得到这个称谓。在找到路径之前，我们真的不知道实际的距离，因为途中有各种东西（墙，水，等等）。在本教程里给出了一种计算H的方法，但在网上你能找到很多其他的文章。
　　我们需要的路径是这样生成的：反复的遍历开放列表，选择具有最小F值的方块。这个过程在本文稍后会详细描述。先让我们看看如何计算前面提到的等式。
　　如上所述，G是经由到达它的路径，从开始点到给定方块的移动代价。在本例中，我们为每个水平/垂直的移动指定代价为10，而斜角的移动代价为14。我们使用这些值，因为斜角移动的实际距离是2的平方根（别害怕），或者大概1.414倍的水平/垂直的移动代价。出于简化的目的使用了10和14。比例大致是正确的，而我们却避免了方根和小数的计算。倒不是我们没有能力做或者不喜欢数学。使用这些数字也能让计算更快一些。以后你就会发现，如果不使用这些技巧，寻路的计算非常慢。
　　既然我们沿着到达给定方块的路径来计算G的值，找出那个方块的G值的方法就是找到其父亲的G值，再加上10或者14而得，这依赖于他处于其父亲的斜角或者直角（非斜角）而定。这在本例后面会更加清晰，随着我们从开始点离开而得到更多的方块。
　　H能通过多种方法估算。我们这里用到的方法叫做Manhattan方法，计算从当前方块经过水平/垂直移动而到达目标方块的方块总数。然后将总数乘以 10。这种方法之所以叫做Manhattan方法，因为他很象计算从一个地点到达另一个地点的城市街区数量计算，此时你不能斜向的穿越街区。重要的是，当计算H的时候，要忽略任何路径中的障碍。这是一个对剩余距离的 估算值，而不是实际值，这就是试探法的称谓由来。想知道更多？关于试探法的更多说明在这里。
　　G和H相加就算出了F。第一步搜索的结果见下图的描述。F，G，和H值都写入了每个方块。如开始方块相邻右边的方块，F显示在左上方，G显示在左下方，而 H显示在右下方。
 
[图 3][Figure 3]
　　好，让我们来观察某些方块。在有字母的方块中，G = 10。这是由于在水平方向上从开始点（到那里）只有一个方块（的距离）。开始点相邻上方，下方和左边的方块都具有同样的G值：10。斜角的方块G值为 14。
　　H的计算通过估算Manhattan距离而得，即：水平/垂直移动，忽略途中的障碍，到达红色的目标方块的距离。用这种方法，开始点相邻右边的方块和红色方块相距3个方块，那么H值就是30。其上的方块距离为4（记住，只能水平或者垂直移动），H就是40。你也许可以看看其他方块的H值是如何算出的。
　　每个方块的F值，再说一下，不过就是G和H相加。
　　持续的搜索
　　为了继续搜索，我们简单的选择开放列表里具有最小F值的方块。然后对选定的方块做如下操作：
　　将他从开放列表取出，并加入封闭列表。
　　测试所有的相邻方块。忽略封闭列表内的和不可行走的（墙，水及其它非法地形）方块，如果方块不在开放列表中，则添加进去。将选定方块作为这些新加入方块的父亲。
　　如果一个相邻方块已经存在于开放列表，检查到达那个方块的路径是否更优。换句话说，检查经由当前方块到达那里是否具有更小的G 值。如果没有，不做任何事。
　　相反，如果新路径的G值更小，把这个相邻方块的父亲改为当前选定的方块（在上图中，修改其指针方向指向选定方块）。最后，重新计算那个方块的F和G值。如果这样还是很迷惑的话，后面还会有图解说明。
　　好了，让我们看看它是怎样工作的。在初始的9个方块中，当开始方块被纳入封闭列表后，我们的开放列表就只有8个方块了。在这些块中，具有最小F值的是开始方块相邻右边的那个，其F值为40。所以我们选定这个块作为下一个方块。在随后的图例中，它以高亮的蓝色表示。

[图 4][Figure 4]
　　首先，我们把它从开放列表取出，并加入到封闭列表（这就是它现在是高亮的蓝色的原因）。然后我们检查相邻的方块。然而，这个方块相邻右边的是代表墙的方块，所以忽略它们。其相邻左边是开始方块。它处于封闭列表内，所以也忽略它
　　其它4个已经在开放列表中了，所以我们需要检查经由当前方块到达他们是否是更优的路径，使用G值为参考点。我们来看看这个选定方块上面右边的那个方块。它的当前G值是14。如果我们经由当前方块到达那里，G值将是20（10，到达当前方块的G值，再加上10垂直移动到它上面的方块）。20 &#62; 14，所以这不是一个好的路径。看看图解能更好的理解这些。从开始方块斜向移动到那个方块更直接，而不是水平移动一个方块，再垂直移动一个方块。
　　当我们对已经存在于开放列表的所有4个相邻方块都重复这个过程，我们发现经由当前方块没有更佳的路径，所以什么也不用改变。现在看看所有的相邻方块，我们已经处理完毕，并准备移动到下一个方块。
　　现在，我们再遍历开放列表，它只有7个方块了，选择具有最小F值的那个。有趣的是，此时有两个方块都有值54。那么我们选择哪个？实际上这不算什么。为了速度的目的，选择你最后加入到开放列表的那个方块更快。当你更接近目标的时候，它倾向于后发现的方块。但这真的没什么关系。（不同的处理造成了两个版本的A*可能找到不同的等长路径。）
　　我们选择下面的那个，位于开始方块的右边，如下图所示。

[图 5][Figure 5]
　　这一次，当检查相邻的方块时，我们相邻右边的是一个墙方块，所以忽略它。对那个方块上面的块同样忽略。我们也忽略墙下面的方块。为什么？因为你不把临近墙的角切开就无法直接到达那个方块。实际上你需要先向下走，然后越过那个方块，在这个过程中都是围绕角在移动。（说明：切开角的规则是可选的。它的使用依赖于你的节点如何放置。）
　　这样就剩下5个方块了。当前方块下的两个方块不在开放列表中，所以要添加他们，并把当前方块作为它们的父亲。在另外三个方块中，有两个已经在封闭列表中了（开始方块，和当前方块上面的那个，它们都用高亮的蓝色在图中标出来了），所以忽略它们。最后一个方块，当前方块相邻左边的那个，检查经由当前方块到达那里是否得到更小的G值。没有。所以处理完毕，并准备检查开放列表中的下一个方块。
　　我们重复这个过程，直到把目标点添加到开放列表，此时的情形如下图所示。

[图 6][Figure 6]
　　注意开始方块向下的第二个方块，在前面的描述中其父亲已经发生改变。开始它的G值为28，指向其右上角的方块。现在它的值是20，指向其上方的方块。这是在搜索方法中某处发生的吗？在那里G值被检查，而且使用新的路径后，它得到了更小的值。所以它的父亲切换了，G和F也重新计算。而这个改变在本例中不见得非常重要，还有足够多的可能位置，在决定最佳路径的时候，持续的检查会产生各种差别。
　　那么我们怎样决定实际的路径呢？简单，从红色目标方块开始，向后移动到它的父亲，跟从箭头的指示。最终你会回到开始方块，这就是路径！它应该如下图所示。从方块A移动到目标方块B就是从每一个方块（节点）的中心移动到路径上的下一个方块的中心的简单过程，直到到达目标。简单！

[图 7][Figure 7]
 
　　A*方法汇总
　　好了，现在你已经读完了解释，让我们在这里一步一步的列出所有操作：
　　添加开始方块到开放列表。
　　重复下面的过程：
　　a) 查找开放列表中具有最小F值的方块。我们把它作为当前方块。
　　b) 把它放入封闭列表。
　　c) 对当前方块的8个相邻方块的每一个？
　　如果它不可行走，或者存在于封闭列表，忽略它。否则执行下面操作。
　　如果它不在开放列表，将它添加进去。以当前方块作为其父亲。记录这个方块的F，G和H值。
　　如果它已经在开放列表了，检查到达那个方块的路径是否更优，以G值为测量值。更低的G值意味着更好的路径。如果找到，这个方块的父亲改为当前方块，并重新计算这个方块的G和F值。如果你保持开放列表按F值排序的话，可能需要重新排序来解决这个变化。
　　d) 结束循环，当你
　　将目标方块加入到开放列表，此时路径已经找到，或者
　　没有找到目标方块，并且开放列表是空的。此时，没有路径。
　　保存路径。从目标方块往回走，从每个方块走到它的父亲方块，直到抵达开始方块。那就是路径。
　　一点感慨
　　原谅我离题了，但是值得指出的是，当你在网上和分类论坛阅读很多讨论A*寻路的时候，你有时候会发现某些人所指的A*代码实际上并不是真正的A*算法。对于应用中的A*方法，你需要包含上面讨论到的元素  &#8212; 特别是开放列表和封闭列表，以及使用F，G，和H的路径排序。有很多其他的寻路算法，但是其它的方法不是A* &#8212; 通常认为它是最好的算法。Bryan Stout在本文后面的一个参考文档里讨论了这些算法，有正面的，也有反面的。有时候在特定的环境下，选择其他的算法会更好，但是你应该理解你做了什么。好了，说够了。回到文章中来。
关于实现的提示
　　现在你已经理解了基本的方法，这里是当你写自己的程序时要考虑的更多东西。下面的某些材料引用了我用C++和Blitz Basic写的程序，但是这些要点对其他语言也是同样有效的。
　　1.维护开放列表 : 实际上这是A*寻路函数最耗费时间的元素之一。每次访问开放列表时，你都需要找到具有最小F值的方块。有很多种方法可以做到这点。你可以保存所需的路径项目，每次当你需要找到最小F值的方块时，简单的遍历整个列表。这很简单，不过路径长的时候非常慢。这个方法可以改进，通过维护一个排序的列表，每次需要最小F值的方块时，简单的抓出第一个项目就可以了。当我写自己的程序时，这是我用到的第一个方法。
　　这个方法对小的地图相当的好，但不是最快的方案。真正需要速度的认真的A*程序员使用叫做二元堆[binary ...]]></description>
			<content:encoded><![CDATA[<p>　　虽然掌握了A*（读作A-star）算法就认为它很容易，对于初学者来说，它却是复杂的。网上有很多解释A*的文章，不过大多数是写给理解了基础知识的人。本文是给初学者的。</p>
<p>　　本文并不想成为关于这个主题的权威论文。实际上它讨论了基础知识并为你做一些准备，以便进一步阅读其他资料和理解它们讨论的内容。本文的后面列出了几个最好的文章，在进阶阅读中。</p>
<p>　　最后，本文不是编程规范的。你应该能够改写这里的东西到任何计算机语言上。如你所期望的，同时，我包含了一个示例程序的链接，在本文后面结束的地方。这个程序包有两个版本：一个是C++，另一个用Blitz Basic语言编写。如果你只是想看看A*的行为，里面也含有可执行exe文件。</p>
<p>　　但我们要超越自己。让我们从头开始 &#8230;</p>
<p>　　<strong><span style="color: #ff6600;">介绍：搜索区域</span></strong></p>
<p>　　我们假设某人想从A点到达B点，一堵墙把它们分开了。如下图所示，绿色是开始点A，红色是结束点B，而蓝色填充的方块是中间的墙。</p>
<p><img class="aligncenter size-full wp-image-224" title="A* Pathfinding for Beginners" src="http://www.ray77.com/wp-content/uploads/2008/11/image001.jpg" alt="" width="362" height="256" /></p>
<p><span id="more-223"></span>[图 1][Figure 1]</p>
<p>　　你应该注意的第一件事是，我们把搜索区域分割成了方块的格子。简化搜索区域，如你目前完成的那样，这是寻路的第一步。这个特殊方法把搜索区域简化成了一个二维数组。数组的每一个项目代表了格子里的一个方块，它的状态记录成可行走和不可行走。通过计算出从A到达B应该走哪些方块，就找到了路径。一旦路径找到，我们的人从一个方块的中心移动到下一个方块的中心，直到抵达目标。</p>
<p>　　这些中心点称作“节点”。当你在其它地方阅读关于寻路时，你将经常发现人们讨论节点。为什么不直接把它们认为是方块呢？因为有可能你要把你的寻路区域以非方块的东西来分割。它们可能是矩形，六角形，或任何形状，真的。而节点可以放到形状内的任何位置。在中心，或者沿着边缘，或其它地方。然而我们使用这个系统，因为它最简单。</p>
<p>　　<strong><span style="color: #ff6600;">开始搜索</span></strong></p>
<p>一旦我们把搜索区域简化成了可以管理的大量节点，就象我们上面所做的那样采用格子的布局，下一步就是引导一个搜索来找出最短路径。在A*寻路的做法，我们从开始点A做起，检查它周围的方块，并且向外普通的搜索，直到找到目标。</p>
<p>　　我们这样开始搜索：</p>
<p>　　从开始点A起，添加它到待考虑的方块的“开放列表”。开放列表有点象购物列表。此时只有一个项目在里面，但很快我们会得到更多。它包含了你可能取用的沿途的方块，也可能不用它。基本上，这是需要检查的方块的列表。</p>
<p>　　观察开始点邻近的所有可到达或可行走的方块，忽略有墙，水或其他非法地形的方块。也把它们添加到开放列表。对每一个方块，保存A 点作为它们的“父亲”。这个父亲方块在跟踪路径时非常重要。后面会更多的解释。</p>
<p>　　把开始方块A从开放列表中取出，并放到“封闭列表”内，它是所有现在不需要再关注的方块的列表。</p>
<p>　　在此，你应该有了类似下图的东西。在这个图中，中间的深绿色的方块就是开始方块。它有浅蓝色的外框，表示它被添加到封闭列表了。所有的相邻方块现在都进入要检查的方块的开放列表中了，它们有浅绿的外框。每一个都有灰色的指针指回它的父亲，它就是开始方块。</p>
<p><img class="aligncenter size-full wp-image-225" title="A* Pathfinding for Beginners" src="http://www.ray77.com/wp-content/uploads/2008/11/image002.jpg" alt="" width="151" height="150" /></p>
<p>[图 2][Figure 2]</p>
<p>　　下一步，我们从开放列表中，选出一个相邻的方块，然后多多少少重复早先的过程，下面会说到。但是我们选择哪一个呢？具有最小F值的那个。</p>
<p>　　<strong><span style="color: #ff6600;">路径排序</span></strong></p>
<p>　　找到形成路径的方块的关键是下面的等式：</p>
<p>　　F = G + H</p>
<p>　　这里</p>
<p>　　G = 从开始 点A到格子中给定方块的移动代价，沿着到达该方块而生成的那个路径。</p>
<p>　　H = 从格子中给定 的方块到最终目标 B点的评估移动代价。这种方式通常称作试探法，有点让人混乱。因为这是一个猜测，所以得到这个称谓。在找到路径之前，我们真的不知道实际的距离，因为途中有各种东西（墙，水，等等）。在本教程里给出了一种计算H的方法，但在网上你能找到很多其他的文章。</p>
<p>　　我们需要的路径是这样生成的：反复的遍历开放列表，选择具有最小F值的方块。这个过程在本文稍后会详细描述。先让我们看看如何计算前面提到的等式。</p>
<p>　　如上所述，G是经由到达它的路径，从开始点到给定方块的移动代价。在本例中，我们为每个水平/垂直的移动指定代价为10，而斜角的移动代价为14。我们使用这些值，因为斜角移动的实际距离是2的平方根（别害怕），或者大概1.414倍的水平/垂直的移动代价。出于简化的目的使用了10和14。比例大致是正确的，而我们却避免了方根和小数的计算。倒不是我们没有能力做或者不喜欢数学。使用这些数字也能让计算更快一些。以后你就会发现，如果不使用这些技巧，寻路的计算非常慢。</p>
<p>　　既然我们沿着到达给定方块的路径来计算G的值，找出那个方块的G值的方法就是找到其父亲的G值，再加上10或者14而得，这依赖于他处于其父亲的斜角或者直角（非斜角）而定。这在本例后面会更加清晰，随着我们从开始点离开而得到更多的方块。</p>
<p>　　H能通过多种方法估算。我们这里用到的方法叫做Manhattan方法，计算从当前方块经过水平/垂直移动而到达目标方块的方块总数。然后将总数乘以 10。这种方法之所以叫做Manhattan方法，因为他很象计算从一个地点到达另一个地点的城市街区数量计算，此时你不能斜向的穿越街区。重要的是，当计算H的时候，要忽略任何路径中的障碍。这是一个对剩余距离的 估算值，而不是实际值，这就是试探法的称谓由来。想知道更多？关于试探法的更多说明在这里。</p>
<p>　　G和H相加就算出了F。第一步搜索的结果见下图的描述。F，G，和H值都写入了每个方块。如开始方块相邻右边的方块，F显示在左上方，G显示在左下方，而 H显示在右下方。</p>
<p> <img class="aligncenter size-full wp-image-226" title="A* Pathfinding for Beginners" src="http://www.ray77.com/wp-content/uploads/2008/11/image003.jpg" alt="" width="362" height="255" /></p>
<p>[图 3][Figure 3]</p>
<p>　　好，让我们来观察某些方块。在有字母的方块中，G = 10。这是由于在水平方向上从开始点（到那里）只有一个方块（的距离）。开始点相邻上方，下方和左边的方块都具有同样的G值：10。斜角的方块G值为 14。<br />
　　H的计算通过估算Manhattan距离而得，即：水平/垂直移动，忽略途中的障碍，到达红色的目标方块的距离。用这种方法，开始点相邻右边的方块和红色方块相距3个方块，那么H值就是30。其上的方块距离为4（记住，只能水平或者垂直移动），H就是40。你也许可以看看其他方块的H值是如何算出的。</p>
<p>　　每个方块的F值，再说一下，不过就是G和H相加。<br />
　　<strong><span style="color: #ff6600;">持续的搜索</span></strong></p>
<p>　　为了继续搜索，我们简单的选择开放列表里具有最小F值的方块。然后对选定的方块做如下操作：</p>
<p>　　将他从开放列表取出，并加入封闭列表。</p>
<p>　　测试所有的相邻方块。忽略封闭列表内的和不可行走的（墙，水及其它非法地形）方块，如果方块不在开放列表中，则添加进去。将选定方块作为这些新加入方块的父亲。</p>
<p>　　如果一个相邻方块已经存在于开放列表，检查到达那个方块的路径是否更优。换句话说，检查经由当前方块到达那里是否具有更小的G 值。如果没有，不做任何事。</p>
<p>　　相反，如果新路径的G值更小，把这个相邻方块的父亲改为当前选定的方块（在上图中，修改其指针方向指向选定方块）。最后，重新计算那个方块的F和G值。如果这样还是很迷惑的话，后面还会有图解说明。</p>
<p>　　好了，让我们看看它是怎样工作的。在初始的9个方块中，当开始方块被纳入封闭列表后，我们的开放列表就只有8个方块了。在这些块中，具有最小F值的是开始方块相邻右边的那个，其F值为40。所以我们选定这个块作为下一个方块。在随后的图例中，它以高亮的蓝色表示。</p>
<p><img class="aligncenter size-full wp-image-227" title="A* Pathfinding for Beginners" src="http://www.ray77.com/wp-content/uploads/2008/11/image004.jpg" alt="" width="357" height="256" /></p>
<p>[图 4][Figure 4]</p>
<p>　　首先，我们把它从开放列表取出，并加入到封闭列表（这就是它现在是高亮的蓝色的原因）。然后我们检查相邻的方块。然而，这个方块相邻右边的是代表墙的方块，所以忽略它们。其相邻左边是开始方块。它处于封闭列表内，所以也忽略它</p>
<p>　　其它4个已经在开放列表中了，所以我们需要检查经由当前方块到达他们是否是更优的路径，使用G值为参考点。我们来看看这个选定方块上面右边的那个方块。它的当前G值是14。如果我们经由当前方块到达那里，G值将是20（10，到达当前方块的G值，再加上10垂直移动到它上面的方块）。20 &gt; 14，所以这不是一个好的路径。看看图解能更好的理解这些。从开始方块斜向移动到那个方块更直接，而不是水平移动一个方块，再垂直移动一个方块。</p>
<p>　　当我们对已经存在于开放列表的所有4个相邻方块都重复这个过程，我们发现经由当前方块没有更佳的路径，所以什么也不用改变。现在看看所有的相邻方块，我们已经处理完毕，并准备移动到下一个方块。</p>
<p>　　现在，我们再遍历开放列表，它只有7个方块了，选择具有最小F值的那个。有趣的是，此时有两个方块都有值54。那么我们选择哪个？实际上这不算什么。为了速度的目的，选择你最后加入到开放列表的那个方块更快。当你更接近目标的时候，它倾向于后发现的方块。但这真的没什么关系。（不同的处理造成了两个版本的A*可能找到不同的等长路径。）</p>
<p>　　我们选择下面的那个，位于开始方块的右边，如下图所示。</p>
<p><img class="aligncenter size-full wp-image-228" title="A* Pathfinding for Beginners" src="http://www.ray77.com/wp-content/uploads/2008/11/image005.jpg" alt="" width="357" height="254" /></p>
<p>[图 5][Figure 5]</p>
<p>　　这一次，当检查相邻的方块时，我们相邻右边的是一个墙方块，所以忽略它。对那个方块上面的块同样忽略。我们也忽略墙下面的方块。为什么？因为你不把临近墙的角切开就无法直接到达那个方块。实际上你需要先向下走，然后越过那个方块，在这个过程中都是围绕角在移动。（说明：切开角的规则是可选的。它的使用依赖于你的节点如何放置。）</p>
<p>　　这样就剩下5个方块了。当前方块下的两个方块不在开放列表中，所以要添加他们，并把当前方块作为它们的父亲。在另外三个方块中，有两个已经在封闭列表中了（开始方块，和当前方块上面的那个，它们都用高亮的蓝色在图中标出来了），所以忽略它们。最后一个方块，当前方块相邻左边的那个，检查经由当前方块到达那里是否得到更小的G值。没有。所以处理完毕，并准备检查开放列表中的下一个方块。</p>
<p>　　我们重复这个过程，直到把目标点添加到开放列表，此时的情形如下图所示。</p>
<p><img class="aligncenter size-full wp-image-229" title="A* Pathfinding for Beginners" src="http://www.ray77.com/wp-content/uploads/2008/11/image006.jpg" alt="" width="404" height="307" /></p>
<p>[图 6][Figure 6]</p>
<p>　　注意开始方块向下的第二个方块，在前面的描述中其父亲已经发生改变。开始它的G值为28，指向其右上角的方块。现在它的值是20，指向其上方的方块。这是在搜索方法中某处发生的吗？在那里G值被检查，而且使用新的路径后，它得到了更小的值。所以它的父亲切换了，G和F也重新计算。而这个改变在本例中不见得非常重要，还有足够多的可能位置，在决定最佳路径的时候，持续的检查会产生各种差别。</p>
<p>　　那么我们怎样决定实际的路径呢？简单，从红色目标方块开始，向后移动到它的父亲，跟从箭头的指示。最终你会回到开始方块，这就是路径！它应该如下图所示。从方块A移动到目标方块B就是从每一个方块（节点）的中心移动到路径上的下一个方块的中心的简单过程，直到到达目标。简单！</p>
<p><img class="aligncenter size-full wp-image-230" title="A* Pathfinding for Beginners" src="http://www.ray77.com/wp-content/uploads/2008/11/image007.jpg" alt="" width="411" height="308" /></p>
<p>[图 7][Figure 7]</p>
<p> </p>
<p>　　<strong><span style="color: #800080;">A*方法汇总</span></strong></p>
<p>　　好了，现在你已经读完了解释，让我们在这里一步一步的列出所有操作：</p>
<p>　　添加开始方块到开放列表。</p>
<p>　　重复下面的过程：</p>
<p>　　a) 查找开放列表中具有最小F值的方块。我们把它作为当前方块。</p>
<p>　　b) 把它放入封闭列表。</p>
<p>　　c) 对当前方块的8个相邻方块的每一个？</p>
<p>　　如果它不可行走，或者存在于封闭列表，忽略它。否则执行下面操作。</p>
<p>　　如果它不在开放列表，将它添加进去。以当前方块作为其父亲。记录这个方块的F，G和H值。</p>
<p>　　如果它已经在开放列表了，检查到达那个方块的路径是否更优，以G值为测量值。更低的G值意味着更好的路径。如果找到，这个方块的父亲改为当前方块，并重新计算这个方块的G和F值。如果你保持开放列表按F值排序的话，可能需要重新排序来解决这个变化。</p>
<p>　　d) 结束循环，当你</p>
<p>　　将目标方块加入到开放列表，此时路径已经找到，或者</p>
<p>　　没有找到目标方块，并且开放列表是空的。此时，没有路径。</p>
<p>　　保存路径。从目标方块往回走，从每个方块走到它的父亲方块，直到抵达开始方块。那就是路径。</p>
<p>　　一点感慨</p>
<p>　　原谅我离题了，但是值得指出的是，当你在网上和分类论坛阅读很多讨论A*寻路的时候，你有时候会发现某些人所指的A*代码实际上并不是真正的A*算法。对于应用中的A*方法，你需要包含上面讨论到的元素  &#8212; 特别是开放列表和封闭列表，以及使用F，G，和H的路径排序。有很多其他的寻路算法，但是其它的方法不是A* &#8212; 通常认为它是最好的算法。Bryan Stout在本文后面的一个参考文档里讨论了这些算法，有正面的，也有反面的。有时候在特定的环境下，选择其他的算法会更好，但是你应该理解你做了什么。好了，说够了。回到文章中来。<br />
关于实现的提示</p>
<p>　　现在你已经理解了基本的方法，这里是当你写自己的程序时要考虑的更多东西。下面的某些材料引用了我用C++和Blitz Basic写的程序，但是这些要点对其他语言也是同样有效的。</p>
<p>　　1.维护开放列表 : 实际上这是A*寻路函数最耗费时间的元素之一。每次访问开放列表时，你都需要找到具有最小F值的方块。有很多种方法可以做到这点。你可以保存所需的路径项目，每次当你需要找到最小F值的方块时，简单的遍历整个列表。这很简单，不过路径长的时候非常慢。这个方法可以改进，通过维护一个排序的列表，每次需要最小F值的方块时，简单的抓出第一个项目就可以了。当我写自己的程序时，这是我用到的第一个方法。<br />
　　这个方法对小的地图相当的好，但不是最快的方案。真正需要速度的认真的A*程序员使用叫做二元堆[binary heap]的东西，这也是我的代码中所使用的。以我的经验，在大多数解决方案中，这个方法会快至少2-3倍，在长路径上更快（10倍以上）。如果你有兴趣发现更多二元堆的奥秘，参考我的文章，Using Binary Heaps in A* Pathfinding。</p>
<p>　　2. 其他单元： 如果你碰巧深入的阅读我的范例代码，将会注意到它完全忽略了地图上的其他单元。我的寻路怪物实际上是穿越彼此而通过。依赖于游戏，这可能是正确的，或者是不正确的。如果你要考虑地图上的其他单元，并让他们能围绕彼此移动，我建议你在寻路代码里忽略其他的单元，而另外写一些代码来检测两个单元是否发生了碰撞。当碰撞发生时，你可以生成一个新路径或者使用一些标准的移动规则，直到障碍不在路上，然后生成新路径。当计算初始路径时，为什么不包含其他单元？嗯，因为其他单元会动，他们可能不在自己的位置，当你到达那里的时候。这会造成一些怪异的结果，路径计算后，在某处单元突然转向避开一个不再停留在那里的单元，却撞上了另一个经过它路径的单元。</p>
<p>　　寻路代码中忽略其他的单元，然而，这意味着你要写单独的代码来处理碰撞。这是和游戏很相关的，所以我把决定权留给你。本文后面的参考资料一节里， Bryan Stout的文章值得一读，里面有一些可能的解决方案（如强力跟踪[robust tracing]，等等）。</p>
<p>　　3. 关于速度的更多技巧 ：当你开发自己的A*程序，或者改编我所写的那个，最终你会发现寻路使用了大块的 CPU时间，特别是当你有大量的寻路怪物，运行在一个相当大的地图上的时候。如果你读网上的资料，你会发现甚至象星际争霸[Starcraft]或者帝国时代[Age of Empires]这样的专业游戏也会遇到这些问题。如果你发现由于寻路导致运行变慢，这里有一些可能提高速度的主意：<br />
　　考虑小一些的地图或者少一些的怪物。</p>
<p>　　不要一次对太多的怪物做寻路。而是把他们放入队列，从而把他们分散到更多的游戏循环。如果你的游戏运行在，比如，40帧/秒，没有人会注意到。但是他们会注意到每一小段时间的游戏变慢，当大量的怪物都在同一时间寻路的时候。</p>
<p>　　考虑对地图使用大一些的方块。这样就减少了寻路要搜索的方块总数。如果你有雄心的话，可以设计两种或更多寻路系统，依赖于路径的长度而用于不同的场合。这就是专业人士的做法，对长路径使用大的区域，然后当接近目标时切换到使用小一些的方块/区域的精确搜索。如果你对这个概念有兴趣，参考我的文章Two-Tiered A* Pathfinding。</p>
<p>　　考虑对较长的路径应用路点系统，或者设计预计算[precalculated]的多个路径，它们对游戏是固定不变 [hardwired]的。</p>
<p>　　考虑预处理地图，计算出哪些区域是从其他区域不可到达的。我把这些区域叫做“岛屿”。实际上，他们也可以是岛屿或者其他围了墙而无法到达的区域。A*的缺点之一就是，如果你告诉它搜索到达这些区域的路径，它会搜索整个地图，仅当每一个开放列表和封闭列表中的可到达方块/节点都处理后，才会停止。那会浪费大量的CPU时间。这种现象是可以避免的，通过预先决定那些区域是无法到达的，用数组或者类似的数据结构记录这些信息，然后在开始路径搜索前检查它。在我的代码的Blitz版本中，我创建了一个地图预处理器[map pre-processor]来做这件事。它也预先检查寻路算法可以忽略的死点[dead-ends]，这样速度就提高了很多。</p>
<p>　　4. 多样的地形代价： 在本教程以及我的附带程序里，地形只有两种情况：可行走和不可行走。如果你有可以行走但移动代价更高的地形怎么办？沼泽，山坡，地下城的楼梯，等等？这些都是可行走而移动代价高于平坦地面的地形实例。同样的，道路可能具有比它周围地形小一些的移动代价。</p>
<p>　　这个问题很容易解决，当计算任意给定的方块的G值时，加上地形的代价。简单的加上一个奖励代价给这些方块。A*寻路算法已经写成查找最小代价的路径，应该容易处理它。在我描述得简单示例中，当地形只有可行走和不可行走时，A*能找到最短，最直接得路径。但是在多代价[variable-cost]地形环境中，最小代的得路径可能行走了较长的距离。如同选择围绕沼泽的道路，而不是直接穿越沼泽。</p>
<p>　　还有一个有趣的附加考虑是被专业人士称作“影响映射[influence mapping]”的东西。如同上面描述的多代价地形一样，你可以创建一个附加的点系统，并引用到AI的路径中。想象你有一个地图，有大量的怪物守护着穿越山区的通道。每次当电脑送某人到经过这个通道的路径时，都会被困住。如果你愿意，你可以创建一个影响地图，处罚发生大量流血残杀处的那些方块。这会教电脑偏好安全的路径，并帮助它避免不利的位置：仅仅由于路径更短（但更危险），而不停的输送部队和怪物通过这个路径。</p>
<p>　　5. 处理未探索区域： 你是否玩过一款PC游戏，在那里电脑总是准确的知道路该如何走，即使地图没有探索？依赖与游戏，那样的寻路太好了反而不够真实。幸运的是，这个问题很容易解决。</p>
<p>　　答案就是创建一个独立的“发现可行走[knowWalkability]”数组用于每一个玩家以及电脑对手（每一个玩家，不是每一个单元 &#8212; 那将需要更多的计算机内存）。每个数组包含了玩家已探索区域的信息，另一方面，地图上其它区域直到被证实后才被假设是可行走的。使用这个方法，单元会漫步于死点位置，重复做相同的错误选择，直到他们发现周围的路。一旦地图都探索了，寻路就正常工作。</p>
<p>　　6. 更平滑的路径： 虽然A*会自动给出最短的，最低代价的路径，它不会自动给出看起来最平滑的路径。看一看本例最后计算出来的路径（图 7）。那条路径的第一步位于开始方块的右下方。如果第一步的方块就是开始方块相邻下方的方块，路径会不会更平滑些？</p>
<p>　　有很多方法可以解决这个问题。当你计算路径的时候，要处罚那些改变方向的方块，给它们一个附加的G值扣分。这样计算后，你可以走一遍那条路径，看一看那些选择了邻近方块而让路径看起来更好的地方。关于这个问题的完整信息，参考Gamasutra.com上Macro Pinter的文章 Toward More Realistic Pathfinding，它是免费的，但需要注册。</p>
<p>　　7. 非方块搜索区域： 在我们的示例中，我们使用了一个简单的2D方块布局。你不必使用这个方法。你可以使用不规则的形状区域。考虑一下棋盘游戏Risk，和游戏中的国家。你可以设计一个象那样的寻路关卡。为此，你将需要创建一个表来存储哪些国家和哪些国家相邻，以及相关的从一个国家移动到另一个国家的G值。你也需要选择一种估算H值的方法。其它的处理就和上面示例一样。当添加新项目到开放列表中时，你将简单的查找表中的国家，而不是邻近的方块。</p>
<p>　　同样的，你可以创建一个路点系统，对于固定地形场景中的路径。路点通常是一条路径上往来移动的点，这路径可能是一条道路，也可能是一个地下城的关键通道。作为游戏设计者，你能预先指定这些路点。如果没有障碍存在于两个路点间的直线路径上，就可以认为它们是“相邻”的，如同在Risk中那样，你应该保存这些邻接信息到一个某种类型的查找表中，当生成新的开放列表项目的时候使用它。然后记录相关的G值（可能是节点间的直线距离）和H值（可能是节点到目标的直线距离）。其他的和往常一样处理。</p>
<p>　　另有一个使用非方块搜索区域进行斜视角RPG地图搜索的例子，参考我的文章Two-Tiered A* Pathfinding。</p>
<p>　　进阶阅读</p>
<p>　　好了，现在你具备了基础知识和对一些高级概念的感觉。在这里，我建议你到我的代码中跋涉。程序包有两个版本，一个是C++的，另一个是用Blitz Basic语言写的。两个版本都有大量注释，应该容易理解。链接在这里。</p>
<p>　　如果你无法使用C++或者Blitz Basic，可以找到C++版本的两个exe执行文件。通过在Blitz Basic的网站下载免费的demo版Blitz Basic 3D，就能运行Blitz Basic版本。还能找到Ben O‘Neill写的在线使用示范。</p>
<p>　　你也应该考虑通读下面的网页。阅读了本文后，它们应该非常容易理解了。</p>
<p>　　Amit&#8217;s A* Pages: 这是Amit Patel维护的一个很广泛的参考页面，如果你开始没有读过本文，它是有点混乱的。很值得一读。特别看一下关于Amit本人的思想那个主题。</p>
<p>　　Smart Moves: Intelligent Path Finding : 这篇文章位于Gamasutra.com，Bryan Stout所写，需要注册才能阅读。注册是免费的，为了这篇文章是值得的，因为此类的文章可不多见了。程序是Bryan用delphi语言写的，它帮助我学会了A*，我的A*程序背后也有它的灵感。它也讨论了一些替代A*的解决方案。</p>
<p>　　Terrain Analysis: 这是一篇高阶的文章（但很有趣），Dave Pottinger所写。Ensemble Studios的专家。这些家伙开发了帝国时代[Age of Empires]和帝国时代II：帝王时代[Age of Kings]。不要指望能理解这里的所有东西，但它是一篇有趣的文章，也许能带给你一些属于你自己的想法。它包含了一些mip映射[mip- mapping]，影响映射[influence mapping]，和一些其他的高级AI/寻路的概念的讨论。对”洪水泛滥[flood filling]“的讨论给予了我设计自己的&#8221;死点[dead ends]&#8220;和&#8221;岛屿&#8221;地图预处理代码的灵感，这些都包含在我的程序的Blitz版本里面。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ray77.com/a-pathfinding-for-beginners.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
