<?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>Computer, Electron and Technology &#187; unix</title>
	<atom:link href="http://www.donevii.com/post/tag/unix/feed" rel="self" type="application/rss+xml" />
	<link>http://www.donevii.com</link>
	<description>DoneVII CET &#38; CPPLITE</description>
	<lastBuildDate>Wed, 02 Jun 2010 10:45:52 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>google背后的分布式架构</title>
		<link>http://www.donevii.com/post/504.html</link>
		<comments>http://www.donevii.com/post/504.html#comments</comments>
		<pubDate>Tue, 23 Sep 2008 08:48:29 +0000</pubDate>
		<dc:creator>dengwei</dc:creator>
				<category><![CDATA[life]]></category>
		<category><![CDATA[blog]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[server]]></category>
		<category><![CDATA[unix]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[平台]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[测试]]></category>
		<category><![CDATA[类]]></category>
		<category><![CDATA[缓存]]></category>
		<category><![CDATA[芯片]]></category>

		<guid isPermaLink="false">http://www.donevii.com/post/504.html</guid>
		<description><![CDATA[　　Google是与众不同的。它的独特不仅仅表现于革新的思维和充满创意的应用 (比如那个大堂里的地球模型)，更在于其有别常规的IT策略…… 　　加利福尼亚州山景城(Mountain View)Google公司(Google... ]]></description>
			<content:encoded><![CDATA[<p>　　Google是与众不同的。它的独特不仅仅表现于革新的思维和充满创意的应用  (比如那个大堂里的地球模型)，更在于其有别常规的IT策略……</p>
<p>　　加利福尼亚州山景城(Mountain  View)Google公司(Google，下称Google)总部有一个43号大楼，该建筑的中央大屏幕上显示着一个与Google地球(Google  Earth)相仿的世界地图，一个转动的地球上不停地闪动着五颜六色的光点，恍如罗马宫廷的千万烛灯，每一次闪动标志着地球的这个角落一名Google用  户发起了一次新的搜索。</p>
<p>　　这同时意味着Google又一次满足了人们对未知信息的好奇与渴望。</p>
<p>　　Google是与众不同的。它的独特不仅仅表现于革新的思维和充满创意的应用  (比如那个大堂里的地球模型)，更在于其有别常规的IT策略。从人们的常理来看，简单的硬件商品和免费软件是无法构建出一个帝国的，但是Google做到  了。在性能调整后，Google把它们变成一个无可比拟的分布式计算平台，该平台能够支持大规模的搜索和不断涌现的新兴应用。我们原本认为这些应用都是个  人消费级别的，但是Google改变了这一切。现在商业世界也在使用它们，这就令这家搜索公司显得那么与众不同。</p>
<p>　　GoogleWeb  服务背后的IT架构对无数使用搜索引擎的用户来说也许并不是非常重要，但它是Google几百位致力于把全球信息组织起来，实现“随处可达，随时可用”目  标的工程师们的最核心工作。这就需要一个在覆盖范围和野心上都与Google的商业愿景完全相符的IT蓝图作为支撑。</p>
<p>　　Google  的经理们一直对公司的IT策略话题保持沉默，他们厌恶谈及特定的厂商或者产品，当被问到他们的服务器和数据中心时，他们总是闭口不谈。但与几位  Google的IT领导一起呆了一天后，我们最终得以揭示该公司的IT是如何运作的，那可不仅仅是一个运行在无数服务器集群上的、表面看来非常简单的搜索  引擎。在其简单的外表下，蕴涵着许多内部研发软件、定制硬件、人工智能，以及对性能的执着追求和打破常规的人力管理模式。</p>
<p>　　IT理念方面，Google对同行有一条建议：尽量避免那些人人都在使用的系统和软件，以自己的方式做事会更有独特的竞争优势。</p>
<p>　　“企业文化决定了你的做事方式。”道格拉斯&#8221;美林(Douglas  Merrill)，这位Google工程副总裁和事实上的首席信息官(CIO)  指出，“到了我们这样的发展阶段，企业观念和文化非常与众不同，这也反过来鞭策我们必须要采用与众不同的方式来运行那些他人看来很常规的系统。”<br />
　　Google  最大的IT优势在于它能建造出既富于性价比(并非廉价)又能承受极高负载的高性能系统。因此IT顾问史蒂芬&#8221;阿诺德(Stephen  Arnold)指出，Google与竞争对手，如亚马逊网站(Amazon)、电子港湾公司(eBay)、微软公司(Microsoft，下称微软)和雅 虎公司  (Yahoo，下称雅虎)等公司相比，具有更大的成本优势。Google程序员的效率比其他Web公司同行们高出50%～100%，原因是Google已  经开发出了一整套专用于支持大规模并行系统编程的定制软件库。据他估算，其他竞争公司可能要花上四倍的时间才能获得同等的效果。</p>
<p>　　打造服务器</p>
<p>　　Google  究竟是怎样做到这点的呢?其中一个手段，美林认为，“是因为我们自己动手打造硬件。”Google并不制造计算机系统，但它根据自己的参数定制硬件，然后  像MTV的节目“靓车打造”(Pimp My Ride)那样自己安装和调整硬件系统。开源程序经理克里斯&#8221;迪博纳(Chris  DiBona)评论道：“我们很善于购买商业服务器，并且改造他们为我们所用，最后把性能压榨和发挥到极致，以致有时候他们热得像要融化了似的。”</p>
<p>　　这种亲手打造的方式，来源于Google从车库诞生时与生俱来的节俭风格，更与Google那超大型的系统规模息息相关，良好的习惯一直延续至  今。据说 Google在65个数据中心拥有20万～45万台服务器—这个数目会有偏差(取决于你如何定义服务器和由谁来做这项统计)。但是，不变的是持续上升的趋势。</p>
<p>　　Google不会去讨论这些资产，因为它认为保密也是一种竞争优势。事实上，Google之所以喜欢开源软件也是因为它的私密性。“如果我们购  买了软件许可或代码许可，人们只要对号入座，就可以猜出Google的IT基础架构。”迪博纳分析说，  “使用开源软件，就使我们多了一条把握自己命运的途径。”</p>
<p>　　Google喜欢规模化的服务器运行方式。当有成百上千台机器时，定制服务器的优势也会成倍增加，效果也会更趋明显。Google正在俄勒冈州  哥伦比亚河边的达勒斯市建造一个占地30亩的数据中心，在那儿它可以获得运算和降温需要的低价水力电力能源(参见边栏《Google数据中心自有一套》)。</p>
<p>　　Google以“单元”(Cell)的形式组织这些运行  Linux操作系统的服务器，迪博纳把这种形式比喻成互联网服务的“磁盘驱动器”(但别和一直谣传的Google存储服务Gdrive混淆了，“并没有  Gdrive这回事。”一位Google女发言人明确表示。)，公司的软件程序都驻扎在这些并不昂贵的电脑机箱里，由程序员决定它们的冗余工作量。这种由  很多单元组成的文件系统代替了商业存储设备;迪博纳表示Google这些单元设备更易于建造和维护，他还暗示他们能处理更大规模的数据。</p>
<p>　　Google  不会漏过对任何技术细节的关注。多年来，公司的工程师就在研究微处理器的内部工作机制，随着Google规模的持续壮大，必然会用到特别定制和调节过的芯  片。知名工程师路易斯&#8221;巴罗索(Luiz  Barroso)去年在一篇发表在工业杂志上的论文中证实，近年来Google的主要负荷都由单核设计的系统承担着。但许多服务器端的应用，如  Google搜索索引服务，所需的并行计算在单核芯片的指令级别上执行得并不好。</p>
<p>　　曾在数据设备公司(Digital  Equipment)和康柏公司(Compaq)当过芯片设计师的巴罗索认为，随着AMD公司、英特尔公司(Intel)、太阳计算机系统公司(Sun)开始制造多核芯片，必将会出现越来越多芯片级别的并行计算。</p>
<p>　　Google  也曾考虑过自己制造计算机芯片，但从业界潮流来看，这个冒险的举动似乎不是很必要。“微处理器的设计非常复杂而且成本昂贵，”运营高级副总裁乌尔斯&#8221;霍尔 茨勒(Urs  Holzle)表示。Google宁愿与芯片制造商合作，让他们去理解自己的应用并设计适合的芯片。这是一种客户建议式的设计，其关注点在于总体吞吐量、  效能，以及耗电比，而不是看单线程的峰值性能。霍尔茨勒表示，“这也是最近多核CPU的设计潮流与未来方向。”<br />
裁缝般地定制软件</p>
<p>　　为了能尽量压榨硬件性能，Google开发了相当数量的定制软件。创新产品主要包括用于简化处理和创建大规模数据集的编程模型  MapReduce;用于存储和管理大规模数据的系统BigTable;分析分布式运算环境中大规模数据集的解释编程语言Sawzall;用于数据密集型  应用的分布式文件系统的 “Google文件系统”(Google File  System);还有为处理分布式系统队列分组和任务调度的“Google工作队列”(Google  Workqueue)。</p>
<p>　　正是从Sawzall这些工具里体现出Google对计算效率的执著关注。并不是每家公司都能从底层去解决效率问题，但是对Google来说，  为常规关系型数据库无法容纳的大规模数据集专门设计一种编程语言是完全合理的。即使其他编程工具可以解决问题，Google的工程师们仍然会为了追求效率  而另外开发一套定制方案。Google工程师认为，Sawzall能与C++中的MapReduce相媲美，而且它更容易编写一些。</p>
<p>　　Google  对效率的关注使它不可能对标准Linux内核感到满意;Google会根据自己的需要运行修改过的内核版本。通过调整Linux的底层性能，Google  工程师们在提高了整体系统可靠性的基础上，还一并解决了数据损坏和数据瓶颈等一系列棘手问题。对内核的修改也使Google的计算机集群系统因为通信效率  的提高而运行得更快。</p>
<p>　　当然，Google偶尔也会出现系统故障，情况一旦发生，无数的用户就会受到影响了。三年前一次持续30分钟的系统故障使20%的搜索流量受到影响。</p>
<p>　　Google  开发了自己的网站服务器却没有使用开源的Apache服务器，尽管它在网站服务器的市场占有率超过60%。迪博纳认为，Google的网站服务器可以运行  在更多数量的主机上，对Google站点上内容庞大又彼此互相依赖的应用程序来说，这种服务器的负载均衡能力远比Apache的能力更高。同时，在用标准  公共网关接口(CGI)访问数据库动态网页方面，Google服务器的编程难度要比  Apache更高，但是最终运行速度却更快。“如果我们能够压榨出10%～20%的性能，我们就可以节省出更多系统资源、电量和人力了。”迪博纳在总结中指出。</p>
<p>　　Google还设计了自己的客户关系管理(CRM)系统用于支持自己基于竞价和点击的互联网广告收费业务。但对是否需要设计自己的工具，Google的态度也不是一成不变的。比如在财会软件上，它就使用了甲骨文公司(Oracle)的Financials软件。</p>
<p>　　美林拿着一只叉子举例说明现成的产品也可以带来价值。但在有些场合现成的软件产品就不一定适用了。“我们的文化在各个层面对我们的运作都有深远影响，”他表示，“所以我们不想让购买所得的工具改变我们的工作方式和文化层面。”<br />
Google&#8217;s  BigTable 原理 （翻译）</p>
<p>    题记：google 的成功除了一个个出色的创意外，还因为有 Jeff Dean  这样的软件架构天才。<br />
                                                  &#8212;&#8212; 编者</p>
<p>官方的  Google Reader blog 中有对BigTable 的解释。这是Google 内部开发的一个用来处理大数据量的系统。这种系统适合处理半结构化的数据比如  RSS 数据源。 以下发言  是 Andrew Hitchcock  在 2005 年10月18号 基于： Google 的工程师 Jeff Dean  在华盛顿大学的一次谈话 (Creative Commons License).</p>
<p>首先，BigTable 从 2004  年初就开始研发了，到现在为止已经用了将近8个月。（2005年2月）目前大概有100个左右的服务使用BigTable，比如： Print,Search  History,Maps和 Orkut。根据Google的一贯做法，内部开发的BigTable是为跑在廉价的PC机上设计的。BigTable  让Google在提供新服务时的运行成本降低，最大限度地利用了计算能力。</p>
<p>BigTable 是建立在 GFS ，Scheduler ，Lock Service 和  MapReduce 之上的。</p>
<p>每个Table都是一个多维的稀疏图 sparse map。Table 由行和列组成，并且每个存储单元 cell  都有一个时间戳。在不同的时间对同一个存储单元cell有多份拷贝，这样就可以记录数据的变动情况。在他的例子中，行是URLs  ，列可以定义一个名字，比如：contents。Contents  字段就可以存储文件的数据。或者列名是：”language”，可以存储一个“EN”的语言代码字符串。</p>
<p>为了管理巨大的Table，把Table根据行分割，这些分割后的数据统称为：Tablets。每  个Tablets大概有 100-200 MB，每个机器存储100个左右的  Tablets。底层的架构是：GFS。由于GFS是一种分布式的文件系统，采用Tablets的机制后，可以获得很好的负载均衡。比如：可以把经常响应  的表移动到其他空闲机器上，然后快速重建。</p>
<p>Tablets在系统中的存储方式是不可修改的 immutable  的SSTables，一台机器一个日志文件。当系统的内存满后，系统会压缩一些Tablets。由于Jeff在论述这点的时候说的很快，所以我没有时间把听到的都记录下来，因此下面是一个大概的说明：</p>
<p>压缩分为：主要和次要的两部分。次要的压缩仅仅包括几个Tablets，而主要的压缩时关于整个系统的压缩。主压缩有回收硬盘空间的功能。Tablets的位置实际上是存储在几个特殊的BigTable的存储单元cell中。看起来这是一个三层的系统。<br />
客户端有一个指向METAO的Tablets的指针。如果METAO的Tablets被频繁使用，那个这台机器就会放弃其他的tablets专门支持  METAO这个Tablets。METAO tablets  保持着所有的META1的tablets的记录。这些tablets中包含着查找tablets的实际位置。（老实说翻译到这里，我也不太明白。）在这个系统中不存在大的瓶颈，因为被频繁调用的数据已经被提前获得并进行了缓存。</p>
<p>     现在我们返回到对列的说明：列是类似下面的形式：  family:optional_qualifier。在他的例子中，行：www.search-analysis.com   也许有列：”contents:其中包含<a href="http://www.donevii.com/post/tag/html" class="st_tag internal_tag" rel="tag" title="Posts tagged with html">html</a>页面的代码。 “ anchor:cnn.com/news” 中包含着  相对应的url，”anchor:www.search-analysis.com/” 包含着链接的文字部分。列中包含着类型信息。<br />
     (翻译到这里我要插一句，以前我看过一个关于万能数据库的文章，当时很激动，就联系了作者，现在回想起来，或许google的 bigtable  才是更好的方案，切不说分布式的特性，就是这种建华的表结构就很有用处。)</p>
<p>     注意这里说的是列信息，而不是列类型。列的信息是如下信息，一般是：属性/规则。 比如：保存n份数据的拷贝或者保存数据n天长等等。当 tablets  重新建立的时候，就运用上面的规则，剔出不符合条件的记录。由于设计上的原因，列本身的创建是很容易的，但是跟列相关的功能确实非常复杂的，比如上文提到 的  类型和规则信息等。为了优化读取速度，列的功能被分割然后以组的方式存储在所建索引的机器上。这些被分割后的组作用于 列 ,然后被分割成不同的  SSTables。这种方式可以提高系统的性能，因为小的，频繁读取的列可以被单独存储，和那些大的不经常访问的列隔离开来。</p>
<p>在一台机器上的所有的  tablets  共享一个log，在一个包含1亿的tablets的集群中，这将会导致非常多的文件被打开和写操作。新的log块经常被创建，一般是64M大小，这个GFS的块大小相等。当一个机器down掉后，控制机器就会重新发布他的log块到其他机器上继续进行处理。这台机器重建tablets然后询问控制机器处理结构的存储位置，然后直接对重建后的数据进行处理。这个系统中有很多冗余数据，因此在系统中大量使用了压缩技术。</p>
<p>     Dean 对压缩的部分说的很快，我没有完全记下来，所以我还是说个大概吧：压缩前先寻找相似的 \行，列，和时间数据。</p>
<p>    他们使用不同版本的：  BMDiff 和 Zippy 技术。</p>
<p>   BMDiff 提供给他们非常快的写速度： 100MB/s – 1000MB/s 。Zippy 是和 LZW  类似的。Zippy 并不像 LZW 或者 gzip 那样压缩比高，但是他处理速度非常快。</p>
<p>    Dean 还给了一个关于压缩 web  蜘蛛数据的例子。这个例子的蜘蛛 包含 2.1B 的页面，行按照以下的方式命名：“com.cnn.www/index.<a href="http://www.donevii.com/post/tag/html" class="st_tag internal_tag" rel="tag" title="Posts tagged with html">html</a>:http”.在未压缩前的web  page 页面大小是：45.1 TB ，压缩后的大小是：4.2 TB ， 只是原来的 9.2%。Links 数据压缩到原来的 13.9% ,  链接文本数据压缩到原来的 12.7%。<br />
<span id="more-504"></span><br />
Google 还有很多没有添加但是已经考虑的功能。<br />
    1.   数据操作表达式，这样可以把脚本发送到客户端来提供修改数据的功能。<br />
    2. 多行数据的事物支持。<br />
    3.   提高大数据存储单元的效率。<br />
    4. BigTable 作为服务运行。<br />
    好像：每个服务比如： maps 和 search history  历史搜索记录都有他们自己的集群运行 BigTable。<br />
    他们还考虑运行一个全局的 BigTable  系统，但这需要比较公平的分割资源和计算时间。</p>
<p>大表(Bigtable):结构化数据的分布存储系统</p>
<p>http://labs.google.com/papers/bigtable-osdi06.pdf</p>
<p>｛中是译者评论,程序除外｝<br />
{本文的翻译可能有不准确的地方,详细资料请参考原文.}</p>
<p>摘要<br />
bigtable是设计来分布存储大规模结构化数据的，从设计上它可以扩展到上２^50字节，分布存储在几千个普通服务器上．google的很多项目使用  bt来存储数据，包括网页查询，google  earth和google金融．这些应用程序对bt的要求各不相同：数据大小（从URL到网页到卫星图象）不同，反应速度不同（从后端的大批处理到实时数  据服务）．对于不同的要求，bt都成功的提供了灵活高效的服务．在本文中，我们将描述bt的数据模型．这个数据模型让用户动态的控制数据的分布和结构．我  们还将描述BT的设计和实现．<br />
１．介绍<br />
在过去两年半里，我们设计，实现并部署了BT．BT是用来分布存储和管理结构化数据的．BT的设计使它能够管理2^50  bytes(petabytes)数据，并可以部署到上千台机器上．BT完成了以下目标：应用广泛，可扩展，高性能和高可用性（high availability）.  包括google analytics, google finance, orkut, personalized search, writely和google  earth在内的60多个项目都使用BT.这些应用对BT的要求各不相同，有的需要高吞吐量的批处理，有的需要快速反应给用户数据．它们使用的BT集群也各不相同，有的只有几台机器，有的有上千台，能够存储2^40字节(terabytes)数据．<br />
BT在很多地方和数据库很类似：它使用了很多数据库的实现策略．并行数据库[14]和内存数据库[13]有可扩展性和高性能，但是BT的界面不同．BT不支持完全的关系数据模型；而是为客户提供了简单的数据模型，让客户来动态控制数据的分布和格式{就是只存储字串，格式由客户来解释}，并允许客户推断底层存储数据的局部性｛以提高访问速度｝．数据下标是行和列的名字，数据本身可以是任何字串．BT的数据是字串，没有解释｛类型等｝．客户会在把各种结构或者半结构化的数据串行化｛比如说日期串｝到数据中．通过仔细选择数据表示，客户可以控制数据的局部化．最后，可以使用BT模式来控制数据是放在内存里还是在硬盘上．｛就是说用模式，你可以把数据放在离应用最近的地方．毕竟程序在一个时间只用到一块数据．在体系结构里，就是：locality,  locality,  locality｝<br />
第二节描述数据模型细节．第三节关于客户API概述．第四节简介BT依赖的google框架．第五节描述BT的实现关键部分．第6节叙述提高BT性  能的一些调整．第7节提供BT性能的数据．在第8节，我们提供BT的几个使用例子，第9节是经验教训．在第10节，我们列出相关研究．最后是我们的结论．<br />
２．数据模型<br />
BT是一个稀疏的，长期存储的｛存在硬盘上｝，多维度的，排序的映射表．这张表的索引是行关键字，列关键字和时间戳．每个值是一个不解释的字符数组．｛数据都是字符串，没类型，客户要解释就自力更生吧｝．<br />
(row:string,  column:string,time:int64)-&gt;string  {能编程序的都能读懂，不翻译了}<br />
我们仔细查看过好些类似bigtable的系统之后定下了这个数据模型。举一个具体例子（它促使我们做出某些设计决定），  比如我们想要存储大量网页及相关信息，以用于很多不同的项目；我们姑且叫它Webtable。在Webtable里，我们将用URL作为行关键字，用网页  的某些属性作为列名，把网页内容存在contents:列中并用获取该网页的时间戳作为标识，如图一所示。<br />
图一：一个存储Web网页的范例列表片断。行名是一个反向URL｛即com.cnn.www｝。contents列族｛原文用  family，译为族，详见列族｝ 存放网页内容，anchor列族存放引用该网页的锚链接文本。CNN的主页被Sports  Illustrater｛即所谓SI，CNN的王牌体育节目｝和MY-look的主页引用，因此该行包含了名叫“anchor:cnnsi.com”和  “anchhor:my.look.ca”的列。每个锚链接只有一个版本｛由时间戳标识，如t9，t8｝；而contents列则有三个版本，分别由时间  戳t3，t5，和t6标识。<br />
行<br />
表中的行关键字可以是任意字符串（目前支持最多64KB，多数情况下10－100字节足够了）。在一个行关键字下的每一个读写操作都是原子操作（不管读写这一行里多少个不同列），这是一个设计决定，这样在对同一行进行并发操作时，用户对于系统行为更容易理解和掌控。<br />
Bigtable通过行关键字的字典序来维护数据。一张表可以动态划分成多个连续行。连续行在这里叫做“子表”｛tablet｝，是数据分布和负载  均衡的单位。这样一来，读较少的连续行就比较有效率，通常只需要较少机器之间的通信即可。用户可以利用这个属性来选择行关键字，从而达到较好数据访问地域  性｛locality｝。举例来说，在Webtable里，通过反转URL中主机名的方式，可以把同一个域名下的网页组织成连续行。具体来说，可以把  maps.google.com/index.<a href="http://www.donevii.com/post/tag/html" class="st_tag internal_tag" rel="tag" title="Posts tagged with html">html</a>中的数据存放在关键字com.google.maps/index.<a href="http://www.donevii.com/post/tag/html" class="st_tag internal_tag" rel="tag" title="Posts tagged with html">html</a>下。按照相同或属性相  近的域名来存放网页可以让基于主机和基于域名的分析更加有效。<br />
列族<br />
一组列关键字组成了“列族”，这是访问控制的基本单位。同一列族下存放的所有数据通常都是同一类型（同一列族下的数据可压缩在一起）。列族必须先创  建，然后在能在其中的列关键字下存放数据；列族创建后，族中任何一个列关键字均可使用。我们希望，一张表中的不同列族不能太多（最多几百个），并且列族在  运作中绝少改变。作为对比，一张表可以有无限列。<br />
列关键字用如下语法命名：列族：限定词。  列族名必须是看得懂｛printable｝的字串，而限定词可以是任意字符串。比如，Webtable可以有个列族叫language，存放撰写网页的语  言。我们在language列族中只用一个列关键字，用来存放每个网页的语言标识符。该表的另一个有用的列族是anchor；给列族的每一个列关键字代表  一个锚链接，如图一所示。而这里的限定词则是引用该网页的站点名；表中一个表项存放的是链接文本。<br />
访问控制，磁盘使用统计，内存使用统计，均可在列族这个层面进行。在Webtable举例中，我们可以用这些控制来管理不同应用：有的应用添加新的基本数据，有的读取基本数据并创建引申的列族，有的则只能浏览数据（甚至可能因为隐私权原因不能浏览所有数据）。<br />
时间戳<br />
Bigtable表中每一个表项都可以包含同一数据的多个版本，由时间戳来索引。Bigtable的时间戳是64位整型。可以由Bigtable来  赋值，表示准确到毫秒的“实时”；或者由用户应用程序来赋值。需要避免冲突的应用程序必须自己产生具有唯一性的时间戳。不同版本的表项内容按时间戳倒序排  列，即最新的排在前面。<br />
为了简化对于不同数据版本的数据的管理，我们对每一个列族支持两个设定，以便于Bigtable对表项的版本自动进行垃圾清除。用户可以指明只保留表项的最后n个版本，或者只保留足够新的版本（比如，只保留最近7天的内容）。<br />
在Webtable举例中，我们在contents:列中存放确切爬行一个网页的时间戳。如上所述的垃圾清除机制可以让我们只保留每个网页的最近三个版本。<br />
3.API<br />
BT的API提供了建立和删除表和列族的函数．还提供了函数来修改集群，表和列族的元数据，比如说访问权限．<br />
//  Open the table<br />
Table *T = OpenOrDie(”/bigtable/web/webtable”);<br />
// Write a  new anchor and delete an old anchor<br />
RowMutation r1(T,  “com.cnn.www”);<br />
r1.Set(”anchor:www.c-span.org”,  “CNN”);<br />
r1.Delete(”anchor:www.abc.com”);<br />
Operation op;<br />
Apply(&amp;op,  &amp;r1);<br />
图 2:  写入Bigtable.<br />
在BT中，客户应用可以写或者删除值，从每个行中找值，或者遍历一个表中的数据子集．图2的c++代码是使用RowMutation抽象表示来进行一系列的更新（为保证代码精简，没有包括无关的细节）．调用Apply函数，就对Ｗebtable进行了一个原子修改：它为http://www.cnn.com/增加了一个锚点，并删除了另外一个锚点．<br />
Scanner  scanner(T);<br />
ScanStream *stream;<br />
stream =  scanner.FetchColumnFamily(”anchor”);<br />
stream-&gt;SetReturnAllVersions();<br />
scanner.Lookup(”com.cnn.www”);<br />
for  (; !stream-&gt;Done(); stream-&gt;Next()) {<br />
printf(”%s %s %lld  %s\n”,<br />
scanner.RowName(),<br />
stream-&gt;ColumnName(),<br />
stream-&gt;MicroTimestamp(),<br />
stream-&gt;Value());<br />
}<br />
图3:  从Bigtable读数据.<br />
图3的C++代码是使用Scanner抽象来遍历一个行内的所有锚点．客户可以遍历多个列族．有很多方法可以限制一次扫描中产生的行，列和时间戳．  例如，我们可以限制上面的扫描，让它只找到那些匹配正则表达式*.cnn.com的锚点，或者那些时间戳在当前时间前10天的锚点．<br />
BT还支持其他一些更复杂的处理数据的功能．首先，BT支持单行处理．这个功能可以用来对存储在一个行关键字下的数据进行原子的读-修改-写操作．  BT目前不支持跨行关键字的处理，但是它有一个界面，可以用来让客户进行批量的跨行关键字处理操作．其次，BT允许把每个表项用做整数记数器．最后，BT  支持在服务器的地址空间内执行客户端提供的脚本程序．脚本程序的语言是google开发的Sawzall[28]数据处理语言．目前，我们基于的  Sawzall的API还不允许客户脚本程序向BT内写数据，但是它允许多种形式的数据变换，基于任何表达式的过滤和通过多种操作符的摘要．<br />
BT可以和MapReduce[12]一起使用．MapReduce是google开发的大规模并行计算框架．我们为编写了一套外层程序，使BT可以作为MapReduce处理的数据源头和输出结果．<br />
4.建立BT的基本单元<br />
BT是建立在其他数个google框架单元上的．BT使用google分布式文件系统(GFS)[17]来存储日志和数据文件{yeah,  right, what else can it use,  FAT32?}．一个BT集群通常在一个共享的机器池中工作，池中的机器还运行其他的分布式应用{虽然机器便宜的跟白菜似的，可是一样要运行多个程序，命苦的象小白菜}，BT和其他程序共享机器｛BT的瓶颈是ＩＯ/内存，可以和CPU要求高的程序并存｝．BT依赖集群管理系统来安排工作，在共享的机器上管理资源，处理失效机器并监视机器状态｛典型的<a href="http://www.donevii.com/post/tag/server" class="st_tag internal_tag" rel="tag" title="Posts tagged with server">server</a>  farm结构，BT是上面的应用之一｝．<br />
BT内部存储数据的格式是google  SSTable格式．一个SSTable提供一个从关键字到值的映射，关键字和值都可以是任意字符串．映射是排序的，存储的｛不会因为掉电而丢失｝，不可改写的．可以进行以下操作：查询和一个关键字相关的值；或者根据给出的关键字范围遍历所有的关键字和值．在内部，每个SSTable包含一列数据块（通常每个块的大小是64KB,但是大小是可以配置的｛索引大小是16  bits，应该是比较好的一个数｝）．块索引（存储在SSTable的最后）用来定位数据块；当打开SSTable的时候，索引被读入内存｛性能｝．每次查找都可以用一个硬盘搜索完成｛根据索引算出数据在哪个道上，一个块应该不会跨两个道，没必要省那么点空间｝：首先在内存中的索引里进行二分查找找到数据块的位置，然后再从硬盘读去数据块．最佳情况是：整个SSTable可以被放在内存里，这样一来就不必访问硬盘了．｛想的美，前面是谁口口声声说要跟别人共享机器来着？你把内存占满了别人上哪睡去？｝<br />
BT还依赖一个高度可用的，存储的分布式数据锁服务Chubby[8]｛看你怎么把这个high  performance给说圆喽｝．一个Chubby服务由5个活的备份｛机器｝构成，其中一个被这些备份选成主备份，并且处理请求．这个服务只有在大多数备份都活着并且互相通信的时候才是活的｛绕口令？去看原文吧，是在有出错的前提下的冗余算法｝．当有机器失效的时候，Chubby使用Paxos算法[9,23]来保证备份的一致性｛这个问题还是比较复杂的，建议去看引文了解一下问题本身｝．Chubby提供了一个名字空间，里面包括了目录和小文件｛万变不离其宗｝．每个目录或者文件可以当成一个锁来用，读写文件操作都是原子化的．Chubby客户端的程序库提供了对Chubby文件的一致性缓存｛究竟是提高性能还是降低性能？如果访问是分布的，就是提高性能｝．每个Chubby客户维护一个和Chubby服务的会话．如果一个客户不能在一定时间内更新它的会话，这个会话就过期失效了｛还是针对大<a href="http://www.donevii.com/post/tag/server" class="st_tag internal_tag" rel="tag" title="Posts tagged with server">server</a>  farm里机器失效的频率设计的｝．当一个会话失效时，其拥有的锁和打开的文件句柄都失效｛根本设计原则：失效时回到安全状态｝．Chubby客户可以在文件和目录上登记回调函数，以获得改变或者会话过期的通知．｛翻到这里，有没有人闻到java的味道了？｝<br />
BT使用Chubby来做以下几个任务：保证任何时间最多只有一个活跃的主备份；来存储BT数据的启动位置（参考5.1节）；发现小表  （tablet）服务器，并完成tablet服务器消亡的善后（5.2节）；存储BT数据的模式信息（每张表的列信息）；以及存储访问权限列表．如果有相当长的时间Chubby不能访问，BT就也不能访问了｛任何系统都有其弱点｝．最近我们在使用11个Chubby服务实例的14个BT集群中度量了这个效果，由于Chubby不能访问而导致BT中部分数据不能访问的平均百分比是0.0047%,这里Chubby不能访问的原因是Chubby本身失效或者网络问题．单个集群里，受影响最大的百分比是0.0326%｛基于文件系统的Chubby还是很稳定的｝.<br />
GFS是一个可扩展的分布式文件系统，用于大型的、分布式的、对大量数据进行访问的应用。它运行于廉价的普通硬件上，但可以提供容错功能。它可以给大量的用户提供总体性能较高的服务。<br />
出处：http://labs.google.com/papers/gfs.<a href="http://www.donevii.com/post/tag/html" class="st_tag internal_tag" rel="tag" title="Posts tagged with html">html</a><br />
1、设计概览<br />
（1）设计想定<br />
GFS与过去的分布式文件系统有很多相同的目标，但GFS的设计受到了当前及预期的应用方面的工作量及技术环境的驱动，这反映了它与早期的文件系统明显不同的设想。这就需要对传统的选择进行重新检验并进行完全不同的设计观点的探索。<br />
GFS与以往的文件系统的不同的观点如下：<br />
1、部件错误不再被当作异常，而是将其作为常见的情况加以处理。因为文件系统由成百上千个用于存储的机器构成，而这  些机器是由廉价的普通部件组成并被大量的客户机访问。部件的数量和质量使得一些机器随时都有可能无法工作并且有一部分还可能无法恢复。所以实时地监控、错  误检测、容错、自动恢复对系统来说必不可少。<br />
2、按照传统的标准，文件都非常大。长度达几个GB的文件是很平常的。每个文件通常包含很多应用对象。当经常要处理  快速增长的、包含数以万计的对象、长度达TB的数据集时，我们很难管理成千上万的KB规模的文件块，即使底层文件系统提供支持。因此，设计中操作的参数、  块的大小必须要重新考虑。对大型的文件的管理一定要能做到高效，对小型的文件也必须支持，但不必优化。<br />
3、大部分文件的更新是通过添加  新数据完成的，而不是改变已存在的数据。在一个文件中随机的操作在实践中几乎不存在。一旦写完，文件就只可读，很多数据都有这些特性。一些数据可能组成一  个大仓库以供数据分析程序扫描。有些是运行中的程序连续产生的数据流。有些是档案性质的数据，有些是在某个机器上产生、在另外一个机器上处理的中间数据。  由于这些对大型文件的访问方式，添加操作成为性能优化和原子性保证的焦点。而在客户机中缓存数据块则失去了吸引力。<br />
4、工作量主要由两种读操作构成：对大量数据的流方式的读操作和对少量数据的随机方式的读操作。在前一种读操作中，  可能要读几百KB，通常达  1MB和更多。来自同一个客户的连续操作通常会读文件的一个连续的区域。随机的读操作通常在一个随机的偏移处读几个KB。性能敏感的应用程序通常将对少量  数据的读操作进行分类并进行批处理以使得读操作稳定地向前推进，而不要让它来来回回的读。<br />
5、工作量还包含许多对大量数据进行的、连续的、向文件添加数据的写操作。所写的数据的规模和读相似。一旦写完，文件很少改动。在随机位置对少量数据的写操作也支持，但不必非常高效。<br />
6、系统必须高效地实现定义完好的大量客户同时向同一个文件的添加操作的语义。<br />
（2）系统接口<br />
GFS提供了一个相似地文件系统界面，虽然它没有向POSIX那样实现标准的API。文件在目录中按层次组织起来并由路径名标识。<br />
（3）体系结构：<br />
一个GFS集群由一个master和大量的chunkserver构成，并被许多客户（Client）访问。如图1  所示。Master和  chunkserver通常是运行用户层服务进程的Linux机器。只要资源和可靠性允许，chunkserver和client可以运行在同一个机器  上。<br />
文件被分成固定大小的块。每个块由一个不变的、全局唯一的64位的chunk－handle标识，chunk－ handle是在块创建时由  master分配的。ChunkServer将块当作Linux文件存储在本地磁盘并可以读和写由chunk－handle和位区间指定的数据。出于可靠  性考虑，每一个块被复制到多个chunkserver上。默认情况下，保存3个副本，但这可以由用户指定。<br />
Master维护文件系统所以的元数据（metadata），包括名字空间、访问控制信息、从文件到块的映射以及块  的当前位置。它也控制系统范围的活动，如块租约（lease）管理，孤儿块的垃圾收集，chunkserver间的块迁移。Master定期通过  HeartBeat消息与每一个  chunkserver通信，给chunkserver传递指令并收集它的状态。<br />
与每个应用相联的GFS客户代码实现了文件系统的API并与master和chunkserver通信以代表应用程序读和写数据。客户与master的交换只限于对元数据（metadata）的操作，所有数据方面的通信都直接和chunkserver联系。<br />
客户和chunkserver都不缓存文件数据。因为用户缓存的益处微乎其微，这是由于数据太多或工作集太大而无法  缓存。不缓存数据简化了客户程序和整个系统，因为不必考虑缓存的一致性问题。但用户缓存元数据（metadata）。Chunkserver也不必缓存文  件，因为块时作为本地文件存储的。<br />
（4）单master。<br />
只有一个master也极大的简化了设计并使得master可以根据全局情况作出先进的块放置和复制决定。但是我们  必须要将master对读和写的参与减至最少，这样它才不会成为系统的瓶颈。Client从来不会从master读和写文件数据。Client只是询问  master它应该和哪个  chunkserver联系。Client在一段限定的时间内将这些信息缓存，在后续的操作中Client直接和chunkserver交互。<br />
以图1解释一下一个简单的读操作的交互。<br />
1、client使用固定的块大小将应用程序指定的文件名和字节偏移转换成文件的一个块索引（chunk  index）。<br />
2、给master发送一个包含文件名和块索引的请求。<br />
3、master回应对应的chunk  handle和副本的位置（多个副本）。<br />
4、client以文件名和块索引为键缓存这些信息。（handle和副本的位置）。<br />
5、Client  向其中一个副本发送一个请求，很可能是最近的一个副本。请求指定了chunk handle（chunkserver以chunk  handle标识chunk）和块内的一个字节区间。<br />
6、除非缓存的信息不再有效（cache for a limited  time）或文件被重新打开，否则以后对同一个块的读操作不再需要client和master间的交互。<br />
通常Client可以在一个请求中询问多个chunk的地址，而master也可以很快回应这些请求。<br />
（5）块规模：<br />
块规模是设计中的一个关键参数。我们选择的是64MB，这比一般的文件系统的块规模要大的多。每个块的副本作为一个普通的Linux文件存储，在需要的时候可以扩展。<br />
块规模较大的好处有：<br />
1、减少client和master之间的交互。因为读写同一个块只是要在开始时向master请求块位置信息。对于读写大型文件这种减少尤为重要。即使对于访问少量数据的随机读操作也可以很方便的为一个规模达几个TB的工作集缓缓存块位置信息。<br />
2、Client在一个给定的块上很可能执行多个操作，和一个chunkserver保持较长时间的TCP连接可以减少网络负载。<br />
3、这减少了master上保存的元数据（metadata）的规模，从而使得可以将metadata放在内存中。这又会带来一些别的好处。<br />
不利的一面：<br />
一个小文件可能只包含一个块，如果很多Client访问改文件的话，存储这些块的chunkserver将成为访问的热点。但在实际应用中，应用程序通常顺序地读包含多个块的文件，所以这不是一个主要问题。<br />
（6）元数据（metadata）：<br />
master存储了三中类型的metadata：文件的名字空间和块的名字空间，从文件到块的映射，块的副本的位  置。所有的metadata都放在内存中。前两种类型的metadata通过向操作日志登记修改而保持不变，操作日志存储在master的本地磁盘并在几  个远程机器上留有副本。使用日志使得我们可以很简单地、可靠地更新master的状态，即使在master崩溃的情况下也不会有不一致的问题。相反，  mater在每次启动以及当有  chuankserver加入的时候询问每个chunkserver的所拥有的块的情况。<br />
A、内存数据结构：<br />
因为metadata存储在内存中，所以master的操作很快。进一步，master可以轻易而且高效地定期在后台扫描它的整个状态。这种定期地扫描被用于实现块垃圾收集、chunkserver出现故障时的副本复制、为平衡负载和磁盘空间而进行的块迁移。<br />
这种方法的一个潜在的问题就是块的数量也即整个系统的容量是否受限与master的内存。实际上，这并不是一个严重  的问题。Master为每个  64MB的块维护的metadata不足64个字节。除了最后一块，文件所有的块都是满的。类似的，每个文件的名字空间数据也不足64个字节，因为文件名  是以一种事先确定的压缩方式存储的.如果要支持更大的文件系统，那么增加一些内存的方法对于我们将元数据（metadata）保存在内存种所获得的简单  性、可靠性、高性能和灵活性来说，这只是一个很小的代价。<br />
B、块位置：<br />
master并不为chunkserver所拥有的块的副本的保存一个不变的记录。它在启动时通过简单的查询来获得这些信息。Master可以保持这些信息的更新，因为它控制所有块的放置并通过HeartBeat消息来监控chunkserver的状态。<br />
这样做的好处：因为chunkserver可能加入或离开集群、改变路径名、崩溃、重启等，一个集群重有成百个<a href="http://www.donevii.com/post/tag/server" class="st_tag internal_tag" rel="tag" title="Posts tagged with server">server</a>，这些事件经常发生，这种方法就排除了master与chunkserver之间的同步问题。<br />
另一个原因是：只有chunkserver才能确定它自己到底有哪些块，由于错误，chunkserver中的一些块可能会很自然的消失，这样在master中就没有必要为此保存一个不变的记录。<br />
C、操作日志：<br />
操作日志包含了对metadata所作的修改的历史记录。它作为逻辑时间线定义了并发操作的执行顺序。文件、块以及它们的版本号都由它们被创建时的逻辑时间而唯一地、永久地被标识。<br />
操作日志是如此的重要，我们必须要将它可靠地保存起来，并且只有在metadata的改变固定下来之后才将变化呈现给用户。所以我们将操作日志复制到数个远程的机器上，并且只有在将相应的日志记录写到本地和远程的磁盘上之后才回答用户的请求。<br />
Master可以用操作日志来恢复它的文件系统的状态。为了将启动时间减至最小，日志就必须要比较小。每当日志的长度增长到超过一定的规模后，master就要检查它的状态，它可以从本地磁盘装入最近的检查点来恢复状态。<br />
创建一个检查点比较费时，master的内部状态是以一种在创建一个检查点时并不耽误即将到来的修改操作的方式来组  织的。Master切换到一个新的日子文件并在一个单独的线程中创建检查点。这个新的检查点记录了切换前所有的修改。在一个有数十万文件的集群中用一分钟  左右就能完成。创建完后，将它写入本地和远程的磁盘。<br />
（7）数据完整性<br />
名字空间的修改必须是原子性的，它们只能有master处理：名字空间锁保证了操作的原子性和正确性，而master的操作日志在全局范围内定义了这些操作的顺序。<br />
文  件区间的状态在修改之后依赖于修改的类型，不论操作成功还是失败，也不论是不是并发操作。如果不论从哪个副本上读，所有的客户都看到同样的数据，那么文件  的这个区域就是一致的。如果文件的区域是一致的并且用户可以看到修改操作所写的数据，那么它就是已定义的。如果修改是在没有并发写操作的影响下完成的，那  么受影响的区域是已定义的，所有的client都能看到写的内容。成功的并发写操作是未定义但却是一致的。失败的修改将使区间处于不一致的状态。<br />
Write操作在应用程序指定的偏移处写入数据，而record  append操作使得数据（记录）即使在有并发修改操作的情况下也至少原子性的被加到GFS指定的偏移处，偏移地址被返回给用户。<br />
在一系列成功的修改操作后，最后的修改操作保证文件区域是已定义的。GFS通过对所有的副本执行同样顺序的修改操作并且使用块版本号检测过时的副本（由于chunkserver退出而导致丢失修改）来做到这一点。<br />
因为用户缓存了会位置信息，所以在更新缓存之前有可能从一个过时的副本中读取数据。但这有缓存的截止时间和文件的重新打开而受到限制。<br />
在修改操作成功后，部件故障仍可以是数据受到破坏。GFS通过master和chunkserver间定期的handshake，借助校验和来检测对数据的破坏。一旦检测到，就从一个有效的副本尽快重新存储。只有在GFS检测前，所有的副本都失效，这个块才会丢失。<br />
2、系统交互<br />
（1）租约（lease）和修改顺序<br />
（2）数据流<br />
我们的目标是充分利用每个机器的网络带宽，避免网络瓶颈和延迟<br />
为了有效的利用网络，我们将数据流和控制流分离。数据是以流水线的方式在选定的chunkerserver链上线性的传递的。每个机器的整个对外带宽都被用作传递数据。为避免瓶颈，每个机器在收到数据后，将它收到数据尽快传递给离它最近的机器。<br />
（3）原子性的record  Append：<br />
GFS提供了一个原子性的添加操作：record  append。在传统的写操作中，client指定被写数据的偏移位置，向同一个区间的并发的写操作是不连续的：区间有可能包含来自多个client的数  据碎片。在record append中，  client只是指定数据。GFS在其选定的偏移出将数据至少原子性的加入文件一次，并将偏移返回给client。<br />
在分布式的应用中，不同机器上的许多client可能会同时向一个文件执行添加操作，添加操作被频繁使用。如果用传  统的write操作，可能需要额外的、复杂的、开销较大的同步，例如通过分布式锁管理。在我们的工作量中，这些文件通常以多个生产者单个消费者队列的方式  或包含从多个不同 client的综合结果。<br />
Record  append和前面讲的write操作的控制流差不多，只是在primary上多了一些逻辑判断。首先，client将数据发送到文件最后一块的所有副本  上。然后向primary发送请求。Primary检查添加操作是否会导致该块超过最大的规模（64M）。如果这样，它将该块扩充到最大规模，并告诉其它  副本做同样的事，同时通知client该操作需要在下一个块上重新尝试。如果记录满足最大规模的要求，primary就会将数据添加到它的副本上，并告诉  其它的副本在在同样的偏移处写数据，最后primary向client报告写操作成功。如果在任何一个副本上record  append操作失败，client将重新尝试该操作。这时候，同一个块的副本可能包含不同的数据，因为有的可能复制了全部的数据，有的可能只复制了部  分。GFS不能保证所有的副本每个字节都是一样的。它只保证每个数据作为一个原子单元被写过至少一次。这个是这样得出的：操作要是成功，数据必须在所有的  副本上的同样的偏移处被写过。进一步，从这以后，所有的副本至少和记录一样长，所以后续的记录将被指定到更高的偏移处或者一个不同的块上，即使另一个副本  成了primary。根据一致性保证，成功的record  append操作的区间是已定义的。而受到干扰的区间是不一致的。<br />
（4）快照（snapshot）<br />
快照操作几乎在瞬间构造一个文件和目录树的副本，同时将正在进行的其他修改操作对它的影响减至最小。<br />
我们使用copy-on-write技术来实现snapshot。当master受到一个snapshot请求时，  它首先将要snapshot的文件上块上的lease。这使得任何一个向这些块写数据的操作都必须和master交互以找到拥有lease的副本。这就给  master一个创建这个块的副本的机会。<br />
副本被撤销或终止后，master在磁盘上登记执行的操作，然后复制源文件或目录树的metadata以对它的内存状态实施登记的操作。这个新创建的snapshot文件和源文件（其metadata）指向相同的块（chunk）。<br />
Snapshot之后，客户第一次向chunk  c写的时候，它发一个请求给master以找到拥有lease的副本。Master注意到chunk c的引用记数比1大，它延迟对用户的响应，选择一个chunk  handle C’,然后要求每一有chunk c的副本的chunkserver创建一个块C’。每个chunkserver在本地创建chunk  C’避免了网络开销。从这以后和对别的块的操作没有什么区别。<br />
3、MASTER操作<br />
MASTER执行所有名字空间的操作，除此之外，他还在系统范围管理数据块的复制：决定数据块的放置方案，产生新数据块并将其备份，和其他系统范围的操作协同来确保数据备份的完整性，在所有的数据块服务器之间平衡负载并收回没有使用的存储空间。<br />
3.1  名字空间管理和加锁<br />
与传统文件系统不同的是，GFS没有与每个目录相关的能列出其所有文件的数据结构，它也不支持别名（unix中的硬连接或符号连接），不管是对文件或是目录。GFS的名字空间逻辑上是从文件元数据到路径名映射的一个查用表。<br />
MASTER在执行某个操作前都要获得一系列锁，例如，它要对/d1/d2…/dn/leaf执行操作，则它必须获  得/d1，/d1/d2，…，  /d1/d2/…/dn的读锁，/d1/d2…/dn/leaf的读锁或写锁（其中leaf可以使文件也可以是目录）。MASTER操作的并行性和数据的  一致性就是通过这些锁来实现的。<br />
3.2  备份存储放置策略<br />
一个GFS集群文件系统可能是多层分布的。一般情况下是成千上万个文件块服务器分布于不同的机架上，而这些文件块服  务器又被分布于不同机架上的客户来访问。因此，不同机架上的两台机器之间的通信可能通过一个或多个交换机。数据块冗余配置策略要达到连个目的：最大的数据  可靠性和可用性，最大的网络带宽利用率。因此，如果仅仅把数据的拷贝置于不同的机器上很难满足这两个要求，必须在不同的机架上进行数据备份。这样即使整个  机架被毁或是掉线，也能确保数据的正常使用。这也使数据传输，尤其是读数据，可以充分利用带宽，访问到多个机架，而写操作，则不得不涉及到更多的机架。<br />
3.3  产生、重复制、重平衡数据块<br />
当MASTER产生新的数据块时，如何放置新数据块，要考虑如下几个因素：（1）尽量放置在磁盘利用率低的数据块服  务器上，这样，慢慢地各服务器的磁盘利用率就会达到平衡。（2）尽量控制在一个服务器上的“新创建”的次数。（3）由于上一小节讨论的原因，我们需要把数  据块放置于不同的机架上。<br />
MASTER在可用的数据块备份低于用户设定的数目时需要进行重复制。这种情况源于多种原因：服务器不可用，数据被  破坏，磁盘被破坏，或者备份数目被修改。每个被需要重复制的数据块的优先级根据以下几项确定：第一是现在的数目距目标的距离，对于能阻塞用户程序的数据  块，我们也提高它的优先级。最后，  MASTER按照产生数据块的原则复制数据块，并把它们放到不同的机架内的服务器上。<br />
MASTER周期性的平衡各服务器上的负载：它检查chunk分布和负载平衡，通过这种方式来填充一个新的服务器而  不是把其他的内容统统放置到它上面带来大量的写数据。数据块放置的原则与上面讨论的相同，此外，MASTER还决定那些数据块要被移除，原则上他会清除那  些空闲空间低于平均值的那些服务器。<br />
3.4  垃圾收集<br />
在一个文件被删除之后，GFS并不立即收回磁盘空间，而是等到垃圾收集程序在文件和数据块级的的检查中收回。<br />
当一个文件被应用程序删除之后，MASTER会立即记录下这些变化，但文件所占用的资源却不会被立即收回，而是重新  给文件命了一个隐藏的名字，并附上了删除的时间戳。在MASTER定期检查名字空间时，它删除超过三天（可以设定）的隐藏的文件。在此之前，可以以一个新  的名字来读文件，还可以以前的名字恢复。当隐藏的文件在名字空间中被删除以后，它在内存中的元数据即被擦除，这就有效地切断了他和所有数据块的联系。<br />
在一个相似的定期的名字空间检查中，MASTER确认孤儿数据块（不属于任何文件）并擦除他的元数据，在和MASTER的心跳信息交换中，每个服务器报告他所拥有的数据块，MASTER返回元数据不在内存的数据块，服务器即可以删除这些数据块。<br />
3.5  过时数据的探测<br />
在数据更新时如果服务器停机了，那么他所保存的数据备份就会过时。对每个数据块，MASTER设置了一个版本号来区别更新过的数据块和过时的数据块。<br />
当MASTER授权一个新的lease时，他会增加数据块的版本号并会通知更新数据备份。MASTER和备份都会记  录下当前的版本号，如果一个备份当时不可用，那么他的版本号不可能提高，当ChunkServer重新启动并向MASTER报告他的数据块集时，  MASTER就会发现过时的数据。<br />
MASTER在定期的垃圾收集程序中清除过时的备份，在此以前，处于效率考虑，在各客户及英大使，他会认为根本不存  在过时的数据。作为另一个安全措施，  MASTER在给客户及关于数据块的应答或是另外一个读取数据的服务器数据是都会带上版本信息，在操作前客户机和服务器会验证版本信息以确保得到的是最新  的数据。<br />
4、容错和诊断<br />
4.1 高可靠性<br />
4.1.1  快速恢复<br />
不管如何终止服务，MASTER和数据块服务器都会在几秒钟内恢复状态和运行。实际上，我们不对正常终止和不正常终止进行区分，服务器进程都会被切断而终止。客户机和其他的服务器会经历一个小小的中断，然后它们的特定请求超时，重新连接重启的服务器，重新请求。<br />
4.1.2  数据块备份<br />
如上文所讨论的，每个数据块都会被备份到放到不同机架上的不同服务器上。对不同的名字空间，用户可以设置不同的备份级别。在数据块服务器掉线或是数据被破坏时，MASTER会按照需要来复制数据块。<br />
4.1.3  MASTER备份<br />
为确保可靠性，MASTER的状态、操作记录和检查点都在多台机器上进行了备份。一个操作只有在数据块服务器硬盘上  刷新并被记录在MASTER和其备份的上之后才算是成功的。如果MASTER或是硬盘失败，系统监视器会发现并通过改变域名启动它的一个备份机，而客户机  则仅仅是使用规范的名称来访问，并不会发现MASTER的改变。<br />
4.2  数据完整性<br />
每个数据块服务器都利用校验和来检验存储数据的完整性。原因：每个服务器随时都有发生崩溃的可能性，并且在两个服务器间比较数据块也是不现实的，同时，在两台服务器间拷贝数据并不能保证数据的一致性。<br />
每个Chunk按64kB的大小分成块，每个块有32位的校验和，校验和和日志存储在一起，和用户数据分开。<br />
在读数据时，服务器首先检查与被读内容相关部分的校验和，因此，服务器不会传播错误的数据。如果所检查的内容和校验  和不符，服务器就会给数据请求者返回一个错误的信息，并把这个情况报告给MASTER。客户机就会读其他的服务器来获取数据，而MASTER则会从其他的  拷贝来复制数据，等到一个新的拷贝完成时，MASTER就会通知报告错误的服务器删除出错的数据块。<br />
附加写数据时的校验和计算优化了，因为这是主要的写操作。我们只是更新增加部分的校验和，即使末尾部分的校验和数据已被损坏而我们没有检查出来，新的校验和与数据会不相符，这种冲突在下次使用时将会被检查出来。<br />
相反，如果是覆盖现有数据的写，在写以前，我们必须检查第一和最后一个数据块，然后才能执行写操作，最后计算和记录校验和。如果我们在覆盖以前不先检查首位数据块，计算出的校验和则会因为没被覆盖的数据而产生错误。<br />
在空闲时间，服务器会检查不活跃的数据块的校验和，这样可以检查出不经常读的数据的错误。一旦错误被检查出来，服务器会拷贝一个正确的数据块来代替错误的。<br />
4.3  诊断工具<br />
广泛而细致的诊断日志以微小的代价换取了在问题隔离、诊断、性能分析方面起到了重大的作用。GFS服务器用日志来记  录显著的事件（例如服务器停机和启动）和远程的应答。远程日志记录机器之间的请求和应答，通过收集不同机器上的日志记录，并对它们进行分析恢复，我们可以  完整地重现活动的场景，并用此来进行错误分析。<br />
5 测量<br />
5.1  测试环境<br />
一台主控机，两台主控机备份，16台数据块服务器，16台客户机。<br />
每台机器：2块PIII1.4G处理器，2G内存，2块80G5400rpm的硬盘，1块100Mbps全双工网卡<br />
19台服务器连接到一个HP2524交换机上，16台客户机俩接到领外一台交换机上，两台交换机通过1G的链路相连。</p>
<p>原文转自：http://bigweb.group.javaeye.com/group/topic/5749</p>

	<h4>相关文章</h4>
	<ul class="st-related-posts">
	<li><a href="http://www.donevii.com/post/78.html" title="[转]C/C++/Perl/汇编/Java效率比较 (2006-10-11)">[转]C/C++/Perl/汇编/Java效率比较</a> (0)</li>
	<li><a href="http://www.donevii.com/post/44.html" title="ACE自适配通信环境 (2006-08-23)">ACE自适配通信环境</a> (0)</li>
	<li><a href="http://www.donevii.com/post/129.html" title="大型高并发高负载网站的系统架构 (2006-10-26)">大型高并发高负载网站的系统架构</a> (0)</li>
	<li><a href="http://www.donevii.com/post/9.html" title="flickr对javascript干的好事 (2006-08-17)">flickr对javascript干的好事</a> (0)</li>
	<li><a href="http://www.donevii.com/post/128.html" title="应用加速 (2006-10-26)">应用加速</a> (0)</li>
</ul>

]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/504.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[转] 一步一步学习Vim 全图解释</title>
		<link>http://www.donevii.com/post/357.html</link>
		<comments>http://www.donevii.com/post/357.html#comments</comments>
		<pubDate>Mon, 17 Dec 2007 01:10:14 +0000</pubDate>
		<dc:creator>gavinkwoe</dc:creator>
				<category><![CDATA[doc]]></category>
		<category><![CDATA[unix]]></category>
		<category><![CDATA[vim]]></category>
		<category><![CDATA[基本]]></category>
		<category><![CDATA[操作]]></category>
		<category><![CDATA[类]]></category>
		<category><![CDATA[键盘]]></category>

		<guid isPermaLink="false">http://www.donevii.com/post/357.html</guid>
		<description><![CDATA[Whole 中文版 感谢 fdl 的翻译工作 以下是学习这些基本的VIM命令的基本步骤，在学习完之后，可以依照上图进一步的学习和使用！！ 步骤1 注释1: 1) h j k l为光标键，分别左下上右 2) i 插入键进入... ]]></description>
			<content:encoded><![CDATA[<p><img resized="1" border="0" vspace="5" width="450" src="http://unix-cd.com/vc/attachment/070821/26a4d44679.gif" style="display: block" title="ctrl+鼠标滚轮缩放" /><br />
Whole 中文版 感谢 fdl 的翻译工作</p>
<p><img resized="1" border="0" vspace="5" width="450" src="http://unix-cd.com/vc/attachment/070821/48c300a889.png" style="display: block" title="ctrl+鼠标滚轮缩放" /><br />
以下是学习这些基本的VIM命令的基本步骤，在学习完之后，可以依照上图进一步的学习和使用！！<br />
步骤1</p>
<p>注释1:<br />
1) h j k l为光标键，分别左下上右<br />
2) i 插入键进入编辑模式，Esc键退出到一般模式<br />
3) x 删除当前光标所在字符，X删除当前光标前的一个字符<br />
4) A 在一行的末尾添加text，a在当前光标后添加text<br />
5) u 执行Undo操作，Ctrl+R表示Redo<br />
6) 0 跳到行首，$ 跳到行尾 ^跳到行首(类似正则式$和^的意义)<br />
7) w b e移动一个单词word(全部是字符或者符号)<br />
8) W B E移动一个单词WORD(以空格隔开)<br />
9) R 进入插入编辑模式，并且对被编辑位置进行覆盖<br />
10) :w 保存 :q 退出 :q! 强制退出</p>
<p align="center"><img resized="1" border="0" vspace="5" width="450" src="http://unix-cd.com/vc/attachment/070821/018e06e545.gif" style="display: block" title="ctrl+鼠标滚轮缩放" /></p>
<p>步骤2</p>
<p>注释2:<br />
1) f 移动光标从当前位置到下一个f后跟的字符的右边,包含此指定字符,F 方向相反为前一个，需要和操作(operator)配合操作<br />
2) t 和 T类似f 和 F，只是它们一直到指定的字符左才停，即不包含指定的字符。<br />
3) d 删除操作 与上面 w f t 等配合：例如&#8221;dw&#8221;删除下一word，&#8221;df-&#8221;从当前位置一直往前删除到字符&#8221;-&#8221;，不保留&#8221;-&#8221;，&#8221;dt-&#8221;删除直到&#8221;-&#8221;但是保留&#8221;-&#8221;<br />
4) c 删除且进入编辑模式(类似d和i的结合),同样可以与f t T w 等motion结合<br />
5) j k 可以和c和d结合，删除所有末尾或开头的所有行<br />
6) . 可以重复最后一次编辑的所有操作，注意是在一般模式下重复最后一次编辑操作<br />
7) 操作和motion可以和数字组合，&#8221;d2w&#8221; 删除后2个单词word &#8220;d2t,&#8221;删除知道这一行的第2个&#8221;,&#8221;<br />
8) cc 和 dd 删除当前行<br />
9) v 进入可视模式</p>
<p align="center"><img resized="1" border="0" vspace="5" width="450" src="http://unix-cd.com/vc/attachment/070821/a94bd4850c.gif" style="display: block" title="ctrl+鼠标滚轮缩放" /></p>
<p>步骤3</p>
<p>注释3:<br />
1) y 后面跟任意的motion执行copy操作<br />
2) p (paster)粘贴，如果复制的是字符形式，则粘贴到右边，行形式粘贴到下面<br />
3) P 同上p操作，但是粘贴方向相反，在左边或上面<br />
4) yy 复制当前行<br />
5) y 也可以在可视模式(visual mode)下工作，同时，d,c,x..等text删除操作也将被删除内容进行复制(这一点要注意)<br />
6) &#8220;和a-z字符如果在复制/删除/粘贴命令前表示选择一个寄存器暂存<br />
7) 在复制/删除前的A-Z 寄存器意味进行叠加复制，就是多个复制操作的内容将被加起来<br />
8) &#8220;*操作或者&#8221;+操作选择系统的剪贴板<br />
9) o 在当前行下新其一行进入插入模式，O 操作类似o，只是在当前行的上新起一行</p>
<p align="center"><img resized="1" border="0" vspace="5" width="450" src="http://unix-cd.com/vc/attachment/070821/94ec70d670.gif" style="display: block" title="ctrl+鼠标滚轮缩放" /></p>
<p>步骤4</p>
<p>注释4:<br />
1) / 是基本的查找motion，可以和operation结合执行操作，也可以单独使用，直接查找匹配后面的正则式<br />
2) ? 与/相同，但是方向相反backward，/ 方向forward<br />
3) n 重复最后一次查找方向， N 重复最后一次查找的相反方向<br />
4) * 和 # 查找当前光标所在的实体相同的实体，前者方向向前，后者方向向后，两者仅在vim下有效(vi not support)</p>
<p align="center"><img resized="1" border="0" vspace="5" width="450" src="http://unix-cd.com/vc/attachment/070821/64599d02d8.gif" style="display: block" title="ctrl+鼠标滚轮缩放" /></p>
<p>步骤5</p>
<p>注释5:<br />
1) m 操作后跟a-z字符用来设置一个标记mark<br />
2) ` 后面跟a-z字符可以去这个字符表示的标记处<br />
3) &#8216; 和一个字符可以到所在行的第一个非空处<br />
4) A-Z 标记为全局标记 a-z仅在每个buffer内可见<br />
5) `. 表示到最后一个修改的地方<br />
6) q 后跟字符a-z用来记录宏<br />
7) @ 后跟字符用了重放宏，@@ 重放最后一个宏</p>
<p align="center"><img resized="1" border="0" vspace="5" width="450" src="http://unix-cd.com/vc/attachment/070821/fa0e3069f7.gif" style="display: block" title="ctrl+鼠标滚轮缩放" /></p>
<p>步骤6</p>
<p>注释6:<br />
1) % 在配对的( 和 ) [ 和 ]等 之间跳动<br />
2) H M L 直接跳到整个屏幕的最上面，中间和最下面<br />
3) G 跳到文件的末尾，G 前面跟行号表明跳到指定的行<br />
4) &#8211; 或者 + 直接跳到前一行 或 后一行<br />
5) K 跳到帮助<br />
6) ( 和 )跳到当前句子的最前和最后<br />
7) { 和 }跳到前一个空行 或 后一个空行<br />
8) [[ 跳到前一个第0列是{的位置(必须{为第0列)<br />
9) ]] 跳到下一个第0列是}的位置</p>
<p align="center"><img resized="1" border="0" vspace="5" width="450" src="http://unix-cd.com/vc/attachment/070821/07f967eb50.gif" style="display: block" title="ctrl+鼠标滚轮缩放" /></p>
<p>步骤7</p>
<p>注释7:<br />
1) J 连接当前行和下一行在一般模式，或所有行在可是模式<br />
2) r 后跟任意字符替换当前字符<br />
3) C 为c$的简写，表示删除当前位置到行结束并进入插入模式<br />
4) D 为d$的简写，表示删除当前到行结束<br />
5) Y 为yy的简写，复制整个行<br />
6) s 删除当前光标所在字符并进入插入模式<br />
7) S 清除当前行，并进入插入模式<br />
8) &gt; 和一个motion 表示缩进一行或多行<br />
9) &lt; 和一个motion 表示不缩进<br />
10) = 和一个motion 表示重新格式化text<br />
11) &gt; &lt; 和 = 工作在可视模式下，且均可重复例如&gt;&gt;表示缩进整个当前行</p>
<p align="center"><img resized="1" border="0" vspace="5" width="450" src="http://unix-cd.com/vc/attachment/070821/57f82f590b.gif" style="display: block" title="ctrl+鼠标滚轮缩放" /></p>

	<h4>相关文章</h4>
	<ul class="st-related-posts">
	<li><a href="http://www.donevii.com/post/229.html" title="设计模式之Life time controller模式 (2006-11-27)">设计模式之Life time controller模式</a> (0)</li>
	<li><a href="http://www.donevii.com/post/297.html" title="目前主流开发技术的分析和总结 (2007-01-19)">目前主流开发技术的分析和总结</a> (0)</li>
	<li><a href="http://www.donevii.com/post/89.html" title="用rsync对网站进行镜像备份 (2006-10-18)">用rsync对网站进行镜像备份</a> (0)</li>
	<li><a href="http://www.donevii.com/post/128.html" title="应用加速 (2006-10-26)">应用加速</a> (0)</li>
	<li><a href="http://www.donevii.com/post/78.html" title="[转]C/C++/Perl/汇编/Java效率比较 (2006-10-11)">[转]C/C++/Perl/汇编/Java效率比较</a> (0)</li>
</ul>

]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/357.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>为了父母, 你需要知道患了中风该怎么做？</title>
		<link>http://www.donevii.com/post/358.html</link>
		<comments>http://www.donevii.com/post/358.html#comments</comments>
		<pubDate>Sun, 09 Dec 2007 11:28:56 +0000</pubDate>
		<dc:creator>dengwei</dc:creator>
				<category><![CDATA[life]]></category>
		<category><![CDATA[blog]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[raise]]></category>
		<category><![CDATA[smile]]></category>
		<category><![CDATA[talk]]></category>
		<category><![CDATA[unix]]></category>
		<category><![CDATA[中风]]></category>
		<category><![CDATA[父母]]></category>
		<category><![CDATA[疾病]]></category>
		<category><![CDATA[症兆]]></category>

		<guid isPermaLink="false">http://www.donevii.com/post/358.html</guid>
		<description><![CDATA[不怕一万,就怕万一! 这样的小常识,还是挺不错的, 大家看看吧! 了解知识，预防意外，有备无患！ 为了父母,你需要知道患了中风，脑部的微血管， 会慢慢的破裂，遇到这种情形, 千万别慌， 患... ]]></description>
			<content:encoded><![CDATA[<p>不怕一万,就怕万一! 这样的小常识,还是挺不错的, 大家看看吧!<br />
了解知识，预防意外，有备无患！<br />
为了父母,你需要知道患了中风，脑部的微血管，<br />
会慢慢的破裂，遇到这种情形, 千万别慌，<br />
患者无论在什么地方(不管是浴室、卧房或客厅），千万不可搬动他。<br />
因为，如果移动，会加速微血管的破裂。<br />
所以要先原地把患者扶起坐稳, 以防止再摔倒，这时才开始（放血)。 <br />
家中如有专为注射用的针，当然最好。<br />
如果没有 ，就拿缝衣用的针，就在患者的十个手指头尖儿（没有固定穴道，大约距离手指甲一分之处刺上去，要刺出血来(万一血不出来，可用手挤)，<br />
等十个手指头都流出血来( 每指一 滴) ，大约几分钟之后，患者就会自然清醒。<br />
如果嘴也歪了，就拉他的耳朵，把耳朵拉红，在两耳的耳垂儿的部位各刺两针，也各流两滴血，几分钟以后，嘴就恢复原状了。<br />
等患者一切恢复正常感觉没有异状时再送医，就一定可以转危为安，否则，若急着抬上救护车送医，经一路的颠跛震动恐怕还没到医院，他脑部微血管，差不多已经都破裂了。万一能够吉人天相，保全老命，能象孙院长，容得勉强行动，那得要靠祖上积德了。<br />
放血救命法， 是住在新竹的中医师夏伯挺先生说的。且是经自己亲身实验,敢说百分之百有效。<br />
大概是民国六十八年一位在台中逢甲学院任教的教师，有天上午正在上课，一位老师 跑到他的教室上气不接下气的说∶刘老师快来 ，主任中风了；他立刻跑到三楼， 看到陈幅添主任，气色不正， 语意模糊，嘴也歪了，很明显的是中风了。立即请工读生到校门外的西药房，买来一支注射用的针头，就在陈主任十个手指头上直刺。等十个手指尖儿都见血了（豆粒似的一滴），大约几分钟以后，陈主任的气色就变过来了，两眼也有神了，只有嘴还歪着，他就拉搓陈主任的耳朵，使之充血，等把耳朵拉红，就在左右耳垂之处，各刺两 针，待两耳垂都流出两滴血来，奇迹就出现了，大约不到三五分钟，他的嘴形，恢复正常了，说话也清清楚楚了。让陈主任静坐一阵子，喝了一 杯热茶，才扶他下楼，开车送到惠华医院，打一罐点滴，休息了一夜就出院回学校上课了。一切照常工作，毫无后遗症。<br />
反观一般脑中风患者，都是送医院治疗时，经过一路震荡血管急速破裂，以致多数患者一病不起，所以脑中 风，在死因排行榜上高居第二位，其最幸运者，也仅能保住老命，而落得终身残废。这是一个多么可怕的病症。如果大家都能记住这（放血救命) 的方法，立刻施救，在短短时间它能起死回生，而且保证百分之百的正常。<br />
这个急救法，希望大家告诉大家。那脑中风，在死因排行榜上，就可以除名。<br />
     ●阅后传知他人，功德无量●<br />
辨识中风~很短<br />
有个朋友瑛格莉，在一次烤肉聚会当中绊倒了，摔了一跤，旁边的朋友建议找医护人员，但她很确定自己没事，只是穿了新鞋被砖块绊了一下罢了。<br />
瑛格莉还有点危危颤颤站立不稳的时候，朋友们帮她清洗干净，又为她承了一盘新的食物，然后她就跟着大家一起享受接下来的时光了。<br />
瑛格莉的先生后来打电话通知大家，他的太太被送到医院，傍晚六点，瑛格莉就过世了，原因是她在烤肉聚餐的时候中风。<br />
如果他们懂得辨识中风的症兆，瑛格莉现在也许还跟我们在一起。<br />
有些人不会死，但结局是处于无助无望的景况中。<br />
只需要花一分钟的时间读完这篇文章，神经科医师说，如果他能在三小时之内接触到中风患者，他就可以将中风的后果完全扭转过来。<br />
诀窍就是辨识诊断出中风的问题，并让病患在三小时之内接受医疗，而这是很难的。<br />
辨识中风<br />
感谢上帝让我们记住STR三步骤，请阅读并学习 !<br />
有时候中风的症兆很难辨认，不幸的是，缺乏警觉就会带来灾难。<br />
身边的人辨认不出中风的征兆，中风患者就会造成严重的脑伤。<br />
医生说，旁边的人只要问三个简单的问题，就可以辨识中风：<br />
     S ：(smile) 要求患者笑一下<br />
     T ：（talk）要求患者说一句简单的句子（要有条理，有连惯性）例如：今天天气晴朗。<br />
     R ：（<a href="http://www.donevii.com/post/tag/raise" class="st_tag internal_tag" rel="tag" title="Posts tagged with raise">raise</a>）要求患者举起双手<br />
※注意：另外一项中风症兆是：要求患者伸出舌头，如果舌头是「弯曲」的，如果舌 头偏向一边，那也是中风的症兆。<br />
＊上面四个动作，患者如果有任何一个动作做不来，就要立刻打120！！！<br />
并且把症状描述给接线员听。<br />
心脏科医师说，收到这封电函的人，若能将它转寄给10 个人，就至少可以救一条命。</p>
<p>转自：<a href="http://blog.chinaunix.net/u/524/showart_317750.html" title="http://blog.chinaunix.net/u/524/showart_317750.html">http://blog.chinaunix.net/u/524/showart_317750.html</a></p>

	<h4>相关文章</h4>
	<ul class="st-related-posts">
	<li><a href="http://www.donevii.com/post/348.html" title="redhad下的openssl(安装和卸载) (2007-09-21)">redhad下的openssl(安装和卸载)</a> (0)</li>
	<li><a href="http://www.donevii.com/post/504.html" title="google背后的分布式架构 (2008-09-23)">google背后的分布式架构</a> (0)</li>
	<li><a href="http://www.donevii.com/post/9.html" title="flickr对javascript干的好事 (2006-08-17)">flickr对javascript干的好事</a> (0)</li>
	<li><a href="http://www.donevii.com/post/124.html" title="C++资源之不完全导引 (2006-10-26)">C++资源之不完全导引</a> (0)</li>
	<li><a href="http://www.donevii.com/post/112.html" title="這十八招Web2.0創業者千萬不要學 (2006-10-24)">這十八招Web2.0創業者千萬不要學</a> (0)</li>
</ul>

]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/358.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>redhad下的openssl(安装和卸载)</title>
		<link>http://www.donevii.com/post/348.html</link>
		<comments>http://www.donevii.com/post/348.html#comments</comments>
		<pubDate>Fri, 21 Sep 2007 10:47:46 +0000</pubDate>
		<dc:creator>dengwei</dc:creator>
				<category><![CDATA[doc]]></category>
		<category><![CDATA[blog]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[openssl]]></category>
		<category><![CDATA[redhat]]></category>
		<category><![CDATA[server]]></category>
		<category><![CDATA[unix]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://www.donevii.com/?p=348</guid>
		<description><![CDATA[转至: http://blog.csdn.net/baitianhai/archive/2004/10/27/155461.aspx 最近在鼓捣redhat linux，想自己以源代码方式安装软件，不想用rpm方式安装。 首先从httpd开始，先卸载在安装倒是比较容易，不过后来像添加... ]]></description>
			<content:encoded><![CDATA[<p>转至: http://blog.csdn.net/baitianhai/archive/2004/10/27/155461.aspx</p>
<p>最近在鼓捣redhat linux，想自己以源代码方式安装软件，不想用rpm方式安装。</p>
<p>首先从httpd开始，先卸载在安装倒是比较容易，不过后来像添加ssl功能，发现编译的时候需要用<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>的安装目录，本人比较愚笨，一顿好找也没有找到，于是就想把<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>也以源代码方式安装。先卸载，此时出现问题，系统好多东西依赖于<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>的库，我查了好多资料也没找到什么办法，于是我最后一狠心，用rpm -e &#8211;nodeps给卸载了，然后手动安装了<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>，然后重新启动，这下坏了，好多服务都起不来了，smb,ssh等等，图形模式也起不来了，我欲哭无泪。</p>
<p>因为我是在虚拟机上安装的，smb起不来了，我只能重新安装系统了。这次安装我大多数东西都没选择，一路安装完毕，结果在文本方式发现vi编辑没有颜色了，哎，也不知道是少装了那个东西弄得（各位谁知道麻烦告诉告诉我一下），只能按照猜测重新安装了又添加了一些东西。不过幸运的vi高亮显示功能又有了，遗憾的是具体是那个软件我还是不清楚。有了上次的教训我不敢轻易卸掉系统原来的<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>了，我从网上搜索到了一篇安装<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>的英文文章，地址在 http://www.devside.net/web/<a href="http://www.donevii.com/post/tag/server" class="st_tag internal_tag" rel="tag" title="Posts tagged with server">server</a>/linux/<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a> 我按照上面说的安装了zlib,<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>。步骤简介如下（怕以后忘了）</p>
<p>安装zlib</p>
<p>Home : http://www.gzip.org/zlib/</p>
<p>Package(linux source) : http://www.gzip.org/zlib/</p>
<p>Our Configuration</p>
<p>Install to : /usr/local</p>
<p>Module types : dynamically and staticly loaded modules, *.so and *.a</p>
<p>Build Instructions</p>
<p>zlib library files are placed into /usr/local/lib and zlib header files are placed into /usr/local/include, by default.</p>
<p>Build static libraries</p>
<p>&#8230;/zlib-1.2.1]# ./configure</p>
<p>&#8230;/zlib-1.2.1]# make test</p>
<p>&#8230;/zlib-1.2.1]# make install</p>
<p>Build shared libraries</p>
<p>&#8230;/zlib-1.2.1]# make clean</p>
<p>&#8230;/zlib-1.2.1]# ./configure &#8211;shared</p>
<p>&#8230;/zlib-1.2.1]# make test</p>
<p>&#8230;/zlib-1.2.1]# make install</p>
<p>&#8230;/zlib-1.2.1]# cp zutil.h /usr/local/include</p>
<p>&#8230;/zlib-1.2.1]# cp zutil.c /usr/local/include</p>
<p>/usr/local/lib should now contain&#8230;</p>
<p>libz.a</p>
<p>libz.so -&gt; libz.so.1.2.1</p>
<p>libz.so.1 -&gt; libz.so.1.2.1</p>
<p>libz.so.1.2.1</p>
<p>/usr/local/include should now contain&#8230;</p>
<p>zconf.h</p>
<p>zlib.h</p>
<p>zutil.h</p>
<p>[Optional] Instructions for non-standard placement of zlib</p>
<p>Create the directory that will contain zlib</p>
<p>&#8230;/zlib-1.2.1]# mkdir /usr/local/zlib</p>
<p>Follow the given procedure above, except</p>
<p>&#8230;/zlib-1.2.1]# ./configure &#8211;prefix=/usr/local/zlib</p>
<p>Update the Run-Time Linker</p>
<p>/etc/ld.so.cache will need to be updated with the new zlib shared lib: libz.so.1.2.1</p>
<p>For standard zlib installation&#8230;</p>
<p>Add /usr/local/lib to /etc/ld.so.conf, if specified path is not present</p>
<p>/etc]# ldconfig</p>
<p>If zlib was installed with a prefix&#8230;</p>
<p>Add /usr/local/zlib/lib to /etc/ld.so.conf</p>
<p>/etc]# ldconfig</p>
<p>安装<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a></p>
<p>Download</p>
<p>Home : http://www.<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>.org/</p>
<p>Package(source) : <a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>-0.9.7d.tar.gz</p>
<p>Our Configuration</p>
<p>install to : /usr/local/ssl</p>
<p>module types : dynamically and staticly loaded modules, *.so *.a</p>
<p>Build Instructions</p>
<p>&#8230;/<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>-0.9.7d]# ./config</p>
<p>&#8211;prefix=/usr/local/ssl</p>
<p>[default location]</p>
<p>shared</p>
<p>[in addition to the usual static libraries, create shared libraries]</p>
<p>zlib-dynamic</p>
<p>[like "zlib", but has <a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">OpenSSL</a> load the zlib library dynamically when needed]</p>
<p>&#8230;/<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>-0.9.7d]# ./config -t</p>
<p>[display guess on system made by ./config]</p>
<p>&#8230;/<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>-0.9.7d]# make</p>
<p>&#8230;/<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>-0.9.7d]# make test</p>
<p>&#8230;/<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>-0.9.7d]# make install</p>
<p>Update the Run-time Linker</p>
<p>ld.so.cache will need to be updated with the location of the new <a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">OpenSSL</a> shared libs: libcrypto.so.0.9.7 and libssl.so.0.9.7</p>
<p>Sometimes it is sufficient to just add these two files to /lib, but we recommend you follow these instructions instead.</p>
<p>Edit /etc/ld.so.conf</p>
<p>Add /usr/local/ssl/lib to the bottom.</p>
<p>&#8230;]# ldconfig</p>
<p>Update the PATH</p>
<p>Edit /root/.bash_profile</p>
<p>Add /usr/local/ssl/bin to the PATH variable.</p>
<p>Re-login</p>
<p>Testing</p>
<p>&#8230;]# <a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a> version</p>
<p>Should display <a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">OpenSSL</a> 0.9.7d 17 Mar 2004</p>
<p>If an older version is shown, your system contains a previously installed <a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">OpenSSL</a>.</p>
<p>Repeate the steps in Update the PATH, except place the specified location at the start of the PATH variable.</p>
<p>[the older <a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>, on most systems, is located under /usr/bin]</p>
<p>[the command 'which <a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>' should display the path of the <a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a> that your system is using]</p>
<p>/usr/local/ssl/bin]# ./<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a> version should display the correct version.</p>
<p>但是我最后没有得到想要的结果，系统原来的<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>还是没能卸载掉，我该怎么做那？我继续搜索资料，哈，幸运的我找了，在一个国内论坛上是这么说的</p>
<p>cd /usr/local/ssl/lib</p>
<p>ln -s libcrypto.so.0.9.7 libcrypto.so.2</p>
<p>ln -s libssl.so.0.9.7 libssl.so.2</p>
<p>//最后要刷新系统的动态连接库配置</p>
<p>echo /usr/local/ssl/lib &gt;&gt; /etc/ld.so.conf</p>
<p>ldconfig -v</p>
<p>这下子我豁然开朗，原来依赖的那2个文件是个软链接啊，我把它修改为我现在真正的<a href="http://www.donevii.com/post/tag/openssl" class="st_tag internal_tag" rel="tag" title="Posts tagged with openssl">openssl</a>库文件不是就行了吗？于是一顿忙碌后，我终于执行了 rpm -e -nodeps ，然后重新启动系统，一路运行下去，全是绿灯。一时间感觉自己好幸福啊</p>
<p>为了这个问题我查了国内的几个比较大的unix/linux网站都没找到资料，不过从这里http://bbs.netbuddy.org/unix/737.<a href="http://www.donevii.com/post/tag/html" class="st_tag internal_tag" rel="tag" title="Posts tagged with html">html</a>还是找到了（国外的E文大概意思能看懂，但是查找起来还是没找到，也不知道这方面好点的网站），</p>

	<h4>相关文章</h4>
	<ul class="st-related-posts">
	<li><a href="http://www.donevii.com/post/504.html" title="google背后的分布式架构 (2008-09-23)">google背后的分布式架构</a> (0)</li>
	<li><a href="http://www.donevii.com/post/78.html" title="[转]C/C++/Perl/汇编/Java效率比较 (2006-10-11)">[转]C/C++/Perl/汇编/Java效率比较</a> (0)</li>
	<li><a href="http://www.donevii.com/post/758.html" title="清除指定squid缓存文件的脚本 (2009-02-09)">清除指定squid缓存文件的脚本</a> (0)</li>
	<li><a href="http://www.donevii.com/post/128.html" title="应用加速 (2006-10-26)">应用加速</a> (0)</li>
	<li><a href="http://www.donevii.com/post/129.html" title="大型高并发高负载网站的系统架构 (2006-10-26)">大型高并发高负载网站的系统架构</a> (0)</li>
</ul>

]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/348.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>目前主流开发技术的分析和总结</title>
		<link>http://www.donevii.com/post/297.html</link>
		<comments>http://www.donevii.com/post/297.html#comments</comments>
		<pubDate>Fri, 19 Jan 2007 04:32:34 +0000</pubDate>
		<dc:creator>dengwei</dc:creator>
				<category><![CDATA[doc]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[ror]]></category>
		<category><![CDATA[unix]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[windows]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[类]]></category>

		<guid isPermaLink="false">http://www.donevii.com/?p=297</guid>
		<description><![CDATA[转至: www.csdn.net 主流的程序设计语言：C++、Delphi(ObjectPascal)、Java、C# 　　桌面应用程序框架：MFC、VCL、QT、JavaAWT\SWING、.Net 　　企业应用程序框架：WindowsDNA（ASP、COM、COM+）、J2EE、.NetFramework 　... ]]></description>
			<content:encoded><![CDATA[<p>转至: www.csdn.net</p>
<p>主流的程序设计语言：C++、Delphi(ObjectPascal)、Java、C#</p>
<p>　　桌面应用程序框架：MFC、VCL、QT、JavaAWT\SWING、.Net</p>
<p>　　企业应用程序框架：WindowsDNA（ASP、COM、COM+）、J2EE、.NetFramework</p>
<p>　　开发工具：VisualBasic、Delphi、VisualC++、C++Builder、VisualC#<br />*程序设计语言：C++\Delphi（本来应该是ObjectPascal，但为了简单，我就语言和工具混为一谈吧）\Java\C#(虽然他刚刚推出，但因为微软为之倾注了大量心血，一定会成为一种重要的开发语言)</p>
<p>　　　*桌面应用程序框架:MFC\VCL</p>
<p>　　　*企业应用程序框架:WindowsDNA\J2EE\.Net</p>
<p>　　　*COM技术：我单独提出这项技术，是因为它无法简单的被视为语言、桌面应用程序框架或企业应用程序框架，它与这些都有关系。</p>
<p>　　2.1 程序设计语言</p>
<p>　　2.1.1 C++语言的演进</p>
<p>　 　最初要从二进制代码和汇编说起，但那太遥远了。我们就从面向过程的语言说起吧（包括Basic\C\Fortran\Pascal）。这种面向过程的高 级语言终于把计算机带入了寻常的应用领域。其中的C语言因为它的简单和灵活造就了Unix和Windows这样的伟大的软件。</p>
<p>　　面向对象 的语言是计算机语言的一个合乎逻辑的进化，因为在没有过多的影响效率、简单性的前提下提供了一种更好的组织数据的方法，可使程序更容易理解，更容易管理 &mdash;&mdash;这一点可能会引出不同意见，但事实胜于雄辩，C++终于让C语言的领地越来越小，当今还活着的计算机语言或多或少的都具备面向对象的特征，所以这一点 并不会引起太多困惑。C++的成功很大程度要归因于C，C++成为它今天的样子是合乎逻辑的产物。因为在面向过程的时代，C几乎已经统一天下了。今天著名 的语言象Java\C#都从C借鉴了很多东西，C#本来的意思就是C++++。其实C++曾经很有理由统一面向对象程序设计语言的天下来着，但可惜的是， C++太复杂了。即使是一个熟练的程序员，要你很清楚的解释一些问题你也会很头痛。举几个还不是那么复杂的例子来说：</p>
<p>　　对=的重载\成员转换函数\拷贝构造函数\转化构造函数之间有什么区别和联系呢？</p>
<p>　　定义一个类成员函数private:virtualvoidMemFun()=0;是什么意义呢？</p>
<p>　　int(*(*x(int))[4])(double);是什么意思？</p>
<p>　 　还有其他的特征，比如说可以用来制造一种新语言的typedef和宏（虽然宏不是C++的一部分，但它与C++的关系实在太密切了），让你一不小心就摔 跤的内存问题（只要new和delete就可以了吗？有没有考虑一个对象存放在容器中的情况？）&hellip;&hellip;诸如此类，C++是如此的复杂以至于要学会它就需要很 长的时间，而且你会发现即使你用C++已经好几年了，你还会发现经常有新东西可学。你想解决一个应用领域的问题&mdash;&mdash;比如说从数据库里面查询数据、更改数据 那样的问题，可是你却需要首先为C++头痛一阵子才可以，是的，你精通C++，你可以很容易的回答我的问题，可是你有没有想过你付出了多大的代价呢？我不 是想过分的谴责C++，我本人喜欢C++，我甚至建议一个认真的开发普通的应用系统的程序员也去学习一下C++，C++中的一些特性，比如说指针运算\模 板\STL几乎让人爱不释手，宏可以用几个字符代替很多代码，对系统级的程序员来说，C++的地位是不可替代的，Java的虚拟机就是C++写的。C++ 还将继续存在而且有旺盛的生命力。<br />2.1.2 Java和C#</p>
<p>　　Java和C#相对于C++的不同最大的有两点：第一点是他们运 行在一个虚拟环境之中，第二点是语法简单。对于开发人员而言，在语法和语言机制的角度可以把Java和C#视为同一种语言。C#更多的是个政治的产物而不 是技术产物。如果不是Sun为难微软的话，我想微软不会费尽心力推出一个和Java差不多的C++++，记得Visual J++吗，记得WFC吗？看看那些东西就会知道微软为Java曾经倾注了多少心血。而且从更广泛的角度来说，两者也是非常相似的&mdash;&mdash;C#和Java面对的 是同样的问题，面向应用领域的问题：事务处理、远程访问、Webservice、Web页面发布、图形界面。那么在这一段中，我暂且用Java这个名字指 代Java和C#两种语言&mdash;&mdash;尽管两者在细节上确实有区别。Java是适合解决应用领域的问题的语言。最大的原因Java对于使用者来说非常简单。想想你 学会并且能够使用Java需要多长时间，学会并且能够使用C++要多长时间。由于Java很大程度上屏蔽了内存管理问题，而且没有那么多为了微小的性能提 升定义的特殊的内容（比如说，在Java里面没有virtual这个关键字,Java也不允许你直接在栈上创建对象，Java明确的区分bool和整型变 量），他让你尽量一致的方式操作所有的东西，除了基本数据类型，所有的东西都是对象，你必须通过引用来操作他们；除了这些之外，Java还提供了丰富的类 库帮助你解决应用问题&mdash;&mdash;因为它是面向应用的语言，它为你提供了多线程标准、JDBC标准、GUI标准，而这些标准在C++中是不存在的，因为C++并不 是直接面向解决应用问题的用户，有人试图在C++中加入这些内容，但并不成功，因为C++本身太复杂了，用这种复杂的语言来实现这种复杂的应用程序框架本 身就是一件艰难的事情，稍后我们会提到这种尝试&mdash;&mdash;COM技术。渐渐的，人们不会再用C++开发应用领域的软件，象MFC\QT\COM这一类的东西最终 也将退出历史舞台。</p>
<p>　　2.1.3 Delphi</p>
<p>　　Delphi是从用C++开发应用系统转向用Java开发应用系统的一 个中间产物。它比C++简单，简单的几乎象Java一样，因为它的简单，定义和使用丰富的类库成为可能，而且Delphi也这么做了，结果就是VCL和其 他的组件库。而另一方面，它又比运行于虚拟环境的Java效率要高一些，这样在简单性和效率的平衡之中，Delphi找到了自己的生存空间。而且预计在未 来的一段时间之内，这个生存空间将仍然是存在的。可以明显的看出，微软放弃了这个领域，他专注于两方面：系统语言C++和未来的Java(其实是. Net)。也许这对于Borland来说，是一件很幸运的事情。如果我能够给Borland提一些建议的话，那就是不要把Delphi弄得越来越复杂，如 果那样，就是把自己的用户赶到了C++或Java的领地。在虚拟机没有最终占领所有的应用程序开发领域之前，Delphi和Delphi的用户仍然会生存 得很好。</p>
<p>　　2.2桌面应用程序框架</p>
<p>　　目前真正成功的桌面应用程序框架只有两个，一个是MFC，一个是VCL，还有一些其他的，但事实上并未进入应用领域。遗憾的是我对两个桌面应用程序框架都不精通。但这不妨碍我对他做出正确的评价。</p>
<p>　　2.2.1MFC</p>
<p>　 　MFC（还有曾经的OWL）是SDK编程的正常演化的结果，就象是C++是C的演化结果一样。MFC本身是一件了不起但不那么成功的作品，而且它过时 了。这就是我的结论。MFC凝聚了很多天才的智慧&mdash;&mdash;当然，OWL和VCL也一样，侯捷的《深入浅出MFC》把这些智慧摆在了我们的面前。但是这件东西用 起来估计不会有人觉得很舒服，如果你一直在用Java、VB或者Delphi，再回过头来用MFC，不舒服的感觉会更加强烈。我不能够解释MFC为什么没 有能够最终发展成和VCL一样简单好用的桌面程序框架，也许是微软没精力或者没动力，总之MFC就是那个样子了，而且也不会再有发展，它已经被抛弃了。我 有时候想，也许基于C++这种复杂的语言开发MFC这样的东西本身就是错误的&mdash;&mdash;可以开发这样的一个框架，但不应当要求使用它的人熟悉了整个框架之后才能 够使用这个系统，但很显然，如果你不了解MFC的内部机制，是不太可能把它用好的，我不能解释清楚为什么会出现这种现象。</p>
<p>　　2.2.2VCL</p>
<p>　 　相比之下VCL要成功的得多。我相信很多使用VCL的人可能没有像MFC的用户研究MFC那样费劲的研究过VCL的内部机制。但这不妨碍他们开发出好用 好看的应用程序，这就足够了，还有什么好说的呢？VCL给你提供了一种简单一致的机制，让你可以写出复杂的应用程序。在李维的Borland故事那篇文章 中曾经说过，在Borland C++ 3.1推出之后Borland就有人提出开发类似C++ Builder一类的软件，后来竟未成行。是啊，如果C++ Builder是在那个时候出现的，今天的软件开发领域将会是怎么样的世界呢？真的不能想象。也许再过一段时间，这些都将不再重要。因为新生的语言如 Java和C#都提供了类似于VCL的桌面应用程序框架。那个时候，加上Java和C#本身的简单性，如果他们的速度有足够块，连Delphi这种语言也 要消失了，还有什么好争论的呢？只是对于今天的桌面程序开发人员来说，VCL确实是最好的选择。<br />2.3 企业应用程序框架</p>
<p>　　2.3.1 Windows DNA</p>
<p>　 　Windows DNA的起源无从探究了。随着.Net的推出，事实上Windows DNA将成为历史的陈迹。Windows DNA虽然是几乎所有的企业应用程序开发人员都知道的一个名词，但我相信Windows DNA事实上应用的最广泛的是ASP而不是COM+。真正的COM开发有多少人真正的掌握了呢，更不要提COM+(我有必要解释一下：COM+是COM的 执行环境，它提供了一系列如事务处理、安全等基础服务，让应用程序开发人员尽量少在基础架构设计上花精力)&mdash;&mdash;当然我这里指的COM开发不是指VB下的 COM开发，所以要这么说，是因为我觉得如果不能理解用C++进行COM开发，也就不能真正的理解COM技术。如果以一种技术没有被广泛理解和应用作为失 败的标志，那么Windows DNA实际上是失败了，但这不是它本身的错，而是基于C++的COM技术的失败造成的。多层应用、系统开发角色分离的概念依然没有过时。<br />&nbsp;2.3.2 J2EE</p>
<p>　 　J2EE是第一套成功的企业应用程序开发框架。因为它把事务处理、远程访问、安全这些概念带入了寻常百姓家。这种成功我认为要归因于Java的简单性。 Java的简单，对于J2EE容器供应商来说一样重要。开发一个基于Java的应用服务器要比基于C++的更容易。而且由于Java的简单性，应用系统开 发者出错的机会也会少一些，不会像C++的开发者那样受到那么多挫折。开发基于Java的企业应用系统的周期会更短，这恐怕是不容争辩的事实。不论如何， 是J2EE让事务处理、远程访问、安全这些原来几乎都是用在金融系统中的概念也被一般的企业用户使用并从中获得利益。</p>
<p>　　2.3.3 .NET</p>
<p>　 　.Net有什么好说的呢？其实，它不过是微软的J2EE。事务处理、安全、远程访问，这些概念在.Net中都找得到。更有力的说明是，微软也利用了. Net实现了一个PetStore。所以，.Net与J2EE几乎是可以完全对等的。但微软确实是一家值得尊敬的公司&mdash;&mdash;我指从技术上，象Web form这种东西，我相信很多Web应用开发者都梦想过甚至自己实现过，但Sun却一直无动于衷，而且似乎Borland也没有为此作过太多努力，好像有 过类似的东西，但肯定是不怎么成功&mdash;&mdash;Borland已经很让人敬佩了，我们也许无法要求太多。<br />2.4 COM技术</p>
<p>　　COM应当 是个更广泛的词汇，其实我觉得Axtive X、OLE、Auto mation、COM+都应当提及，因为如果你不理解COM，上面的东西你是无法理解的。而且，我只是想说明COM是一种即将消亡的技术，仅仅说说COM 的复杂性和他的问题就够了，所以不会提及那些东西。为什么会出现COM技术？COM的根本目标是实现代码的对象化的二进制重用，进而实现软件的组件化、软 件开发工作的分工。这要求他做到两点：第一，能够跨越不同的语言，第二，要跨越同一种语言的不同编译器。COM技术为这个目标付出了沉重的代价，尤其是为 了跨越不同的编译器，以至于无论对于使用者而言还是开发者而言，他都是一个痛苦的技术。但幸运的事，这一切终归要结束了。</p>
<p>　　让我们从这个 目的出发看看COM为什么会成为它现在的样子。其实COM不是什么新玩意，最初的DLL就是重用二进制代码的技术。DLL在C的年代可能还不错，但到了C ++的年代就不行了。原因在于如果你在.h文件中改变了类定义（增加或者减少了成员变量），代码库用户的代码必须重新编译才可以，否则用户的代码会按你的 旧类的结构为你的新类分配内存，这将产生什么后果可想而知。这就是为什么通过接口继承和通过接口操作对象成为COM的强制规范的原因，能够通过对象的方式 组织代码是COM的重要努力。那么著名的IUnknown接口又是怎么回事呢？这是为了让使用不同编译器的C++开发人员能够共享劳动成果。</p>
<p>&nbsp;</p>
<p>　 　首先看QueryInterface，因为COM是基于接口的，那么一个类可能实现了几个接口，而对于用户来说，你又只能通过操作接口来操作类，这样你 必须把类造型成某个特定的接口，使用Dynamic_cast吗？不行，因为这是编译器相关的，那么，就只好把它放在类的实现代码中了，这就是 QueryInterface的由来。至于AddRef和Release，他们产生的第一个原因是delete这个操作要求一个类具有虚析构函数（否则的 话，他的父类的析构函数将不会被调用），但不幸的是不同的编译器中析构函数在vtbl中的位置并不相同，这就是说你不能让用户直接调用delete，那么 你的COM对象必须提供自己删除自己的方法；另外的原因在于一个COM对象可能作为几个接口在被用户同时使用，如果用户粗暴的删掉了某个接口，那么其他的 接口也就不能用了，这样，只好要求用户在想用一个接口的时候通过AddRef通知COM对象&ldquo;我要使用你了，你多了一个用户&rdquo;，而在不用之后要调用 Release告诉COM对象&ldquo;我不用你了，你少了一个用户&rdquo;，如果COM对象发现自己没有用户了，它就把自己删掉。</p>
<p>　　再看看诡异的 HRESULT，这是跨语言和跨编译器的代价。其实，异常处理是物竞天择的结果&mdash;&mdash;连一直用效率作标榜的C++都肯牺牲效率来实现这个try- catch，可见它的意义，但COM为了照顾那些低级的语言居然抛弃了这个特征&mdash;&mdash;产生的结果就是HRESULT。我们可以看看他是多么愚蠢的东西。首 先，我们要通过一个整数来传递错误信息，通过IErrorInfo来查找真正的错误描述，本来在现代语言中一个try-catch可以解决的问题，现在我 们却需要让用户和开发者都走很多路才能解决，你怎么评价这个结果？其次，由于这个返回计算结果的位置被占用了，我们不得不用怪异的out参数来返回结果。 想想一个简单的int add(intop1,intop2)在COM中竟然要变成HRESULT add(intop1,intop2,int* result)，我不知道你对此作何感想。而且由于COM的方法无法返回一个对象而只能返回一个指针，为了完成一个简单的std::string GetName()这一类的操作，你要费多少周折&mdash;&mdash;你需要先分配一块内存空间，然后在COM实现中把一个字符串拷贝到这个空间，用完了你要删掉这个空 间，不知道你是否觉得这种工作很幸福，反正我觉得很痛苦。还有COM为了照顾那些解释性的语言，又加入了Automation技术，你有没有为此觉得痛 苦？本来一个简单的方法调用，现在却需要传给你一个标志变量，然后让你根据这个标志变量去执行相应的操作。（这一点我现在仍然不明白，为什么解释性的语言 需要通过这个方式来执行一个方法）。&ldquo;我受够了，我觉得头痛&rdquo;，你会说，是啊，我想所有的人都受够了，所有这些因素实际上是把COM技术变成了一头让人无 法驾驭的怪兽。</p>
<p>　　人对复杂事物的掌控能力终究是有限的，C++本身已经够复杂了，COM的复杂性已经超出了我们大部分人的控制能力，你需 要忍受种种痛苦得到的东西与你付出的代价相比是不是太不值得了？我们学习是为了解决问题，而现在我们却需要为了学习这件事情本身耗费太多的精力。这些痛苦 的东西太多了，我在这里说到的，其实只是一小部分而已。计算机技术是为人类服务的，而不是少数人的游戏（我想COM技术可能是他的设计者和一部分技术作者 的游戏），难道我们愿意成为计算机的奴隶吗？通过一种过于复杂的技术抛弃所有的人其实等于被所有的人抛弃，这么多年中选择J2EE的人我相信不乏高手，你 是不是因为COM的过于复杂才选择J2EE的？因为它可以用简单的途径实现差不多的目标&mdash;&mdash;软件的&ldquo;二进制&rdquo;重用、组件化、专业分工（容器开发商和应用开 发商的分工）。事实上，你是被微软所抛弃的，同时，你也抛弃了微软。</p>
<p>　　现在让我们回来吧，我把你带进了COM的迷宫，现在让我把你领回 来。再让我们看看COM到底想实现什么目标，其实很简单，不过是代码的二进制重用，也就是说你不必给别人源代码，而且你的组件可以象计算机硬件那样&ldquo;即插 即用&rdquo;。我们回过头来看看Java，其实，这种二进制重用的困难是C++所带来的（这不是C++本身的错，而是静态编译的错），当Java出现的时候，很 多问题已经不存在了。你不需要一个头文件，因为Java的字节码是自解释的，它说明了自己是什么样子的，可以做什么事情。不像C++那样需要一个头文件来 解释这些事情；也不需要事先了解对象的内存结构，因为内存是在运行的时候才分配的。如果我们现在再回过头来解决COM要解决的问题，你会怎么做呢？首先你 会不再选择C++这种语言来实现代码的&ldquo;二进制&rdquo;重用，其次，你会把所有的语言编译成同样的&ldquo;二进制&rdquo;代码（实际上，应当说是字节码），这显然是更聪明的 做法，从前COM试图在源代码的级别抹平二进制层次的差异，这实际上是让人在做本来应当由机器做的事情，很愚蠢是吗？但他们一直做了那么多年，而且把这个 痛苦带给了整个计算机世界&mdash;&mdash;因为他们掌握着事实的标准，为了用户，为了利润，为了能够在Windows上运行，尽管你知道你在做着一个很不聪明的事情， 但你还是做了。</p>
<p>　　COM技术为了照顾VB这个小兄弟和实现统一二进制世界的野心，实在浪费了太多的精力。首先，落后的东西的消亡是必然 的，就象C、Pascal在应用领域的消亡一样，Basic一点一点的改良运动是不符合历史潮流的做法，先进的东西和落后的东西在一起，要么先进的东西被 落后的东西拖住后腿，要么是同化落后的东西，显然我们更愿意看见后者，现在Basic终于被现代的计算机语言同化了。其次，统一二进制世界好像不是那么简 单的事情，而且也没什么必要，微软的COM技术奋战了10年，现在也只有他自己和Borland支持，.Net终于放弃了这个野心。这一切终于要结束了。</p>
<p>过去J2EE高歌猛进地占领着应用开发的领地，COM在这种进攻面前多少显得苍白无力。现在微软终于也有了可以和J2EE一较长短的.NET，对于开发 人员来讲，基于字节码的组件的二进制重用现在是天经地义的事情；你不用再为了能够以类方式发布组件做那么多乱七八糟的事情，因为现在所有的东西本来就是类 了；实现软件开发的分工也很自然，你是专业人员，就用C#吧，你是应用开发人员，你喜欢用VB.Net，你就用吧，反正你们写的东西最终都被翻译成了一样 的语言（其实我觉得这一点意义不大，因为一些不那么好用的语言被淘汰是正常的事情，C风格成为程序设计语言的主流风格，肯定是有它的原因的，语言的统一其 实是大势所趋，就象中国人民都要说普通话一样，我觉得Java不支持多语言算不上缺点&mdash;&mdash;本来就是一种很好看很好用的语言了，为什么要把简单问题复杂化 呢？）。COM不会在短期内死去，因为我估计微软还不会马上用C#开发Office和Windows的图形界面部分，但我相信COM技术退出历史舞台的日 子已经不远了，作为一般的开发人员，忘了这段不愉快的历史吧&mdash;&mdash;你将不会在你的世界里直接和COM打交道。若干年以后，我想COM也许会成为一场笑话，用 来讽刺那种野心过大、钻牛角尖的愚蠢的聪明人。</p>

	<h4>相关文章</h4>
	<ul class="st-related-posts">
	<li><a href="http://www.donevii.com/post/78.html" title="[转]C/C++/Perl/汇编/Java效率比较 (2006-10-11)">[转]C/C++/Perl/汇编/Java效率比较</a> (0)</li>
	<li><a href="http://www.donevii.com/post/9.html" title="flickr对javascript干的好事 (2006-08-17)">flickr对javascript干的好事</a> (0)</li>
	<li><a href="http://www.donevii.com/post/229.html" title="设计模式之Life time controller模式 (2006-11-27)">设计模式之Life time controller模式</a> (0)</li>
	<li><a href="http://www.donevii.com/post/283.html" title="免费软件套装 (2007-01-08)">免费软件套装</a> (0)</li>
	<li><a href="http://www.donevii.com/post/387.html" title="[转]嵌入式系统 Boot Loader 技术内幕 (2008-01-14)">[转]嵌入式系统 Boot Loader 技术内幕</a> (0)</li>
</ul>

]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/297.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>设计模式之Life time controller模式</title>
		<link>http://www.donevii.com/post/229.html</link>
		<comments>http://www.donevii.com/post/229.html#comments</comments>
		<pubDate>Mon, 27 Nov 2006 08:45:42 +0000</pubDate>
		<dc:creator>dengwei</dc:creator>
				<category><![CDATA[c/c++/c#]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[unix]]></category>
		<category><![CDATA[windows]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[平台]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[类]]></category>

		<guid isPermaLink="false">http://www.donevii.com/?p=229</guid>
		<description><![CDATA[转至 www.Huihoo.org (作者:Douglas C. Schmidt ,by huihoo.org CORBA课题 Thzhang 译 , Allen整理,制作) 目的 对象生命周期管理者模式可以被用来控制对象的整个生命周期，从对象被首次使用前创建它们到应用程... ]]></description>
			<content:encoded><![CDATA[<p>转至 <a href="http://www.Huihoo.org">www.Huihoo.org</a></p>
<p></p>
<p> (作者:Douglas C. Schmidt ,<font color="green"><em>by </em>huihoo.org</font> CORBA课题 Thzhang 译 , Allen整理,制作) </p>
<h3>目的</h3>
<p>对象生命周期管理者模式可以被用来控制对象的整个生命周期，从对象被首次使用前创建它们到应用程序中止前完全的销毁它们。此外通过在应用启动/中止时进行对象自动的预先创建/销毁，使这个模式能够用来替代静态对象的创建/销毁。 </p>
<h3>例子</h3>
<p>单例（singleton）是一种通用的创建模式，它对唯一的类实例提供了一个全局的访问点同时能够延迟实例的创建直到它首次被访问。如果一个单例在程序 的整个生命周期中没有被需要，它将不会被创建。单例模式并没有提及在什么时候它的实例应该被销毁这个问题，但对于特定的应用或操作系统这将是个问题。 <br />为了说明为什么提及销毁语义是重要的，考虑下面的日志组件，它通过向客户提供编程API接口实现分布式的日志服务。 		 </p>
<pre><code>class Logger{public:// Global access point to Logger singleton.static Logger *instance (void) {if (instance_ == 0)instance_ = new Logger;return instance_;}// Write some information to the log.int log (const char *format, ...);protected:// Default constructor (protected to// ensure Singleton pattern usage).Logger (void);static Logger *instance_;// Contained Logger singleton instance.// . . . other resources that are held by the singleton . . .};// Initialize the instance pointer.Logger *Logger::instance_ = 0;

</code> </pre>
<p>Logger的构造函数，出于简化而忽略掉了，实现分配各种OS的endsystem资源，像SOCKET句柄，共享内存段，和/或系统范围的信号量，它们被用于实现日志服务提供的客户API。 <br />为了减少尺寸，提高记录信息的可读性，一个应用可以选择用批处理而不是分立的方式来记录某些数据，像时间统计数据。例如，下面的统计类根据每个单独的标识，批量处理时间数据：  </p>
<pre><code>class Stats{public:// Global access point to the statistics singleton.static Stats *instance (void) {if (instance_ == 0)instance_ = new Stats;return instance_;}// Record a timing data point.int record (int id,const timeval &amp;tv);// Report recorded statistics to the log.void report (int id) {Logger::instance ()-&gt;log (&quot;Avg timing %d: &quot;&quot;%ld sec %ld usec\n&quot;,id,average_i (id).tv_sec,average_i (id).tv_usec);}protected:// Default constructor.Stats (void);// Internal accessor for an average.const timeval &amp;average_i (void);// Contained Stats singleton instance.static Stats *instance_;// . . . other resources that are held by the instance . . .};// Initialize the instance pointer.Stats *Stats::instance_ = 0;}</code> </pre>
<p>在记录了各种统计数据后，程序调用report方法，它将使用Logger单例根据标识来记录平均的时间统计。 <br />Logger和Stats类对应用提供了截然不同的服务：Logger类提供了通用的日志能力，而Stats类提供了具体化的批量处理和记录时间统计功能。这些类都是使用单例模式实现的，于是在应用中每一个类都只有一个实例。 <br />下面的例子展示了一个应用可能使用Logger和Stats单例对象的一种情况： </p>
<pre><code>int main (int argc, char *argv[]){// Interval timestamps.timeval start_tv, stop_tv;// Logger, Stats singletons do not yet exist.// Logger and Stats singletons created  during the first iteration.for (int i = 0; i &lt; argc; ++i) {::gettimeofday (&amp;start_tv);// do some work between timestamps . . .::gettimeofday (&amp;stop_tv);// then record the stats . . .timeval delta_tv;delta_tv.sec = stop_tv.sec - start_tv.sec;delta_tv.usec = stop_tv.usec - start_tv.usec;Stats::instance ()-&gt;record (i, delta_tv);// . . . and log some output.Logger::instance ()-&gt;log (&quot;Arg %d [%s]\n&quot;, i, argv[i]);Stats::instance()-&gt;report (i);}// Logger and Stats singletons are not cleaned up when main returns.return 0;}</code> </pre>
<p>注意，应用没有清晰的创建或销毁Logger和Stats单例，也就是说它们的生命周期管理是与应用逻辑相分离的。这是一个通常的习惯在应用程序退出的时候不对单例对象进行销毁处理。 <br />但是，由于单例模式仅仅涉及单例对象的创建，而没有应对它的销毁，这将产生几个缺陷。值得注意的是，当上面的应用程序中止时，Logger和Stats单 例对象都没有被清理。在最好的情况下，这将导致内存泄漏的错误报告，最坏的情况下，重要的系统资源可能没有被完全的释放和销毁。 <br />例如，如果Logger和Stats单例对象持有OS的资源，像系统范围的信号量，I/O缓冲，或者其他被分配的OS资源，将产生问题。当程序关闭的时候 没有适当的释放这些资源可能导致死锁和其他同步灾难。为了减少这样的问题，在程序退出前每个单例实例的析构函数应该被调用。 <br />一种试图保证单例销毁的单例模式实现方法是，在一个文件范围内声明一个利用区域锁习惯用法实现的静态单例类实例。例如,下面的Singleton Destroyer模板提供了一个析构器用于销毁单例对象。 </p>
<pre><code>	template <class t=""> Singleton_Destroyer{public:Singleton_Destroyer (void): t_ (0) {}void register (T *) { t_ = t; }Singleton_Destroyer (void) { delete t_; }private:T *t_; // Holds the singleton instance.};</class></code> </pre>
<p>为了使用这个类，所有应该做的是通过定义一个静态的Singleton_Destroyer类实例来修改Logger和Stats类。下面的例子展示了对Logger类的修改： 
<pre><code>static Singleton_Destroyer<logger> logger_destroyer;// Global access point to the// Logger singleton.static Logger *instance (void) {if (instance_ == 0) {instance_ = new Logger;// Register the singleton so it will be// destroyed when the destructor of logger_destroyer is run.logger_destroyer.register (instance_);}return instance_;}</logger></code> </pre>
<p>注意，logger_destroyer是如何获取单例对象并在程序退出时将其销毁的。针对Stats类也有类似的修改。 </p>
<p>不幸的是，清晰的实例化一个静态的Singleton_Destroyer实例将带来一些问题。例如，在C++中，每一个 Singleton_Destroyer应该在不同的编译单元中定义（译者：内联定义。因为静态数据具有内联特性，一般不将其放在头文件中，因为每个包含 这个头文件的编译单元都将拥有一个静态数据的副本。）。在这种情况下，它们的析构函数的调用次序是没有保证的，这将导致未定义的行为。特别的，如果在不同 编译单元中的单例对象共享了一些资源，如SOCKET句柄，共享内存片段和/或系统范围的信号量，程序将不能干净的退出。在C++中单例对象的这种不确定 的销毁次序将很难保证（1）在最后一个使用资源的单例对象被完全的销毁前，而不是（2）在一个单例对象存在，并依然使用这些资源前，OS去回收这些资源。 </p>
<p>总的来说，在上面的例子中没有解决的关键问题是：(1)被单例分配的资源应该在程序退出时被最终释放。(2)不受约束的创建和销毁静态实例的次序将导致严重的程序错误。(3)将软件开发人员和管理对象生命周期的责任分离开来，将减少系统出错的可能。 </p>
<h3>语境</h3>
<p>&gt; 	一个应用和系统对其所创建的对象进行全面的生命周期控制，对于保证系统的正确性是必要的。 </p>
<h3>问题</h3>
<p>许多应用并没有适当的处理对象的整个生命周期。特别是那些使用创建模式的应用，如单例模式，经常没有考虑这些对象的销毁。同样的，那些使用静态对象来提供对象销毁的应用经常遭受不一致的初始化和销毁过程。下面是这些问题的简要说明： </p>
<p>1、单例对象析构带来的问题。单例对象的实例可能被动态创建，一个动态创建的单例实例必将会造成资源的泄漏，如果它们没有被销毁。但是，单例对象造成的泄漏经常被忽略，这是因为： <br />（1）它们对应用不会造成显著的影响， <br />（2）在大多数通用的操作系统中，如UNIX、WINDOWS，在程序中止时它们会被自动清理。 不幸的是，在下面的环境中资源泄漏会导致麻烦： <br />（a）当系统需要优雅的关闭时。单例对象应该为其所获取的系统资源，如系统范围的锁、打开的网络连接、共享的内存片断负责。清晰的销毁单例对象是值得做的，这可以保证当程序中止时，所有这些资源能够在预定好的点上被销毁。 <br />（b）当一个单例对象拥有另一个单例对象的引用时。清晰的管理单例对象的销毁次序，对于避免在应用中止时由于空悬（dangling）引用产生的问题是必要的。 <br />（c）当检测内存泄漏时。内存检测工具，如NuMega BoundsCheck, ParaSoft Insure++,和Rational Purify，被用于检测像C/C++这类需要清晰的动态分配和释放内存的语言。这些工具会指示单例实例作为一处内存泄漏。 <br />（d）当从一个全局内存池中动态分配内存时。一些实时操作系统，如VxWorks和PSOS，有一个对应于所有应用的唯一的全局堆。因此，应用任务在中止时必须释放任何动态分配的内存，否则，这些内存不会被释放而被其他应用使用直到系统重新启动。 </p>
<p>2、静态对象生命周期的问题。一些对象必须先于任何使用前被创建。在C++语言中，这些实例往往被定义成静态变量，它们先于程序主函数入口点被调用前创建，在程序中止时被销毁。但是静态对象有几个严重的缺陷： <br />（a）不确定的构造/析构的次序。C++语言仅仅指定了在一个编译单元内部静态对象的构造/析构的次序。构造的次序是对象声明的次序、析构则与之相反。但 是没有指定处于不同编译单元的静态对象的构造/析构的次序。因此构造/析构的次序是实现依赖的。使用静态对象来处理初始化相关性是很难写出可以移植的C+ +代码。通常，比较简单的方式是完全避免使用静态对象，而不是解决、防止这些相关性。为了在某些平台上实现正确的程序操作，清晰的单例管理是必须的，因为 它们能够事先销毁单例对象。例如，早先JDK中的垃圾回收器，能够销毁没有任何参考指向的对象，即使这个对象是被设置成单例。虽然这个问题在后续的JDK 中被解决，但是对象生命周期管理者能够在的应用的控制下，通过维护单例的参考来解决这个问题。 <br />（b）不能被嵌入式系统很好的支持。由于历史原因，嵌入系统都是使用C语言因此，它们总是不能对OO程序语言特性提供无缝的支持。例如，在C+ +语言中的静态对象的构造/析构经常会复杂化嵌入系统的编程。嵌入式OS可能已经支持清晰的调用静态的构造/析构函数，但没有达到程序员期望的最佳状态。 一些嵌入式OS不支持一个程序有一个唯一的入口点的概念。例如VxWorks支持多个task，task类似于线程因为它们共享一个地址空间。但是对于每 一个应用并没有指定主task。因此，这些嵌入式系统可以被配置成在模块载入/卸载时分别的调用静态对象的构造器/析构器。 <br />（c）静态对象增加了应用启动的时间。静态对象可能先于任何主入口点的调用，在应用启动时被初始化。如果这些对象在一个特定的运行中没有被使 用，那么应用启动（退出）的时间被白白的增加。减少这种浪费的一种方法是，使用安需创建对象替代静态对象，如使用单例模式。使用单例替代静态对象也可以用 于延迟对象对象的构建直到被首次使用时，而且这也将减少启动时间。一些实时的应用已经发现在进入主入口点后和对象被正是需要前的一个特定时间点创建单例对 象带来的好处。一个或多个这样静态对象的缺点就足以提供将它们从程序中移走的全部动机。通常，比较好的做法是不使用它们，而是应用下面的方案替代。 </p>
<h3>解决之道</h3>
<p>定义一个对象生命周期管理者，它是一个包含了预分配对象和被管理对象集合的单例。它的职责是在应用启动和中止时分别创建和销毁预分配对象。它更深一层的职责是在程序中止时保证所有的被管理对象能够被完全的销毁。 </p>
<h3>适应性</h3>
<p>在下列情况下使用对象生命周期管理者： </p>
<p>1、在程序中止时，单例或其他动态分配的对象能够在不需要程序本身进行干预的情况下被销毁。单例和其他创建模式都没有提及它们所创建的对 象应该什么时候被销毁和由谁来销毁这个问题。与之相反，对象生命周期管理者提供了一个便利的用于删除被动态创建对象的全局对象。由创建模式产生的对象可以 向对象生命周期管理者进行注册，确保在程序中止时能够被销毁。 <br />2、静态对象必须从应用中移除。正像前面所描述的那样，静态对象是麻烦的制造者，特别是在某些语言和某些平台上。对象生命周期管理者提供了一种利用预先分配对象替代静态对象的机制。预分配对象在应用使用它们前被创建，在应用中止前被销毁。 <br />3、对于那些不支持静态对象创建和销毁的平台。一些嵌入式系统，如VXWORKS和PSOS不总是在程序启动时创建静态对象，在程序中止时销毁静态对象。通常，比较好的方式是移除静态对象。 <br />4、虽然应用需要这样，但是底层平台不能提供一个主程序的概念。在缺少对主程序概念支持的平台上，将缺少对静态对象创建和销毁的支持。对象生命周期管理者通过划分地址空间的方式可以被用来仿效一个主程序。从应用的角度来看，每一个对象生命周期管理者描绘了一个程序的范畴。 <br />5、对象被销毁的次序必须由应用来指定。动态创建的对象可以通过向对象生命周期管理者注册方式来使自身被销毁。对象生命周期管理者可以以任意希望的次序来实现对象的销毁。 <br />6、应用需要一个明确的单例对象管理机制。像上面所描述的，单例对象可能被过早的销毁。如早期的JAVA平台。对象生命周期管理者可以延迟对象的销毁直到应用中止。 </p>
<h3>结构和参与者</h3>
<p>在下面的图中，使用UML方式展示了对象生命周期管理者的结构和参与者。 对象生命周期管理者：每一个对象生命周期管理者，它是一个包含了预分配对象和被管理对象集合的单例。 </p>
<p><center> <img src="http://www.huihoo.com/ace_tao/i/lifecycle1.jpg" alt="" /> </center> </p>
<p>1、被管理对象：任何一个向对象生命周期管理者注册并由其负责销毁的对象。对象销毁发生在对象生命周期管理者本身被销毁的时候，通常都是在程序中止的时候。 <br />2、预分配对象：被对象生命周期管理者在其内部通过硬编码方式实现创建和销毁的对象。它和对象生命周期管理者具有相同的生命周期，也就是执行应用的进程的生命周期。 <br />3、应用：应用清晰或非清晰的创建和销毁对象生命周期管理者。此外，应用向本身可能包含预分配对象的对象生命周期管理者注册被管理对象。 </p>
<h3>动态特征</h3>
<p>下图中展示了对象生命周期管理者模式中的参与者之间的动态协作： <br />这个图描述了四个分离的活动： <br /><center> <img src="http://www.huihoo.com/ace_tao/i/lifecycle2.jpg" alt="" /> </center>  </p>
<p>1、对象生命周期管理者创建和初始化，它将依次创建预分配对象。 <br />2、应用创建管理对象，并向对象生命周期管理者注册。 <br />3、应用使用已经注册的管理对象和预先分配对象。 <br />4、对象生命周期管理者的销毁，这将销毁所有它控制的管理对象和预分配对象。 </p>
<h3>实现</h3>
<p>对象生命周期管理者可以用下面展示的步骤来实现。这个实现是基于ACE框架提供的对象管理者来实现的，它将引出一些将在这个单元讨论的有趣问题。下面讨论的一些步骤是语言相关的，因为ACE是用C++语言编写的。 <br />1、定义对象生命周期管理者组件。这个组件向应用提供一个接口，通过它注册那些生命周期必须被管理对象，确保在系统中止时能够完全的销毁这些对 象。此外，这个组件还定义了一个智囊团（repository）用于确保它所管理的对象能够被完全销毁。对于那些被注册，在程序中止时被销毁的预分配对象 和管理对象来说，对象生命周期管理者是一个容器。 <br />下面是用于实现对象生命周期管理者组件的子步骤： <br />（a）定义一个用于注册被管理对象的接口。一种用于向对象生命周期管理者注册被管理对象的方法是使用C语言库中的atexit函数，它在程序退出 时将调用特定的中止函数（用于实现退出时的清理操作）。但是，不是所有的平台都支持atexit函数，而且atexit函数的实现限制了最大被注册的中止 函数数目为32。因此。对象生命周期管理者必须支持下面的两个技术用于实现被管理对象的注册：使用一个容器来容纳所有的被管理对象，在程序退出时自动清除 这些被管理对象。 <br />i.定义一个cleanup函数接口。对象生命周期管理者允许应用向其注册任意类型的对象。当程序中止时，对象生命周期管理者将自动的清理这些对象。下面的C++类展示了一个在ACE中使用的特殊的CLEANUP_FUNC，用于注册能够被清除的对象或数组。 </p>
<pre><code>typedef void (*CLEANUP_FUNC)(void *object,void *param);class Object_Lifetime_Manager{public:static int at_exit (void *object,CLEANUP_FUNC cleanup_hook,void *param);};</code> </pre>
<p>这个静态的at_exit函数注册一个能够在程序退出时被清除的对象或数组对象。Cleanup_hook参数指向全局的函数或时静态的 方法，该方法在清理时被调用用于销毁对象或数组。在销毁时，对象生命周期管理者向Cleanup_hook函数传递对象和相关的参数。参数包含任何被 Cleanup_hook函数需要的额外信息，如数组中对象的个数等。 <br />ii.定义一个cleanup基类接口。这个接口允许应用向对象生命周期管理者注册销毁任何从cleanup接口继承的对象。这个 cleanup基类接口应该有一个虚的析构函数和一个虚的cleanup方法，这个方法的实现仅仅是简单的调用delete this，它将导致所有继承类的析构函数被依次调用。下面的代码段展示了在ACE中这个接口是如何实现的： </p>
<pre><code>class Cleanup{public:// Destructor.virtual ?Cleanup (void);// By default, simply deletes this.virtual void cleanup (void *param = 0);};</code> </pre>
<p>下面的代码段展示了在ACE中，对象生命周期管理者被用于注册从cleanup接口继承对象的接口。
<pre><code>class Object_Lifetime_Manager{public:static int at_exit (Cleanup *object,void *param = 0);};<code> </code></code></pre>
<p>这个静态的at_exit函数注册一个能够在程序退出时被清除的cleanup对象。在析构时，对象生命周期管理者调用cleanup对象中的cleanup方法，param参数包含了任何被cleanup函数需要的额外信息。 <br />（b）定义一个单例适配器。虽然使用上面定义的对象生命周期管理者方法是可以清晰的编码实现单例对象，但这样做是冗余的和易错的。因此，定义一个 单例的适配器模板用于封装创建单例对象和向对象生命周期管理者注册的细节是非常有用的。此外，单例适配器可以使用线程安全的双检测加锁优化模式 （double checked locking optimization pattern）来创建和访问类型指定的单例对象实例。 下面的代码片断展示了单例适配器在ACE中是如何实现的： </p>
<pre><code>template <class type="">class Singleton : public Cleanup{public:// Global access point to the	wrapped singleton.static TYPE *instance (void) {// Details of Double Checked  Locking Optimization omitted . . .if (singleton_ == 0) {singleton_ = new Singleton<type>;// Register with the Object Lifetime// Manager to control destruction.Object_Lifetime_Manager::at_exit (singleton_);}return &amp;singleton_-&gt;instance_;}protected:// Default constructor.Singleton (void);// Contained instance.TYPE instance_;// Instance of the singleton adapter.static Singleton<type> *singleton_;};</type></type></class></code> </pre>
<p>Singleton类模板从cleanup类继承，这样就允许单例实例向对象生命周期管理者注册自己 <br />（d）定义一个用于注册预分配对象的接口。预分配对象总是在应用的主进程启动前被创建。例如，在一些应用中，同步锁必须在任何使用前被创建，用于 防止竞争条件。这样一来，这些对象必须在每个对象生命周期管理者类中通过硬编码的方式实现。将这些对象的创建封装在对象生命周期管理者的初始化阶段，不给 应用代码添加任何的复杂性。 <br />对象生命周期管理者能够预先分配对象或数组。它要么能够静态的在全局数据中实现这些预先分配，要么在堆中动态的实现。一种有效的实现方式是在数 组中存储每个预分配对象。像C++这种特定的语言不支持数组容纳异类对象。因此，用指针替代对象本身来实现数组存储。实际的对象是被对象生命周期管理者在 其初始化的过程中动态创建的，在其析构的时候被销毁。 <br />下面的子步骤用于实现预分配对象： <br />1.	限制暴露。为了最小化头文件的暴露，通过宏或枚举来标识预分配对象。 
<pre><code>enum Preallocated_Object_ID{ACE_FILECACHE_LOCK,ACE_STATIC_OBJECT_LOCK,ACE_LOG_MSG_INSTANCE_LOCK,ACE_DUMP_LOCK,ACE_SIG_HANDLER_LOCK,ACE_SINGLETON_NULL_LOCK,ACE_SINGLETON_RECURSIVE_THREAD_LOCK,ACE_THREAD_EXIT_LOCK,};</code> </pre>
<p>2.	使用cleanup适配器。Cleanup适配器类模板从cleanup基类继承，包装那些没有从cleanup基类继承的类型，使它们能够被对象生命周期管理者管理起来。 </p>
<pre><code>#define PREALLOCATE_OBJECT(TYPE, ID) {\Cleanup_Adapter<type> *obj_p;\obj_p = new Cleanup_Adapter<type>;\preallocated_object[ID] = obj_p;\}#define DELETE_PREALLOCATED_OBJECT(TYPE, ID)\cleanup_destroyer (\static_cast<cleanup_adapter><type> *&gt;\(preallocated_object[ID]), 0);\preallocated_object[ID] = 0;</type></cleanup_adapter></type></type></code> </pre>
<p>3.定义预分配对象的访问接口。应用需要简便的和类型安全的预分配对象的访问接口。因为对象生命周期管理者支持不同类型的预分配对象。提供一个分 离的，包含有一个接受一个ID参数的成员函数的，在函数内部对象被预先分配，返回一个指向对象的正确的类型指针的类模板适配器是必要的。 下面的代码片断展示了在ACE中通过类模板适配器如何实现这个接口： </p>
<pre><code>template <class type="">class Preallocated_Object_Interface{public:static TYPE *get_preallocated_object(Object_Lifetime_Manager::Preallocated_Object_ID id){// Cast the return type of the object// pointer based on the type of the// function template parameter.return&amp;(static_cast<cleanup_adapter><type> *&gt;(Object_Lifetime_Manager::preallocated_object[id]))-&gt;object ();}// . . . other methods omitted.};</type></cleanup_adapter></class></code> </pre>
<p>（e）定义被注册对象的析构次序。对象生命周期管理者可以实现以任意次序销毁被注册对象。例如，可以使用优先级别标注，使销毁的次序按照优先级降 序进行。应该提供一个用于设定和改变对象优先级别的接口函数。我们已经发现，以注册顺序相反的次序销毁对象的策略对于ACE应用来说已经足够了。一个应用 可以通过控制对象的注册次序来指定对象的销毁次序。 <br />（f）定义一个中止函数接口。到目前为止所讨论的生命周期管理功能仅涉及在程序中止时销毁被管理对象。但是对象生命周期管理者能够提供一个更通 常的功能：使用其内部的相同实现机制在程序中止时能够调用一个函数。 例如，为了在程序中止时确保完全清楚打开的win32 SOCKET,WSACleanup函数必须被调用。可以使用一个区域锁习惯用法的变化实现，创建一个特殊的包装门面类，它的构造函数会调用初始化函数， 它的析构函数会调用cleantup函数。于是，应用可以向对象生命周期管理者注册这样类的实现，这样一来在对象生命周期管理者销毁时会销毁内部的被管理 对象，中止API也将会被调用。 <br />但是，这样的设计对大多数应用来说太困难了，而且它还是容易出错的，因为应用必须保证这个类被用作单例对象，因为这些API函数只能被调用一次。 作为替代，这些API中止函数将被作为对象生命周期管理者的中止方法的一部分被调用。 <br />（g）移动通用接口和实现细节到一个基础类中。分解一些内部的细节到Object Lifetime Manager Base类中将使Object Lifetime Manager的实现更加简单，健壮。定义一个Object Lifetime Manager Base类用于支持创建不同类型的Object Lifetime Manager。 </p>
<p>3、确定如何管理对象生命周期管理者自己的生命周期。对象生命周期管理者有责任初始化其他全局对象和静态对象，但是这将引发连带的问题，就是这个单例对象如何初始化和析构它自己？下面是用于初始化对象生命周期管理者实例的几种选择： <br />（a）静态初始化。如果应用对静态对象的创建和销毁没有次序限制，可以创建对象生命周期管理者为静态对象。例如，ACE中的对象生命周期管理者可以被作为静态对象创建。  <br />（b）栈初始化。当一个主程序线程明确的定义了程序的入口点和中止点时，可以在主程序线程的栈上创建对象生命周期管理者，这样可以简化创建和销毁 对象生命周期管理者的编程逻辑。这种用于初始化对象生命周期管理者的方法是假设每个程序中只有一个唯一的主线程。这个线程确定了程序本身，也就是，程序在 运行，当且仅当主线程是活动的。这个方法有一个显著的优点：对象生命周期管理者实例在每一条离开MAIN函数的路径上都将被自动的销毁。 <br />（c）清晰的初始化。这个方法是，在应用程序的控制之下，清晰的创建对象生命周期管理者实例，对象生命周期管理者类中init和fini方法允许应用在任何需要的时候创建和销毁对象生命周期管理者实例。这个选择减轻了在使用DLL带来的复杂度。 <br />（d）动态库初始化。在这个方法中，创建和销毁对象生命周期管理者分别在它的DLL被加载和卸载的时候。许多动态库工具都包含调用如下方法能力：(1)一个出世化方法当DLL被加载时（2）一个中止函数当DLL被卸载时。 </p>
<h3>结论</h3>
<p>使用它带来的好处： 	<br />1、	在程序中止时销毁单例对象和其他被管理对象。对象生命周期管理者模式允许应用&quot;干净的&quot;中止，释放被管理对象占用的内存，连同它们持有的其他资源。 <br />2、	指定销毁的次序。对象被销毁的顺序可以被指定。这个指定的销毁机制可以任意复杂任意简单。 <br />3、	从库和应用中移除了静态对象。可以使用预分配对象替代静态对象。这将防止应用依赖于静态对象的创建和销毁的次序。 <br />使用它带来的缺陷: <br />1、管理者自己的生命周期管理。应用本身必须保证尊重对象生命周期管理者的生命周期，不在其外调用对象生命周期管理者的服务。例如，应用不要先于 对象生命周期管理者完全出世化之前试图去访问预分配对象，同样的，应用不要先于最后访问预分配对象或被管理对象前销毁对象生命周期管理者。最后，如果可以 假定对象生命周期管理者仅被一个线程初始化，那么实现对象生命周期管理者是非常简单的。这将免除在其初始化函数中使用静态锁 <br />2、和共享库一起使用。在支持共享库运行时动态加载和卸载的平台上，应用程序必须小心的对待平台指定的特性对对象生命周期管理者的生命周期的影 响。例如，在windows NT平台上，对象生命周期管理者应该被应用或是包含它的DLL来初始化，这将避免潜在死锁状态，由于OS内部已经串行化装载DLL操作。 一个相关的问题是在DLL创建单例对象，被应用代码中的对象生命周期管理者管理。当DLL先于应用中止前被卸载，那么在应用中止时，对象生命周期管理者就 会试图去使用已经不在应用中的代码来销毁这个单例对象。 </p>
<h3>实现样例代码</h3>
<p>下面的代码仅仅展示了预分配对象的处理过程。（译者：而被管理对象的处理过程被忽略了，这个过程比较复杂，涉及较多的ACE其他的封装类，包括ACE_OS_Exit_Info、ACE_Cleanup_Info_Node等。后面将有比较详细的讨论。） </p>
<pre><code>class Object_Lifetime_Manager_Base{public:virtual int init (void) = 0;// Explicitly initialize. Returns 0 on success,// -1 on failure due to dynamic allocation// failure (in which case errno is set to// ENOMEM), or 1 if it had already been called.virtual int fini (void) = 0;// Explicitly destroy. Returns 0 on success,// -1 on failure because the number of <fini>// calls hasn't reached the number of <init>// calls, or 1 if it had already been called.enum Object_Lifetime_Manager_State {OBJ_MAN_UNINITIALIZED,OBJ_MAN_INITIALIZING,OBJ_MAN_INITIALIZED,OBJ_MAN_SHUTTING_DOWN,OBJ_MAN_SHUT_DOWN};protected:Object_Lifetime_Manager_Base (void) :object_manager_state_ (OBJ_MAN_UNINITIALIZED),dynamically_allocated_ (0),next_ (0) {}virtual ?Object_Lifetime_Manager_Base (void) {// Clear the flag so that fini// doesn't delete again.dynamically_allocated_ = 0;}int starting_up_i (void) {return object_manager_state_ &lt;OBJ_MAN_INITIALIZED;}// Returns 1 before Object_Lifetime_Manager_Base// has been constructed. This flag can be used// to determine if the program is constructing// static objects. If no static object spawns// any threads, the program will be// single-threaded when this flag returns 1.int shutting_down_i (void) {return object_manager_state_ &gt;OBJ_MAN_INITIALIZED;}// Returns 1 after Object_Lifetime_Manager_Base// has been destroyed.Object_Lifetime_Manager_State object_manager_state_;// State of the Object_Lifetime_Manager;u_int dynamically_allocated_;// Flag indicating whether the// Object_Lifetime_Manager instance was// dynamically allocated by the library.// (If it was dynamically allocated by the// application, then the application is// responsible for deleting it.)Object_Lifetime_Manager_Base *next_;// Link to next Object_Lifetime_Manager,// for chaining.};class Object_Lifetime_Manager :public Object_Lifetime_Manager_Base{public:virtual int init (void);virtual int fini (void);static int starting_up (void) {return instance_ ?instance_-&gt;starting_up_i () : 1;}static int shutting_down (void) {return instance_ ?instance_-&gt;shutting_down_i () : 1;}enum Preallocated_Object{# if defined (MT_SAFE) &amp;&amp; (MT_SAFE != 0)OS_MONITOR_LOCK,TSS_CLEANUP_LOCK,# else// Without MT_SAFE, There are no// preallocated objects. Make// sure that the preallocated_array// size is at least one by declaring// this dummy.EMPTY_PREALLOCATED_OBJECT,# endif /* MT_SAFE */// This enum value must be last!PREALLOCATED_OBJECTS};// Unique identifiers for Preallocated Objects.static Object_Lifetime_Manager *instance (void);// Accessor to singleton instance.public:// Application code should not use these// explicitly, so they're hidden here. They're// public so that the Object_Lifetime_Manager// can be onstructed/destructed in main, on// the stack.Object_Lifetime_Manager (void) {// Make sure that no further instances are// created via instance.if (instance_ == 0)instance_ = this;init ();}?Object_Lifetime_Manager (void) {// Don't delete this again in fini.dynamically_allocated_ = 0;fini ();}private:static Object_Lifetime_Manager *instance_;// Singleton instance pointer.static void *preallocated_object[PREALLOCATED_OBJECTS];// Array of Preallocated Objects.};Object_Lifetime_Manager *Object_Lifetime_Manager::instance_ = 0;// Singleton instance pointer.Object_Lifetime_Manager *Object_Lifetime_Manager::instance (void){// This function should be called during// construction of static instances, or// before any other threads have been created// in the process. So, it's not thread safe.if (instance_ == 0) {Object_Lifetime_Manager *instance_pointer =new Object_Lifetime_Manager;// instance_ gets set as a side effect of the// Object_Lifetime_Manager allocation, by// the default constructor. Verify that . . .assert (instance_pointer == instance_);instance_pointer-&gt;dynamically_allocated_ = 1;}return instance_;}intObject_Lifetime_Manager::init (void){if (starting_up_i ()) {// First, indicate that this// Object_Lifetime_Manager instance// is being initialized.object_manager_state_ = OBJ_MAN_INITIALIZING;if (this == instance_) {# if defined (MT_SAFE) &amp;&amp; (MT_SAFE != 0)PREALLOCATE_OBJECT (mutex_t,OS_MONITOR_LOCK)// Mutex initialization omitted.PREALLOCATE_OBJECT (recursive_mutex_t,TSS_CLEANUP_LOCK)// Recursive mutex initialization omitted.# endif /* MT_SAFE */// Open Winsock (no-op on other// platforms).socket_init (/* WINSOCK_VERSION */);// Other startup code omitted.}// Finally, indicate that the// Object_Lifetime_Manager instance// has been initialized.object_manager_state_ = OBJ_MAN_INITIALIZED;return 0;} else {// Had already initialized.return 1;}}intObject_Lifetime_Manager::fini (void){if (shutting_down_i ())// Too late. Or, maybe too early. Either// <fini> has already been called, or// <init> was never called.return object_manager_state_ ==OBJ_MAN_SHUT_DOWN ? 1 : -1;// Indicate that the Object_Lifetime_Manager// instance is being shut down.// This object manager should be the last one// to be shut down.object_manager_state_ = OBJ_MAN_SHUTTING_DOWN;// If another Object_Lifetime_Manager has// registered for termination, do it.if (next_) {next_-&gt;fini ();// Protect against recursive calls.next_ = 0;}// Only clean up Preallocated Objects when// the singleton Instance is being destroyed.if (this == instance_) {// Close down Winsock (no-op on other// platforms).socket_fini ();// Cleanup the dynamically preallocated// objects.# if defined (MT_SAFE) &amp;&amp; (MT_SAFE != 0)// Mutex destroy not shown . . .DELETE_PREALLOCATED_OBJECT (mutex_t,MONITOR_LOCK)// Recursive mutex destroy not shown . . .DELETE_PREALLOCATED_OBJECT (recursive_mutex_t,TSS_CLEANUP_LOCK)# endif /* MT_SAFE */}// Indicate that this Object_Lifetime_Manager// instance has been shut down.object_manager_state_ = OBJ_MAN_SHUT_DOWN;if (dynamically_allocated_)delete this;if (this == instance_)instance_ = 0;return 0;}</init></fini></init></fini></code> </pre>
<p>
<h3>译者补充</h3>
<p>被管理对象的处理类图如下： </p>
<p><center> <img src="http://www.huihoo.com/ace_tao/i/lifecycle3.gif" alt="" /> </center> </p>
<p>每个ACE_Object_Manager包含一个私有的ACE_OS_Exit_Info成员。当应用通过 ACE_Object_Manager提供的at_exit函数注册被管理对象时，这个函数将调用ACE_Object_Manager中的一个私有函数 at_exit_i，这个函数将调用ACE_OS_Exit_Info成员中的at_exit_i函数。在这个函数中进行如下操作： <br />ACE_Cleanup_Info new_info;  <br />new_info.object_ = object; <br />new_info.cleanup_hook_ = cleanup_hook;  <br />new_info.param_ = param; <br />registered_objects_ ＝ registered_objects_-&gt;insert <br />(new_info)；其中registered_objects是指向ACE_Cleanup_Info_Node的指针，是ACE_OS_Exit_Info的私有数据成员。 <br />ACE_Cleanup_Info_Node本身构成一个单向的链表，其包含一个ACE_Cleanup_Info实例和指向下一个ACE_Cleanup_Info_Node节点的指针。 </p>
<p>这样，应用注册的被管理对象的指针、中止函数和需要参数都将以链表的形式存放在ACE_OS_Exit_Info中。当 ACE_Object_Manager退出时，其会调用其内部的fini函数，该函数将调用ACE_OS_Exit_Info中的call_hooks函 数。这个函数按照注册相反的顺序一次遍历每个ACE_Cleanup_Info_Node，并执行其中的 <br />ACE_Cleanup_Info中的相关退出操作： </p>
<pre><code>for (ACE_Cleanup_Info_Node *iter = registered_objects_;       iter  &amp;&amp;  iter-&gt;next_ != 0;       iter = iter-&gt;next_)    {      ACE_Cleanup_Info &amp;info = iter-&gt;cleanup_info_;      // The object is an ACE_Cleanup.      ace_cleanup_destroyer (ACE_reinterpret_cast (ACE_Cleanup *,                                                     info.object_),                               info.param_);     }

</code> </pre>

	<h4>相关文章</h4>
	<ul class="st-related-posts">
	<li><a href="http://www.donevii.com/post/78.html" title="[转]C/C++/Perl/汇编/Java效率比较 (2006-10-11)">[转]C/C++/Perl/汇编/Java效率比较</a> (0)</li>
	<li><a href="http://www.donevii.com/post/124.html" title="C++资源之不完全导引 (2006-10-26)">C++资源之不完全导引</a> (0)</li>
	<li><a href="http://www.donevii.com/post/504.html" title="google背后的分布式架构 (2008-09-23)">google背后的分布式架构</a> (0)</li>
	<li><a href="http://www.donevii.com/post/9.html" title="flickr对javascript干的好事 (2006-08-17)">flickr对javascript干的好事</a> (0)</li>
	<li><a href="http://www.donevii.com/post/44.html" title="ACE自适配通信环境 (2006-08-23)">ACE自适配通信环境</a> (0)</li>
</ul>

]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/229.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Linux 套接字编程中的 5 个隐患</title>
		<link>http://www.donevii.com/post/170.html</link>
		<comments>http://www.donevii.com/post/170.html#comments</comments>
		<pubDate>Sat, 04 Nov 2006 13:46:13 +0000</pubDate>
		<dc:creator>gavinkwoe</dc:creator>
				<category><![CDATA[doc]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[ror]]></category>
		<category><![CDATA[unix]]></category>
		<category><![CDATA[平台]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[测试]]></category>
		<category><![CDATA[类]]></category>

		<guid isPermaLink="false">http://www.donevii.com/?p=170</guid>
		<description><![CDATA[Socket API 是网络应用程序开发中实际应用的标准 API。尽管该 API 简单，但是开发新手可能会经历一些常见的问题。本文识别一些最常见的隐患并向您显示如何避免它们。 在 4.2 BSD UNIX&#174; 操作系... ]]></description>
			<content:encoded><![CDATA[<blockquote><p>Socket API 是网络应用程序开发中实际应用的标准 API。尽管该 API 简单，但是开发新手可能会经历一些常见的问题。本文识别一些最常见的隐患并向您显示如何避免它们。</p></blockquote>
<p><!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->  <!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
<p>在 4.2 BSD UNIX&reg; 操作系统中首次引入，Sockets API 现在是任何操作系统的标准特性。事实上，很难找到一种不支持 Sockets API 的现代语言。该 API 相当简单，但新的开发人员仍然会遇到一些常见的隐患。</p>
<p>本文识别那些隐患并向您显示如何避开它们。</p>
<p>第一个隐患很明显，但它是开发新手最容易犯的一个错误。如果您忽略函数的返回状态，当它们失败或部分成功的时候，您也许会迷失。反过来，这可能传播错误，使定位问题的源头变得困难。</p>
<p>捕获并检查每一个返回状态，而不是忽略它们。考虑清单 1 显示的例子，一个套接字 <code>send</code> 函数。</p>
<p>
<table width="100%" cellspacing="0" cellpadding="5" border="1" bgcolor="#eeeeee">
<tbody>
<tr>
<td>
<pre><code class="section">

int status, sock, mode;

/* Create a new stream (TCP) socket */sock = <span class="boldcode">socket</span>( AF_INET, SOCK_STREAM, 0 );

...

status = <span class="boldcode">send</span>( sock, buffer, buflen, MSG_DONTWAIT );

if (status == -1) {

  /* send failed */  printf( &quot;send failed: %s\n&quot;, strerror(errno) );

} else {

  /* send succeeded -- or did it? */

}</code></pre>
</td>
</tr>
</tbody>
</table>
<p>
<p>清单 1 探究一个函数片断，它完成套接字 <code>send</code> 操作（通过套接字发送数据）。函数的错误状态被捕获并测试，但这个例子忽略了 <code>send</code> 在无阻塞模式（由 <code>MSG_DONTWAIT</code> 标志启用）下的一个特性。</p>
<p><code>send</code> API 函数有三类可能的返回值：</p>
<ul>
<li>如果数据成功地排到传输队列，则返回 0。</li>
<li>如果排队失败，则返回 -1（通过使用 <code>errno</code> 变量可以了解失败的原因）。</li>
<li>如果不是所有的字符都能够在函数调用时排队，则最终的返回值是发送的字符数。</li>
</ul>
<p>由于 <code>send</code> 的 <code>MSG_DONTWAIT</code> 变量的无阻塞性质，函数调用在发送完所有的数据、一些数据或没有发送任何数据后返回。在这里忽略返回状态将导致不完全的发送和随后的数据丢失。</p>
<p>UNIX 有趣的一面是您几乎可以把任何东西看成是一个文件。文件本身、目录、管道、设备和套接字都被当作文件。这是新颖的抽象，意味着一整套的 API 可以用在广泛的设备类型上。
<p>考虑 <code>read</code> API 函数，它从文件读取一定数量的字节。<code>read</code> 函数返回读取的字节数（最高为您指定的最大值）；或者 -1，表示错误；或者 0，如果已经到达文件末尾。</p>
<p>如果在一个套接字上完成一个 <code>read</code> 操作并得到一个为 0 的返回值，这表明远程套接字端的对等层调用了 <code>close</code> API 方法。该指示与文件读取相同 &mdash;&mdash; 没有多余的数据可以通过描述符读取（参见 清单 2）。</p>
<table width="100%" cellspacing="0" cellpadding="5" border="1" bgcolor="#eeeeee">
<tbody>
<tr>
<td>
<pre><code class="section">

int sock, status;

sock = <span class="boldcode">socket</span>( AF_INET, SOCK_STREAM, 0 );

...

status = <span class="boldcode">read</span>( sock, buffer, buflen );

if (status &gt; 0) {

  /* Data read from the socket */

} else if (status == -1) {

  /* Error, check errno, take action... */

} else if (status == 0) {

  /* Peer closed the socket, finish the close */  <span class="boldcode">close</span>( sock );

  /* Further processing... */

}</code></pre>
</td>
</tr>
</tbody>
</table>
<p>
<p>同样，可以用 <code>write</code> API 函数来探测对等套接字的闭包。在这种情况下，接收 <code>SIGPIPE</code> 信号，或如果该信号阻塞，<code>write</code> 函数将返回 -1 并设置 <code>errno</code> 为 <code>EPIPE</code>。</p>
<p>您可以使用 <code>bind</code> API 函数来绑定一个地址（一个接口和一个端口）到一个套接字端点。可以在服务器设置中使用这个函数，以便限制可能有连接到来的接口。也可以在客户端设置中使用这个函数，以便限制应当供出去的连接所使用的接口。<code>bind</code> 最常见的用法是关联端口号和服务器，并使用通配符地址（<code>INADDR_ANY</code>），它允许任何接口为到来的连接所使用。
<p><code>bind</code> 普遍遭遇的问题是试图绑定一个已经在使用的端口。该陷阱是也许没有活动的套接字存在，但仍然禁止绑定端口（<code>bind</code> 返回 <code>EADDRINUSE</code>），它由 TCP 套接字状态 <code>TIME_WAIT</code> 引起。该状态在套接字关闭后约保留 2 到 4 分钟。在 <code>TIME_WAIT</code> 状态退出之后，套接字被删除，该地址才能被重新绑定而不出问题。</p>
<p>等待 <code>TIME_WAIT</code> 结束可能是令人恼火的一件事，特别是如果您正在开发一个套接字服务器，就需要停止服务器来做一些改动，然后重启。幸运的是，有方法可以避开 <code>TIME_WAIT</code> 状态。可以给套接字应用 <code>SO_REUSEADDR</code> 套接字选项，以便端口可以马上重用。</p>
<p>考虑清单 3 的例子。在绑定地址之前，我以 <code>SO_REUSEADDR</code> 选项调用 <code>setsockopt</code>。为了允许地址重用，我设置整型参数（<code>on</code>）为 1 （不然，可以设为 0 来禁止地址重用）。 </p>
<table width="100%" cellspacing="0" cellpadding="5" border="1" bgcolor="#eeeeee">
<tbody>
<tr>
<td>
<pre><code class="section">

int sock, ret, on;struct sockaddr_in servaddr;

/* Create a new stream (TCP) socket */sock = <span class="boldcode">socket</span>( AF_INET, SOCK_STREAM, 0 ):

/* Enable address reuse */<em>on = 1;ret = <span class="boldcode">setsockopt</span>( sock, SOL_SOCKET, SO_REUSEADDR, &amp;on, sizeof(on) );</em>

/* Allow connections to port 8080 from any available interface */memset( &amp;servaddr, 0, sizeof(servaddr) );servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = <span class="boldcode">htonl</span>( INADDR_ANY );servaddr.sin_port = <span class="boldcode">htons</span>( 45000 );

/* Bind to the address (interface/port) */ret = <span class="boldcode">bind</span>( sock, (struct sockaddr *)&amp;servaddr, sizeof(servaddr) );</code></pre>
</td>
</tr>
</tbody>
</table>
<p>
<p>在应用了 <code>SO_REUSEADDR</code> 选项之后，<code>bind</code> API 函数将允许地址的立即重用。</p>
<p>套接字是发送无结构二进制字节流或 ASCII 数据流（比如 HTTP 上的 HTTP 页面，或 SMTP 上的电子邮件）的完美工具。但是如果试图在一个套接字上发送二进制数据，事情将会变得更加复杂。
<p>比如说，您想要发送一个整数：您可以肯定，接收者将使用同样的方式来解释该整数吗？运行在同一架构上的应用程序可以依赖它们共同的平台来对该类型的 数据做出相同的解释。但是，如果一个运行在高位优先的 IBM PowerPC 上的客户端发送一个 32 位的整数到一个低位优先的 Intel x86，那将会发生什么呢？字节排列将引起不正确的解释。</p>
<table width="40%" cellspacing="0" cellpadding="0" border="0" align="right">
<tbody>
<tr>
<td width="10"><img width="10" height="1" alt="" src="http://www.ibm.com/i/c.gif" /></td>
<td>
<table width="100%" cellspacing="0" cellpadding="5" border="1">
<tbody>
<tr>
<td bgcolor="#eeeeee">
<p> <em>Endianness</em> 是指内存中字节的排列顺序。<em>高位优先（big endian）</em> 按最高有效字节在前排列，然而 <em>低位优先（little endian）</em> 按照最低有效字节在前排序。</p>
<p>高位优先架构（比如 PowerPC&reg;）比低位优先架构（比如 Intel&reg; Pentium&reg; 系列，其网络字节顺序是高位优先）有优势。这意味着，对高位优先的机器来说，在 TCP/IP 内控制数据是自然有序的。低位优先架构要求字节交换 &mdash;&mdash; 对网络应用程序来说，这是一个轻微的性能弱点。</p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<p>通过套接字发送一个 C 结构会怎么样呢？这里，也会遇到麻烦，因为不是所有的编译器都以相同的方式排列一个结构的元素。结构也可能被压缩以便使浪费的空间最少，这进一步使结构中的元素错位。</p>
<p>幸好，有解决这个问题的方案，能够保证两端数据的一致解释。过去，远程过程调用（Remote Procedure Call，RPC）套装工具提供所谓的外部数据表示（External Data Representation，XDR）。XDR 为数据定义一个标准的表示来支持异构网络应用程序通信的开发。</p>
<p>现在，有两个新的协议提供相似的功能。可扩展标记语言/远程过程调用（XML/RPC）以 XML 格式安排 HTTP 上的过程调用。数据和元数据用 XML 进行编码并作为字符串传输，并通过主机架构把值和它们的物理表示分开。SOAP 跟随 XML-RPC，以更好的特性和功能扩展了它的思想。参见 <a href="http://www-128.ibm.com/developerworks/cn/linux/l-sockpit/#resources">参考资料</a> 小节，获取更多关于每个协议的信息。</p>
<p></p>
<p>TCP 不提供帧同步，这使得它对于面向字节流的协议是完美的。这是 TCP 与 UDP（User Datagram Protocol，用户数据报协议）的一个重要区别。UDP 是面向消息的协议，它保留发送者和接收者之间的消息边界。TCP 是一个面向流的协议，它假定正在通信的数据是无结构的，如图 1 所示。</p>
<p><img width="565" height="503" alt="帧同步能力" src="http://www-128.ibm.com/developerworks/cn/linux/l-sockpit/figure1.gif" /> 
<p>图 1 的上部说明一个 UDP 客户端和服务器。左边的对等层完成两个套接字的写操作，每个 100 字节。协议栈的 UDP 层追踪写的数量，并确保当右边的接收者通过套接字获取数据时，它以同样数量的字节到达。换句话说，为读者保留了写者提供的消息边界。</p>
<p>现在，看图 1 的底部．它为 TCP 层演示了相同粒度的写操作。两个独立的写操作（每个 100 字节）写入流套接字。但在本例中，流套接字的读者得到的是 200 字节。协议栈的 TCP 层聚合了两次写操作。这种聚合可以发生在 TCP/IP 协议栈的发送者或接收者中任何一方。重要的是，要注意到聚合也许不会发生 &mdash;&mdash; TCP 只保证数据的有序发送。</p>
<p>对大多数开发人员来说，该陷阱会引起困惑。您想要获得 TCP 的可靠性和 UDP 的帧同步。除非改用其他的传输协议，比如流传输控制协议（STCP），否则就要求应用层开发人员来实现缓冲和分段功能。</p>
<p>GNU/Linux 提供几个工具，它们可以帮助您发现套接字应用程序中的一些问题。此外，使用这些工具还有教育意义，而且能够帮助解释应用程序和 TCP/IP 协议栈的行为。在这里，您将看到对几个工具的概述。查阅下面的 <a href="http://www-128.ibm.com/developerworks/cn/linux/l-sockpit/#resources">参考资料</a> 了解更多的信息。</p>
<p><code>netstat</code> 工具提供查看 GNU/Linux 网络子系统的能力。使用 <code>netstat</code>，可以查看当前活动的连接（按单个协议进行查看），查看特定状态的连接（比如处于监听状态的服务器套接字）和许多其他的信息。清单 4 显示了 <code>netstat</code> 提供的一些选项和它们启用的特性。</p>
<table width="100%" cellspacing="0" cellpadding="5" border="1" bgcolor="#eeeeee">
<tbody>
<tr>
<td>
<pre><code class="section">

View all TCP sockets currently active$ netstat --tcp

View all UDP sockets$ netstat --udp

View all TCP sockets in the listening state$ netstat --listening

View the multicast group membership information$ netstat --groups

Display the list of masqueraded connections$ netstat --masquerade

View statistics for each protocol$ netstat --statistics</code></pre>
</td>
</tr>
</tbody>
</table>
<p>
<p>尽管存在许多其他的实用程序，但 <code>netstat</code> 的功能很全面，它覆盖了 <code>route</code>、<code>ifconfig</code> 和其他标准 GNU/Linux 工具的功能。</p>
<p>可以使用 GNU/Linux 的几个工具来检查网络上的低层流量。<code>tcpdump</code> 工具是一个比较老的工具，它从网上&ldquo;嗅探&rdquo;网络数据包，打印到 <code>stdout</code> 或记录在一个文件中。该功能允许查看应用程序产生的流量和 TCP 生成的低层流控制机制。一个叫做 <code>tcpflow</code> 的新工具与 <code>tcpdump</code> 相辅相成，它提供协议流分析和适当地重构数据流的方法，而不管数据包的顺序或重发。清单 5 显示 <code>tcpdump</code> 的两个用法模式。</p>
<table width="100%" cellspacing="0" cellpadding="5" border="1" bgcolor="#eeeeee">
<tbody>
<tr>
<td>
<pre><code class="section">

Display all traffic on the eth0 interface for the local host$ tcpdump -l -i eth0

Show all traffic on the network coming from or going to host plato$ tcpdump host plato

Show all HTTP traffic for host camus$ tcpdump host camus and (port http)

View traffic coming from or going to TCP port 45000 on the local host$ tcpdump tcp port 45000</code></pre>
</td>
</tr>
</tbody>
</table>
<p>
<p><code>tcpdump</code> 和 <code>tcpflow</code> 工具有大量的选项，包括创建复杂过滤表达式的能力。查阅下面的 <a href="http://www-128.ibm.com/developerworks/cn/linux/l-sockpit/#resources">参考资料</a> 获取更多关于这些工具的信息。 </p>
<p><code>tcpdump</code> 和 <code>tcpflow</code> 都是基于文本的命令行工具。如果您更喜欢图形用户界面（GUI），有一个开放源码工具 <code>Ethereal</code> 也许适合您的需要。<code>Ethereal</code> 是一个专业的协议分析软件，它可以帮助调试应用层协议。它的插入式架构（plug-in architecture）可以分解协议，比如 HTTP 和您能想到的任何协议（写本文的时候共有 637 个协议）。</p>
<p>套接字编程是容易而有趣的，但是您要避免引入错误或至少使它们容易被发现，这就需要考虑本文中描述的这 5 个常见的陷阱，并且采用标准的防错性程序设计实践。GNU/Linux 工具和实用程序还可以帮助发现一些程序中的小问题。记住：在查看实用程序的帮助手册时候，跟踪相关的或&ldquo;请参见&rdquo;工具。您也许会发现一个必要的新工具。</p>

	<h4>相关文章</h4>
	<ul class="st-related-posts">
	<li><a href="http://www.donevii.com/post/78.html" title="[转]C/C++/Perl/汇编/Java效率比较 (2006-10-11)">[转]C/C++/Perl/汇编/Java效率比较</a> (0)</li>
	<li><a href="http://www.donevii.com/post/44.html" title="ACE自适配通信环境 (2006-08-23)">ACE自适配通信环境</a> (0)</li>
	<li><a href="http://www.donevii.com/post/43.html" title="ACE 简介 (2006-08-23)">ACE 简介</a> (0)</li>
	<li><a href="http://www.donevii.com/post/387.html" title="[转]嵌入式系统 Boot Loader 技术内幕 (2008-01-14)">[转]嵌入式系统 Boot Loader 技术内幕</a> (0)</li>
	<li><a href="http://www.donevii.com/post/137.html" title="SOAP(简单对象访问协议) 简介 (2006-10-27)">SOAP(简单对象访问协议) 简介</a> (0)</li>
</ul>

]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/170.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>FTP命令大全</title>
		<link>http://www.donevii.com/post/159.html</link>
		<comments>http://www.donevii.com/post/159.html#comments</comments>
		<pubDate>Tue, 31 Oct 2006 07:48:25 +0000</pubDate>
		<dc:creator>gavinkwoe</dc:creator>
				<category><![CDATA[doc]]></category>
		<category><![CDATA[debug]]></category>
		<category><![CDATA[shell]]></category>
		<category><![CDATA[unix]]></category>
		<category><![CDATA[类]]></category>

		<guid isPermaLink="false">http://www.donevii.com/?p=159</guid>
		<description><![CDATA[FTP命令非常使用，尤其是在UNIX系统下FTP命令是Internet用户使用最频繁的命令之一，不论是在DOS还是UNIX操作系统下使用FTP，都会遇到大量的FTP内部命令。熟悉并灵活应用FTP的内部命令，可以大大... ]]></description>
			<content:encoded><![CDATA[<p>FTP命令非常使用，尤其是在UNIX系统下FTP命令是Internet用户使用最频繁的命令之一，不论是在DOS还是UNIX操作系统下使用<br />FTP，都会遇到大量的FTP内部命令。熟悉并灵活应用FTP的内部命令，可以大大方便使用者，<br />并收到事半功倍之效。</p>
<p>　　FTP的命令行格式为： ftp -v -d -i -n -g [主机名] ，其中<br />　　-v 显示远程服务器的所有响应信息；<br />　　-d 使用调试方式；<br />　　-i 限制ftp的自动登录，即不使用；<br />　　-n etrc文件；<br />　　-g 取消全局文件名。</p>
<p>　　ftp使用的内部命令如下(中括号表示可选项):<br />　　1. ![cmd[args]]：在本地机中执行交互<a href="http://www.donevii.com/post/tag/shell" class="st_tag internal_tag" rel="tag" title="Posts tagged with shell">shell</a>，exit回到ftp环境，如：!ls*.zip。<br />　　2. $ macro-ame[args]：执行宏定义macro-name。<br />　　3. account[password]：提供登录远程系统成功后访问系统资源所需的补充口令。<br />　　4. append local-file[remote-file]：将本地文件追加到远程系统主机，若未指定远程系统文件名，则使用本地文件名。<br />　　5. ascii：使用ascii类型传输方式。<br />　　6. bell：每个命令执行完毕后计算机响铃一次。<br />　　7. bin：使用二进制文件传输方式。<br />　　8. bye：退出ftp会话过程。<br />　　9. case：在使用mget时，将远程主机文件名中的大写转为小写字母。<br />　　10. cd remote-dir：进入远程主机目录。<br />　　11. cdup：进入远程主机目录的父目录。<br />　　12. chmod mode file-name：将远程主机文件file-name的存取方式设置为mode，如： chmod 777 a.out 。<br />　　13. close：中断与远程服务器的ftp会话(与open对应)。<br />　　14. cr：使用asscii方式传输文件时，将回车换行转换为回行。<br />　　15. delete remote-file：删除远程主机文件。<br />　　16. debug[debug-value]：设置调试方式， 显示发送至远程主机的每条命令，如： deb up 3，若设为0，表示取消debug。<br />　　17. dir[remote-dir][local-file]：显示远程主机目录，并将结果存入本地文件local-file。<br />　　18. disconnection：同close。<br />　　19. form format：将文件传输方式设置为format，缺省为file方式。<br />　　20. get remote-file[local-file]： 将远程主机的文件remote-file传至本地硬盘的local-file。<br />　　21. glob：设置mdelete，mget，mput的文件名扩展，缺省时不扩展文件名，同命令行的-g参数。<br />　　22. hash：每传输1024字节，显示一个hash符号(#)。<br />　　23. help[cmd]：显示ftp内部命令cmd的帮助信息，如：help get。<br />　　24. idle[seconds]：将远程服务器的休眠计时器设为[seconds]秒。<br />　　25. image：设置二进制传输方式(同binary)。<br />　　26. lcd[dir]：将本地工作目录切换至dir。<br />　　27. ls[remote-dir][local-file]：显示远程目录remote-dir， 并存入本地文件local-file。<br />　　28. macdef macro-name：定义一个宏，遇到macdef下的空行时，宏定义结束。<br />　　29. mdelete[remote-file]：删除远程主机文件。<br />　　30. mdir remote-files local-file：与dir类似，但可指定多个远程文件，如： mdir *.o.*.zipoutfile 。<br />　　31. mget remote-files：传输多个远程文件。<br />　　32. mkdir dir-name：在远程主机中建一目录。<br />　　33. mls remote-file local-file：同nlist，但可指定多个文件名。<br />　　34. mode[modename]：将文件传输方式设置为modename， 缺省为stream方式。<br />　　35. modtime file-name：显示远程主机文件的最后修改时间。<br />　　36. mput local-file：将多个文件传输至远程主机。<br />　　37. newer file-name： 如果远程机中file-name的修改时间比本地硬盘同名文件的时间更近，则重传该文件。<br />　　38. nlist[remote-dir][local-file]：显示远程主机目录的文件清单，并存入本地硬盘的local-file。<br />　　39. nmap[inpattern outpattern]：设置文件名映射机制， 使得文件传输时，文件中的某些字符相互转换，如：nmap $1.$2.$3[$1，$2].[$2，$3]，则传输文件a1.a2.a3时，文件名变为a1，a2。该命令特别适用于远程主机为非UNIX机的情况。<br />　　40. ntrans[inchars[outchars]]：设置文件名字符的翻译机制，如ntrans1R，则文件名LLL将变为RRR。<br />　　41. open host[port]：建立指定ftp服务器连接，可指定连接端口。<br />　　42. passive：进入被动传输方式。<br />　　43. prompt：设置多个文件传输时的交互提示。<br />　　44. proxy ftp-cmd：在次要控制连接中，执行一条ftp命令，该命令允许连接两个ftp服务器，以在两个服务器间传输文件。第一条ftp命令必须为open，以首先建立两个服务器间的连接。<br />　　45. put local-file[remote-file]：将本地文件local-file传送至远程主机。<br />　　46. pwd：显示远程主机的当前工作目录。<br />　　47. quit：同bye，退出ftp会话。<br />　　48. quote arg1，arg2&#8230;：将参数逐字发至远程ftp服务器，如：quote syst。<br />　　49. recv remote-file[local-file]：同get。<br />　　50. reget remote-file[local-file]：类似于get，但若local-file存在，则从上次传输中断处续传。<br />　　51. rhelp[cmd-name]：请求获得远程主机的帮助。<br />　　52. rstatus[file-name]：若未指定文件名，则显示远程主机的状态，否则显示文件状态。<br />　　53. rename[from][to]：更改远程主机文件名。<br />　　54. reset：清除回答队列。<br />　　55. restart marker：从指定的标志marker处，重新开始get或put，如：restart 130。<br />　　56. rmdir dir-name：删除远程主机目录。<br />　　57. runique：设置文件名唯一性存储。<br />　　58. send local-file[remote-file]：同put。<br />　　59. sendport：设置PORT命令的使用。<br />　　60. site arg1，arg2&#8230;：将参数作为SITE命令逐字发送至远程ftp主机。<br />　　61. size file-name：显示远程主机文件大小，如：site idle 7200。<br />　　62. status：显示当前ftp状态。<br />　　63. struct[struct-name]：将文件传输结构设置为struct-name，缺省时使用stream结构。<br />　　64. sunique：将远程主机文件名存储设置为唯一(与runique对应)。<br />　　65. system：显示远程主机的操作系统类型。<br />　　66. tenex：将文件传输类型设置为TENEX机的所需的类型。<br />　　67. tick：设置传输时的字节计数器。<br />　　68. trace：设置包跟踪。<br />　　69. type[type-name]：设置文件传输类型为type-name，缺省为ascii，如：type binary，设置二进制传输方式。<br />　　70. umask[newmask]：将远程服务器的缺省umask设置为newmask，如：umask 3。<br />　　71. user user-name[password][account]：向远程主机表明自己的身份，需要口令时，必须输入口令，如：user anonymous my@email。<br />　　72. verbose：同命令行的-v参数，即设置详尽报告方式，ftp服务器的所有响应都将显示给用户，缺省为on。<br />　　73. ?[cmd]：同help。</p>

	<h4>相关文章</h4>
	<ul class="st-related-posts">
	<li><a href="http://www.donevii.com/post/283.html" title="免费软件套装 (2007-01-08)">免费软件套装</a> (0)</li>
	<li><a href="http://www.donevii.com/post/314.html" title="Shell编程 &#8211; 傻瓜教程1 (2007-03-24)">Shell编程 &#8211; 傻瓜教程1</a> (0)</li>
	<li><a href="http://www.donevii.com/post/87.html" title="shell使用指南 (2006-10-17)">shell使用指南</a> (0)</li>
	<li><a href="http://www.donevii.com/post/312.html" title="IE编程 &#8211; ToolBar (2007-03-24)">IE编程 &#8211; ToolBar</a> (0)</li>
	<li><a href="http://www.donevii.com/post/229.html" title="设计模式之Life time controller模式 (2006-11-27)">设计模式之Life time controller模式</a> (0)</li>
</ul>

]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/159.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SOAP(简单对象访问协议) 简介</title>
		<link>http://www.donevii.com/post/137.html</link>
		<comments>http://www.donevii.com/post/137.html#comments</comments>
		<pubDate>Fri, 27 Oct 2006 04:48:32 +0000</pubDate>
		<dc:creator>gavinkwoe</dc:creator>
				<category><![CDATA[doc]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[server]]></category>
		<category><![CDATA[unix]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[平台]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[测试]]></category>
		<category><![CDATA[类]]></category>

		<guid isPermaLink="false">http://www.donevii.com/?p=137</guid>
		<description><![CDATA[SOAP(Simple Object Access Protocal，简单对象访问协议) 技术有助于实现大量异构程序和平台之间的互操作性，从而使存在的应用能够被广泛的用户所访问。SOAP是把成熟的基于HTTP的WEB技术与XML的灵活性... ]]></description>
			<content:encoded><![CDATA[<p><span style="FONT-SIZE: 13px">SOAP(Simple Object Access Protocal，简单对象访问协议) 技术有助于实现大量异构程序和平台之间的互操作性，从而使存在的应用能够被广泛的用户所访问。SOAP是把成熟的基于HTTP的WEB技术与XML的灵活性和可扩展性组合在了一起。</span>
<p><span style="FONT-SIZE: 13px">SOAP由MS和IBM共同制定<br />用于规范WEB服务标准 实现异构程序与平台间的数据交换</p>
<p>它是基于XML的协议，包括三个部分： 封套(envelope)定义了消息内容和处理的框架、一套编码规则用来表达应用定义数据类型的实例以及表达远程过程调用和响应的协定。</p>
<p>与已定义的中间件不同 SOAP只是定义了一种基于XML的文本格式 而没有定义什么ORB代理或是SOAP API 因此用户可以方便的开发自己的应用而不必担心兼容性(corba与dcom间的兼容性在soap中不会再出现)</p>
<p>下面是一篇简介 更多介绍在动网先锋中可以找到<br />http://www.aspsky.net/article/show.aspx?id=2001</p>
<p>简单对象协议（SOAP）简介<br />作者：何杭军</p>
<p>简单对象访问协议-CNXML标准教程 <br />2000-9-25 作者：何杭军</p>
<p>&quot;SOAP是在非集中、分布环境中交换信息的轻量级协议。它是基于XML的协议，包括三个部分： 封套(envelope)定义了消息内容和处理的框架、一套编码规则用来表达应用定义数据类型的实例以及表达远程过程调用和响应的协定。&quot;<br />&mdash;&mdash;SOAP 1.1规范</p>
<p>第一节 SOAP简介</p>
<p>SOAP(Simple Object Access Protocal，简单对象访问协议) 技术有助于实现大量异构程序和平台之间的互操作性，从而使存在的应用能够被广泛的用户所访问。SOAP是把成熟的基于HTTP的WEB技术与XML的灵活性和可扩展性组合在了一起。</p>
<p>SOAP的一个主要目标是使存在的应用能被更广泛的用户所使用。为了实现这个目的，没有任何SOAP API或SOAP 对象请求代理（SOAP ORB），SOAP是假设你将使用尽可能多的存在的技术。几个主要的CORBA厂商已经承诺在他们的ORB产品中支持SOAP协议。微软也承诺在将来的 COM版本中支持SOAP。DevelopMentor已经开发了参考实现，它使得在任何平台上的任何Java或Perl程序员都可以使用SOAP。而且 IBM和Sun也陆续支持了SOAP协议，和MS合作共同开发SOAP规范和应用。目前SOAP已经成为了W3C和IETF的参考标准之一。</p>
<p>SOAP的指导理念是&ldquo;它是第一个没有发明任何新技术的技术&rdquo;。它采用了已经广泛使用的两个协议：HTTP和XML。HTTP用于实现SOAP的RPC风格的传输，而XML是它的编码模式。采用几行代码和一个XML解析器，HTTP服务器（如MS的IIS或Apache）立刻成为了SOAP的ORBs。因为目前超过一半的Web服务器采用IIS或Apache, SOAP将会从这两个产品的广泛而可靠的使用中获取利益。这并不意味着所有的SOAP请求必须通过Web服务器来路由，传统的Web 服务器只是分派SOAP请求的一种方式。因此Web服务如IIS或Apache对建立SOAP性能的应用是充分的，但决不是必要的。</p>
<p>SOAP把XML的使用代码化为请求和响应参数编码模式，并用HTTP作传输。这似乎有点抽象。具体地讲，一个SOAP方法可以简单地看作遵循SOAP编码规则的HTTP请求和响应。一个SOAP终端则可以看作一个基于HTTP的URL，它用来识别方法调用的目标。象CORBA/IIOP一样，SOAP不需要具体的对象被绑定到一个给定的终端，而是由具体实现程序来决定怎样把对象终端标识符映射到服务器端的对象。</p>
<p>SOAP请求是一个HTTP POST请求。SOAP请求的content-type必须用text/xml。而且它必须包含一个请求-URI。服务器怎样解释这个请求-URI是与实现相关的，但是许多实现中可能用它来映射到一个类或者一个对象。一个SOAP请求也必须用SOAPMethodName HTTP头来指明将被调用的方法。简单地讲，SOAPMethodName头是被URI指定范围的应用相关的方法名，它是用#符作为分隔符将方法名与 URI分割开：</p>
<p>SOAPMethodName: urn:strings-com:IString#reverse </p>
<p>这个头表明方法名是reverse，范围URI是urn:strings-com:Istring。 在SOAP中，规定方法名范围的名域URI在功能上等同于在DCOM 或 IIOP中规定方法名范围的接口ID。</p>
<p>简单的说，一个SOAP请求的HTTP体是一个XML文档，它包含方法中[in]和[in,out]参数的值。这些值被编码成为一个显著的调用元素的子元素，这个调用元素具有SOAPMethodName HTTP头的方法名和名域URI。调用元素必须出现在标准的SOAP &lt;Envelope&gt;;和&lt;Body&gt;;元素内（后面会更多讨论这两个元素）。下面是一个最简单的SOAP方法请求：</p>
<p>POST /string_server/Object17 HTTP/1.1<br />Host: 209.110.197.2<br />Content-Type: text/xml<br />Content-Length: 152<br />SOAPMethodName: urn:strings-com:IString#reverse <br />&lt;Envelope&gt;;<br />&lt;Body&gt;;<br />&lt;m:reverse xmlns:m=&#8221;urn:strings-com:IString&#8221;&gt;;<br />&lt;theString&gt;;Hello, World&lt;/theString&gt;;<br />&lt;/m:reverse&gt;;<br />&lt;/Body&gt;;<br />&lt;/Envelope&gt;;<br />SOAPMethodName头必须与&lt;Body&gt;;下的第一个子元素相匹配，否则调用将被拒绝。这允许防火墙管理员在不解析XML的情况下有效地过滤对一个具体方法的调用。</p>
<p>SOAP响应的格式类似于请求格式。响应体包含方法的[out]和 [in,out]参数，这个方法被编码为一个显著的响应元素的子元素。这个元素的名字与请求的调用元素的名字相同，但以Response后缀来连接。下面是对前面的SOAP请求的SOAP响应：</p>
<p>200 OK Content-Type: text/xml <br />Content-Length: 162 <br />&lt;Envelope&gt;; <br />&lt;Body&gt;; <br />&lt;m:reverseResponse xmlns:m=&#8221;urn:strings-com:IString&#8221;&gt;;<br />&lt;result&gt;;dlroW ,olleH&lt;/result&gt;;<br />&lt;/m:reverseResponse&gt;;<br />&lt;/Body&gt;;<br />&lt;/Envelope&gt;; <br />这里响应元素被命名为reverseResponse，它是方法名紧跟Response后缀。要注意的是这里是没有SOAPMethodName HTTP头的。这个头只在请求消息中需要，在响应消息中并不需要。</p>
<p>第二节 SOAP体的核心</p>
<p>SOAP的XML特性是为把数据类型的实例序列化成XML的编码模式。为了达到这个目的，SOAP不要求使用传统的RPC风格的代理。而是一个SOAP方法调用包含至少两个数据类型：请求和响应。考虑这下面个COM IDL代码：</p>
<p>[ uuid(DEADF00D-BEAD-BEAD-BEAD-BAABAABAABAA) ]<br />interface IBank : IUnknown {<br />HRESULT withdraw([in] long account, <br />[out] float *newBalance,<br />[in, out] float *amount<br />[out, retval] VARIANT_BOOL *overdrawn);<br />}<br />在任何RPC协议下，account和amount参数的值将出现在请求消息中，newBalance、overdrawn参数的值，还有amount参数的更新值将出现在响应消息中。</p>
<p>SOAP把方法请求和方法响应提升到了一流状态。在SOAP中，请求和响应实际上类型的实例。为了理解一个方法比如IBank::withdraw怎样映射一个SOAP请求和响应类型，考虑下列的数据类型：</p>
<p>struct withdraw {<br />long account;<br />float amount;<br />};</p>
<p>这时所有的请求参数被打包成为单一的结构类型。同样下面的数据表示打包所有响应参数到单一的数据类型。 </p>
<p>struct withdrawResponse {<br />float newBalance;<br />float amount;<br />VARIANT_BOOL overdrawn;<br />};<br />再给出下面的简单的Visual Basic程序，它使用了以前定义的Ibank接口：</p>
<p>Dim bank as IBank<br />Dim amount as Single<br />Dim newBal as Single<br />Dim overdrawn as Boolean<br />amount = 100<br />Set bank = GetObject(&quot;soap:http://bofsoap.com/am&quot;<img src="http://bbs.chinaunix.net/images/smilies/icon_wink.gif" align="middle" border="0" alt="" /><br />overdrawn = bank.withdraw(3512, amount, newBal)</p>
<p>这里，在发送请求消息之前，参数被序列化成为一个请求对象。同样被响应消息接收到的响应对象被反序列化为参数。一个类似的转变同样发生在调用的服务器端。</p>
<p>当通过SOAP调用方法时，请求对象和响应对象被序列化成一种已知的格式。每个SOAP体是一个XML文档，它具有一个显著的称为&lt; Envelope&gt;;的根元素。标记名&lt;Envelope&gt;;由SOAP URI (urn:schemas-xmlsoap-org:soap.v1)来划定范围，所有SOAP专用的元素和属性都是由这个URI来划定范围的。SOAP Envelope包含一个可选的&lt;Header&gt;;元素，紧跟一个必须的&lt;Body&gt;;元素。&lt;Body&gt;;元素也有一个显著的根元素，它或者是一个请求对象或者是一个响应对象。下面是一个IBank::withdraw请求的编码：</p>
<p>&lt;soap:Envelope xmlns:soap=&#8221;urn:schemas-xmlsoap-org:soap.v1&#8221;&gt;;<br />&lt;soap:Body&gt;;<br />&lt;IBank:withdraw xmlns:IBank=&#8221;urn:uuid<img src="http://bbs.chinaunix.net/images/smilies/icon_biggrin.gif" align="middle" border="0" alt="" />EADF00D-BEAD-BEAD-BEAD-BAABAABAABAA&#8221;&gt;;<br />&lt;account&gt;;3512&lt;/account&gt;;<br />&lt;amount&gt;;100&lt;/amount&gt;;<br />&lt;/IBank:withdraw&gt;;<br />&lt;/soap:Body&gt;;<br />&lt;/soap:Envelope&gt;;<br />下列响应消息被编码为： <br />&lt;soap:Envelope xmlns:soap=&#8221;urn:schemas-xmlsoap-org:soap.v1&#8221;&gt;;<br />&lt;soap:Body&gt;;<br />&lt;IBank:withdrawResponse xmlns:IBank=&#8221;urn:uuid<img src="http://bbs.chinaunix.net/images/smilies/icon_biggrin.gif" align="middle" border="0" alt="" />EADF00D-BEAD-BEAD-BEAD-BAABAABAABAA&#8221;&gt;;<br />&lt;newBalance&gt;;0&lt;/newBalance&gt;;<br />&lt;amount&gt;;5&lt;/amount&gt;; <br />&lt;overdrawn&gt;;true&lt;/overdrawn&gt;;<br />&lt;/IBank:withdrawResponse&gt;;<br />&lt;/soap:Body&gt;;<br />&lt;/soap:Envelope&gt;;<br />注意[in, out]参数出现在两个消息中。在检查了请求和响应对象的格式后，你可能已经注意到序列化格式通常是： </p>
<p>&lt;t:typename xmlns:t=&#8221;namespaceuri&#8221;&gt;;<br />&lt;fieldname1&gt;;field1value&lt;/fieldname1&gt;;<br />&lt;fieldname2&gt;;field2value&lt;/fieldname2&gt;;<br />&#8230;&#8230;<br />&lt;/t:typename&gt;; <br />在请求的情况下，类型是隐式的C风格的结构，它由对应方法中的[in]和[in, out]参数组成。对响应来说，类型也是隐式的C风格的结构，它由对应方法中的[out]和[in, out]参数组成。这种每个域对应一个子元素的风格有时被称为元素正规格式(ENF)。一般情况下，SOAP只用XML特性来传达描述包含在元素内容中信息的注释。</p>
<p>象DCOM和IIOP一样，SOAP支持协议头扩展。SOAP用可选的&lt;Header&gt;;元素来传载被协议扩展所使用的信息。如果客户端的 SOAP软件包含要发送头信息，原始的请求将可能如图9所示。在这种情况下命名causality的头将与请求一起序列化。收到请求后，服务器端软件能查看头的名域URI，并处理它识别出的头扩展。这个头扩展被http://comstuff.com URI识别，并期待一个如下的对象：</p>
<p>struct causality { <br />UUID id; <br />}; <br />在这种情况下的请求，如果头元素的URI不能被识别，头元素可以被安全地忽略。</p>
<p>但你不能安全的忽略所有的SOAP体中的头元素。如果一个特定的SOAP头对正确处理消息是很关键的，这个头元素能被用SOAP属性 mustUnderstand=&rsquo;true&rsquo;标记为必须的。这个属性告诉接收者头元素必须被识别并被处理以确保正确的使用。为了强迫前面 causality头成为一个必须的头，消息将被写成如下形式：</p>
<p>&lt;soap:Envelope xmlns:soap=&#8221;urn:schemas-xmlsoap-org:soap.v1&#8221;&gt;;<br />&lt;soap:Header&gt;;<br />&lt;causality soap:mustUnderstand=&#8221;true&#8221;xmlns=&quot;http://comstuff.com&quot;&gt;;<br />&lt;id&gt;;362099cc-aa46-bae2-5110-99aac9823bff&lt;/id&gt;;<br />&lt;/causality&gt;; <br />&lt;/soap:Header&gt;;<br />&lt;/soap:Envelope&gt;;<br />SOAP软件遇到不能识别必须的头元素情况时，必须拒绝这个消息并出示一个错误。如果服务器在一个SOAP请求中发现一个不能识别的必须的头元素，它必须返回一个错误响应并且不发送任何调用到目标对象。如果客户端在一个SOAP请求中发现一个不能识别出的必须的头元素，它必须向调用者返回一个运行时错误。在COM情况下，这将映射为一个明显的HRESULT。</p>
<p>第三节 SOAP数据类型</p>
<p>在SOAP消息中，每个元素可能是一个SOAP结构元素、根元素、存取元素或一个独立的元素。在SOAP中，soap:Envelope、soap:Body和soap:Header是唯一的组成元素。它们的基本关系由下列XML Schema所描述: </p>
<p>&lt;schema targetNamespace=&#8221;urn:schemas-xmlsoap-org:soap.v1&#8221;&gt;;<br />&lt;element name=&#8221;Envelope&#8221;&gt;;<br />&lt;type&gt;;<br />&lt;element name=&#8221;Header&#8221; type=&#8221;Header&#8221; minOccurs=&#8221;0&#8221; /&gt;;<br />&lt;element name=&#8221;Body&#8221; type=&#8221;Body&#8221;minOccurs=&#8221;1&#8221; /&gt;;<br />&lt;/type&gt;;<br />&lt;/element&gt;;<br />&lt;/schema&gt;;<br />在SOAP元素的四种类型中，除了结构元素外都被用作表达类型的实例或对一个类型实例的引用。</p>
<p>根元素是显著的元素，它是soap:Body 或是 soap:Header的直接的子元素。其中soap: Body只有一个根元素，它表达调用、响应或错误对象。这个根元素必须是soap:Body的第一个子元素，它的标记名和域名URI必须与HTTP SOAPMethodName头或在错误消息情况下的soap:Fault相对应。而soap:Header元素有多个根元素，与消息相联系的每个头扩展对应一个。这些根元素必须是soap:Header的直接子元素，它们的标记名和名域URI表示当前存在扩展数据的类型。</p>
<p>存取元素被用作表达类型的域、属性或数据成员。一个给定类型的域在它的SOAP表达将只有一个存取元素。存取元素的标记名对应于类型的域名。考虑下列Java 类定义：</p>
<p>package com.bofsoap.IBank; <br />public class adjustment { <br />public int account ;<br />public float amount ;<br />}<br />在一个SOAP消息中被序列化的实例如下所示：</p>
<p>&lt;t:adjustment xmlns:t=&#8221;urn:develop-com:java:com.bofsoap.IBank&#8221;&gt;;<br />&lt;account&gt;;3514&lt;/account&gt;;<br />&lt;amount&gt;;100.0&lt;/amount&gt;;<br />&lt;/t:adjustment&gt;;<br />在这个例子中，存取元素account和amount被称着简单存取元素。对引用简单类型的存取元素，元素值被简单地编码为直接在存取元素下的字符数据，如上所示。对引用组合类型的存取元素（就是那些自身用子存取元素来构造的存取元素），有两个技术来对存取元素进行编码。最简单的方法是把被结构化的值直接嵌入在存取元素下。考虑下面的Java类定义：</p>
<p>package com.bofsoap.IBank;<br />public class transfer {<br />public adjustment from;<br />public adjustment to; <br />}<br />如果用嵌入值编码存取元素，在SOAP中一个序列化的transfer对象如下所示：</p>
<p>&lt;t:transfer xmlns:t=&#8221;urn:develop-com:java:com.bofsoap.IBank&#8221;&gt;;<br />&lt;from&gt;;<br />&lt;account&gt;;3514&lt;/account&gt;;<br />&lt;amount&gt;;-100.0&lt;/amount&gt;;<br />&lt;/from&gt;;<br />&lt;to&gt;;<br />&lt;account&gt;;3518&lt;/account&gt;;<br />&lt;amount&gt;;100.0&lt;/amount&gt;;<br />&lt;/to&gt;;<br />&lt;/t:transfer&gt;;<br />在这种情况下，adjustment对象的值被直接编码在它们的存取元素下。在考虑组合存取元素时，需要说明几个问题。先考虑上面的transfer类。类的from和to的域是对象引用，它可能为空。SOAP用XML Schemas的null属性来表示空值或引用。下面例子表示一个序列化的transfer对象，它的from域是空的：</p>
<p>&lt;t:transfer xmlns:t=&#8221;urn:develop-com:java:com.bofsoap.IBank&#8221; <br />xmlns<img src="http://bbs.chinaunix.net/images/smilies/icon_mad.gif" align="middle" border="0" alt="" />sd=&#8221;http://www.w3.org/1999/XMLSchema/instance&#8221;&gt;;<br />&lt;from xsd:null=&#8221;true&#8221; /&gt;;<br />&lt;to&gt;;<br />&lt;account&gt;;3518&lt;/account&gt;;<br />&lt;amount&gt;;100.0&lt;/amount&gt;; <br />&lt;/to&gt;; <br />&lt;/t:transfer&gt;;<br />在不存在的情况下， xsd:null属性的隐含值是false。给定元素的能否为空的属性是由XML Schema定义来控制的。例如下列XML Schema将只允许from存取元素为空：</p>
<p>&lt;type name=&#8221;transfer&#8221; &gt;;<br />&lt;element name=&#8221;from&#8221; type=&#8221;adjustment&#8221; nullable=&#8221;true&#8221; /&gt;;<br />&lt;element name=&#8221;to&#8221; type=&#8221;adjustment&#8221; nullable=&#8221;false&#8221;/&gt;;<br />&lt;/type&gt;;<br />在一个元素的Schema声明中如果没有nullable属性，就意味着在一个XML文档中的元素是不能为空的。Null存取元素的精确格式当前还在修订中&amp;#0;要了解用更多信息参考最新版本的SOAP规范。</p>
<p>与存取元素相关的另一个问题是由于类型关系引起的可代换性。由于前面的adjustment类不是一个final类型的类，transfer对象的 from和to域实际引用继承类型的实例是可能的。为了支持这种类型兼容的替换，SOAP使用一个名域限定的类型属性的XML Schema约定。这种类型属性的值是一个对元素具体的类型的限制的名字。考虑下面的adjustment扩展类：</p>
<p>package com.bofsoap.IBank;<br />public class auditedadjustment extends adjustment {<br />public int auditlevel;<br />}<br />给出下面Java语言：</p>
<p>transfer xfer = new transfer();<br />xfer.from = new auditedadjustment();<br />xfer.from.account = 3514; <br />xfer.from.amount = -100;<br />xfer.from.auditlevel = 3;<br />xfer.to = new adjustment();<br />xfer.to.account = 3518; <br />xfer.from.amount = 100;</p>
<p>在SOAP中transfer对象的序列化形式如下所示：</p>
<p>&lt;t:transfer xmlns<img src="http://bbs.chinaunix.net/images/smilies/icon_mad.gif" align="middle" border="0" alt="" />sd=&#8221;http://www.w3.org/1999/XMLSchema&#8221;<br />xmlns:t=&#8221;urn:develop-com:java:com.bofsoap.IBank&#8221;&gt;;<br />&lt;from xsd:type=&#8221;t:auditedadjustment&#8221; &gt;;<br />&lt;account&gt;;3514&lt;/account&gt;;<br />&lt;amount&gt;;-100.0&lt;/amount&gt;;<br />&lt;auditlevel&gt;;3&lt;/auditlevel &gt;;<br />&lt;/from&gt;;<br />&lt;to&gt;;<br />&lt;account&gt;;3518&lt;/account&gt;;<br />&lt;amount&gt;;100.0&lt;/amount&gt;;<br />&lt;/to&gt;;<br />&lt;/t:transfer&gt;;</p>
<p>在这里xsd:type属性引用一个名域限定的类型名，它能被反序列化程序用于实例化对象的正确类型。因为to存取元素引用到一个被预料的类型的实例（而不是一个可代替的继承类型），xsd:type属性是不需要的。</p>
<p>刚才的transfer类设法回避了一个关键问题。如果正被序列化的transfer对象用下面这种方式初始化将会发生什么情况：</p>
<p>transfer xfer = new transfer();<br />xfer.from = new adjustment();<br />xfer.from.account = 3514; xfer.from.amount = -100;<br />xfer.to = xfer.from;<br />基于以前的议论，在SOAP 中transfer对象的序列化形式如下所示：</p>
<p>&lt;t:transfer xmlns:t=&#8221;urn:develop-com:java:com.bofsoap.IBank&#8221;&gt;;<br />&lt;from&gt;;<br />&lt;account&gt;;3514&lt;/account&gt;;<br />&lt;amount&gt;;-100.0&lt;/amount&gt;;<br />&lt;/from&gt;;<br />&lt;to&gt;;<br />&lt;account&gt;;3514&lt;/account&gt;;<br />&lt;amount&gt;;-100.0&lt;/amount&gt;;<br />&lt;/to&gt;;<br />&lt;/t:transfer&gt;;<br />这个表达有两个问题。首先最容易理解的问题是同样的信息被发送了两次,这导致了一个比实际所需要消息的更大的消息。一个更微妙的但是更重要的问题是由于反序列化程序不能分辨两个带有同样值的adjustment对象与在两个地方被引用的一个单一的adjustment对象的区别，两个存取元素间的身份关系就被丢失。如果这个消息接收者已经在结果对象上执行了下面的测试，(xfer.to == xfer.from)将不会返回true。</p>
<p>void processTransfer(transfer xfer) {<br />if (xfer.to == xfer.from)<br />handleDoubleAdjustment(xfer.to);<br />else <br />handleAdjustments(xfer.to, xfer.from);<br />}<br />为了支持必须保持身份关系的类型的序列化，SOAP支持多引用存取元素。目前我们接触到的存取元素是单引用存取元素，也就是说，元素值是嵌入在存取元素下面的，而且其它存取元素被允许引用那个值（这很类似于在NDR中的[unique]的概念）。多引用存取元素总是被编码为只包含已知的soap:href 属性的空元素。soap:href属性总是包含一个代码片段标识符，它对应于存取元素引用到的实例。如果to和from存取元素已经被编码为多引用存取元素，序列化的transfer对象如下所示：</p>
<p>&lt;t:transfer xmlns:t=&#8221;urn:develop-com:java:com.bofsoap.IBank&#8221;&gt;; <br />&lt;from soap:href=&#8221;#id1&#8221; /&gt;; <br />&lt;to soap:href=&#8221;#id1&#8221; /&gt;; <br />&lt;/t:transfer&gt;;<br />这个编码假设与adjustment类兼容的一个类型的实例已经在envelope中的其它地方被序列化，而且这个实例已经被用soap:id属性标记，如下所示：</p>
<p>&lt;t:adjustment soap:id=&#8221;id1&#8221;xmlns:t=&#8221;urn:develop-com:java:com.bofsoap.IBank&#8221;&gt;;<br />&lt;account&gt;;3514&lt;/account&gt;;<br />&lt;amount&gt;;-100.0&lt;/amount&gt;;<br />&lt;/t:adjustment&gt;;</p>
<p>第四节 结语</p>
<p>一个遗留的HTTP问题还需要进一步阐明。SOAP支持(但不需要)HTTP扩展框架约定来指定必须的HTTP头扩展。这些约定主要有两个目的。首先，它们允许任意的URI被用于限定给定的HTTP头的范围(类似XML名域)。第二,这些约定允许把必须的头与可选的头区分开来(象soap: mustUnderstand)。下面是一个使用HTTP扩展框架来把SOAPMethodName头定义成为一个必须的头扩展：</p>
<p>M-POST /foobar HTTP/1.1 <br />Host: 209.110.197.2 <br />Man: &quot;urn:schemas-xmlsoap-org:soap.v1; ns=42&quot; <br />42-SOAPMethodName: urn:bobnsid:IFoo#DoIt <br />Man头映射SOAP URI到前缀为42的头，并表示没有认出SOAP的服务器必须返回一个HTTP错误，状态代码为501 (没有被实现) 或 510 (没有被扩展)。HTTP方法必须是M-POST，表明目前是必须的头扩展。SOAP是一个被类型化的序列化格式，它恰巧用HTTP 作为请求/响应消息传输协议。SOAP被设计为与正将出现的XML Schema规范密切配合，并支持在Internet的任何地方运行的COM、CORBA、Perl、Tcl、和Java、C、Python或 PHP等程序间的互操作性。</span></p>

	<h4>相关文章</h4>
	<ul class="st-related-posts">
	<li><a href="http://www.donevii.com/post/78.html" title="[转]C/C++/Perl/汇编/Java效率比较 (2006-10-11)">[转]C/C++/Perl/汇编/Java效率比较</a> (0)</li>
	<li><a href="http://www.donevii.com/post/118.html" title="Web2.0遭遇生死劫？ (2006-10-25)">Web2.0遭遇生死劫？</a> (0)</li>
	<li><a href="http://www.donevii.com/post/504.html" title="google背后的分布式架构 (2008-09-23)">google背后的分布式架构</a> (0)</li>
	<li><a href="http://www.donevii.com/post/44.html" title="ACE自适配通信环境 (2006-08-23)">ACE自适配通信环境</a> (0)</li>
	<li><a href="http://www.donevii.com/post/43.html" title="ACE 简介 (2006-08-23)">ACE 简介</a> (0)</li>
</ul>

]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/137.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>应用加速</title>
		<link>http://www.donevii.com/post/128.html</link>
		<comments>http://www.donevii.com/post/128.html#comments</comments>
		<pubDate>Thu, 26 Oct 2006 08:10:37 +0000</pubDate>
		<dc:creator>dengwei</dc:creator>
				<category><![CDATA[software & hardware]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[server]]></category>
		<category><![CDATA[unix]]></category>
		<category><![CDATA[windows]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[类]]></category>
		<category><![CDATA[缓存]]></category>

		<guid isPermaLink="false">http://www.donevii.com/?p=128</guid>
		<description><![CDATA[应用加速（Application Acceleration）是当前信息产业中流行的一个新的时髦词语，但它到底是什么含义呢？ 缓慢的应用软件响应时间可能会让用户感到失望并且失去生产力，从而对公司的核心实力... ]]></description>
			<content:encoded><![CDATA[<p>应用加速（Application Acceleration）是当前信息产业中流行的一个新的时髦词语，但它到底是什么含义呢？ </p>
<p>缓慢的应用软件响应时间可能会让用户感到失望并且失去生产力，从而对公司的核心实力造成影响，所以，应用软件加速成为信息产业中的一个新的时髦词语也就不足为奇了，但 它到底是什么含义呢？您是否需要它呢？如果需要的话，那么最具扩展性的实现方式是什么呢？让我们来看看不同的应用软件加速技术以及如何创建应用软件加速策略来与您的公 司一同成长。 </p>
<p>本地VS网络应用软件 </p>
<p>在过去，大部分应用软件都是安装并运行在本地计算机，现在的小型公司也通常是这样。对于本地应用软件而言，性能主要取决于本地系统资源，它受到处理器速度、处理器数量 、物理和虚拟内存数量和共享这些资源的其他应用软件（多任务）的影响。 </p>
<p>对于这种类型的系统环境，提高应用软件性能是在单独的机器上通过升级硬件、关闭消耗宝贵资源的视觉效果，禁用不必要的与您的应用软件争夺处理器时间和内存的后台服务， 使用预取（prefetching）技术来提高应用软件的加载时间，定期整理磁盘碎片，优化各种设置比如设定进程的优先级、调整与性能相关的注册表条目等等。 </p>
<p>然而，在当今的大中型企业中，很多应用软件都是通过局域网、城域网或互联网传递的，这对原有的方法带来了新的问题，同时也为网络带宽和网络协议带来了新的问题。 </p>
<p>应用软件转移到了互联网上</p>
<p>越来越多的应用软件正在通过互联网来传递，从便利性的角度来看，这很有意义，因为几乎所有与互联网相连的计算机都有网络浏览器，这就排除了在用户系统上安装任何软件的 需要，HTTP是最普遍的网络协议中的一个，这种应用软件的&ldquo;互联网化&rdquo;为用户创建了一个通用界面和环境，同时也是系统管理员的通用传递系统。 </p>
<p>在基于网络的应用软件加速的问题上，一个关键因素是使用一个逆向代理（reverse proxy）来缓存静态对象，我们在这个栏目里所讨论的缓冲方案在以往更多地被应用于正向缓存 ，这是一种加速内部用户访问互联网内容的方法：让代理服务器在本地存储从互联网站点上下载的对象。 </p>
<p>逆向代理，正如名字所暗示的，是反方向工作的，逆向代理服务器存储了在您的内部服务器上被访问过的对象，然后将它们返回给请求者，这样请求者不必到服务器上去检索这些 数据，对于外部用户而言就意味着性能的提高。 </p>
<p>扩展一个逆向代理的解决方案</p>
<p>互联网应用软件加速经常是在企业环境下讨论的，但即使是小型公司也可以使用逆向服务器来加速基于互联网的应用软件，您不必花费很多钱来实现这一点，免费的和廉价的逆向 代理软件包括： </p>
<p>Squid：一个基于GPL许可的开源的代理服务器，它可以从<a href="ftp://ftp.squid-cache.org/pub/">ftp://ftp.squid-cache.org/pub/</a>上免费下载，它 可以在Linux、FreeBSD、Mac OS X和很多版本的UNIX上运行，您可以使用Cygwin开发环境（Cygwin development environment）在Windows上编译和运行Squid。 </p>
<p>IIS逆向代理：一个用于互联网信息服务（IIS）的开源逆向代理软件，它是基于MIT许可的，可以安装在运行IIS的Windows2000、XP或Windows Server2003上，您可以在<a href="http://www.saltypickle.com/Home/16">http://www.saltypickle.com/Home/16</a>上下载到这一软件。</p>
<p>Orenosp安全逆向代理：一个逆向代理的共享软件，还具有平衡负载和安全端口的转发功能，它可以运行在Windows NT、2000、XP和2003，以及Linux和Mac OS X上，如果需要 更多的信息可以参见<a href="http://hp.vector.co.jp/authors/VA027031/orenosp/index_en.html">http://hp.vector.co.jp/authors/VA027031/orenosp/index_en.html</a> </p>
<p>通常，随着公司的发展，您将会需要对互联网应用软件进行加速，功能不复杂的逆向代理服务器会缓存所有的数据，而高级一些的会允许您像外科手术般地来操作哪些数据需要缓 存，哪些不需要。您可能还希望考虑可扩展性的协议支持：您需要缓存的可能不仅仅是HTTP对象。 </p>
<p>从免费的和低廉的解决方案中提升一步，您可以发现一些适合大中型公司需求的中等价位的产品，例如：</p>
<p>微软的ISA服务器：ISA可以作为正向的或逆向代理服务器和应用层过滤防火墙，所以您可以应用很多相关的功能。标准版可以运行在Windows 2000 <a href="http://www.donevii.com/post/tag/server" class="st_tag internal_tag" rel="tag" title="Posts tagged with server">Server</a>或Windows <a href="http://www.donevii.com/post/tag/server" class="st_tag internal_tag" rel="tag" title="Posts tagged with server">Server</a> 2003上，每个处理器的价格为1499美元，企业版可以部署在服务器阵列上，因而具备更强的灵活性与可扩展性，每个处理器价格为5999美元。需要更多的信息，请点击此处。 </p>
<p>Vigos AG网站加速器：对于仅仅需要一个逆向代理而不需要ISA的附加功能的用户而言，这是一个提高他们网络站点性能的选择。Vigos还可以实时压缩发送的数据，&ldquo;简易&rdquo; 版（支持单一域）的价格是499美元，标准版（支持最多十个域）的价格是999美元，企业版（支持无限个域）的价格为1999美元。需要更多的信息，请点击此处。在企业级，逆向代理允许您对多个前端互联网服务器实施逆向代理方案，并提供SSL的安全连接保障。为企业级市场设计的高端逆向代理解决方案需要花费30000美元或更多，一些 可选方案包括： </p>
<p>Blue Coat Proxy SG：作为一套配置齐全立即可用的设备，Proxy SG有很多产品系列(400, 800和8000)以满足不通的预算和需求，您可以对上游的互联网服务器实施正向缓存 ，甚至对上游的服务器进行状态检查，高端的8000系列可以支持超过50000接入用户并支持最高300Mbps的城域网吞吐量，型号为8000-4的价格为99000美元，需要更多的信息，请点击此处。</p>
<p>思科内容引擎（Content Engine）：这是Cisco的缓存与内容过滤设备，可以部署在您的互联网站点之前来缓存或分流请求，以及卸载互联网服务器的数据流量，以此来增加网 络应用软件的性能。内容引擎7325的价格在40000美元到50000之间。</p>

	<h4>相关文章</h4>
	<ul class="st-related-posts">
	<li><a href="http://www.donevii.com/post/78.html" title="[转]C/C++/Perl/汇编/Java效率比较 (2006-10-11)">[转]C/C++/Perl/汇编/Java效率比较</a> (0)</li>
	<li><a href="http://www.donevii.com/post/504.html" title="google背后的分布式架构 (2008-09-23)">google背后的分布式架构</a> (0)</li>
	<li><a href="http://www.donevii.com/post/129.html" title="大型高并发高负载网站的系统架构 (2006-10-26)">大型高并发高负载网站的系统架构</a> (0)</li>
	<li><a href="http://www.donevii.com/post/9.html" title="flickr对javascript干的好事 (2006-08-17)">flickr对javascript干的好事</a> (0)</li>
	<li><a href="http://www.donevii.com/post/124.html" title="C++资源之不完全导引 (2006-10-26)">C++资源之不完全导引</a> (0)</li>
</ul>

]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/128.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
