Posted: September 23, 2008 at 4:48 pm | Tags: blog, cache, html, java, linux, server, unix, web, 优化, 平台, 开发, 技术, 测试, 类, 缓存, 芯片
Google是与众不同的。它的独特不仅仅表现于革新的思维和充满创意的应用 (比如那个大堂里的地球模型),更在于其有别常规的IT策略……
加利福尼亚州山景城(Mountain View)Google公司(Google,下称Google)总部有一个43号大楼,该建筑的中央大屏幕上显示着一个与Google地球(Google Earth)相仿的世界地图,一个转动的地球上不停地闪动着五颜六色的光点,恍如罗马宫廷的千万烛灯,每一次闪动标志着地球的这个角落一名Google用 户发起了一次新的搜索。
这同时意味着Google又一次满足了人们对未知信息的好奇与渴望。
Google是与众不同的。它的独特不仅仅表现于革新的思维和充满创意的应用 (比如那个大堂里的地球模型),更在于其有别常规的IT策略。从人们的常理来看,简单的硬件商品和免费软件是无法构建出一个帝国的,但是Google做到 了。在性能调整后,Google把它们变成一个无可比拟的分布式计算平台,该平台能够支持大规模的搜索和不断涌现的新兴应用。我们原本认为这些应用都是个 人消费级别的,但是Google改变了这一切。现在商业世界也在使用它们,这就令这家搜索公司显得那么与众不同。
GoogleWeb 服务背后的IT架构对无数使用搜索引擎的用户来说也许并不是非常重要,但它是Google几百位致力于把全球信息组织起来,实现“随处可达,随时可用”目 标的工程师们的最核心工作。这就需要一个在覆盖范围和野心上都与Google的商业愿景完全相符的IT蓝图作为支撑。
Google 的经理们一直对公司的IT策略话题保持沉默,他们厌恶谈及特定的厂商或者产品,当被问到他们的服务器和数据中心时,他们总是闭口不谈。但与几位 Google的IT领导一起呆了一天后,我们最终得以揭示该公司的IT是如何运作的,那可不仅仅是一个运行在无数服务器集群上的、表面看来非常简单的搜索 引擎。在其简单的外表下,蕴涵着许多内部研发软件、定制硬件、人工智能,以及对性能的执着追求和打破常规的人力管理模式。
IT理念方面,Google对同行有一条建议:尽量避免那些人人都在使用的系统和软件,以自己的方式做事会更有独特的竞争优势。
“企业文化决定了你的做事方式。”道格拉斯”美林(Douglas Merrill),这位Google工程副总裁和事实上的首席信息官(CIO) 指出,“到了我们这样的发展阶段,企业观念和文化非常与众不同,这也反过来鞭策我们必须要采用与众不同的方式来运行那些他人看来很常规的系统。”
Google 最大的IT优势在于它能建造出既富于性价比(并非廉价)又能承受极高负载的高性能系统。因此IT顾问史蒂芬”阿诺德(Stephen Arnold)指出,Google与竞争对手,如亚马逊网站(Amazon)、电子港湾公司(eBay)、微软公司(Microsoft,下称微软)和雅 虎公司 (Yahoo,下称雅虎)等公司相比,具有更大的成本优势。Google程序员的效率比其他Web公司同行们高出50%~100%,原因是Google已 经开发出了一整套专用于支持大规模并行系统编程的定制软件库。据他估算,其他竞争公司可能要花上四倍的时间才能获得同等的效果。
打造服务器
Google 究竟是怎样做到这点的呢?其中一个手段,美林认为,“是因为我们自己动手打造硬件。”Google并不制造计算机系统,但它根据自己的参数定制硬件,然后 像MTV的节目“靓车打造”(Pimp My Ride)那样自己安装和调整硬件系统。开源程序经理克里斯”迪博纳(Chris DiBona)评论道:“我们很善于购买商业服务器,并且改造他们为我们所用,最后把性能压榨和发挥到极致,以致有时候他们热得像要融化了似的。”
这种亲手打造的方式,来源于Google从车库诞生时与生俱来的节俭风格,更与Google那超大型的系统规模息息相关,良好的习惯一直延续至 今。据说 Google在65个数据中心拥有20万~45万台服务器—这个数目会有偏差(取决于你如何定义服务器和由谁来做这项统计)。但是,不变的是持续上升的趋势。
Google不会去讨论这些资产,因为它认为保密也是一种竞争优势。事实上,Google之所以喜欢开源软件也是因为它的私密性。“如果我们购 买了软件许可或代码许可,人们只要对号入座,就可以猜出Google的IT基础架构。”迪博纳分析说, “使用开源软件,就使我们多了一条把握自己命运的途径。”
Google喜欢规模化的服务器运行方式。当有成百上千台机器时,定制服务器的优势也会成倍增加,效果也会更趋明显。Google正在俄勒冈州 哥伦比亚河边的达勒斯市建造一个占地30亩的数据中心,在那儿它可以获得运算和降温需要的低价水力电力能源(参见边栏《Google数据中心自有一套》)。
Google以“单元”(Cell)的形式组织这些运行 Linux操作系统的服务器,迪博纳把这种形式比喻成互联网服务的“磁盘驱动器”(但别和一直谣传的Google存储服务Gdrive混淆了,“并没有 Gdrive这回事。”一位Google女发言人明确表示。),公司的软件程序都驻扎在这些并不昂贵的电脑机箱里,由程序员决定它们的冗余工作量。这种由 很多单元组成的文件系统代替了商业存储设备;迪博纳表示Google这些单元设备更易于建造和维护,他还暗示他们能处理更大规模的数据。
Google 不会漏过对任何技术细节的关注。多年来,公司的工程师就在研究微处理器的内部工作机制,随着Google规模的持续壮大,必然会用到特别定制和调节过的芯 片。知名工程师路易斯”巴罗索(Luiz Barroso)去年在一篇发表在工业杂志上的论文中证实,近年来Google的主要负荷都由单核设计的系统承担着。但许多服务器端的应用,如 Google搜索索引服务,所需的并行计算在单核芯片的指令级别上执行得并不好。
曾在数据设备公司(Digital Equipment)和康柏公司(Compaq)当过芯片设计师的巴罗索认为,随着AMD公司、英特尔公司(Intel)、太阳计算机系统公司(Sun)开始制造多核芯片,必将会出现越来越多芯片级别的并行计算。
Google 也曾考虑过自己制造计算机芯片,但从业界潮流来看,这个冒险的举动似乎不是很必要。“微处理器的设计非常复杂而且成本昂贵,”运营高级副总裁乌尔斯”霍尔 茨勒(Urs Holzle)表示。Google宁愿与芯片制造商合作,让他们去理解自己的应用并设计适合的芯片。这是一种客户建议式的设计,其关注点在于总体吞吐量、 效能,以及耗电比,而不是看单线程的峰值性能。霍尔茨勒表示,“这也是最近多核CPU的设计潮流与未来方向。”
裁缝般地定制软件
为了能尽量压榨硬件性能,Google开发了相当数量的定制软件。创新产品主要包括用于简化处理和创建大规模数据集的编程模型 MapReduce;用于存储和管理大规模数据的系统BigTable;分析分布式运算环境中大规模数据集的解释编程语言Sawzall;用于数据密集型 应用的分布式文件系统的 “Google文件系统”(Google File System);还有为处理分布式系统队列分组和任务调度的“Google工作队列”(Google Workqueue)。
正是从Sawzall这些工具里体现出Google对计算效率的执著关注。并不是每家公司都能从底层去解决效率问题,但是对Google来说, 为常规关系型数据库无法容纳的大规模数据集专门设计一种编程语言是完全合理的。即使其他编程工具可以解决问题,Google的工程师们仍然会为了追求效率 而另外开发一套定制方案。Google工程师认为,Sawzall能与C++中的MapReduce相媲美,而且它更容易编写一些。
Google 对效率的关注使它不可能对标准Linux内核感到满意;Google会根据自己的需要运行修改过的内核版本。通过调整Linux的底层性能,Google 工程师们在提高了整体系统可靠性的基础上,还一并解决了数据损坏和数据瓶颈等一系列棘手问题。对内核的修改也使Google的计算机集群系统因为通信效率 的提高而运行得更快。
当然,Google偶尔也会出现系统故障,情况一旦发生,无数的用户就会受到影响了。三年前一次持续30分钟的系统故障使20%的搜索流量受到影响。
Google 开发了自己的网站服务器却没有使用开源的Apache服务器,尽管它在网站服务器的市场占有率超过60%。迪博纳认为,Google的网站服务器可以运行 在更多数量的主机上,对Google站点上内容庞大又彼此互相依赖的应用程序来说,这种服务器的负载均衡能力远比Apache的能力更高。同时,在用标准 公共网关接口(CGI)访问数据库动态网页方面,Google服务器的编程难度要比 Apache更高,但是最终运行速度却更快。“如果我们能够压榨出10%~20%的性能,我们就可以节省出更多系统资源、电量和人力了。”迪博纳在总结中指出。
Google还设计了自己的客户关系管理(CRM)系统用于支持自己基于竞价和点击的互联网广告收费业务。但对是否需要设计自己的工具,Google的态度也不是一成不变的。比如在财会软件上,它就使用了甲骨文公司(Oracle)的Financials软件。
美林拿着一只叉子举例说明现成的产品也可以带来价值。但在有些场合现成的软件产品就不一定适用了。“我们的文化在各个层面对我们的运作都有深远影响,”他表示,“所以我们不想让购买所得的工具改变我们的工作方式和文化层面。”
Google’s BigTable 原理 (翻译)
题记:google 的成功除了一个个出色的创意外,还因为有 Jeff Dean 这样的软件架构天才。
—— 编者
官方的 Google Reader blog 中有对BigTable 的解释。这是Google 内部开发的一个用来处理大数据量的系统。这种系统适合处理半结构化的数据比如 RSS 数据源。 以下发言 是 Andrew Hitchcock 在 2005 年10月18号 基于: Google 的工程师 Jeff Dean 在华盛顿大学的一次谈话 (Creative Commons License).
首先,BigTable 从 2004 年初就开始研发了,到现在为止已经用了将近8个月。(2005年2月)目前大概有100个左右的服务使用BigTable,比如: Print,Search History,Maps和 Orkut。根据Google的一贯做法,内部开发的BigTable是为跑在廉价的PC机上设计的。BigTable 让Google在提供新服务时的运行成本降低,最大限度地利用了计算能力。
BigTable 是建立在 GFS ,Scheduler ,Lock Service 和 MapReduce 之上的。
每个Table都是一个多维的稀疏图 sparse map。Table 由行和列组成,并且每个存储单元 cell 都有一个时间戳。在不同的时间对同一个存储单元cell有多份拷贝,这样就可以记录数据的变动情况。在他的例子中,行是URLs ,列可以定义一个名字,比如:contents。Contents 字段就可以存储文件的数据。或者列名是:”language”,可以存储一个“EN”的语言代码字符串。
为了管理巨大的Table,把Table根据行分割,这些分割后的数据统称为:Tablets。每 个Tablets大概有 100-200 MB,每个机器存储100个左右的 Tablets。底层的架构是:GFS。由于GFS是一种分布式的文件系统,采用Tablets的机制后,可以获得很好的负载均衡。比如:可以把经常响应 的表移动到其他空闲机器上,然后快速重建。
Tablets在系统中的存储方式是不可修改的 immutable 的SSTables,一台机器一个日志文件。当系统的内存满后,系统会压缩一些Tablets。由于Jeff在论述这点的时候说的很快,所以我没有时间把听到的都记录下来,因此下面是一个大概的说明:
压缩分为:主要和次要的两部分。次要的压缩仅仅包括几个Tablets,而主要的压缩时关于整个系统的压缩。主压缩有回收硬盘空间的功能。Tablets的位置实际上是存储在几个特殊的BigTable的存储单元cell中。看起来这是一个三层的系统。
客户端有一个指向METAO的Tablets的指针。如果METAO的Tablets被频繁使用,那个这台机器就会放弃其他的tablets专门支持 METAO这个Tablets。METAO tablets 保持着所有的META1的tablets的记录。这些tablets中包含着查找tablets的实际位置。(老实说翻译到这里,我也不太明白。)在这个系统中不存在大的瓶颈,因为被频繁调用的数据已经被提前获得并进行了缓存。
现在我们返回到对列的说明:列是类似下面的形式: family:optional_qualifier。在他的例子中,行:www.search-analysis.com 也许有列:”contents:其中包含html页面的代码。 “ anchor:cnn.com/news” 中包含着 相对应的url,”anchor:www.search-analysis.com/” 包含着链接的文字部分。列中包含着类型信息。
(翻译到这里我要插一句,以前我看过一个关于万能数据库的文章,当时很激动,就联系了作者,现在回想起来,或许google的 bigtable 才是更好的方案,切不说分布式的特性,就是这种建华的表结构就很有用处。)
注意这里说的是列信息,而不是列类型。列的信息是如下信息,一般是:属性/规则。 比如:保存n份数据的拷贝或者保存数据n天长等等。当 tablets 重新建立的时候,就运用上面的规则,剔出不符合条件的记录。由于设计上的原因,列本身的创建是很容易的,但是跟列相关的功能确实非常复杂的,比如上文提到 的 类型和规则信息等。为了优化读取速度,列的功能被分割然后以组的方式存储在所建索引的机器上。这些被分割后的组作用于 列 ,然后被分割成不同的 SSTables。这种方式可以提高系统的性能,因为小的,频繁读取的列可以被单独存储,和那些大的不经常访问的列隔离开来。
在一台机器上的所有的 tablets 共享一个log,在一个包含1亿的tablets的集群中,这将会导致非常多的文件被打开和写操作。新的log块经常被创建,一般是64M大小,这个GFS的块大小相等。当一个机器down掉后,控制机器就会重新发布他的log块到其他机器上继续进行处理。这台机器重建tablets然后询问控制机器处理结构的存储位置,然后直接对重建后的数据进行处理。这个系统中有很多冗余数据,因此在系统中大量使用了压缩技术。
Dean 对压缩的部分说的很快,我没有完全记下来,所以我还是说个大概吧:压缩前先寻找相似的 \行,列,和时间数据。
他们使用不同版本的: BMDiff 和 Zippy 技术。
BMDiff 提供给他们非常快的写速度: 100MB/s – 1000MB/s 。Zippy 是和 LZW 类似的。Zippy 并不像 LZW 或者 gzip 那样压缩比高,但是他处理速度非常快。
Dean 还给了一个关于压缩 web 蜘蛛数据的例子。这个例子的蜘蛛 包含 2.1B 的页面,行按照以下的方式命名:“com.cnn.www/index.html:http”.在未压缩前的web page 页面大小是:45.1 TB ,压缩后的大小是:4.2 TB , 只是原来的 9.2%。Links 数据压缩到原来的 13.9% , 链接文本数据压缩到原来的 12.7%。
Continue Reading
Posted: December 17, 2007 at 9:10 am | Tags: unix, vim, 基本, 操作, 类, 键盘

Whole 中文版 感谢 fdl 的翻译工作

以下是学习这些基本的VIM命令的基本步骤,在学习完之后,可以依照上图进一步的学习和使用!!
步骤1
注释1:
1) h j k l为光标键,分别左下上右
2) i 插入键进入编辑模式,Esc键退出到一般模式
3) x 删除当前光标所在字符,X删除当前光标前的一个字符
4) A 在一行的末尾添加text,a在当前光标后添加text
5) u 执行Undo操作,Ctrl+R表示Redo
6) 0 跳到行首,$ 跳到行尾 ^跳到行首(类似正则式$和^的意义)
7) w b e移动一个单词word(全部是字符或者符号)
8) W B E移动一个单词WORD(以空格隔开)
9) R 进入插入编辑模式,并且对被编辑位置进行覆盖
10) :w 保存 :q 退出 :q! 强制退出

步骤2
注释2:
1) f 移动光标从当前位置到下一个f后跟的字符的右边,包含此指定字符,F 方向相反为前一个,需要和操作(operator)配合操作
2) t 和 T类似f 和 F,只是它们一直到指定的字符左才停,即不包含指定的字符。
3) d 删除操作 与上面 w f t 等配合:例如”dw”删除下一word,”df-”从当前位置一直往前删除到字符”-”,不保留”-”,”dt-”删除直到”-”但是保留”-”
4) c 删除且进入编辑模式(类似d和i的结合),同样可以与f t T w 等motion结合
5) j k 可以和c和d结合,删除所有末尾或开头的所有行
6) . 可以重复最后一次编辑的所有操作,注意是在一般模式下重复最后一次编辑操作
7) 操作和motion可以和数字组合,”d2w” 删除后2个单词word “d2t,”删除知道这一行的第2个”,”
8) cc 和 dd 删除当前行
9) v 进入可视模式

步骤3
注释3:
1) y 后面跟任意的motion执行copy操作
2) p (paster)粘贴,如果复制的是字符形式,则粘贴到右边,行形式粘贴到下面
3) P 同上p操作,但是粘贴方向相反,在左边或上面
4) yy 复制当前行
5) y 也可以在可视模式(visual mode)下工作,同时,d,c,x..等text删除操作也将被删除内容进行复制(这一点要注意)
6) “和a-z字符如果在复制/删除/粘贴命令前表示选择一个寄存器暂存
7) 在复制/删除前的A-Z 寄存器意味进行叠加复制,就是多个复制操作的内容将被加起来
8) “*操作或者”+操作选择系统的剪贴板
9) o 在当前行下新其一行进入插入模式,O 操作类似o,只是在当前行的上新起一行

步骤4
注释4:
1) / 是基本的查找motion,可以和operation结合执行操作,也可以单独使用,直接查找匹配后面的正则式
2) ? 与/相同,但是方向相反backward,/ 方向forward
3) n 重复最后一次查找方向, N 重复最后一次查找的相反方向
4) * 和 # 查找当前光标所在的实体相同的实体,前者方向向前,后者方向向后,两者仅在vim下有效(vi not support)

步骤5
注释5:
1) m 操作后跟a-z字符用来设置一个标记mark
2) ` 后面跟a-z字符可以去这个字符表示的标记处
3) ‘ 和一个字符可以到所在行的第一个非空处
4) A-Z 标记为全局标记 a-z仅在每个buffer内可见
5) `. 表示到最后一个修改的地方
6) q 后跟字符a-z用来记录宏
7) @ 后跟字符用了重放宏,@@ 重放最后一个宏

步骤6
注释6:
1) % 在配对的( 和 ) [ 和 ]等 之间跳动
2) H M L 直接跳到整个屏幕的最上面,中间和最下面
3) G 跳到文件的末尾,G 前面跟行号表明跳到指定的行
4) – 或者 + 直接跳到前一行 或 后一行
5) K 跳到帮助
6) ( 和 )跳到当前句子的最前和最后
7) { 和 }跳到前一个空行 或 后一个空行
8) [[ 跳到前一个第0列是{的位置(必须{为第0列)
9) ]] 跳到下一个第0列是}的位置

步骤7
注释7:
1) J 连接当前行和下一行在一般模式,或所有行在可是模式
2) r 后跟任意字符替换当前字符
3) C 为c$的简写,表示删除当前位置到行结束并进入插入模式
4) D 为d$的简写,表示删除当前到行结束
5) Y 为yy的简写,复制整个行
6) s 删除当前光标所在字符并进入插入模式
7) S 清除当前行,并进入插入模式
8) > 和一个motion 表示缩进一行或多行
9) < 和一个motion 表示不缩进
10) = 和一个motion 表示重新格式化text
11) > < 和 = 工作在可视模式下,且均可重复例如>>表示缩进整个当前行

Posted: December 9, 2007 at 7:28 pm | Tags: blog, html, raise, smile, talk, unix, 中风, 父母, 疾病, 症兆
不怕一万,就怕万一! 这样的小常识,还是挺不错的, 大家看看吧!
了解知识,预防意外,有备无患!
为了父母,你需要知道患了中风,脑部的微血管,
会慢慢的破裂,遇到这种情形, 千万别慌,
患者无论在什么地方(不管是浴室、卧房或客厅),千万不可搬动他。
因为,如果移动,会加速微血管的破裂。
所以要先原地把患者扶起坐稳, 以防止再摔倒,这时才开始(放血)。
家中如有专为注射用的针,当然最好。
如果没有 ,就拿缝衣用的针,就在患者的十个手指头尖儿(没有固定穴道,大约距离手指甲一分之处刺上去,要刺出血来(万一血不出来,可用手挤),
等十个手指头都流出血来( 每指一 滴) ,大约几分钟之后,患者就会自然清醒。
如果嘴也歪了,就拉他的耳朵,把耳朵拉红,在两耳的耳垂儿的部位各刺两针,也各流两滴血,几分钟以后,嘴就恢复原状了。
等患者一切恢复正常感觉没有异状时再送医,就一定可以转危为安,否则,若急着抬上救护车送医,经一路的颠跛震动恐怕还没到医院,他脑部微血管,差不多已经都破裂了。万一能够吉人天相,保全老命,能象孙院长,容得勉强行动,那得要靠祖上积德了。
放血救命法, 是住在新竹的中医师夏伯挺先生说的。且是经自己亲身实验,敢说百分之百有效。
大概是民国六十八年一位在台中逢甲学院任教的教师,有天上午正在上课,一位老师 跑到他的教室上气不接下气的说∶刘老师快来 ,主任中风了;他立刻跑到三楼, 看到陈幅添主任,气色不正, 语意模糊,嘴也歪了,很明显的是中风了。立即请工读生到校门外的西药房,买来一支注射用的针头,就在陈主任十个手指头上直刺。等十个手指尖儿都见血了(豆粒似的一滴),大约几分钟以后,陈主任的气色就变过来了,两眼也有神了,只有嘴还歪着,他就拉搓陈主任的耳朵,使之充血,等把耳朵拉红,就在左右耳垂之处,各刺两 针,待两耳垂都流出两滴血来,奇迹就出现了,大约不到三五分钟,他的嘴形,恢复正常了,说话也清清楚楚了。让陈主任静坐一阵子,喝了一 杯热茶,才扶他下楼,开车送到惠华医院,打一罐点滴,休息了一夜就出院回学校上课了。一切照常工作,毫无后遗症。
反观一般脑中风患者,都是送医院治疗时,经过一路震荡血管急速破裂,以致多数患者一病不起,所以脑中 风,在死因排行榜上高居第二位,其最幸运者,也仅能保住老命,而落得终身残废。这是一个多么可怕的病症。如果大家都能记住这(放血救命) 的方法,立刻施救,在短短时间它能起死回生,而且保证百分之百的正常。
这个急救法,希望大家告诉大家。那脑中风,在死因排行榜上,就可以除名。
●阅后传知他人,功德无量●
辨识中风~很短
有个朋友瑛格莉,在一次烤肉聚会当中绊倒了,摔了一跤,旁边的朋友建议找医护人员,但她很确定自己没事,只是穿了新鞋被砖块绊了一下罢了。
瑛格莉还有点危危颤颤站立不稳的时候,朋友们帮她清洗干净,又为她承了一盘新的食物,然后她就跟着大家一起享受接下来的时光了。
瑛格莉的先生后来打电话通知大家,他的太太被送到医院,傍晚六点,瑛格莉就过世了,原因是她在烤肉聚餐的时候中风。
如果他们懂得辨识中风的症兆,瑛格莉现在也许还跟我们在一起。
有些人不会死,但结局是处于无助无望的景况中。
只需要花一分钟的时间读完这篇文章,神经科医师说,如果他能在三小时之内接触到中风患者,他就可以将中风的后果完全扭转过来。
诀窍就是辨识诊断出中风的问题,并让病患在三小时之内接受医疗,而这是很难的。
辨识中风
感谢上帝让我们记住STR三步骤,请阅读并学习 !
有时候中风的症兆很难辨认,不幸的是,缺乏警觉就会带来灾难。
身边的人辨认不出中风的征兆,中风患者就会造成严重的脑伤。
医生说,旁边的人只要问三个简单的问题,就可以辨识中风:
S :(smile) 要求患者笑一下
T :(talk)要求患者说一句简单的句子(要有条理,有连惯性)例如:今天天气晴朗。
R :(raise)要求患者举起双手
※注意:另外一项中风症兆是:要求患者伸出舌头,如果舌头是「弯曲」的,如果舌 头偏向一边,那也是中风的症兆。
*上面四个动作,患者如果有任何一个动作做不来,就要立刻打120!!!
并且把症状描述给接线员听。
心脏科医师说,收到这封电函的人,若能将它转寄给10 个人,就至少可以救一条命。
转自:http://blog.chinaunix.net/u/524/showart_317750.html
Posted: September 21, 2007 at 6:47 pm | Tags: blog, cache, html, linux, openssl, redhat, server, unix, web
转至: http://blog.csdn.net/baitianhai/archive/2004/10/27/155461.aspx
最近在鼓捣redhat linux,想自己以源代码方式安装软件,不想用rpm方式安装。
首先从httpd开始,先卸载在安装倒是比较容易,不过后来像添加ssl功能,发现编译的时候需要用openssl的安装目录,本人比较愚笨,一顿好找也没有找到,于是就想把openssl也以源代码方式安装。先卸载,此时出现问题,系统好多东西依赖于openssl的库,我查了好多资料也没找到什么办法,于是我最后一狠心,用rpm -e –nodeps给卸载了,然后手动安装了openssl,然后重新启动,这下坏了,好多服务都起不来了,smb,ssh等等,图形模式也起不来了,我欲哭无泪。
因为我是在虚拟机上安装的,smb起不来了,我只能重新安装系统了。这次安装我大多数东西都没选择,一路安装完毕,结果在文本方式发现vi编辑没有颜色了,哎,也不知道是少装了那个东西弄得(各位谁知道麻烦告诉告诉我一下),只能按照猜测重新安装了又添加了一些东西。不过幸运的vi高亮显示功能又有了,遗憾的是具体是那个软件我还是不清楚。有了上次的教训我不敢轻易卸掉系统原来的openssl了,我从网上搜索到了一篇安装openssl的英文文章,地址在 http://www.devside.net/web/server/linux/openssl 我按照上面说的安装了zlib,openssl。步骤简介如下(怕以后忘了)
安装zlib
Home : http://www.gzip.org/zlib/
Package(linux source) : http://www.gzip.org/zlib/
Our Configuration
Install to : /usr/local
Module types : dynamically and staticly loaded modules, *.so and *.a
Build Instructions
zlib library files are placed into /usr/local/lib and zlib header files are placed into /usr/local/include, by default.
Build static libraries
…/zlib-1.2.1]# ./configure
…/zlib-1.2.1]# make test
…/zlib-1.2.1]# make install
Build shared libraries
…/zlib-1.2.1]# make clean
…/zlib-1.2.1]# ./configure –shared
…/zlib-1.2.1]# make test
…/zlib-1.2.1]# make install
…/zlib-1.2.1]# cp zutil.h /usr/local/include
…/zlib-1.2.1]# cp zutil.c /usr/local/include
/usr/local/lib should now contain…
libz.a
libz.so -> libz.so.1.2.1
libz.so.1 -> libz.so.1.2.1
libz.so.1.2.1
/usr/local/include should now contain…
zconf.h
zlib.h
zutil.h
[Optional] Instructions for non-standard placement of zlib
Create the directory that will contain zlib
…/zlib-1.2.1]# mkdir /usr/local/zlib
Follow the given procedure above, except
…/zlib-1.2.1]# ./configure –prefix=/usr/local/zlib
Update the Run-Time Linker
/etc/ld.so.cache will need to be updated with the new zlib shared lib: libz.so.1.2.1
For standard zlib installation…
Add /usr/local/lib to /etc/ld.so.conf, if specified path is not present
/etc]# ldconfig
If zlib was installed with a prefix…
Add /usr/local/zlib/lib to /etc/ld.so.conf
/etc]# ldconfig
安装openssl
Download
Home : http://www.openssl.org/
Package(source) : openssl-0.9.7d.tar.gz
Our Configuration
install to : /usr/local/ssl
module types : dynamically and staticly loaded modules, *.so *.a
Build Instructions
…/openssl-0.9.7d]# ./config
–prefix=/usr/local/ssl
[default location]
shared
[in addition to the usual static libraries, create shared libraries]
zlib-dynamic
[like "zlib", but has OpenSSL load the zlib library dynamically when needed]
…/openssl-0.9.7d]# ./config -t
[display guess on system made by ./config]
…/openssl-0.9.7d]# make
…/openssl-0.9.7d]# make test
…/openssl-0.9.7d]# make install
Update the Run-time Linker
ld.so.cache will need to be updated with the location of the new OpenSSL shared libs: libcrypto.so.0.9.7 and libssl.so.0.9.7
Sometimes it is sufficient to just add these two files to /lib, but we recommend you follow these instructions instead.
Edit /etc/ld.so.conf
Add /usr/local/ssl/lib to the bottom.
…]# ldconfig
Update the PATH
Edit /root/.bash_profile
Add /usr/local/ssl/bin to the PATH variable.
Re-login
Testing
…]# openssl version
Should display OpenSSL 0.9.7d 17 Mar 2004
If an older version is shown, your system contains a previously installed OpenSSL.
Repeate the steps in Update the PATH, except place the specified location at the start of the PATH variable.
[the older openssl, on most systems, is located under /usr/bin]
[the command 'which openssl' should display the path of the openssl that your system is using]
/usr/local/ssl/bin]# ./openssl version should display the correct version.
但是我最后没有得到想要的结果,系统原来的openssl还是没能卸载掉,我该怎么做那?我继续搜索资料,哈,幸运的我找了,在一个国内论坛上是这么说的
cd /usr/local/ssl/lib
ln -s libcrypto.so.0.9.7 libcrypto.so.2
ln -s libssl.so.0.9.7 libssl.so.2
//最后要刷新系统的动态连接库配置
echo /usr/local/ssl/lib >> /etc/ld.so.conf
ldconfig -v
这下子我豁然开朗,原来依赖的那2个文件是个软链接啊,我把它修改为我现在真正的openssl库文件不是就行了吗?于是一顿忙碌后,我终于执行了 rpm -e -nodeps ,然后重新启动系统,一路运行下去,全是绿灯。一时间感觉自己好幸福啊
为了这个问题我查了国内的几个比较大的unix/linux网站都没找到资料,不过从这里http://bbs.netbuddy.org/unix/737.html还是找到了(国外的E文大概意思能看懂,但是查找起来还是没找到,也不知道这方面好点的网站),
Posted: January 19, 2007 at 12:32 pm | Tags: java, ror, unix, web, windows, 开发, 技术, 类
转至: www.csdn.net
主流的程序设计语言:C++、Delphi(ObjectPascal)、Java、C#
桌面应用程序框架:MFC、VCL、QT、JavaAWT\SWING、.Net
企业应用程序框架:WindowsDNA(ASP、COM、COM+)、J2EE、.NetFramework
开发工具:VisualBasic、Delphi、VisualC++、C++Builder、VisualC#
*程序设计语言:C++\Delphi(本来应该是ObjectPascal,但为了简单,我就语言和工具混为一谈吧)\Java\C#(虽然他刚刚推出,但因为微软为之倾注了大量心血,一定会成为一种重要的开发语言)
*桌面应用程序框架:MFC\VCL
*企业应用程序框架:WindowsDNA\J2EE\.Net
*COM技术:我单独提出这项技术,是因为它无法简单的被视为语言、桌面应用程序框架或企业应用程序框架,它与这些都有关系。
2.1 程序设计语言
2.1.1 C++语言的演进
最初要从二进制代码和汇编说起,但那太遥远了。我们就从面向过程的语言说起吧(包括Basic\C\Fortran\Pascal)。这种面向过程的高 级语言终于把计算机带入了寻常的应用领域。其中的C语言因为它的简单和灵活造就了Unix和Windows这样的伟大的软件。
面向对象 的语言是计算机语言的一个合乎逻辑的进化,因为在没有过多的影响效率、简单性的前提下提供了一种更好的组织数据的方法,可使程序更容易理解,更容易管理 ——这一点可能会引出不同意见,但事实胜于雄辩,C++终于让C语言的领地越来越小,当今还活着的计算机语言或多或少的都具备面向对象的特征,所以这一点 并不会引起太多困惑。C++的成功很大程度要归因于C,C++成为它今天的样子是合乎逻辑的产物。因为在面向过程的时代,C几乎已经统一天下了。今天著名 的语言象Java\C#都从C借鉴了很多东西,C#本来的意思就是C++++。其实C++曾经很有理由统一面向对象程序设计语言的天下来着,但可惜的是, C++太复杂了。即使是一个熟练的程序员,要你很清楚的解释一些问题你也会很头痛。举几个还不是那么复杂的例子来说:
对=的重载\成员转换函数\拷贝构造函数\转化构造函数之间有什么区别和联系呢?
定义一个类成员函数private:virtualvoidMemFun()=0;是什么意义呢?
int(*(*x(int))[4])(double);是什么意思?
还有其他的特征,比如说可以用来制造一种新语言的typedef和宏(虽然宏不是C++的一部分,但它与C++的关系实在太密切了),让你一不小心就摔 跤的内存问题(只要new和delete就可以了吗?有没有考虑一个对象存放在容器中的情况?)……诸如此类,C++是如此的复杂以至于要学会它就需要很 长的时间,而且你会发现即使你用C++已经好几年了,你还会发现经常有新东西可学。你想解决一个应用领域的问题——比如说从数据库里面查询数据、更改数据 那样的问题,可是你却需要首先为C++头痛一阵子才可以,是的,你精通C++,你可以很容易的回答我的问题,可是你有没有想过你付出了多大的代价呢?我不 是想过分的谴责C++,我本人喜欢C++,我甚至建议一个认真的开发普通的应用系统的程序员也去学习一下C++,C++中的一些特性,比如说指针运算\模 板\STL几乎让人爱不释手,宏可以用几个字符代替很多代码,对系统级的程序员来说,C++的地位是不可替代的,Java的虚拟机就是C++写的。C++ 还将继续存在而且有旺盛的生命力。
2.1.2 Java和C#
Java和C#相对于C++的不同最大的有两点:第一点是他们运 行在一个虚拟环境之中,第二点是语法简单。对于开发人员而言,在语法和语言机制的角度可以把Java和C#视为同一种语言。C#更多的是个政治的产物而不 是技术产物。如果不是Sun为难微软的话,我想微软不会费尽心力推出一个和Java差不多的C++++,记得Visual J++吗,记得WFC吗?看看那些东西就会知道微软为Java曾经倾注了多少心血。而且从更广泛的角度来说,两者也是非常相似的——C#和Java面对的 是同样的问题,面向应用领域的问题:事务处理、远程访问、Webservice、Web页面发布、图形界面。那么在这一段中,我暂且用Java这个名字指 代Java和C#两种语言——尽管两者在细节上确实有区别。Java是适合解决应用领域的问题的语言。最大的原因Java对于使用者来说非常简单。想想你 学会并且能够使用Java需要多长时间,学会并且能够使用C++要多长时间。由于Java很大程度上屏蔽了内存管理问题,而且没有那么多为了微小的性能提 升定义的特殊的内容(比如说,在Java里面没有virtual这个关键字,Java也不允许你直接在栈上创建对象,Java明确的区分bool和整型变 量),他让你尽量一致的方式操作所有的东西,除了基本数据类型,所有的东西都是对象,你必须通过引用来操作他们;除了这些之外,Java还提供了丰富的类 库帮助你解决应用问题——因为它是面向应用的语言,它为你提供了多线程标准、JDBC标准、GUI标准,而这些标准在C++中是不存在的,因为C++并不 是直接面向解决应用问题的用户,有人试图在C++中加入这些内容,但并不成功,因为C++本身太复杂了,用这种复杂的语言来实现这种复杂的应用程序框架本 身就是一件艰难的事情,稍后我们会提到这种尝试——COM技术。渐渐的,人们不会再用C++开发应用领域的软件,象MFC\QT\COM这一类的东西最终 也将退出历史舞台。
2.1.3 Delphi
Delphi是从用C++开发应用系统转向用Java开发应用系统的一 个中间产物。它比C++简单,简单的几乎象Java一样,因为它的简单,定义和使用丰富的类库成为可能,而且Delphi也这么做了,结果就是VCL和其 他的组件库。而另一方面,它又比运行于虚拟环境的Java效率要高一些,这样在简单性和效率的平衡之中,Delphi找到了自己的生存空间。而且预计在未 来的一段时间之内,这个生存空间将仍然是存在的。可以明显的看出,微软放弃了这个领域,他专注于两方面:系统语言C++和未来的Java(其实是. Net)。也许这对于Borland来说,是一件很幸运的事情。如果我能够给Borland提一些建议的话,那就是不要把Delphi弄得越来越复杂,如 果那样,就是把自己的用户赶到了C++或Java的领地。在虚拟机没有最终占领所有的应用程序开发领域之前,Delphi和Delphi的用户仍然会生存 得很好。
2.2桌面应用程序框架
目前真正成功的桌面应用程序框架只有两个,一个是MFC,一个是VCL,还有一些其他的,但事实上并未进入应用领域。遗憾的是我对两个桌面应用程序框架都不精通。但这不妨碍我对他做出正确的评价。
2.2.1MFC
MFC(还有曾经的OWL)是SDK编程的正常演化的结果,就象是C++是C的演化结果一样。MFC本身是一件了不起但不那么成功的作品,而且它过时 了。这就是我的结论。MFC凝聚了很多天才的智慧——当然,OWL和VCL也一样,侯捷的《深入浅出MFC》把这些智慧摆在了我们的面前。但是这件东西用 起来估计不会有人觉得很舒服,如果你一直在用Java、VB或者Delphi,再回过头来用MFC,不舒服的感觉会更加强烈。我不能够解释MFC为什么没 有能够最终发展成和VCL一样简单好用的桌面程序框架,也许是微软没精力或者没动力,总之MFC就是那个样子了,而且也不会再有发展,它已经被抛弃了。我 有时候想,也许基于C++这种复杂的语言开发MFC这样的东西本身就是错误的——可以开发这样的一个框架,但不应当要求使用它的人熟悉了整个框架之后才能 够使用这个系统,但很显然,如果你不了解MFC的内部机制,是不太可能把它用好的,我不能解释清楚为什么会出现这种现象。
2.2.2VCL
相比之下VCL要成功的得多。我相信很多使用VCL的人可能没有像MFC的用户研究MFC那样费劲的研究过VCL的内部机制。但这不妨碍他们开发出好用 好看的应用程序,这就足够了,还有什么好说的呢?VCL给你提供了一种简单一致的机制,让你可以写出复杂的应用程序。在李维的Borland故事那篇文章 中曾经说过,在Borland C++ 3.1推出之后Borland就有人提出开发类似C++ Builder一类的软件,后来竟未成行。是啊,如果C++ Builder是在那个时候出现的,今天的软件开发领域将会是怎么样的世界呢?真的不能想象。也许再过一段时间,这些都将不再重要。因为新生的语言如 Java和C#都提供了类似于VCL的桌面应用程序框架。那个时候,加上Java和C#本身的简单性,如果他们的速度有足够块,连Delphi这种语言也 要消失了,还有什么好争论的呢?只是对于今天的桌面程序开发人员来说,VCL确实是最好的选择。
2.3 企业应用程序框架
2.3.1 Windows DNA
Windows DNA的起源无从探究了。随着.Net的推出,事实上Windows DNA将成为历史的陈迹。Windows DNA虽然是几乎所有的企业应用程序开发人员都知道的一个名词,但我相信Windows DNA事实上应用的最广泛的是ASP而不是COM+。真正的COM开发有多少人真正的掌握了呢,更不要提COM+(我有必要解释一下:COM+是COM的 执行环境,它提供了一系列如事务处理、安全等基础服务,让应用程序开发人员尽量少在基础架构设计上花精力)——当然我这里指的COM开发不是指VB下的 COM开发,所以要这么说,是因为我觉得如果不能理解用C++进行COM开发,也就不能真正的理解COM技术。如果以一种技术没有被广泛理解和应用作为失 败的标志,那么Windows DNA实际上是失败了,但这不是它本身的错,而是基于C++的COM技术的失败造成的。多层应用、系统开发角色分离的概念依然没有过时。
2.3.2 J2EE
J2EE是第一套成功的企业应用程序开发框架。因为它把事务处理、远程访问、安全这些概念带入了寻常百姓家。这种成功我认为要归因于Java的简单性。 Java的简单,对于J2EE容器供应商来说一样重要。开发一个基于Java的应用服务器要比基于C++的更容易。而且由于Java的简单性,应用系统开 发者出错的机会也会少一些,不会像C++的开发者那样受到那么多挫折。开发基于Java的企业应用系统的周期会更短,这恐怕是不容争辩的事实。不论如何, 是J2EE让事务处理、远程访问、安全这些原来几乎都是用在金融系统中的概念也被一般的企业用户使用并从中获得利益。
2.3.3 .NET
.Net有什么好说的呢?其实,它不过是微软的J2EE。事务处理、安全、远程访问,这些概念在.Net中都找得到。更有力的说明是,微软也利用了. Net实现了一个PetStore。所以,.Net与J2EE几乎是可以完全对等的。但微软确实是一家值得尊敬的公司——我指从技术上,象Web form这种东西,我相信很多Web应用开发者都梦想过甚至自己实现过,但Sun却一直无动于衷,而且似乎Borland也没有为此作过太多努力,好像有 过类似的东西,但肯定是不怎么成功——Borland已经很让人敬佩了,我们也许无法要求太多。
2.4 COM技术
COM应当 是个更广泛的词汇,其实我觉得Axtive X、OLE、Auto mation、COM+都应当提及,因为如果你不理解COM,上面的东西你是无法理解的。而且,我只是想说明COM是一种即将消亡的技术,仅仅说说COM 的复杂性和他的问题就够了,所以不会提及那些东西。为什么会出现COM技术?COM的根本目标是实现代码的对象化的二进制重用,进而实现软件的组件化、软 件开发工作的分工。这要求他做到两点:第一,能够跨越不同的语言,第二,要跨越同一种语言的不同编译器。COM技术为这个目标付出了沉重的代价,尤其是为 了跨越不同的编译器,以至于无论对于使用者而言还是开发者而言,他都是一个痛苦的技术。但幸运的事,这一切终归要结束了。
让我们从这个 目的出发看看COM为什么会成为它现在的样子。其实COM不是什么新玩意,最初的DLL就是重用二进制代码的技术。DLL在C的年代可能还不错,但到了C ++的年代就不行了。原因在于如果你在.h文件中改变了类定义(增加或者减少了成员变量),代码库用户的代码必须重新编译才可以,否则用户的代码会按你的 旧类的结构为你的新类分配内存,这将产生什么后果可想而知。这就是为什么通过接口继承和通过接口操作对象成为COM的强制规范的原因,能够通过对象的方式 组织代码是COM的重要努力。那么著名的IUnknown接口又是怎么回事呢?这是为了让使用不同编译器的C++开发人员能够共享劳动成果。
首先看QueryInterface,因为COM是基于接口的,那么一个类可能实现了几个接口,而对于用户来说,你又只能通过操作接口来操作类,这样你 必须把类造型成某个特定的接口,使用Dynamic_cast吗?不行,因为这是编译器相关的,那么,就只好把它放在类的实现代码中了,这就是 QueryInterface的由来。至于AddRef和Release,他们产生的第一个原因是delete这个操作要求一个类具有虚析构函数(否则的 话,他的父类的析构函数将不会被调用),但不幸的是不同的编译器中析构函数在vtbl中的位置并不相同,这就是说你不能让用户直接调用delete,那么 你的COM对象必须提供自己删除自己的方法;另外的原因在于一个COM对象可能作为几个接口在被用户同时使用,如果用户粗暴的删掉了某个接口,那么其他的 接口也就不能用了,这样,只好要求用户在想用一个接口的时候通过AddRef通知COM对象“我要使用你了,你多了一个用户”,而在不用之后要调用 Release告诉COM对象“我不用你了,你少了一个用户”,如果COM对象发现自己没有用户了,它就把自己删掉。
再看看诡异的 HRESULT,这是跨语言和跨编译器的代价。其实,异常处理是物竞天择的结果——连一直用效率作标榜的C++都肯牺牲效率来实现这个try- catch,可见它的意义,但COM为了照顾那些低级的语言居然抛弃了这个特征——产生的结果就是HRESULT。我们可以看看他是多么愚蠢的东西。首 先,我们要通过一个整数来传递错误信息,通过IErrorInfo来查找真正的错误描述,本来在现代语言中一个try-catch可以解决的问题,现在我 们却需要让用户和开发者都走很多路才能解决,你怎么评价这个结果?其次,由于这个返回计算结果的位置被占用了,我们不得不用怪异的out参数来返回结果。 想想一个简单的int add(intop1,intop2)在COM中竟然要变成HRESULT add(intop1,intop2,int* result),我不知道你对此作何感想。而且由于COM的方法无法返回一个对象而只能返回一个指针,为了完成一个简单的std::string GetName()这一类的操作,你要费多少周折——你需要先分配一块内存空间,然后在COM实现中把一个字符串拷贝到这个空间,用完了你要删掉这个空 间,不知道你是否觉得这种工作很幸福,反正我觉得很痛苦。还有COM为了照顾那些解释性的语言,又加入了Automation技术,你有没有为此觉得痛 苦?本来一个简单的方法调用,现在却需要传给你一个标志变量,然后让你根据这个标志变量去执行相应的操作。(这一点我现在仍然不明白,为什么解释性的语言 需要通过这个方式来执行一个方法)。“我受够了,我觉得头痛”,你会说,是啊,我想所有的人都受够了,所有这些因素实际上是把COM技术变成了一头让人无 法驾驭的怪兽。
人对复杂事物的掌控能力终究是有限的,C++本身已经够复杂了,COM的复杂性已经超出了我们大部分人的控制能力,你需 要忍受种种痛苦得到的东西与你付出的代价相比是不是太不值得了?我们学习是为了解决问题,而现在我们却需要为了学习这件事情本身耗费太多的精力。这些痛苦 的东西太多了,我在这里说到的,其实只是一小部分而已。计算机技术是为人类服务的,而不是少数人的游戏(我想COM技术可能是他的设计者和一部分技术作者 的游戏),难道我们愿意成为计算机的奴隶吗?通过一种过于复杂的技术抛弃所有的人其实等于被所有的人抛弃,这么多年中选择J2EE的人我相信不乏高手,你 是不是因为COM的过于复杂才选择J2EE的?因为它可以用简单的途径实现差不多的目标——软件的“二进制”重用、组件化、专业分工(容器开发商和应用开 发商的分工)。事实上,你是被微软所抛弃的,同时,你也抛弃了微软。
现在让我们回来吧,我把你带进了COM的迷宫,现在让我把你领回 来。再让我们看看COM到底想实现什么目标,其实很简单,不过是代码的二进制重用,也就是说你不必给别人源代码,而且你的组件可以象计算机硬件那样“即插 即用”。我们回过头来看看Java,其实,这种二进制重用的困难是C++所带来的(这不是C++本身的错,而是静态编译的错),当Java出现的时候,很 多问题已经不存在了。你不需要一个头文件,因为Java的字节码是自解释的,它说明了自己是什么样子的,可以做什么事情。不像C++那样需要一个头文件来 解释这些事情;也不需要事先了解对象的内存结构,因为内存是在运行的时候才分配的。如果我们现在再回过头来解决COM要解决的问题,你会怎么做呢?首先你 会不再选择C++这种语言来实现代码的“二进制”重用,其次,你会把所有的语言编译成同样的“二进制”代码(实际上,应当说是字节码),这显然是更聪明的 做法,从前COM试图在源代码的级别抹平二进制层次的差异,这实际上是让人在做本来应当由机器做的事情,很愚蠢是吗?但他们一直做了那么多年,而且把这个 痛苦带给了整个计算机世界——因为他们掌握着事实的标准,为了用户,为了利润,为了能够在Windows上运行,尽管你知道你在做着一个很不聪明的事情, 但你还是做了。
COM技术为了照顾VB这个小兄弟和实现统一二进制世界的野心,实在浪费了太多的精力。首先,落后的东西的消亡是必然 的,就象C、Pascal在应用领域的消亡一样,Basic一点一点的改良运动是不符合历史潮流的做法,先进的东西和落后的东西在一起,要么先进的东西被 落后的东西拖住后腿,要么是同化落后的东西,显然我们更愿意看见后者,现在Basic终于被现代的计算机语言同化了。其次,统一二进制世界好像不是那么简 单的事情,而且也没什么必要,微软的COM技术奋战了10年,现在也只有他自己和Borland支持,.Net终于放弃了这个野心。这一切终于要结束了。
过去J2EE高歌猛进地占领着应用开发的领地,COM在这种进攻面前多少显得苍白无力。现在微软终于也有了可以和J2EE一较长短的.NET,对于开发 人员来讲,基于字节码的组件的二进制重用现在是天经地义的事情;你不用再为了能够以类方式发布组件做那么多乱七八糟的事情,因为现在所有的东西本来就是类 了;实现软件开发的分工也很自然,你是专业人员,就用C#吧,你是应用开发人员,你喜欢用VB.Net,你就用吧,反正你们写的东西最终都被翻译成了一样 的语言(其实我觉得这一点意义不大,因为一些不那么好用的语言被淘汰是正常的事情,C风格成为程序设计语言的主流风格,肯定是有它的原因的,语言的统一其 实是大势所趋,就象中国人民都要说普通话一样,我觉得Java不支持多语言算不上缺点——本来就是一种很好看很好用的语言了,为什么要把简单问题复杂化 呢?)。COM不会在短期内死去,因为我估计微软还不会马上用C#开发Office和Windows的图形界面部分,但我相信COM技术退出历史舞台的日 子已经不远了,作为一般的开发人员,忘了这段不愉快的历史吧——你将不会在你的世界里直接和COM打交道。若干年以后,我想COM也许会成为一场笑话,用 来讽刺那种野心过大、钻牛角尖的愚蠢的聪明人。
Posted: November 27, 2006 at 4:45 pm | Tags: cache, class, java, unix, windows, 优化, 平台, 开发, 技术, 类
转至 www.Huihoo.org
(作者:Douglas C. Schmidt ,by huihoo.org CORBA课题 Thzhang 译 , Allen整理,制作)
目的
对象生命周期管理者模式可以被用来控制对象的整个生命周期,从对象被首次使用前创建它们到应用程序中止前完全的销毁它们。此外通过在应用启动/中止时进行对象自动的预先创建/销毁,使这个模式能够用来替代静态对象的创建/销毁。
例子
单例(singleton)是一种通用的创建模式,它对唯一的类实例提供了一个全局的访问点同时能够延迟实例的创建直到它首次被访问。如果一个单例在程序 的整个生命周期中没有被需要,它将不会被创建。单例模式并没有提及在什么时候它的实例应该被销毁这个问题,但对于特定的应用或操作系统这将是个问题。
为了说明为什么提及销毁语义是重要的,考虑下面的日志组件,它通过向客户提供编程API接口实现分布式的日志服务。
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;
Logger的构造函数,出于简化而忽略掉了,实现分配各种OS的endsystem资源,像SOCKET句柄,共享内存段,和/或系统范围的信号量,它们被用于实现日志服务提供的客户API。
为了减少尺寸,提高记录信息的可读性,一个应用可以选择用批处理而不是分立的方式来记录某些数据,像时间统计数据。例如,下面的统计类根据每个单独的标识,批量处理时间数据:
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 &tv);// Report recorded statistics to the log.void report (int id) {Logger::instance ()->log ("Avg timing %d: ""%ld sec %ld usec\n",id,average_i (id).tv_sec,average_i (id).tv_usec);}protected:// Default constructor.Stats (void);// Internal accessor for an average.const timeval &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;}
在记录了各种统计数据后,程序调用report方法,它将使用Logger单例根据标识来记录平均的时间统计。
Logger和Stats类对应用提供了截然不同的服务:Logger类提供了通用的日志能力,而Stats类提供了具体化的批量处理和记录时间统计功能。这些类都是使用单例模式实现的,于是在应用中每一个类都只有一个实例。
下面的例子展示了一个应用可能使用Logger和Stats单例对象的一种情况:
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 < argc; ++i) {::gettimeofday (&start_tv);// do some work between timestamps . . .::gettimeofday (&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 ()->record (i, delta_tv);// . . . and log some output.Logger::instance ()->log ("Arg %d [%s]\n", i, argv[i]);Stats::instance()->report (i);}// Logger and Stats singletons are not cleaned up when main returns.return 0;}
注意,应用没有清晰的创建或销毁Logger和Stats单例,也就是说它们的生命周期管理是与应用逻辑相分离的。这是一个通常的习惯在应用程序退出的时候不对单例对象进行销毁处理。
但是,由于单例模式仅仅涉及单例对象的创建,而没有应对它的销毁,这将产生几个缺陷。值得注意的是,当上面的应用程序中止时,Logger和Stats单 例对象都没有被清理。在最好的情况下,这将导致内存泄漏的错误报告,最坏的情况下,重要的系统资源可能没有被完全的释放和销毁。
例如,如果Logger和Stats单例对象持有OS的资源,像系统范围的信号量,I/O缓冲,或者其他被分配的OS资源,将产生问题。当程序关闭的时候 没有适当的释放这些资源可能导致死锁和其他同步灾难。为了减少这样的问题,在程序退出前每个单例实例的析构函数应该被调用。
一种试图保证单例销毁的单例模式实现方法是,在一个文件范围内声明一个利用区域锁习惯用法实现的静态单例类实例。例如,下面的Singleton Destroyer模板提供了一个析构器用于销毁单例对象。
template 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.};
为了使用这个类,所有应该做的是通过定义一个静态的Singleton_Destroyer类实例来修改Logger和Stats类。下面的例子展示了对Logger类的修改:
static Singleton_Destroyer 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_destroyer是如何获取单例对象并在程序退出时将其销毁的。针对Stats类也有类似的修改。
不幸的是,清晰的实例化一个静态的Singleton_Destroyer实例将带来一些问题。例如,在C++中,每一个 Singleton_Destroyer应该在不同的编译单元中定义(译者:内联定义。因为静态数据具有内联特性,一般不将其放在头文件中,因为每个包含 这个头文件的编译单元都将拥有一个静态数据的副本。)。在这种情况下,它们的析构函数的调用次序是没有保证的,这将导致未定义的行为。特别的,如果在不同 编译单元中的单例对象共享了一些资源,如SOCKET句柄,共享内存片段和/或系统范围的信号量,程序将不能干净的退出。在C++中单例对象的这种不确定 的销毁次序将很难保证(1)在最后一个使用资源的单例对象被完全的销毁前,而不是(2)在一个单例对象存在,并依然使用这些资源前,OS去回收这些资源。
总的来说,在上面的例子中没有解决的关键问题是:(1)被单例分配的资源应该在程序退出时被最终释放。(2)不受约束的创建和销毁静态实例的次序将导致严重的程序错误。(3)将软件开发人员和管理对象生命周期的责任分离开来,将减少系统出错的可能。
语境
> 一个应用和系统对其所创建的对象进行全面的生命周期控制,对于保证系统的正确性是必要的。
问题
许多应用并没有适当的处理对象的整个生命周期。特别是那些使用创建模式的应用,如单例模式,经常没有考虑这些对象的销毁。同样的,那些使用静态对象来提供对象销毁的应用经常遭受不一致的初始化和销毁过程。下面是这些问题的简要说明:
1、单例对象析构带来的问题。单例对象的实例可能被动态创建,一个动态创建的单例实例必将会造成资源的泄漏,如果它们没有被销毁。但是,单例对象造成的泄漏经常被忽略,这是因为:
(1)它们对应用不会造成显著的影响,
(2)在大多数通用的操作系统中,如UNIX、WINDOWS,在程序中止时它们会被自动清理。 不幸的是,在下面的环境中资源泄漏会导致麻烦:
(a)当系统需要优雅的关闭时。单例对象应该为其所获取的系统资源,如系统范围的锁、打开的网络连接、共享的内存片断负责。清晰的销毁单例对象是值得做的,这可以保证当程序中止时,所有这些资源能够在预定好的点上被销毁。
(b)当一个单例对象拥有另一个单例对象的引用时。清晰的管理单例对象的销毁次序,对于避免在应用中止时由于空悬(dangling)引用产生的问题是必要的。
(c)当检测内存泄漏时。内存检测工具,如NuMega BoundsCheck, ParaSoft Insure++,和Rational Purify,被用于检测像C/C++这类需要清晰的动态分配和释放内存的语言。这些工具会指示单例实例作为一处内存泄漏。
(d)当从一个全局内存池中动态分配内存时。一些实时操作系统,如VxWorks和PSOS,有一个对应于所有应用的唯一的全局堆。因此,应用任务在中止时必须释放任何动态分配的内存,否则,这些内存不会被释放而被其他应用使用直到系统重新启动。
2、静态对象生命周期的问题。一些对象必须先于任何使用前被创建。在C++语言中,这些实例往往被定义成静态变量,它们先于程序主函数入口点被调用前创建,在程序中止时被销毁。但是静态对象有几个严重的缺陷:
(a)不确定的构造/析构的次序。C++语言仅仅指定了在一个编译单元内部静态对象的构造/析构的次序。构造的次序是对象声明的次序、析构则与之相反。但 是没有指定处于不同编译单元的静态对象的构造/析构的次序。因此构造/析构的次序是实现依赖的。使用静态对象来处理初始化相关性是很难写出可以移植的C+ +代码。通常,比较简单的方式是完全避免使用静态对象,而不是解决、防止这些相关性。为了在某些平台上实现正确的程序操作,清晰的单例管理是必须的,因为 它们能够事先销毁单例对象。例如,早先JDK中的垃圾回收器,能够销毁没有任何参考指向的对象,即使这个对象是被设置成单例。虽然这个问题在后续的JDK 中被解决,但是对象生命周期管理者能够在的应用的控制下,通过维护单例的参考来解决这个问题。
(b)不能被嵌入式系统很好的支持。由于历史原因,嵌入系统都是使用C语言因此,它们总是不能对OO程序语言特性提供无缝的支持。例如,在C+ +语言中的静态对象的构造/析构经常会复杂化嵌入系统的编程。嵌入式OS可能已经支持清晰的调用静态的构造/析构函数,但没有达到程序员期望的最佳状态。 一些嵌入式OS不支持一个程序有一个唯一的入口点的概念。例如VxWorks支持多个task,task类似于线程因为它们共享一个地址空间。但是对于每 一个应用并没有指定主task。因此,这些嵌入式系统可以被配置成在模块载入/卸载时分别的调用静态对象的构造器/析构器。
(c)静态对象增加了应用启动的时间。静态对象可能先于任何主入口点的调用,在应用启动时被初始化。如果这些对象在一个特定的运行中没有被使 用,那么应用启动(退出)的时间被白白的增加。减少这种浪费的一种方法是,使用安需创建对象替代静态对象,如使用单例模式。使用单例替代静态对象也可以用 于延迟对象对象的构建直到被首次使用时,而且这也将减少启动时间。一些实时的应用已经发现在进入主入口点后和对象被正是需要前的一个特定时间点创建单例对 象带来的好处。一个或多个这样静态对象的缺点就足以提供将它们从程序中移走的全部动机。通常,比较好的做法是不使用它们,而是应用下面的方案替代。
解决之道
定义一个对象生命周期管理者,它是一个包含了预分配对象和被管理对象集合的单例。它的职责是在应用启动和中止时分别创建和销毁预分配对象。它更深一层的职责是在程序中止时保证所有的被管理对象能够被完全的销毁。
适应性
在下列情况下使用对象生命周期管理者:
1、在程序中止时,单例或其他动态分配的对象能够在不需要程序本身进行干预的情况下被销毁。单例和其他创建模式都没有提及它们所创建的对 象应该什么时候被销毁和由谁来销毁这个问题。与之相反,对象生命周期管理者提供了一个便利的用于删除被动态创建对象的全局对象。由创建模式产生的对象可以 向对象生命周期管理者进行注册,确保在程序中止时能够被销毁。
2、静态对象必须从应用中移除。正像前面所描述的那样,静态对象是麻烦的制造者,特别是在某些语言和某些平台上。对象生命周期管理者提供了一种利用预先分配对象替代静态对象的机制。预分配对象在应用使用它们前被创建,在应用中止前被销毁。
3、对于那些不支持静态对象创建和销毁的平台。一些嵌入式系统,如VXWORKS和PSOS不总是在程序启动时创建静态对象,在程序中止时销毁静态对象。通常,比较好的方式是移除静态对象。
4、虽然应用需要这样,但是底层平台不能提供一个主程序的概念。在缺少对主程序概念支持的平台上,将缺少对静态对象创建和销毁的支持。对象生命周期管理者通过划分地址空间的方式可以被用来仿效一个主程序。从应用的角度来看,每一个对象生命周期管理者描绘了一个程序的范畴。
5、对象被销毁的次序必须由应用来指定。动态创建的对象可以通过向对象生命周期管理者注册方式来使自身被销毁。对象生命周期管理者可以以任意希望的次序来实现对象的销毁。
6、应用需要一个明确的单例对象管理机制。像上面所描述的,单例对象可能被过早的销毁。如早期的JAVA平台。对象生命周期管理者可以延迟对象的销毁直到应用中止。
结构和参与者
在下面的图中,使用UML方式展示了对象生命周期管理者的结构和参与者。 对象生命周期管理者:每一个对象生命周期管理者,它是一个包含了预分配对象和被管理对象集合的单例。
1、被管理对象:任何一个向对象生命周期管理者注册并由其负责销毁的对象。对象销毁发生在对象生命周期管理者本身被销毁的时候,通常都是在程序中止的时候。
2、预分配对象:被对象生命周期管理者在其内部通过硬编码方式实现创建和销毁的对象。它和对象生命周期管理者具有相同的生命周期,也就是执行应用的进程的生命周期。
3、应用:应用清晰或非清晰的创建和销毁对象生命周期管理者。此外,应用向本身可能包含预分配对象的对象生命周期管理者注册被管理对象。
动态特征
下图中展示了对象生命周期管理者模式中的参与者之间的动态协作:
这个图描述了四个分离的活动:
1、对象生命周期管理者创建和初始化,它将依次创建预分配对象。
2、应用创建管理对象,并向对象生命周期管理者注册。
3、应用使用已经注册的管理对象和预先分配对象。
4、对象生命周期管理者的销毁,这将销毁所有它控制的管理对象和预分配对象。
实现
对象生命周期管理者可以用下面展示的步骤来实现。这个实现是基于ACE框架提供的对象管理者来实现的,它将引出一些将在这个单元讨论的有趣问题。下面讨论的一些步骤是语言相关的,因为ACE是用C++语言编写的。
1、定义对象生命周期管理者组件。这个组件向应用提供一个接口,通过它注册那些生命周期必须被管理对象,确保在系统中止时能够完全的销毁这些对 象。此外,这个组件还定义了一个智囊团(repository)用于确保它所管理的对象能够被完全销毁。对于那些被注册,在程序中止时被销毁的预分配对象 和管理对象来说,对象生命周期管理者是一个容器。
下面是用于实现对象生命周期管理者组件的子步骤:
(a)定义一个用于注册被管理对象的接口。一种用于向对象生命周期管理者注册被管理对象的方法是使用C语言库中的atexit函数,它在程序退出 时将调用特定的中止函数(用于实现退出时的清理操作)。但是,不是所有的平台都支持atexit函数,而且atexit函数的实现限制了最大被注册的中止 函数数目为32。因此。对象生命周期管理者必须支持下面的两个技术用于实现被管理对象的注册:使用一个容器来容纳所有的被管理对象,在程序退出时自动清除 这些被管理对象。
i.定义一个cleanup函数接口。对象生命周期管理者允许应用向其注册任意类型的对象。当程序中止时,对象生命周期管理者将自动的清理这些对象。下面的C++类展示了一个在ACE中使用的特殊的CLEANUP_FUNC,用于注册能够被清除的对象或数组。
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);};
这个静态的at_exit函数注册一个能够在程序退出时被清除的对象或数组对象。Cleanup_hook参数指向全局的函数或时静态的 方法,该方法在清理时被调用用于销毁对象或数组。在销毁时,对象生命周期管理者向Cleanup_hook函数传递对象和相关的参数。参数包含任何被 Cleanup_hook函数需要的额外信息,如数组中对象的个数等。
ii.定义一个cleanup基类接口。这个接口允许应用向对象生命周期管理者注册销毁任何从cleanup接口继承的对象。这个 cleanup基类接口应该有一个虚的析构函数和一个虚的cleanup方法,这个方法的实现仅仅是简单的调用delete this,它将导致所有继承类的析构函数被依次调用。下面的代码段展示了在ACE中这个接口是如何实现的:
class Cleanup{public:// Destructor.virtual ?Cleanup (void);// By default, simply deletes this.virtual void cleanup (void *param = 0);};
下面的代码段展示了在ACE中,对象生命周期管理者被用于注册从cleanup接口继承对象的接口。
class Object_Lifetime_Manager{public:static int at_exit (Cleanup *object,void *param = 0);};
这个静态的at_exit函数注册一个能够在程序退出时被清除的cleanup对象。在析构时,对象生命周期管理者调用cleanup对象中的cleanup方法,param参数包含了任何被cleanup函数需要的额外信息。
(b)定义一个单例适配器。虽然使用上面定义的对象生命周期管理者方法是可以清晰的编码实现单例对象,但这样做是冗余的和易错的。因此,定义一个 单例的适配器模板用于封装创建单例对象和向对象生命周期管理者注册的细节是非常有用的。此外,单例适配器可以使用线程安全的双检测加锁优化模式 (double checked locking optimization pattern)来创建和访问类型指定的单例对象实例。 下面的代码片断展示了单例适配器在ACE中是如何实现的:
template 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;// Register with the Object Lifetime// Manager to control destruction.Object_Lifetime_Manager::at_exit (singleton_);}return &singleton_->instance_;}protected:// Default constructor.Singleton (void);// Contained instance.TYPE instance_;// Instance of the singleton adapter.static Singleton *singleton_;};
Singleton类模板从cleanup类继承,这样就允许单例实例向对象生命周期管理者注册自己
(d)定义一个用于注册预分配对象的接口。预分配对象总是在应用的主进程启动前被创建。例如,在一些应用中,同步锁必须在任何使用前被创建,用于 防止竞争条件。这样一来,这些对象必须在每个对象生命周期管理者类中通过硬编码的方式实现。将这些对象的创建封装在对象生命周期管理者的初始化阶段,不给 应用代码添加任何的复杂性。
对象生命周期管理者能够预先分配对象或数组。它要么能够静态的在全局数据中实现这些预先分配,要么在堆中动态的实现。一种有效的实现方式是在数 组中存储每个预分配对象。像C++这种特定的语言不支持数组容纳异类对象。因此,用指针替代对象本身来实现数组存储。实际的对象是被对象生命周期管理者在 其初始化的过程中动态创建的,在其析构的时候被销毁。
下面的子步骤用于实现预分配对象:
1. 限制暴露。为了最小化头文件的暴露,通过宏或枚举来标识预分配对象。
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,};
2. 使用cleanup适配器。Cleanup适配器类模板从cleanup基类继承,包装那些没有从cleanup基类继承的类型,使它们能够被对象生命周期管理者管理起来。
#define PREALLOCATE_OBJECT(TYPE, ID) {\Cleanup_Adapter *obj_p;\obj_p = new Cleanup_Adapter;\preallocated_object[ID] = obj_p;\}#define DELETE_PREALLOCATED_OBJECT(TYPE, ID)\cleanup_destroyer (\static_cast *>\(preallocated_object[ID]), 0);\preallocated_object[ID] = 0;
3.定义预分配对象的访问接口。应用需要简便的和类型安全的预分配对象的访问接口。因为对象生命周期管理者支持不同类型的预分配对象。提供一个分 离的,包含有一个接受一个ID参数的成员函数的,在函数内部对象被预先分配,返回一个指向对象的正确的类型指针的类模板适配器是必要的。 下面的代码片断展示了在ACE中通过类模板适配器如何实现这个接口:
template 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&(static_cast *>(Object_Lifetime_Manager::preallocated_object[id]))->object ();}// . . . other methods omitted.};
(e)定义被注册对象的析构次序。对象生命周期管理者可以实现以任意次序销毁被注册对象。例如,可以使用优先级别标注,使销毁的次序按照优先级降 序进行。应该提供一个用于设定和改变对象优先级别的接口函数。我们已经发现,以注册顺序相反的次序销毁对象的策略对于ACE应用来说已经足够了。一个应用 可以通过控制对象的注册次序来指定对象的销毁次序。
(f)定义一个中止函数接口。到目前为止所讨论的生命周期管理功能仅涉及在程序中止时销毁被管理对象。但是对象生命周期管理者能够提供一个更通 常的功能:使用其内部的相同实现机制在程序中止时能够调用一个函数。 例如,为了在程序中止时确保完全清楚打开的win32 SOCKET,WSACleanup函数必须被调用。可以使用一个区域锁习惯用法的变化实现,创建一个特殊的包装门面类,它的构造函数会调用初始化函数, 它的析构函数会调用cleantup函数。于是,应用可以向对象生命周期管理者注册这样类的实现,这样一来在对象生命周期管理者销毁时会销毁内部的被管理 对象,中止API也将会被调用。
但是,这样的设计对大多数应用来说太困难了,而且它还是容易出错的,因为应用必须保证这个类被用作单例对象,因为这些API函数只能被调用一次。 作为替代,这些API中止函数将被作为对象生命周期管理者的中止方法的一部分被调用。
(g)移动通用接口和实现细节到一个基础类中。分解一些内部的细节到Object Lifetime Manager Base类中将使Object Lifetime Manager的实现更加简单,健壮。定义一个Object Lifetime Manager Base类用于支持创建不同类型的Object Lifetime Manager。
3、确定如何管理对象生命周期管理者自己的生命周期。对象生命周期管理者有责任初始化其他全局对象和静态对象,但是这将引发连带的问题,就是这个单例对象如何初始化和析构它自己?下面是用于初始化对象生命周期管理者实例的几种选择:
(a)静态初始化。如果应用对静态对象的创建和销毁没有次序限制,可以创建对象生命周期管理者为静态对象。例如,ACE中的对象生命周期管理者可以被作为静态对象创建。
(b)栈初始化。当一个主程序线程明确的定义了程序的入口点和中止点时,可以在主程序线程的栈上创建对象生命周期管理者,这样可以简化创建和销毁 对象生命周期管理者的编程逻辑。这种用于初始化对象生命周期管理者的方法是假设每个程序中只有一个唯一的主线程。这个线程确定了程序本身,也就是,程序在 运行,当且仅当主线程是活动的。这个方法有一个显著的优点:对象生命周期管理者实例在每一条离开MAIN函数的路径上都将被自动的销毁。
(c)清晰的初始化。这个方法是,在应用程序的控制之下,清晰的创建对象生命周期管理者实例,对象生命周期管理者类中init和fini方法允许应用在任何需要的时候创建和销毁对象生命周期管理者实例。这个选择减轻了在使用DLL带来的复杂度。
(d)动态库初始化。在这个方法中,创建和销毁对象生命周期管理者分别在它的DLL被加载和卸载的时候。许多动态库工具都包含调用如下方法能力:(1)一个出世化方法当DLL被加载时(2)一个中止函数当DLL被卸载时。
结论
使用它带来的好处:
1、 在程序中止时销毁单例对象和其他被管理对象。对象生命周期管理者模式允许应用"干净的"中止,释放被管理对象占用的内存,连同它们持有的其他资源。
2、 指定销毁的次序。对象被销毁的顺序可以被指定。这个指定的销毁机制可以任意复杂任意简单。
3、 从库和应用中移除了静态对象。可以使用预分配对象替代静态对象。这将防止应用依赖于静态对象的创建和销毁的次序。
使用它带来的缺陷:
1、管理者自己的生命周期管理。应用本身必须保证尊重对象生命周期管理者的生命周期,不在其外调用对象生命周期管理者的服务。例如,应用不要先于 对象生命周期管理者完全出世化之前试图去访问预分配对象,同样的,应用不要先于最后访问预分配对象或被管理对象前销毁对象生命周期管理者。最后,如果可以 假定对象生命周期管理者仅被一个线程初始化,那么实现对象生命周期管理者是非常简单的。这将免除在其初始化函数中使用静态锁
2、和共享库一起使用。在支持共享库运行时动态加载和卸载的平台上,应用程序必须小心的对待平台指定的特性对对象生命周期管理者的生命周期的影 响。例如,在windows NT平台上,对象生命周期管理者应该被应用或是包含它的DLL来初始化,这将避免潜在死锁状态,由于OS内部已经串行化装载DLL操作。 一个相关的问题是在DLL创建单例对象,被应用代码中的对象生命周期管理者管理。当DLL先于应用中止前被卸载,那么在应用中止时,对象生命周期管理者就 会试图去使用已经不在应用中的代码来销毁这个单例对象。
实现样例代码
下面的代码仅仅展示了预分配对象的处理过程。(译者:而被管理对象的处理过程被忽略了,这个过程比较复杂,涉及较多的ACE其他的封装类,包括ACE_OS_Exit_Info、ACE_Cleanup_Info_Node等。后面将有比较详细的讨论。)
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 // calls hasn't reached the number of // 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_ <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_ >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_->starting_up_i () : 1;}static int shutting_down (void) {return instance_ ?instance_->shutting_down_i () : 1;}enum Preallocated_Object{# if defined (MT_SAFE) && (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->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) && (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// has already been called, or// 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_->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) && (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;}
译者补充
被管理对象的处理类图如下:
每个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函数。在这个函数中进行如下操作:
ACE_Cleanup_Info new_info;
new_info.object_ = object;
new_info.cleanup_hook_ = cleanup_hook;
new_info.param_ = param;
registered_objects_ = registered_objects_->insert
(new_info);其中registered_objects是指向ACE_Cleanup_Info_Node的指针,是ACE_OS_Exit_Info的私有数据成员。
ACE_Cleanup_Info_Node本身构成一个单向的链表,其包含一个ACE_Cleanup_Info实例和指向下一个ACE_Cleanup_Info_Node节点的指针。
这样,应用注册的被管理对象的指针、中止函数和需要参数都将以链表的形式存放在ACE_OS_Exit_Info中。当 ACE_Object_Manager退出时,其会调用其内部的fini函数,该函数将调用ACE_OS_Exit_Info中的call_hooks函 数。这个函数按照注册相反的顺序一次遍历每个ACE_Cleanup_Info_Node,并执行其中的
ACE_Cleanup_Info中的相关退出操作:
for (ACE_Cleanup_Info_Node *iter = registered_objects_; iter && iter->next_ != 0; iter = iter->next_) { ACE_Cleanup_Info &info = iter->cleanup_info_; // The object is an ACE_Cleanup. ace_cleanup_destroyer (ACE_reinterpret_cast (ACE_Cleanup *, info.object_), info.param_); }
Posted: November 4, 2006 at 9:46 pm | Tags: class, java, linux, ror, unix, 平台, 开发, 测试, 类
Socket API 是网络应用程序开发中实际应用的标准 API。尽管该 API 简单,但是开发新手可能会经历一些常见的问题。本文识别一些最常见的隐患并向您显示如何避免它们。
在 4.2 BSD UNIX® 操作系统中首次引入,Sockets API 现在是任何操作系统的标准特性。事实上,很难找到一种不支持 Sockets API 的现代语言。该 API 相当简单,但新的开发人员仍然会遇到一些常见的隐患。
本文识别那些隐患并向您显示如何避开它们。
第一个隐患很明显,但它是开发新手最容易犯的一个错误。如果您忽略函数的返回状态,当它们失败或部分成功的时候,您也许会迷失。反过来,这可能传播错误,使定位问题的源头变得困难。
捕获并检查每一个返回状态,而不是忽略它们。考虑清单 1 显示的例子,一个套接字 send 函数。
int status, sock, mode;
/* Create a new stream (TCP) socket */sock = socket( AF_INET, SOCK_STREAM, 0 );
...
status = send( sock, buffer, buflen, MSG_DONTWAIT );
if (status == -1) {
/* send failed */ printf( "send failed: %s\n", strerror(errno) );
} else {
/* send succeeded -- or did it? */
}
|
清单 1 探究一个函数片断,它完成套接字 send 操作(通过套接字发送数据)。函数的错误状态被捕获并测试,但这个例子忽略了 send 在无阻塞模式(由 MSG_DONTWAIT 标志启用)下的一个特性。
send API 函数有三类可能的返回值:
- 如果数据成功地排到传输队列,则返回 0。
- 如果排队失败,则返回 -1(通过使用
errno 变量可以了解失败的原因)。
- 如果不是所有的字符都能够在函数调用时排队,则最终的返回值是发送的字符数。
由于 send 的 MSG_DONTWAIT 变量的无阻塞性质,函数调用在发送完所有的数据、一些数据或没有发送任何数据后返回。在这里忽略返回状态将导致不完全的发送和随后的数据丢失。
UNIX 有趣的一面是您几乎可以把任何东西看成是一个文件。文件本身、目录、管道、设备和套接字都被当作文件。这是新颖的抽象,意味着一整套的 API 可以用在广泛的设备类型上。
考虑 read API 函数,它从文件读取一定数量的字节。read 函数返回读取的字节数(最高为您指定的最大值);或者 -1,表示错误;或者 0,如果已经到达文件末尾。
如果在一个套接字上完成一个 read 操作并得到一个为 0 的返回值,这表明远程套接字端的对等层调用了 close API 方法。该指示与文件读取相同 —— 没有多余的数据可以通过描述符读取(参见 清单 2)。
int sock, status;
sock = socket( AF_INET, SOCK_STREAM, 0 );
...
status = read( sock, buffer, buflen );
if (status > 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 */ close( sock );
/* Further processing... */
}
|
同样,可以用 write API 函数来探测对等套接字的闭包。在这种情况下,接收 SIGPIPE 信号,或如果该信号阻塞,write 函数将返回 -1 并设置 errno 为 EPIPE。
您可以使用 bind API 函数来绑定一个地址(一个接口和一个端口)到一个套接字端点。可以在服务器设置中使用这个函数,以便限制可能有连接到来的接口。也可以在客户端设置中使用这个函数,以便限制应当供出去的连接所使用的接口。bind 最常见的用法是关联端口号和服务器,并使用通配符地址(INADDR_ANY),它允许任何接口为到来的连接所使用。
bind 普遍遭遇的问题是试图绑定一个已经在使用的端口。该陷阱是也许没有活动的套接字存在,但仍然禁止绑定端口(bind 返回 EADDRINUSE),它由 TCP 套接字状态 TIME_WAIT 引起。该状态在套接字关闭后约保留 2 到 4 分钟。在 TIME_WAIT 状态退出之后,套接字被删除,该地址才能被重新绑定而不出问题。
等待 TIME_WAIT 结束可能是令人恼火的一件事,特别是如果您正在开发一个套接字服务器,就需要停止服务器来做一些改动,然后重启。幸运的是,有方法可以避开 TIME_WAIT 状态。可以给套接字应用 SO_REUSEADDR 套接字选项,以便端口可以马上重用。
考虑清单 3 的例子。在绑定地址之前,我以 SO_REUSEADDR 选项调用 setsockopt。为了允许地址重用,我设置整型参数(on)为 1 (不然,可以设为 0 来禁止地址重用)。
int sock, ret, on;struct sockaddr_in servaddr;
/* Create a new stream (TCP) socket */sock = socket( AF_INET, SOCK_STREAM, 0 ):
/* Enable address reuse */on = 1;ret = setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
/* Allow connections to port 8080 from any available interface */memset( &servaddr, 0, sizeof(servaddr) );servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl( INADDR_ANY );servaddr.sin_port = htons( 45000 );
/* Bind to the address (interface/port) */ret = bind( sock, (struct sockaddr *)&servaddr, sizeof(servaddr) );
|
在应用了 SO_REUSEADDR 选项之后,bind API 函数将允许地址的立即重用。
套接字是发送无结构二进制字节流或 ASCII 数据流(比如 HTTP 上的 HTTP 页面,或 SMTP 上的电子邮件)的完美工具。但是如果试图在一个套接字上发送二进制数据,事情将会变得更加复杂。
比如说,您想要发送一个整数:您可以肯定,接收者将使用同样的方式来解释该整数吗?运行在同一架构上的应用程序可以依赖它们共同的平台来对该类型的 数据做出相同的解释。但是,如果一个运行在高位优先的 IBM PowerPC 上的客户端发送一个 32 位的整数到一个低位优先的 Intel x86,那将会发生什么呢?字节排列将引起不正确的解释。
 |
|
Endianness 是指内存中字节的排列顺序。高位优先(big endian) 按最高有效字节在前排列,然而 低位优先(little endian) 按照最低有效字节在前排序。
高位优先架构(比如 PowerPC®)比低位优先架构(比如 Intel® Pentium® 系列,其网络字节顺序是高位优先)有优势。这意味着,对高位优先的机器来说,在 TCP/IP 内控制数据是自然有序的。低位优先架构要求字节交换 —— 对网络应用程序来说,这是一个轻微的性能弱点。
|
|
通过套接字发送一个 C 结构会怎么样呢?这里,也会遇到麻烦,因为不是所有的编译器都以相同的方式排列一个结构的元素。结构也可能被压缩以便使浪费的空间最少,这进一步使结构中的元素错位。
幸好,有解决这个问题的方案,能够保证两端数据的一致解释。过去,远程过程调用(Remote Procedure Call,RPC)套装工具提供所谓的外部数据表示(External Data Representation,XDR)。XDR 为数据定义一个标准的表示来支持异构网络应用程序通信的开发。
现在,有两个新的协议提供相似的功能。可扩展标记语言/远程过程调用(XML/RPC)以 XML 格式安排 HTTP 上的过程调用。数据和元数据用 XML 进行编码并作为字符串传输,并通过主机架构把值和它们的物理表示分开。SOAP 跟随 XML-RPC,以更好的特性和功能扩展了它的思想。参见 参考资料 小节,获取更多关于每个协议的信息。
TCP 不提供帧同步,这使得它对于面向字节流的协议是完美的。这是 TCP 与 UDP(User Datagram Protocol,用户数据报协议)的一个重要区别。UDP 是面向消息的协议,它保留发送者和接收者之间的消息边界。TCP 是一个面向流的协议,它假定正在通信的数据是无结构的,如图 1 所示。
图 1 的上部说明一个 UDP 客户端和服务器。左边的对等层完成两个套接字的写操作,每个 100 字节。协议栈的 UDP 层追踪写的数量,并确保当右边的接收者通过套接字获取数据时,它以同样数量的字节到达。换句话说,为读者保留了写者提供的消息边界。
现在,看图 1 的底部.它为 TCP 层演示了相同粒度的写操作。两个独立的写操作(每个 100 字节)写入流套接字。但在本例中,流套接字的读者得到的是 200 字节。协议栈的 TCP 层聚合了两次写操作。这种聚合可以发生在 TCP/IP 协议栈的发送者或接收者中任何一方。重要的是,要注意到聚合也许不会发生 —— TCP 只保证数据的有序发送。
对大多数开发人员来说,该陷阱会引起困惑。您想要获得 TCP 的可靠性和 UDP 的帧同步。除非改用其他的传输协议,比如流传输控制协议(STCP),否则就要求应用层开发人员来实现缓冲和分段功能。
GNU/Linux 提供几个工具,它们可以帮助您发现套接字应用程序中的一些问题。此外,使用这些工具还有教育意义,而且能够帮助解释应用程序和 TCP/IP 协议栈的行为。在这里,您将看到对几个工具的概述。查阅下面的 参考资料 了解更多的信息。
netstat 工具提供查看 GNU/Linux 网络子系统的能力。使用 netstat,可以查看当前活动的连接(按单个协议进行查看),查看特定状态的连接(比如处于监听状态的服务器套接字)和许多其他的信息。清单 4 显示了 netstat 提供的一些选项和它们启用的特性。
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
|
尽管存在许多其他的实用程序,但 netstat 的功能很全面,它覆盖了 route、ifconfig 和其他标准 GNU/Linux 工具的功能。
可以使用 GNU/Linux 的几个工具来检查网络上的低层流量。tcpdump 工具是一个比较老的工具,它从网上“嗅探”网络数据包,打印到 stdout 或记录在一个文件中。该功能允许查看应用程序产生的流量和 TCP 生成的低层流控制机制。一个叫做 tcpflow 的新工具与 tcpdump 相辅相成,它提供协议流分析和适当地重构数据流的方法,而不管数据包的顺序或重发。清单 5 显示 tcpdump 的两个用法模式。
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
|
tcpdump 和 tcpflow 工具有大量的选项,包括创建复杂过滤表达式的能力。查阅下面的 参考资料 获取更多关于这些工具的信息。
tcpdump 和 tcpflow 都是基于文本的命令行工具。如果您更喜欢图形用户界面(GUI),有一个开放源码工具 Ethereal 也许适合您的需要。Ethereal 是一个专业的协议分析软件,它可以帮助调试应用层协议。它的插入式架构(plug-in architecture)可以分解协议,比如 HTTP 和您能想到的任何协议(写本文的时候共有 637 个协议)。
套接字编程是容易而有趣的,但是您要避免引入错误或至少使它们容易被发现,这就需要考虑本文中描述的这 5 个常见的陷阱,并且采用标准的防错性程序设计实践。GNU/Linux 工具和实用程序还可以帮助发现一些程序中的小问题。记住:在查看实用程序的帮助手册时候,跟踪相关的或“请参见”工具。您也许会发现一个必要的新工具。
Posted: October 31, 2006 at 3:48 pm | Tags: debug, shell, unix, 类
FTP命令非常使用,尤其是在UNIX系统下FTP命令是Internet用户使用最频繁的命令之一,不论是在DOS还是UNIX操作系统下使用
FTP,都会遇到大量的FTP内部命令。熟悉并灵活应用FTP的内部命令,可以大大方便使用者,
并收到事半功倍之效。
FTP的命令行格式为: ftp -v -d -i -n -g [主机名] ,其中
-v 显示远程服务器的所有响应信息;
-d 使用调试方式;
-i 限制ftp的自动登录,即不使用;
-n etrc文件;
-g 取消全局文件名。
ftp使用的内部命令如下(中括号表示可选项):
1. ![cmd[args]]:在本地机中执行交互shell,exit回到ftp环境,如:!ls*.zip。
2. $ macro-ame[args]:执行宏定义macro-name。
3. account[password]:提供登录远程系统成功后访问系统资源所需的补充口令。
4. append local-file[remote-file]:将本地文件追加到远程系统主机,若未指定远程系统文件名,则使用本地文件名。
5. ascii:使用ascii类型传输方式。
6. bell:每个命令执行完毕后计算机响铃一次。
7. bin:使用二进制文件传输方式。
8. bye:退出ftp会话过程。
9. case:在使用mget时,将远程主机文件名中的大写转为小写字母。
10. cd remote-dir:进入远程主机目录。
11. cdup:进入远程主机目录的父目录。
12. chmod mode file-name:将远程主机文件file-name的存取方式设置为mode,如: chmod 777 a.out 。
13. close:中断与远程服务器的ftp会话(与open对应)。
14. cr:使用asscii方式传输文件时,将回车换行转换为回行。
15. delete remote-file:删除远程主机文件。
16. debug[debug-value]:设置调试方式, 显示发送至远程主机的每条命令,如: deb up 3,若设为0,表示取消debug。
17. dir[remote-dir][local-file]:显示远程主机目录,并将结果存入本地文件local-file。
18. disconnection:同close。
19. form format:将文件传输方式设置为format,缺省为file方式。
20. get remote-file[local-file]: 将远程主机的文件remote-file传至本地硬盘的local-file。
21. glob:设置mdelete,mget,mput的文件名扩展,缺省时不扩展文件名,同命令行的-g参数。
22. hash:每传输1024字节,显示一个hash符号(#)。
23. help[cmd]:显示ftp内部命令cmd的帮助信息,如:help get。
24. idle[seconds]:将远程服务器的休眠计时器设为[seconds]秒。
25. image:设置二进制传输方式(同binary)。
26. lcd[dir]:将本地工作目录切换至dir。
27. ls[remote-dir][local-file]:显示远程目录remote-dir, 并存入本地文件local-file。
28. macdef macro-name:定义一个宏,遇到macdef下的空行时,宏定义结束。
29. mdelete[remote-file]:删除远程主机文件。
30. mdir remote-files local-file:与dir类似,但可指定多个远程文件,如: mdir *.o.*.zipoutfile 。
31. mget remote-files:传输多个远程文件。
32. mkdir dir-name:在远程主机中建一目录。
33. mls remote-file local-file:同nlist,但可指定多个文件名。
34. mode[modename]:将文件传输方式设置为modename, 缺省为stream方式。
35. modtime file-name:显示远程主机文件的最后修改时间。
36. mput local-file:将多个文件传输至远程主机。
37. newer file-name: 如果远程机中file-name的修改时间比本地硬盘同名文件的时间更近,则重传该文件。
38. nlist[remote-dir][local-file]:显示远程主机目录的文件清单,并存入本地硬盘的local-file。
39. nmap[inpattern outpattern]:设置文件名映射机制, 使得文件传输时,文件中的某些字符相互转换,如:nmap $1.$2.$3[$1,$2].[$2,$3],则传输文件a1.a2.a3时,文件名变为a1,a2。该命令特别适用于远程主机为非UNIX机的情况。
40. ntrans[inchars[outchars]]:设置文件名字符的翻译机制,如ntrans1R,则文件名LLL将变为RRR。
41. open host[port]:建立指定ftp服务器连接,可指定连接端口。
42. passive:进入被动传输方式。
43. prompt:设置多个文件传输时的交互提示。
44. proxy ftp-cmd:在次要控制连接中,执行一条ftp命令,该命令允许连接两个ftp服务器,以在两个服务器间传输文件。第一条ftp命令必须为open,以首先建立两个服务器间的连接。
45. put local-file[remote-file]:将本地文件local-file传送至远程主机。
46. pwd:显示远程主机的当前工作目录。
47. quit:同bye,退出ftp会话。
48. quote arg1,arg2…:将参数逐字发至远程ftp服务器,如:quote syst。
49. recv remote-file[local-file]:同get。
50. reget remote-file[local-file]:类似于get,但若local-file存在,则从上次传输中断处续传。
51. rhelp[cmd-name]:请求获得远程主机的帮助。
52. rstatus[file-name]:若未指定文件名,则显示远程主机的状态,否则显示文件状态。
53. rename[from][to]:更改远程主机文件名。
54. reset:清除回答队列。
55. restart marker:从指定的标志marker处,重新开始get或put,如:restart 130。
56. rmdir dir-name:删除远程主机目录。
57. runique:设置文件名唯一性存储。
58. send local-file[remote-file]:同put。
59. sendport:设置PORT命令的使用。
60. site arg1,arg2…:将参数作为SITE命令逐字发送至远程ftp主机。
61. size file-name:显示远程主机文件大小,如:site idle 7200。
62. status:显示当前ftp状态。
63. struct[struct-name]:将文件传输结构设置为struct-name,缺省时使用stream结构。
64. sunique:将远程主机文件名存储设置为唯一(与runique对应)。
65. system:显示远程主机的操作系统类型。
66. tenex:将文件传输类型设置为TENEX机的所需的类型。
67. tick:设置传输时的字节计数器。
68. trace:设置包跟踪。
69. type[type-name]:设置文件传输类型为type-name,缺省为ascii,如:type binary,设置二进制传输方式。
70. umask[newmask]:将远程服务器的缺省umask设置为newmask,如:umask 3。
71. user user-name[password][account]:向远程主机表明自己的身份,需要口令时,必须输入口令,如:user anonymous my@email。
72. verbose:同命令行的-v参数,即设置详尽报告方式,ftp服务器的所有响应都将显示给用户,缺省为on。
73. ?[cmd]:同help。
Posted: October 27, 2006 at 12:48 pm | Tags: class, java, php, python, server, unix, web, 平台, 开发, 技术, 测试, 类
SOAP(Simple Object Access Protocal,简单对象访问协议) 技术有助于实现大量异构程序和平台之间的互操作性,从而使存在的应用能够被广泛的用户所访问。SOAP是把成熟的基于HTTP的WEB技术与XML的灵活性和可扩展性组合在了一起。
SOAP由MS和IBM共同制定
用于规范WEB服务标准 实现异构程序与平台间的数据交换
它是基于XML的协议,包括三个部分: 封套(envelope)定义了消息内容和处理的框架、一套编码规则用来表达应用定义数据类型的实例以及表达远程过程调用和响应的协定。
与已定义的中间件不同 SOAP只是定义了一种基于XML的文本格式 而没有定义什么ORB代理或是SOAP API 因此用户可以方便的开发自己的应用而不必担心兼容性(corba与dcom间的兼容性在soap中不会再出现)
下面是一篇简介 更多介绍在动网先锋中可以找到
http://www.aspsky.net/article/show.aspx?id=2001
简单对象协议(SOAP)简介
作者:何杭军
简单对象访问协议-CNXML标准教程
2000-9-25 作者:何杭军
"SOAP是在非集中、分布环境中交换信息的轻量级协议。它是基于XML的协议,包括三个部分: 封套(envelope)定义了消息内容和处理的框架、一套编码规则用来表达应用定义数据类型的实例以及表达远程过程调用和响应的协定。"
——SOAP 1.1规范
第一节 SOAP简介
SOAP(Simple Object Access Protocal,简单对象访问协议) 技术有助于实现大量异构程序和平台之间的互操作性,从而使存在的应用能够被广泛的用户所访问。SOAP是把成熟的基于HTTP的WEB技术与XML的灵活性和可扩展性组合在了一起。
SOAP的一个主要目标是使存在的应用能被更广泛的用户所使用。为了实现这个目的,没有任何SOAP API或SOAP 对象请求代理(SOAP ORB),SOAP是假设你将使用尽可能多的存在的技术。几个主要的CORBA厂商已经承诺在他们的ORB产品中支持SOAP协议。微软也承诺在将来的 COM版本中支持SOAP。DevelopMentor已经开发了参考实现,它使得在任何平台上的任何Java或Perl程序员都可以使用SOAP。而且 IBM和Sun也陆续支持了SOAP协议,和MS合作共同开发SOAP规范和应用。目前SOAP已经成为了W3C和IETF的参考标准之一。
SOAP的指导理念是“它是第一个没有发明任何新技术的技术”。它采用了已经广泛使用的两个协议: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性能的应用是充分的,但决不是必要的。
SOAP把XML的使用代码化为请求和响应参数编码模式,并用HTTP作传输。这似乎有点抽象。具体地讲,一个SOAP方法可以简单地看作遵循SOAP编码规则的HTTP请求和响应。一个SOAP终端则可以看作一个基于HTTP的URL,它用来识别方法调用的目标。象CORBA/IIOP一样,SOAP不需要具体的对象被绑定到一个给定的终端,而是由具体实现程序来决定怎样把对象终端标识符映射到服务器端的对象。
SOAP请求是一个HTTP POST请求。SOAP请求的content-type必须用text/xml。而且它必须包含一个请求-URI。服务器怎样解释这个请求-URI是与实现相关的,但是许多实现中可能用它来映射到一个类或者一个对象。一个SOAP请求也必须用SOAPMethodName HTTP头来指明将被调用的方法。简单地讲,SOAPMethodName头是被URI指定范围的应用相关的方法名,它是用#符作为分隔符将方法名与 URI分割开:
SOAPMethodName: urn:strings-com:IString#reverse
这个头表明方法名是reverse,范围URI是urn:strings-com:Istring。 在SOAP中,规定方法名范围的名域URI在功能上等同于在DCOM 或 IIOP中规定方法名范围的接口ID。
简单的说,一个SOAP请求的HTTP体是一个XML文档,它包含方法中[in]和[in,out]参数的值。这些值被编码成为一个显著的调用元素的子元素,这个调用元素具有SOAPMethodName HTTP头的方法名和名域URI。调用元素必须出现在标准的SOAP <Envelope>;和<Body>;元素内(后面会更多讨论这两个元素)。下面是一个最简单的SOAP方法请求:
POST /string_server/Object17 HTTP/1.1
Host: 209.110.197.2
Content-Type: text/xml
Content-Length: 152
SOAPMethodName: urn:strings-com:IString#reverse
<Envelope>;
<Body>;
<m:reverse xmlns:m=”urn:strings-com:IString”>;
<theString>;Hello, World</theString>;
</m:reverse>;
</Body>;
</Envelope>;
SOAPMethodName头必须与<Body>;下的第一个子元素相匹配,否则调用将被拒绝。这允许防火墙管理员在不解析XML的情况下有效地过滤对一个具体方法的调用。
SOAP响应的格式类似于请求格式。响应体包含方法的[out]和 [in,out]参数,这个方法被编码为一个显著的响应元素的子元素。这个元素的名字与请求的调用元素的名字相同,但以Response后缀来连接。下面是对前面的SOAP请求的SOAP响应:
200 OK Content-Type: text/xml
Content-Length: 162
<Envelope>;
<Body>;
<m:reverseResponse xmlns:m=”urn:strings-com:IString”>;
<result>;dlroW ,olleH</result>;
</m:reverseResponse>;
</Body>;
</Envelope>;
这里响应元素被命名为reverseResponse,它是方法名紧跟Response后缀。要注意的是这里是没有SOAPMethodName HTTP头的。这个头只在请求消息中需要,在响应消息中并不需要。
第二节 SOAP体的核心
SOAP的XML特性是为把数据类型的实例序列化成XML的编码模式。为了达到这个目的,SOAP不要求使用传统的RPC风格的代理。而是一个SOAP方法调用包含至少两个数据类型:请求和响应。考虑这下面个COM IDL代码:
[ uuid(DEADF00D-BEAD-BEAD-BEAD-BAABAABAABAA) ]
interface IBank : IUnknown {
HRESULT withdraw([in] long account,
[out] float *newBalance,
[in, out] float *amount
[out, retval] VARIANT_BOOL *overdrawn);
}
在任何RPC协议下,account和amount参数的值将出现在请求消息中,newBalance、overdrawn参数的值,还有amount参数的更新值将出现在响应消息中。
SOAP把方法请求和方法响应提升到了一流状态。在SOAP中,请求和响应实际上类型的实例。为了理解一个方法比如IBank::withdraw怎样映射一个SOAP请求和响应类型,考虑下列的数据类型:
struct withdraw {
long account;
float amount;
};
这时所有的请求参数被打包成为单一的结构类型。同样下面的数据表示打包所有响应参数到单一的数据类型。
struct withdrawResponse {
float newBalance;
float amount;
VARIANT_BOOL overdrawn;
};
再给出下面的简单的Visual Basic程序,它使用了以前定义的Ibank接口:
Dim bank as IBank
Dim amount as Single
Dim newBal as Single
Dim overdrawn as Boolean
amount = 100
Set bank = GetObject("soap:http://bofsoap.com/am"
overdrawn = bank.withdraw(3512, amount, newBal)
这里,在发送请求消息之前,参数被序列化成为一个请求对象。同样被响应消息接收到的响应对象被反序列化为参数。一个类似的转变同样发生在调用的服务器端。
当通过SOAP调用方法时,请求对象和响应对象被序列化成一种已知的格式。每个SOAP体是一个XML文档,它具有一个显著的称为< Envelope>;的根元素。标记名<Envelope>;由SOAP URI (urn:schemas-xmlsoap-org:soap.v1)来划定范围,所有SOAP专用的元素和属性都是由这个URI来划定范围的。SOAP Envelope包含一个可选的<Header>;元素,紧跟一个必须的<Body>;元素。<Body>;元素也有一个显著的根元素,它或者是一个请求对象或者是一个响应对象。下面是一个IBank::withdraw请求的编码:
<soap:Envelope xmlns:soap=”urn:schemas-xmlsoap-org:soap.v1”>;
<soap:Body>;
<IBank:withdraw xmlns:IBank=”urn:uuid
EADF00D-BEAD-BEAD-BEAD-BAABAABAABAA”>;
<account>;3512</account>;
<amount>;100</amount>;
</IBank:withdraw>;
</soap:Body>;
</soap:Envelope>;
下列响应消息被编码为:
<soap:Envelope xmlns:soap=”urn:schemas-xmlsoap-org:soap.v1”>;
<soap:Body>;
<IBank:withdrawResponse xmlns:IBank=”urn:uuid
EADF00D-BEAD-BEAD-BEAD-BAABAABAABAA”>;
<newBalance>;0</newBalance>;
<amount>;5</amount>;
<overdrawn>;true</overdrawn>;
</IBank:withdrawResponse>;
</soap:Body>;
</soap:Envelope>;
注意[in, out]参数出现在两个消息中。在检查了请求和响应对象的格式后,你可能已经注意到序列化格式通常是:
<t:typename xmlns:t=”namespaceuri”>;
<fieldname1>;field1value</fieldname1>;
<fieldname2>;field2value</fieldname2>;
……
</t:typename>;
在请求的情况下,类型是隐式的C风格的结构,它由对应方法中的[in]和[in, out]参数组成。对响应来说,类型也是隐式的C风格的结构,它由对应方法中的[out]和[in, out]参数组成。这种每个域对应一个子元素的风格有时被称为元素正规格式(ENF)。一般情况下,SOAP只用XML特性来传达描述包含在元素内容中信息的注释。
象DCOM和IIOP一样,SOAP支持协议头扩展。SOAP用可选的<Header>;元素来传载被协议扩展所使用的信息。如果客户端的 SOAP软件包含要发送头信息,原始的请求将可能如图9所示。在这种情况下命名causality的头将与请求一起序列化。收到请求后,服务器端软件能查看头的名域URI,并处理它识别出的头扩展。这个头扩展被http://comstuff.com URI识别,并期待一个如下的对象:
struct causality {
UUID id;
};
在这种情况下的请求,如果头元素的URI不能被识别,头元素可以被安全地忽略。
但你不能安全的忽略所有的SOAP体中的头元素。如果一个特定的SOAP头对正确处理消息是很关键的,这个头元素能被用SOAP属性 mustUnderstand=’true’标记为必须的。这个属性告诉接收者头元素必须被识别并被处理以确保正确的使用。为了强迫前面 causality头成为一个必须的头,消息将被写成如下形式:
<soap:Envelope xmlns:soap=”urn:schemas-xmlsoap-org:soap.v1”>;
<soap:Header>;
<causality soap:mustUnderstand=”true”xmlns="http://comstuff.com">;
<id>;362099cc-aa46-bae2-5110-99aac9823bff</id>;
</causality>;
</soap:Header>;
</soap:Envelope>;
SOAP软件遇到不能识别必须的头元素情况时,必须拒绝这个消息并出示一个错误。如果服务器在一个SOAP请求中发现一个不能识别的必须的头元素,它必须返回一个错误响应并且不发送任何调用到目标对象。如果客户端在一个SOAP请求中发现一个不能识别出的必须的头元素,它必须向调用者返回一个运行时错误。在COM情况下,这将映射为一个明显的HRESULT。
第三节 SOAP数据类型
在SOAP消息中,每个元素可能是一个SOAP结构元素、根元素、存取元素或一个独立的元素。在SOAP中,soap:Envelope、soap:Body和soap:Header是唯一的组成元素。它们的基本关系由下列XML Schema所描述:
<schema targetNamespace=”urn:schemas-xmlsoap-org:soap.v1”>;
<element name=”Envelope”>;
<type>;
<element name=”Header” type=”Header” minOccurs=”0” />;
<element name=”Body” type=”Body”minOccurs=”1” />;
</type>;
</element>;
</schema>;
在SOAP元素的四种类型中,除了结构元素外都被用作表达类型的实例或对一个类型实例的引用。
根元素是显著的元素,它是soap:Body 或是 soap:Header的直接的子元素。其中soap: Body只有一个根元素,它表达调用、响应或错误对象。这个根元素必须是soap:Body的第一个子元素,它的标记名和域名URI必须与HTTP SOAPMethodName头或在错误消息情况下的soap:Fault相对应。而soap:Header元素有多个根元素,与消息相联系的每个头扩展对应一个。这些根元素必须是soap:Header的直接子元素,它们的标记名和名域URI表示当前存在扩展数据的类型。
存取元素被用作表达类型的域、属性或数据成员。一个给定类型的域在它的SOAP表达将只有一个存取元素。存取元素的标记名对应于类型的域名。考虑下列Java 类定义:
package com.bofsoap.IBank;
public class adjustment {
public int account ;
public float amount ;
}
在一个SOAP消息中被序列化的实例如下所示:
<t:adjustment xmlns:t=”urn:develop-com:java:com.bofsoap.IBank”>;
<account>;3514</account>;
<amount>;100.0</amount>;
</t:adjustment>;
在这个例子中,存取元素account和amount被称着简单存取元素。对引用简单类型的存取元素,元素值被简单地编码为直接在存取元素下的字符数据,如上所示。对引用组合类型的存取元素(就是那些自身用子存取元素来构造的存取元素),有两个技术来对存取元素进行编码。最简单的方法是把被结构化的值直接嵌入在存取元素下。考虑下面的Java类定义:
package com.bofsoap.IBank;
public class transfer {
public adjustment from;
public adjustment to;
}
如果用嵌入值编码存取元素,在SOAP中一个序列化的transfer对象如下所示:
<t:transfer xmlns:t=”urn:develop-com:java:com.bofsoap.IBank”>;
<from>;
<account>;3514</account>;
<amount>;-100.0</amount>;
</from>;
<to>;
<account>;3518</account>;
<amount>;100.0</amount>;
</to>;
</t:transfer>;
在这种情况下,adjustment对象的值被直接编码在它们的存取元素下。在考虑组合存取元素时,需要说明几个问题。先考虑上面的transfer类。类的from和to的域是对象引用,它可能为空。SOAP用XML Schemas的null属性来表示空值或引用。下面例子表示一个序列化的transfer对象,它的from域是空的:
<t:transfer xmlns:t=”urn:develop-com:java:com.bofsoap.IBank”
xmlns
sd=”http://www.w3.org/1999/XMLSchema/instance”>;
<from xsd:null=”true” />;
<to>;
<account>;3518</account>;
<amount>;100.0</amount>;
</to>;
</t:transfer>;
在不存在的情况下, xsd:null属性的隐含值是false。给定元素的能否为空的属性是由XML Schema定义来控制的。例如下列XML Schema将只允许from存取元素为空:
<type name=”transfer” >;
<element name=”from” type=”adjustment” nullable=”true” />;
<element name=”to” type=”adjustment” nullable=”false”/>;
</type>;
在一个元素的Schema声明中如果没有nullable属性,就意味着在一个XML文档中的元素是不能为空的。Null存取元素的精确格式当前还在修订中�要了解用更多信息参考最新版本的SOAP规范。
与存取元素相关的另一个问题是由于类型关系引起的可代换性。由于前面的adjustment类不是一个final类型的类,transfer对象的 from和to域实际引用继承类型的实例是可能的。为了支持这种类型兼容的替换,SOAP使用一个名域限定的类型属性的XML Schema约定。这种类型属性的值是一个对元素具体的类型的限制的名字。考虑下面的adjustment扩展类:
package com.bofsoap.IBank;
public class auditedadjustment extends adjustment {
public int auditlevel;
}
给出下面Java语言:
transfer xfer = new transfer();
xfer.from = new auditedadjustment();
xfer.from.account = 3514;
xfer.from.amount = -100;
xfer.from.auditlevel = 3;
xfer.to = new adjustment();
xfer.to.account = 3518;
xfer.from.amount = 100;
在SOAP中transfer对象的序列化形式如下所示:
<t:transfer xmlns
sd=”http://www.w3.org/1999/XMLSchema”
xmlns:t=”urn:develop-com:java:com.bofsoap.IBank”>;
<from xsd:type=”t:auditedadjustment” >;
<account>;3514</account>;
<amount>;-100.0</amount>;
<auditlevel>;3</auditlevel >;
</from>;
<to>;
<account>;3518</account>;
<amount>;100.0</amount>;
</to>;
</t:transfer>;
在这里xsd:type属性引用一个名域限定的类型名,它能被反序列化程序用于实例化对象的正确类型。因为to存取元素引用到一个被预料的类型的实例(而不是一个可代替的继承类型),xsd:type属性是不需要的。
刚才的transfer类设法回避了一个关键问题。如果正被序列化的transfer对象用下面这种方式初始化将会发生什么情况:
transfer xfer = new transfer();
xfer.from = new adjustment();
xfer.from.account = 3514; xfer.from.amount = -100;
xfer.to = xfer.from;
基于以前的议论,在SOAP 中transfer对象的序列化形式如下所示:
<t:transfer xmlns:t=”urn:develop-com:java:com.bofsoap.IBank”>;
<from>;
<account>;3514</account>;
<amount>;-100.0</amount>;
</from>;
<to>;
<account>;3514</account>;
<amount>;-100.0</amount>;
</to>;
</t:transfer>;
这个表达有两个问题。首先最容易理解的问题是同样的信息被发送了两次,这导致了一个比实际所需要消息的更大的消息。一个更微妙的但是更重要的问题是由于反序列化程序不能分辨两个带有同样值的adjustment对象与在两个地方被引用的一个单一的adjustment对象的区别,两个存取元素间的身份关系就被丢失。如果这个消息接收者已经在结果对象上执行了下面的测试,(xfer.to == xfer.from)将不会返回true。
void processTransfer(transfer xfer) {
if (xfer.to == xfer.from)
handleDoubleAdjustment(xfer.to);
else
handleAdjustments(xfer.to, xfer.from);
}
为了支持必须保持身份关系的类型的序列化,SOAP支持多引用存取元素。目前我们接触到的存取元素是单引用存取元素,也就是说,元素值是嵌入在存取元素下面的,而且其它存取元素被允许引用那个值(这很类似于在NDR中的[unique]的概念)。多引用存取元素总是被编码为只包含已知的soap:href 属性的空元素。soap:href属性总是包含一个代码片段标识符,它对应于存取元素引用到的实例。如果to和from存取元素已经被编码为多引用存取元素,序列化的transfer对象如下所示:
<t:transfer xmlns:t=”urn:develop-com:java:com.bofsoap.IBank”>;
<from soap:href=”#id1” />;
<to soap:href=”#id1” />;
</t:transfer>;
这个编码假设与adjustment类兼容的一个类型的实例已经在envelope中的其它地方被序列化,而且这个实例已经被用soap:id属性标记,如下所示:
<t:adjustment soap:id=”id1”xmlns:t=”urn:develop-com:java:com.bofsoap.IBank”>;
<account>;3514</account>;
<amount>;-100.0</amount>;
</t:adjustment>;
第四节 结语
一个遗留的HTTP问题还需要进一步阐明。SOAP支持(但不需要)HTTP扩展框架约定来指定必须的HTTP头扩展。这些约定主要有两个目的。首先,它们允许任意的URI被用于限定给定的HTTP头的范围(类似XML名域)。第二,这些约定允许把必须的头与可选的头区分开来(象soap: mustUnderstand)。下面是一个使用HTTP扩展框架来把SOAPMethodName头定义成为一个必须的头扩展:
M-POST /foobar HTTP/1.1
Host: 209.110.197.2
Man: "urn:schemas-xmlsoap-org:soap.v1; ns=42"
42-SOAPMethodName: urn:bobnsid:IFoo#DoIt
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等程序间的互操作性。
Posted: October 26, 2006 at 4:10 pm | Tags: cache, html, linux, server, unix, windows, 优化, 开发, 技术, 类, 缓存
应用加速(Application Acceleration)是当前信息产业中流行的一个新的时髦词语,但它到底是什么含义呢?
缓慢的应用软件响应时间可能会让用户感到失望并且失去生产力,从而对公司的核心实力造成影响,所以,应用软件加速成为信息产业中的一个新的时髦词语也就不足为奇了,但 它到底是什么含义呢?您是否需要它呢?如果需要的话,那么最具扩展性的实现方式是什么呢?让我们来看看不同的应用软件加速技术以及如何创建应用软件加速策略来与您的公 司一同成长。
本地VS网络应用软件
在过去,大部分应用软件都是安装并运行在本地计算机,现在的小型公司也通常是这样。对于本地应用软件而言,性能主要取决于本地系统资源,它受到处理器速度、处理器数量 、物理和虚拟内存数量和共享这些资源的其他应用软件(多任务)的影响。
对于这种类型的系统环境,提高应用软件性能是在单独的机器上通过升级硬件、关闭消耗宝贵资源的视觉效果,禁用不必要的与您的应用软件争夺处理器时间和内存的后台服务, 使用预取(prefetching)技术来提高应用软件的加载时间,定期整理磁盘碎片,优化各种设置比如设定进程的优先级、调整与性能相关的注册表条目等等。
然而,在当今的大中型企业中,很多应用软件都是通过局域网、城域网或互联网传递的,这对原有的方法带来了新的问题,同时也为网络带宽和网络协议带来了新的问题。
应用软件转移到了互联网上
越来越多的应用软件正在通过互联网来传递,从便利性的角度来看,这很有意义,因为几乎所有与互联网相连的计算机都有网络浏览器,这就排除了在用户系统上安装任何软件的 需要,HTTP是最普遍的网络协议中的一个,这种应用软件的“互联网化”为用户创建了一个通用界面和环境,同时也是系统管理员的通用传递系统。
在基于网络的应用软件加速的问题上,一个关键因素是使用一个逆向代理(reverse proxy)来缓存静态对象,我们在这个栏目里所讨论的缓冲方案在以往更多地被应用于正向缓存 ,这是一种加速内部用户访问互联网内容的方法:让代理服务器在本地存储从互联网站点上下载的对象。
逆向代理,正如名字所暗示的,是反方向工作的,逆向代理服务器存储了在您的内部服务器上被访问过的对象,然后将它们返回给请求者,这样请求者不必到服务器上去检索这些 数据,对于外部用户而言就意味着性能的提高。
扩展一个逆向代理的解决方案
互联网应用软件加速经常是在企业环境下讨论的,但即使是小型公司也可以使用逆向服务器来加速基于互联网的应用软件,您不必花费很多钱来实现这一点,免费的和廉价的逆向 代理软件包括:
Squid:一个基于GPL许可的开源的代理服务器,它可以从ftp://ftp.squid-cache.org/pub/上免费下载,它 可以在Linux、FreeBSD、Mac OS X和很多版本的UNIX上运行,您可以使用Cygwin开发环境(Cygwin development environment)在Windows上编译和运行Squid。
IIS逆向代理:一个用于互联网信息服务(IIS)的开源逆向代理软件,它是基于MIT许可的,可以安装在运行IIS的Windows2000、XP或Windows Server2003上,您可以在http://www.saltypickle.com/Home/16上下载到这一软件。
Orenosp安全逆向代理:一个逆向代理的共享软件,还具有平衡负载和安全端口的转发功能,它可以运行在Windows NT、2000、XP和2003,以及Linux和Mac OS X上,如果需要 更多的信息可以参见http://hp.vector.co.jp/authors/VA027031/orenosp/index_en.html
通常,随着公司的发展,您将会需要对互联网应用软件进行加速,功能不复杂的逆向代理服务器会缓存所有的数据,而高级一些的会允许您像外科手术般地来操作哪些数据需要缓 存,哪些不需要。您可能还希望考虑可扩展性的协议支持:您需要缓存的可能不仅仅是HTTP对象。
从免费的和低廉的解决方案中提升一步,您可以发现一些适合大中型公司需求的中等价位的产品,例如:
微软的ISA服务器:ISA可以作为正向的或逆向代理服务器和应用层过滤防火墙,所以您可以应用很多相关的功能。标准版可以运行在Windows 2000 Server或Windows Server 2003上,每个处理器的价格为1499美元,企业版可以部署在服务器阵列上,因而具备更强的灵活性与可扩展性,每个处理器价格为5999美元。需要更多的信息,请点击此处。
Vigos AG网站加速器:对于仅仅需要一个逆向代理而不需要ISA的附加功能的用户而言,这是一个提高他们网络站点性能的选择。Vigos还可以实时压缩发送的数据,“简易” 版(支持单一域)的价格是499美元,标准版(支持最多十个域)的价格是999美元,企业版(支持无限个域)的价格为1999美元。需要更多的信息,请点击此处。在企业级,逆向代理允许您对多个前端互联网服务器实施逆向代理方案,并提供SSL的安全连接保障。为企业级市场设计的高端逆向代理解决方案需要花费30000美元或更多,一些 可选方案包括:
Blue Coat Proxy SG:作为一套配置齐全立即可用的设备,Proxy SG有很多产品系列(400, 800和8000)以满足不通的预算和需求,您可以对上游的互联网服务器实施正向缓存 ,甚至对上游的服务器进行状态检查,高端的8000系列可以支持超过50000接入用户并支持最高300Mbps的城域网吞吐量,型号为8000-4的价格为99000美元,需要更多的信息,请点击此处。
思科内容引擎(Content Engine):这是Cisco的缓存与内容过滤设备,可以部署在您的互联网站点之前来缓存或分流请求,以及卸载互联网服务器的数据流量,以此来增加网 络应用软件的性能。内容引擎7325的价格在40000美元到50000之间。
Previous Page