0

清除指定squid缓存文件的脚本


用wget来清空squid缓存

可以使用以下命令来清空squid的缓存:

代码:
/usr/bin/wget -O squid.log -S –header=”ragma: no-”  url

代码:

squidclient -m PURGE -p 80 “http://you.video.sina.com.cn/index.

代码:

http://www.cdn.com/api/purge.?action=purge&host=61.55.111.111&url=http://www.md5.cn/

一款老外的程序,可以批量清除某类URL的Squid缓存,支持正则表达式。

  下载网址:http://www.wa.apana.org.au/~dean/squidpurge/

  编译:
代码:
wget http://www.wa.apana.org.au/~dean … 20040201-src.tar.gz
tar zxvf purge-20040201-src.tar.gz
cd purge
make
  清除Squid缓存示例:
  1、清除 URL 以“.mp3”结尾的缓存文件(例如 http://www.s135.com/abc.mp3、http://www.s135.com/01/a.mp3)
代码:
./purge -p localhost:80 -P 1 -se ‘\.mp3$’
  2、清除URL中包含s135.com的所有缓存:
代码:
./purge -p localhost:80 -P 1 -se ‘s135.com’
  我喜欢将程序推到后台去执行,让它慢慢地去清Squid缓存,同时将输出内容记录到purge.log文件:
代码:
./purge -p localhost:80 -P 1 -se ‘s135.com’ > purge.log 2>&1

 Squid web缓存加速软件目前已经是新浪、搜狐、网易等各大网站广泛应用。Squid会在设置的缓存目录下建立多个目录,每一个目录下又建立多个目录,然后才在最里层的目录中存放缓存文件(object)。squid会根据用户请求网页的URL进行哈希,生成缓存文件,存放在某一个目录中。squid启动之后,将在内存中建立一个哈希表,记录硬盘中缓存文件配置的情形。

  对于类似http://you.video.sina.com.cn/index.html之类的网页,squid只会生成一个缓存文件。可以用squid附带的squidclient工具清除:

代码:
squidclient -m PURGE -p 80 “http://you.video.sina.com.cn/index.html”
  而对于带有参数的网页,例如新浪播客的Flash播放器http://vhead.blog.sina.com.cn/pl … 9852&uid=1278987704,因“?”后面的参数不同,导致URL也不同,squid会生成多个缓存文件,哈希分散存放在不同的目录。如果修改了这个outer_player.swf文件,要更新squid缓存就要去清除不同目录下及内存中的很多个缓存文件,十分麻烦,于是我编写了一个Linux下的shell脚本,去完成这件麻烦的事:

  脚本文件名:clear_squid_cache.sh(8月2日修正了UC网友“城市中的寂寞”反馈的BUG)

代码:
#!/bin/sh
squidcache_path=”/data1/squid/var/cache”
squidclient_path=”/usr/local/squid/bin/squidclient”
grep -a -r $1 $squidcache_path/* | strings | grep “http:” | awk -F’http:’ ‘{print “http:”$2;}’ > cache_list.txt
for url in `cat cache_list.txt`; do
$squidclient_path -m PURGE -p 80 $url
done
  注意:请赋予clear_squid_cache.sh可执行权限(命令:chmod +x ./clear_squid_cache.sh)。请确保脚本所在目录可写。

  设置:
  squidcache_path= 表示squid缓存目录的路径
  squidclient_path= 表示squidclient程序所在的路径,默认为squid安装目录下的bin/squidclient

  用法:
  1、清除所有Flash缓存(扩展名.swf):
  
代码:
./clear_squid_cache.sh swf
  2、清除URL中包含sina.com.cn的所有缓存:
  
代码:
./clear_squid_cache.sh sina.com.cn
  3、清除文件名为zhangyan.jpg的所有缓存:
  
代码:
./clear_squid_cache.sh zhangyan.jpg
  效率:
  经测试,在DELL 2950上清除26000个缓存文件用时2分钟左右。平均每秒可清除缓存文件177个。

0

加快Flex应用启动速度的5种方式


Jun Heider在O’Reilly的InsideRIA站点上发表了一篇精彩的文章,该文章就如何加快Flex应用的启动速度提出了很多建议,以帮助用户减少看见讨厌的“Loading”对话框的出现时间。他深入探讨了问题的不同方面,并对每种技术的优势和劣势进行了评判。

从外部加载媒体(Media)
Heider提到了一个常用的Flex最佳实践——限制嵌入到应用/SWF文件中的媒体的数量,如图像、影片及mp3等资源都可以从外部的SWF文件加载。 Flex框架可以直接将图片、mp3及字体等资源编译到SWF中。当你想让最终用户获得全部资源时,这种方式确实能派上用场,但是这会导致你的应用长时间停留在“Loading”阶段。中国最大的RIA分享社区-与中国闪客一起成长和发展!

在嵌入式字体中限制字符集
Heider建议在嵌入式字体中限制字符集以降低SWF文件的总下载时间: 当你在Flex中嵌入一种字体时,你就会获得该字体的全部字符的支持。尽管这可能是你想要的,但你确信你需要全部字符么?例如,在一个只面向英文的应用中,你确信你真的想花时间下载中文字符数据么?
缓存框架

Heider回顾了Flex 3 support for runtime-shared-libraries (RSL)这篇文章:从Flex 3开始,你可以将Adobe签名的框架——RSLs缓存到Flash Player的cache中。这有两个好处。首先,缓存在Flash Player cache中的签名的框架RSLs可由所有配置好的Flex应用共享。换句话说,如果某人的应用已经下载了500k的签名的框架RSL,并且该RSL仍旧 在Flash Player cache中,那么你的应用就可以使用缓存下来的RSL。其次,即使某人清空了其浏览器缓存,对Flash Player cache也没有任何影响。

考虑模块化
Heider谈到了将Flex应用划分成模块的好处:减少字体加载时间的另一种方式就是将你的Flex应用划分成模块。使用模块的一个好处在于当加载和卸载模块时你能完全操控它。
之所以要划分成模块的最后一个原因是他们更快,而且我能即时加载它们。换句话说,在启动时唯一需要加载的模块就是 Step1.swf模块。因此,在使用模块的情况下,最终用户节省了启动时间,但是当他从一个模块切换到另一个模块时却需要花更多时间,因为每个模块都需 要以JIT形式加载。在我的应用中,只有当用户首次在steps 1-5之间切换时需要花更多时间。

推迟实例化
Heider围绕着Flex组件的“creationPolicy”属性及何时实例化应用的不同部分给出了很多建议。如果你想减少从数据下载到用户真正可以使用的总时间,当务之急就是推迟实例化。这项技术背后的理念就是直到应用真正使用的时候才在内存中创建对象。尽管推迟实例化技术会在应用的整个使用过程中导致少许——通常不那么明显——的延迟,但与长时间的启动延迟相比,它还是可接受的。推迟实例化的另一个好处在于内存使用的优化。 Heider还谈到了一个“实验性”的条款——“使用流”,这是他在讨论Dirk Eismann的帖子(Building monolithic Flex SWFs that still startup quickly.”)时谈及的。Eismann提出一项技术以利用Flash Player中的多个frames以在部分应用中达到流的目的。查看所有的帖子以更多地了解该技术及关于加快Flex启动速度的建议。中国最大的RIA分享社区-与中国闪客一起成长和发展!)

原文出处:http://www.infoq.com/cn/news/2008/05/flex-startup-time

0

google背后的分布式架构


  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 中有对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 还给了一个关于压缩 蜘蛛数据的例子。这个例子的蜘蛛 包含 2.1B 的页面,行按照以下的方式命名:“com.cnn.www/index.:http”.在未压缩前的web page 页面大小是:45.1 TB ,压缩后的大小是:4.2 TB , 只是原来的 9.2%。Links 数据压缩到原来的 13.9% , 链接文本数据压缩到原来的 12.7%。
Continue Reading

0

squid日志TCP命中率字段祥解


squid的日志很重要。常常要了解的,其中最重要的就是命中率啦,不然反向代理做的用就不大。
#cat access.log|gawk ‘{print $4}’|sort|uniq -c|sort -nr
9568 TCP_IMS_HIT/304
6313 TCP_HIT/200
2133 TCP_MISS/200
1568 TCP_MISS/206
587 TCP_MEM_HIT/200
531 TCP_MISS/304
207 TCP_REFRESH_HIT/200
152 TCP_REFRESH_HIT/304
86 TCP_NEGATIVE_HIT/404
69 TCP_MISS/404
9 TCP_MISS/000
4 TCP_MISS/503
1 TCP_REFRESH_MISS/000
1 TCP_DENIED/400
可以使用上面的方法,大约的分析一下命令中比。什么意思就看下面的详解.
#cat /var/log/squid/access.log |grep TCP_MEM_HIT
如果看到很多的TCP_MEM_HIT ,这表明该文件是从内存缓存读取的,squid已经起作用了!你再用浏览器打开该文件,应该是快如闪电了。。呵呵,大功告成了!还有其他类型的HIT,如TCP_HIT等等,这些是从磁盘读取的,我� ��得加速的意义不大,只不过缓解了apache的压力而已。
相应于HTTP请求,下列标签可能出现在access.log文件的第四个域。
TCP_HIT
Squid发现请求资源的貌似新鲜的拷贝,并将其立即发送到客户端。
TCP_MISS
Squid没有请求资源的cache拷贝。
TCP_REFERSH_HIT
Squid发现请求资源的貌似陈旧的拷贝,并发送确认请求到原始服务器。原始服务器返回304(未修改)响应,指示squid的拷贝仍旧是新鲜的。
TCP_REF_FAIL_HIT
Squid发现请求资源的貌似陈旧的拷贝,并发送确认请求到原始服务器。然而,原始服务器响应失败,或者返回的响应Squid不能理解。在此情形下,squid发送现有cache拷贝(很可能是陈旧的)到� �户端。
TCP_REFRESH_MISS
Squid发现请求资源的貌似陈旧的拷贝,并发送确认请求到原始服务器。原始服务器响应新的内容,指示这个cache拷贝确实是陈旧的。
TCP_CLIENT_REFRESH_MISS
Squid发现了请求资源的拷贝,但客户端的请求包含了Cache-Control: no-cache指令。Squid转发客户端的请求到原始服务器,强迫cache确认。
TCP_IMS_HIT
客户端发送确认请求,Squid发现更近来的、貌似新鲜的请求资源的拷贝。Squid发送更新的内容到客户端,而不联系原始服务器。
TCP_SWAPFAIL_MISS
Squid发现请求资源的有效拷贝,但从磁盘装载它失败。这时squid发送请求到原始服务器,就如同这是个cache丢失一样。
TCP_NEGATIVE_HIT
在对原始服务器的请求导致HTTP错误时,Squid也会cache这个响应。在短时间内对这些资源的重复请求,导致了否命中。 negative_ttl指令控制这些错误被cache的时间数量。请注意这些错误只在内存cache,不会写往磁盘。下列HTTP状态码可能导致否定 (也遵循于其他约束): 204, 305, 400, 403, 404, 405, 414, 500, 501, 502, 503, 504。
TCP_MEM_HIT
Squid在内存cache里发现请求资源的有效拷贝,并将其立即发送到客户端。注意这点并非精确的呈现了所有从内存服务的响应。例如,某些cache在内存里,但要求确认的响应,会以TCP_REFRESH_HIT, TCP_REFRESH_MISS等形式记录。
TCP_DENIED
因为http_access或http_reply_access规则,客户端的请求被拒绝了。注意被http_access拒绝的请求在第9域的值是NONE/-,然而被http_reply_access拒绝的请求,在相应地方有一个有效值。
TCP_OFFLINE_HIT
当offline_mode激活时,Squid对任何cache响应返回cache命中,而不用考虑它的新鲜程度。
TCP_REDIRECT
重定向程序告诉Squid产生一个HTTP重定向到新的URI(见11.1节)。正常的,Squid不会记录这些重定向。假如要这样做,必须在编译squid前,手工定义LOG_TCP_REDIRECTS预处理指令。
NONE
无分类的结果用于特定错误,例如无效主机名。
相应于ICP查询,下列标签可能出现在access.log文件的第四域。
UDP_HIT
Squid在cache里发现请求资源的貌似新鲜的拷贝。
UDP_MISS
Squid没有在cache里发现请求资源的貌似新鲜的拷贝。假如同一目标通过HTTP请求,就可能是个cache丢失。请对比UDP_MISS_NOFETCH。
UDP_MISS_NOFETCH
跟UDP_MISS类似,不同的是这里也指示了Squid不愿去处理相应的HTTP请求。假如使用了-Y命令行选项,Squid在启动并编译其内存索引时,会返回这个标签而不是UDP_MISS。
UDP_DENIED
因为icp_access规则,ICP查询被拒绝。假如超过95% 的到某客户端的ICP响应是UDP_DENIED,并且客户端数据库激活了(见附录A),Squid在1小时内,停止发送任何ICP响应到该客户端。若这点发生,你也可在cache.log里见到一个警告。
UDP_INVALID
Squid接受到无效查询(例如截断的消息、无效协议版本、URI里的空格等)。Squid发送UDP_INVALID响应到客户端。

0

[转]AT指令集及S寄存器


AT命令使计算机或终端与调制解调器通讯,所有命令行必须由ASCII字符“AT”开始并由 <Enter> 结束。除了A/指令和推出(缺省为+++)。这些将在后面讨论。字母”AT”用以提醒调制解调器注意,其后将有一条或多条命令出现, “AT”及其后的字母可以是大写或小写。

AT必须同为大写或小写。如”At”或”aT”是不允许的。

    一串命令可以写在一行里。为了便于阅读可以加或不加空格。命令中或命令间的空格会被忽略,命令行的最多字符数为39(包括”AT”)。在输入一条命令期间,可以用退格键(backspace)改正除”AT”以外的错误。若命令行中任一处出现语法错误,本行其后的内容将被忽略,并返回ERROR。大数带有超出正常范围的参数的命令将不被接收并返回 ERROR.本章列出所有设置调制解调器的命令。包括控制ACTIVE调制解调器的贺氏标准AT命令集。贺氏V系列命令集和扩展命令集

AT命令集的描述

      符号 * 表明该命令的设置可用AT&Wn命令存于两个用户方案中的一个

A/        重执行命令

重执行前一AT命令行,主要用于连接时占线,无应答或号码错误。这一命令必须单独构成一命令行并由”/”字符结束,(<Enter> 不能用于结束命令)。

+++       退出字符 缺省:+

切换调制解调器从在线状态到命令状态,而不会中断数据连接。可以通过改变S寄存器S2的值来改变这一字符。

AT=x      写入被选的S寄存器

这一命令将数值x写入当前被选的S寄存器,一个S寄存器可由ATSn命令选择,若 x 是一个数字,所有S 寄存器将返回 OK 响应。

AT?       读被选的S寄存器

  这一命令读并且显示被选的S寄存器的内容。一个S寄存器可由ATSn命令选择。

ATA       应答

它必须是命令行中的最后一条指令。调制解调器在应答方式下继续执行连接程序。在与远端调制解调器交换载波后进入连接状态,如果在由寄存器S7规定的时间内(缺省值=50秒)没有检测到载波, 调制解调器将挂机。在连接过程中,通过DTE输入的任何一个字母都将中断这一命令。

ATBn*     选择ITU-T或Bell模式 缺省=0

 ATB0 选择在1200和300bps速率下通讯的ITU-T V.22和V.21协议
 ATB1 选择在1200和300bps速率下通讯的Bell 212A和103协议

ATCn      载波控制缺省=1

包含这一命令只是为了保证兼容性,执行号只是返回一结果码而没有其它作用。
ATC1 正常传输载波切换

ATDn      拨号

它必须是命令行中的最后一条指令, ATD命令使调制解调器摘机后, 根据输入的参数拨号,以建立连接。如果不带参数,调制解调器摘机后,不拨号进入发起方式。

使用标点可使命令更易读懂。圆括号,连字符和空格符会被忽略。拔号命令行中如果出现了非法字符,则该字符及其后的内容将被忽略。调制解调器允许的拨号命令长度为36个字符。

参数:0-9 A B C D * # L P T R ! @ W , ; ^ S=n
0-9     DTMF 符号0到9
A-D     DTMF 符号A,B,C和D。在一些国家中不使用这些符号
*       “星”号(仅用于音频拨号)
#       “#”号(仅用于音频拨号)
J       为本次呼叫执行在可提供的最高速率下的MNP10链路协商(可选)
K       使本次呼叫MNP10链路协商期间电源电平可调(可选)
L       重拨上一次拨过的号码
P       脉冲拨号
T       双音频拨号
R       逆叫方式。允许调制解调器使用应答方式呼叫只能作为发起使用的调制解调        器, 必须作为命令行中的最后一个字符输入。
!       使调制解调器按照S29中规定的值挂机一段时间再摘机。
@       使调制解调器等待5秒钟的无声回答
w       按照寄存器S7中规定的时间,在拨号前等待拨号音。
,       在拨号过程中,按照寄存器S8中规定的时间,暂停
;       拨号后返回命令状态
^       打开呼叫音
()      被忽视,用于格式化号码串
-       被忽视,用于格式化号码串
<space> 被忽视,用于格式化号码串
S=n     用AT&Zn 命令存在地址n处的号码拨号

ATE*     命令回应           缺省:1

ATE0 关闭命令回应
ATE1 打开 命令回应

ATHn     摘挂机控制       缺省:0

ATH0 使调制解调器挂机
ATH1 当调制解调器处于挂机状态,使调制解调器摘机,返回响 OK,等待进一步的命令。

ATIn     识别

I0 报告产品代码
I1 报告ROM中预先计算的校验和
I2 计算校验和并与ROM中的校验和比较,返回”OK”或”ERROR”结果码
I3 报告固件修正
I4 报告OEM定义的识别串
I5 报告国家代码参数
I6 报告固件修正
I7 报告调制解调器数据泵类型

ATLn*    扬声器音量       缺省:2

ATL0 扬声器低音量
ATL1 扬声器低音量
ATL2 扬声器中音量
ATL3 扬声器高音量

ATMn*    扬声器控制       缺省:1

ATM0 关闭扬声器
ATM1 扬声器在呼叫建立握手阶段打开至检测到来自于远端调制解调器的载波后关闭
ATM2 扬声器持续开
ATM3 扬声器在应答期间打开。当检测到来自于远端的调制解调器的载波和拨号时关闭

ATNn*     调制握手       缺省:1

 ATN0 要求调制解调器S37选择连接速率,若S37=0,则连接速率必须与发出的上一条AT命令的速率相匹配。如果所选择的速率可用不止一个通讯标准实现(如Bell212A或ITU-T V.22 速率在 1200bps)调制解调器同时参考ATB 命令选择。ATN1 允许时使用双方调制解调器都支持的任一速率握手,使能够自动检测。在这一方式下,ATB命令被忽视,调制解调器只用ITU-T方式连接。

ATOn     进入数据在现状态 缺省:0

ATO0 使调制解调器从命令在现状态直接返回数据在线状态,不经过自动均衡。
ATO1 使调制解调器从命令在现状态返回数据在状态,经过自动均衡。

ATP*     设脉冲拨号为缺省
 

ATQn*    结果码显示        缺省:0

ATQ0 调制解调器向DTE发送结果码
ATQ1 禁止调制解调器向DTE发送结果码

点击查看S寄存器详解!

ATSn     设S寄存器n为缺省寄存器
 ATSn?    读S寄存器

读S寄存器中的内容,所有的S寄存器都可以读

ATSn=x   写入S寄存器

将 x值写入指定的S寄存器n

ATT*     设音频拔号为缺省
 

ATVn*    结束码类型 (消息控制)        缺省:1

ATV0 发送短型 (数字型) 结果码
ATV1 发送长型 (字符型) 结果码

ATWn*    协商进程报告                缺省:0

ATW0 不报告纠错呼叫进程
ATW1 报告纠错呼叫进程
ATW2 不报告纠错呼叫进程,CONNECT xxxx指示DCE速率。

ATXn*     扩展结果码            缺省:4

ATX0 调制解调器忽视拨号音和忙音。当由盲拨建立连接时,发送CONNECT信息。ATX1 调制解调器忽视拨号音和忙音。当由盲拨建立连接时,CONNECT XXXX 反映的是比特速率

ATX2 调制解调器忽视忙音,但在拨号前等待拨号音,如果5秒钟内检测不到拨号音,则发送NO DIAL TONE 信息,连接建立后 发送 CONNECT xxxx反映比特速率。

ATX3 调制解调器忽视拨号音,若检测到忙音,发送BUSY信息,当由盲拨建立起连接时, CONNECT XXXX 反映的是比特速率。

ATX4 如果5秒钟内检测不到拨号音,发送NO DIAL TONE 讯息,检测到忙音, 发送BUSY信息。连接建立后发送CONNECT XXXX 反映比特速率。

ATYn*     控制长间隔拆接         缺省:0

ATY0 不允许长间隔拆接
ATY1 允许长间隔拆接

ATZn      复位                缺省:0

重新调出由用户方案规定的动态配置
ATZ0 软复位并重新调出用户方案0
ATZ1 软复位并重新调出用户方案1

AT&An*    握手异常终止(备选)    缺省:1

 AT&A0 在握手时禁止用户进行异常终止。当拨号或应答时,握手不能异常终止,只有DTR 信号下降。AT&A1 用户可以在握手时进行异常终止.在接收到DTE的字符后,发起和应答可以在握手期间随时进行异常终止.

AT&Cn*     RS232-C DCD          设置缺省:1

AT&C0 DCD为ON,不论来自远端的调制解调器的数据载波的状态为何。
AT&C1 DCD 跟随来自于远端调制解调器的数据载波的状态

AT&Dn*    RS232-C DTR          设置缺省:2

决定了调制解调器与来自串口的DTR信号相关的操作。由于跟踪DTR的下降引起的操作在下表列出:

 
&D0 
&D1 
&D2 
&D3 
&Q0 
NONE 
&Q1 
&Q2 
&Q3 
&Q4 
&Q5 
NONE 
&Q6 
NONE 

1 调制解调器断开连接并发送结果码OK
2 若在数据状态下,则进入命令状态,并发送结果码OK
3 调制解调器断开连接并发送结果码OK, DTR 为 OFF时不能自动应答
4 调制解调器执行热启动(即与ATZ命令相同)

AT&Fn     重新调用工厂            设置缺省:0

&F0 重新调用作为V.42bis自动可靠方式的出厂缺省设置
&F1 重新调用作为MNP5自动可靠方式的出厂缺省设置
&F2 重新调用作为DIRECT方式的出厂缺省设置
&F3 重新调用作为MNP10方式自动可靠方式的出厂缺省设置(可选)

AT&Gn*    设置保护音            缺省:0

AT&G0 无保护音
AT&G1 无保护音
AT&G2 1800HZ保护音

AT&Jn*    电话插头选择          缺省:0
 

包含这一命令只是基于兼容性的考虑,没有任何功能
AT&J0 不操作任何功能
AT&J1 不操作任何功能

AT&Kn*    DTE/调制解调器流    控制缺省:3

AT&K0 关闭流控制
AT&K3 使用RTS/CTS流控
AT&K4 使用XON/XOFF流控
AT&K5 使用透明XON/XOFF流控
AT&K6 使用RTS/CTS和XON/XOFF流控(作为传真方式下的缺省)

AT&Ln*    传输线类型            缺省:0

AT&L0 拨号线
AT&L1 二线专线 (备选)
AT&L2 四线专线 (备选)

AT&Mn*    通讯方式

与AT&Q0-3相同

AT&Pn*    拨号脉冲占空比        缺省:0

AT&P0 39%61%占空比@10PPS
AT&P1 33%67%占空比@10PPS
AT&P2 39%61%占空比@20PPS
AT&P3 33%67%占空比@20PPS

AT&Qn*    通讯方式             缺省:5

AT&Q0 选择直接异步操作
AT&Q1 选择同步模式一操作
AT&Q2 选择同步模式二操作
AT&Q3 选择同步模式三操作
AT&Q4 选择自动同步模式操作
AT&Q5 选择纠错模式操作
AT&Q6 选择标准模式下的异步操作

AT&Rn*    RS232-C RTS/CTS   设置缺省:0

AT&R0 CTS跟踪RTS, 本地DTE发送的RTS由OFF变为ON经过由寄存器S26所规定的以10微秒为增量的延迟后,CTS变为ONAT&R1 调制解调器忽视RTS,除非使用了AT&K3命令,CTS保持为ON

AT&Sn*    RS232-C DSR       设置缺省:0

AT&S0 DSR始终为ON
AT&S1 DSR根据EIA-232-C的规定操作

AT&Tn*    测试和诊断            缺省:4

测试只能在非纠错方式下(标准或直接模式)下的异步操作中进行,除参数7和8以外,要中止正在进行中的测试必须首先敲入退出符。若S18非零,则测试经由S18规定的时间后自动中止并显示OK。AT&T0 终止进行中的测试
AT&T1 启动本地模拟回环
AT&T3 在本地启动远端数字回环·,若连接未建通,返回ERROR
AT&T4 允许调制解调器响应来自远端的进行远程数字环回测试的请求
AT&T5 拒绝调制解调器响应来自远端的进行远程数字环回测试的求
AT&T6 启动远端数字环回测试,若连接未通,返回ERROR
T&T7 启动远端数字环回自测试,若连接未建通,返回ERROR
AT&T8 启动本地模拟环回自测试
 

AT&V     看当今配置及用户参数

AT&V0 查看当前配置、用户方案和存储的电话号码
AT&V1 显示最后一次数据连接的详细情况

AT&Wn    储存用户参数              缺省:0

AT&W0 作为用户0存贮
AT&W1 作为用户1存贮

AT&Xn*    选择同步时钟源             缺省:0

AT&X0 调制解调器提供传输时钟,内部时钟。 AT&X1 DTE提供传输时钟,外部时钟。
AT&X2 由调制解调器从接外载波信号中提供传输时钟,从属接收时钟

AT&Yn*    指示缺省用户参数            缺省:0

在硬复位后可选择将使用的用户方案。
AT&Y0 选择用户方案0
AT&Y1 选择用户方案1

AT&Zn=x   储存电话号码(n=0-3)         缺省:0

将一36位数字电话号码(x)存放在一指定电话号码表中(n), 作以后拨号用(参见命令ATDS=n)

AT\An 最大MNP块的大小缺省:2
AT\A0 设最大块为64个字符
AT\A1 设最大块为128个字符
AT\A2 设最大块为192个字符
AT\A3 设最大块为256个字符

AT\Bn     发送中断信号(n=1-9)        缺省:3

当在非MNP连接期间输入此命令,调制解调器向远端调制解器发送一中断信号,中断信号长度参数为n值的100倍(以毫秒            为单位),在MNP模式下,输入此命令,调制解调器向远端调制解调器发送一链路注意码PDU

AT\Gn     调制解调器到调制解调器的流控制    缺省:0

AT\G0 关闭流控(XON/XOFF)
AT\G1 打开流控(XON/XOFF)
 

AT\Jn     DTE速率自动调整控制            缺省:0

AT\J0 关闭匹配线路速率的DTE速率调整功能
AT\J1 打开匹配线路速率的DTE速率调整功能
 

AT\Kn     中断控制                     缺省:5

在数据传输期间收到来自DTE的中断信号时,调制解调器作出如下响应AT\K0,2,4 调制解调器进入连机命令状态,而不向远端发送中断信号
AT\K1 调制解调器清空终端的缓冲器并向远端调制解调器发送中断信号
AT\K3 调制解调器不清空终端的缓冲器,但向远端调制解调器发送中断信号
AT\K5 调制解调器随发送的数据发送中断信号. 调制解调器在连机命令状态时数据传输过程中,做如下操作
AT\K0,1 调制解调器清空终端的缓冲器,并向远端调制解调器发送中断信号
AT\K2,3 调制解调器不清空缓冲器,但向远端调制解调器发送中断信号
AT\K4,5 调制解调器随传输的数据按顺序发送中断信号 在非纠错模式下收到来自DTE的中断信号时,调制解调器做如下操作
AT\K0,1 调制解调器清除终端的缓冲器,并向本地DTE发送中断信号
AT\K2,3 调制解调器不清除缓冲器,但向本地DTE发送中断信号
AT\K4,5 调制解调器随接收的数据按顺序发送中断信号
 
 

AT\Ln     MNP块传输控制                 缺省:0

AT\L0 对于MNP链路连接使用流模式
AT\L1 对于MNP链路连接使用块模式
 

AT\Nn     操作模式控制                 缺省:3

AT\N0 选择标准速度缓存模式(无纠错)
AT\N1 选择直接模式(等效于&M0,&Q0)
AT\N2 选择可靠模式,可靠连接失败会使调制解调器挂机
AT\N3 选择自动可靠模式
AT\N4 选择LAPM纠错模式,LAPM纠错连接失败会使调制解调器挂机
AT\N5 选择MNP纠错模式,MNP纠错连接失败会使调制解调器挂机

AT\Vn     单线连接信息                 缺省:0

AT\V0 关闭单线连接信息。
AT\V1 打开单线连接信息。

AT%C*    压缩控制                    缺省: 3

AT%C0 关闭数据压缩 AT%C1 打开MNP5数据压缩
AT%C2 打开V.42bis数据压缩
AT%C3 打开MNP5和V.42bis数据压缩

AT%En    开/关自动均衡                缺省:2
 

控制是使调制解调器自动监听线路质量并请求均衡(%E1)还是当线路质量不好时降速,线路质量好时升速。

AT%E0 关闭线路质量监听和自动均衡。
AT%E1 打开线路质量监听和自动均衡。
AT%E2 打开线路质量监听和速率自动调整上升或下降。
AT%E3 打开线路质量监听和采用快速挂机的自动均衡。

AT%L     报告接收灵敏度

返回接收信号的电平值,提供以下数值

001=-1dBm接收电平
002=-2dBm接收电平
: :
043=-43dBm接收电平

AT%On     选择应答或呼叫模式             缺省:1

AT%O0 选择应答式模
AT%O1 选择发起式模

AT%Rn     选择接收灵敏度 (适用於专线型号) 缺省:0

AT%R0 -43dBm
AT%R1 -33dBm
备选:适用於拔号线型号,JP2跳线:-33dBM 连接1-2 针;-43 连接2-3针

AT%Q     显示线路信号质量

返回眼图指标(EQM)值的高字节,该字节的表示范围为0到127,当这一数值为70DC±10(依赖于线路速率)或更大时,若已使用了AT%E1命令则调制解调器将自动均衡,标准连接时这一数在0到15之间。到60时则为较差连接。

AT#CIDn   呼叫者身份鉴定                 缺省:0

AT#CID=0关闭呼叫者身份鉴定
AT#CID=1打开DTE格式化形式的呼叫者身份鉴定
AT#CID=2打开DTE非格式化形式的呼叫者身份鉴定
AT#CID? 从调制解调器中恢复当前呼叫者身份鉴定方式
AT#CID=? 返回调制解调器允许模式的列表,表中各部分间用逗号隔开

AT-SDR=n  鉴别性振铃                    缺省:0

AT-SDR=0 允许任何振铃、并报告”RING”
AT-SDR=1 允许一类型振铃
AT-SDR=2 允许二类型振铃
AT-SDR=3 允许一及二类型振铃
AT-SDR=4 允许三类型振铃
AT-SDR=5 允许一及三类型振铃
AT-SDR=6 允许二及三类型振铃
AT-SDR=7 允许一、二及三类型振铃

振铃类型 
振铃时段模式 
响2秒、停4秒
响0.8秒、停0.4秒、响0.8秒、停4秒
响0.4秒、停0.2秒、响0.4秒、停0.2秒、响0.8秒、停4秒

AT+MS*     选择线路调制方式

命令格式为(336型号):
AT+MS=<模式>,<自动模式>,<最小速率>,<最大速率>
缺省值为 AT+MS=11,1,300,33600 (336型号)命令格式为(560型号):
AT+MS=<模式>,<自动模式>,<最小速率>,<最大速率>,
<x_law>,<rb_signal>,<maxup_rate>
缺省值为 AT+MS=12,1,300,56000,33600 (560型号)

AT+MS?  向包含所选选项的DTE发送一信息流

AT+MS=? 向包含所提供选项的DTE发送一信息流
 
 

自动模式 
选 项 
关闭自动模式 
打开自动模式 
模式 
  调制方式选择
可能 波特率(bps) <最小 波特率> <最大 波特率> 
V.21 300
V.22 1200
V.22bis 2400或1200
V.23 1200
V.32 9600或4800
10 
V.32bis 14400,12000,9600,7200 或4800
11 
V.34 33600,31200,28800,26400,24000,21600,19200, 16800,14400,12000, 
9600,7200,4800或2400
12 
V.90 56000,54667,53333,52000,50667,49333,48000,46667,45333,42667, 
41333,40000,38667,37333,36000,34667,33333,32000,30667,29333, 
28000 (560型号适用)
56 
K56flex 56000,54000,52000,50000,48000,46000,44000,42000,40000,38000, 
36000,34000,32000 (560型号适用)
64 
Bell 103 300
69 
Bell 212 1200

<x_law> 是一个可选的数字,用来确定码类型,选择是:

0 = u-Law 1 = A-Law注意:ATZ命令将复位<x_law>值为0 (u-Law)。
 

<rb_signaling> 是一个可选的数字,用于配置一个发送数据的调制解调器产生“丢失位”信号或不产生“丢               失位”信号;或配置一台接收数据的调制解调器检测“丢失位”信号或不检测“丢失位”信               号。选择是:

0 = 发送数据的调制解调器产生丢失位信号。接收数据的调制解调器检测丢失位信号。1= 发送数据的调制解调器不产生丢失位信号。接收数据的调制解调器不检测丢失位信号。

注意:ATZ命令将复位<rb_signaling>值为0。
 

Maxup_rate : 连接速率的最大值。

Trackback: http://tb..csdn.net/TrackBack.aspx?PostId=1535176

01

[转]内存种类知多少


    终于知道为什么SDRAM都被CPU当做Internal RAM来用。
    凡是对电脑有所了解的朋友都知道内存这玩意,可是,可能有不少朋友对内存的认识仅仅局限在SDRAM和DDR SDRAM这两种类型,事实上,内存的种类是非常多的,从能否写入的角度来分,就可以分为RAM(随机存取存储器)和ROM(只读存储器)这两大类。每一类别里面有分别有许多种类的内存。以下就让我们看看内存到底有些什么种类吧!

 
  一、(Random Access Memory,随机存取存储器)
 

 
  RAM的特点是:电脑开机时,操作系统和应用程序的所有正在运行的数据和程序都会放置其中,并且随时可以对存放在里面的数据进行修改和存取。它的工作需要由持续的电力提供,一旦系统断电,存放在里面的所有数据和程序都会自动清空掉,并且再也无法恢复。
 
  根据组成元件的不同,RAM内存又分为以下十八种:
 
  01.DRAM(Dynamic RAM,动态随机存取存储器):
 

 
  这是最普通的RAM,一个电子管与一个电容器组成一个位存储单元,DRAM将每个内存位作为一个电荷保存在位存储单元中,用电容的充放电来做储存动作,但因电容本身有漏电问题,因此必须每几微秒就要刷新一次,否则数据会丢失。存取时间和放电时间一致,约为2~4ms。因为成本比较便宜,通常都用作计算机内的主存储器。
 
  02.SRAM(Static RAM,静态随机存取存储器)
 

 
  静态,指的是内存里面的数据可以长驻其中而不需要随时进行存取。每6颗电子管组成一个位存储单元,因为没有电容器,因此无须不断充电即可正常运作,因此它可以比一般的动态随机处理内存处理速度更快更稳定,往往用来做高速缓存。
 
  03.VRAM(Video RAM,视频内存)
 

 
  它的主要功能是将显卡的视频数据输出到数模转换器中,有效降低绘图显示芯片的工作负担。它采用双数据口设计,其中一个数据口是并行式的数据输出入口,另一个是串行式的数据输出口。多用于高级显卡中的高档内存。
 
  04.FPM DRAM(Fast Page Mode DRAM,快速页切换模式动态随机存取存储器)
 

 
  改良版的DRAM,大多数为72Pin或30Pin的模块。传统的DRAM在存取一个BIT的数据时,必须送出行地址和列地址各一次才能读写数据。而FRM DRAM在触发了行地址后,如果CPU需要的地址在同一行内,则可以连续输出列地址而不必再输出行地址了。由于一般的程序和数据在内存中排列的地址是连续的,这种情况下输出行地址后连续输出列地址就可以得到所需要的数据。FPM将记忆体内部隔成许多页数Pages,从512B到数KB不等,在读取一连续区域内的数据时,就可以通过快速页切换模式来直接读取各page内的资料,从而大大提高读取速度。在96年以前,在486时代和PENTIUM时代的初期,FPM DRAM被大量使用。
 
  05.EDO DRAM(Extended Data Out DRAM,延伸数据输出动态随机存取存储器)
 

 
  这是继FPM之后出现的一种存储器,一般为72Pin、168Pin的模块。它不需要像FPM DRAM那样在存取每一BIT 数据时必须输出行地址和列地址并使其稳定一段时间,然后才能读写有效的数据,而下一个BIT的地址必须等待这次读写操作完成才能输出。因此它可以大大缩短等待输出地址的时间,其存取速度一般比FPM模式快15%左右。它一般应用于中档以下的Pentium主板标准内存,后期的486系统开始支持EDO DRAM,到96年后期,EDO DRAM开始执行。。
 
  06.BEDO DRAM(Burst Extended Data Out DRAM,爆发式延伸数据输出动态随机存取存储器)
 
  这是改良型的EDO DRAM,是由美光公司提出的,它在芯片上增加了一个地址计数器来追踪下一个地址。它是突发式的读取方式,也就是当一个数据地址被送出后,剩下的三个数据每一个都只需要一个周期就能读取,因此一次可以存取多组数据,速度比EDO DRAM快。但支持BEDO DRAM内存的主板可谓少之又少,只有极少几款提供支持(如VIA APOLLO VP2),因此很快就被DRAM取代了。
 
  07.MDRAM(Multi-Bank DRAM,多插槽动态随机存取存储器)
 

 
  MoSys公司提出的一种内存规格,其内部分成数个类别不同的小储存库 (BANK),也即由数个属立的小单位矩阵所构成,每个储存库之间以高于外部的资料速度相互连接,一般应用于高速显示卡或加速卡中,也有少数主机板用于L2高速缓存中。
 
  08.WRAM(Window RAM,窗口随机存取存储器)
 

 
  韩国Samsung公司开发的内存模式,是VRAM内存的改良版,不同之处是它的控制线路有一、二十组的输入/输出控制器,并采用EDO的资料存取模式,因此速度相对较快,另外还提供了区块搬移功能(BitBlt),可应用于专业绘图工作上。
 
  09.RDRAM(Rambus DRAM,高频动态随机存取存储器)
 

 
  Rambus公司独立设计完成的一种内存模式,速度一般可以达到500~530MB/s,是DRAM的10倍以上。但使用该内存后内存控制器需要作相当大的改变,因此它们一般应用于专业的图形加速适配卡或者电视游戏机的视频内存中。
 
  10.(Synchronous DRAM,同步动态随机存取存储器)
 

 
  这是一种与CPU实现外频Clock同步的内存模式,一般都采用168Pin的内存模组,工作电压为3.3V。 所谓clock同步是指内存能够与CPU同步存取资料,这样可以取消等待周期,减少数据传输的延迟,因此可提升计算机的性能和效率。
 
  11.SGRAM(Synchronous Graphics RAM,同步绘图随机存取存储器)
 

 
  SDRAM的改良版,它以区块Block,即每32bit为基本存取单位,个别地取回或修改存取的资料,减少内存整体读写的次数,另外还针对绘图需要而增加了绘图控制器,并提供区块搬移功能(BitBlt),效率明显高于SDRAM。
 
  12.SB SRAM(Synchronous Burst SRAM,同步爆发式静态随机存取存储器)
 
  一般的SRAM是非同步的,为了适应CPU越来越快的速度,需要使它的工作时脉变得与系统同步,这就是SB SRAM产生的原因。
 
  13.PB SRAM(Pipeline Burst SRAM,管线爆发式静态随机存取存储器)
 
  CPU外频速度的迅猛提升对与其相搭配的内存提出了更高的要求,管线爆发式SRAM取代同步爆发式SRAM成为必然的选择,因为它可以有效地延长存取时脉,从而有效提高访问速度。
 
  14.DDR SDRAM(Double Data Rate二倍速率同步动态随机存取存储器)
 

 
  作为SDRAM的换代产品,它具有两大特点:其一,速度比SDRAM有一倍的提高;其二,采用了DLL(Delay Locked Loop:延时锁定回路)提供一个数据滤波信号。这是目前内存市场上的主流模式。
 
  15.SLDRAM (Synchronize Link,同步链环动态随机存取存储器)
 
 

 
 
 
  这是一种扩展型SDRAM结构内存,在增加了更先进同步电路的同时,还改进了逻辑控制电路,不过由于技术显示,投入实用的难度不小。
 
  16.CDRAM(CACHED DRAM,同步缓存动态随机存取存储器)
 
  这是三菱电气公司首先研制的专利技术,它是在DRAM芯片的外部插针和内部DRAM之间插入一个SRAM作为二级CACHE使用。当前,几乎所有的CPU都装有一级CACHE来提高效率,随着CPU时钟频率的成倍提高,CACHE不被选中对系统性能产生的影响将会越来越大,而CACHE DRAM所提供的二级CACHE正好用以补充CPU一级CACHE之不足,因此能极大地提高CPU效率。
 
  17.DDRII (Double Data Rate Synchronous DRAM,第二代同步双倍速率动态随机存取存储器)
 

 
  DDRII 是DDR原有的SLDRAM联盟于1999年解散后将既有的研发成果与DDR整合之后的未来新标准。DDRII的详细规格目前尚未确定。
 
  18.DRDRAM (Direct Rambus DRAM)
 

 
  是下一代的主流内存标准之一,由Rambus 公司所设计发展出来,是将所有的接脚都连结到一个共同的Bus,这样不但可以减少控制器的体积,已可以增加资料传送的效率。
 
  二、ROM(READ Only Memory,只读存储器)
 
  ROM是线路最简单半导体电路,通过掩模工艺,一次性制造,在元件正常工作的情况下,其中的代码与数据将永久保存,并且不能够进行修改。一般应用于PC系统的程序码、主机板上的 BIOS (基本输入/输出系统Basic Input/Output System)等。它的读取速度比RAM慢很多。
 
  根据组成元件的不同,ROM内存又分为以下五种:
 
  1.MASK ROM(掩模型只读存储器)
 
  制造商为了大量生产ROM内存,需要先制作一颗有原始数据的ROM或EPROM作为样本,然后再大量复制,这一样本就是MASK ROM,而烧录在MASK ROM中的资料永远无法做修改。它的成本比较低。
 
  2.PROM(Programmable ROM,可编程只读存储器)
 
  这是一种可以用刻录机将资料写入的ROM内存,但只能写入一次,所以也被称为“一次可编程只读存储器”(One Time Progarmming ROM,OTP-ROM)。PROM在出厂时,存储的内容全为1,用户可以根据需要将其中的某些单元写入数据0(部分的PROM在出厂时数据全为0,则用户可以将其中的部分单元写入1), 以实现对其“编程”的目的。
 
  3.EPROM(Erasable Programmable,可擦可编程只读存储器)
 

 
  这是一种具有可擦除功能,擦除后即可进行再编程的ROM内存,写入前必须先把里面的内容用紫外线照射它的IC卡上的透明视窗的方式来清除掉。这一类芯片比较容易识别,其封装中包含有“石英玻璃窗”,一个编程后的EPROM芯片的“石英玻璃窗”一般使用黑色不干胶纸盖住, 以防止遭到阳光直射。
 
  4.EEPROM(Electrically Erasable Programmable,电可擦可编程只读存储器)
 

 
  功能与使用方式与EPROM一样,不同之处是清除数据的方式,它是以约20V的电压来进行清除的。另外它还可以用电信号进行数据写入。这类ROM内存多应用于即插即用(PnP)接口中。
 
  5. Memory(快闪存储器)
 

 
  这是一种可以直接在主机板上修改内容而不需要将IC拔下的内存,当电源关掉后储存在里面的资料并不会流失掉,在写入资料时必须先将原本的资料清除掉,然后才能再写入新的资料,缺点为写入资料的速度太慢。

0

Shell编程 – 傻瓜教程2


 本文例子代码下载(19K)(codeproject.com)

    在这篇指南的第一部分,我给了大家一个编写外壳扩展的介绍,并且演示了一个一次操作一个简单文件的上下文菜单扩展。在第二部分中,我将要演示如何在一个操作中操作多个文件。这个扩展是一个注册和卸载COM服务器的实用工具。它还演示了如何使用ATL对话框类CDialogImpl。我将通过解释一些特殊的注册表键来讲第二部分,通过这些键你可以使你的扩展被任何文件调用,而不是那些预先选择的类型(.TXT)。

    第二部分假设你已经阅读了第一部分,所以你知道了上下文菜单扩展的基本知识。除此之外,你还必须理解COM,ATL和STL的基本知识。

上下文菜单扩展的开始-它能做什么?

    这个外壳扩展将使你注册和卸载那些在EXE,DLL和OCX里的COM服务器。不像我们在第一部分所做的那样,这个扩展将对所有你右键点击事件发生时所有选中的文件进行操作。

使用AppWizard 开始

运行AppWizard ,做一个新的ATL COM wizard app。我们叫它DllReg。在向导中保持所有默认选项,单击完成。我们现在就有了一个空的将会生成DLLATL项目,但是我们必须添加自己的外壳扩展COM对象。在ClassView树中,右键单击 DllReg classes项,选择New ATL Object

ATL Object 向导,第一面板已经选择了Simple Object ,只要单击下一步就行了。在第二面板中,在Short Name 编辑控件中输入DllRegShlExt ,然后单击确定(面板中的其它的编辑框将会自动完成)。这就创建了一个类名为CDllRegShlExt 的类,它包含了实现一个COM对象的基本代码。我们将向这个类添加我们的代码。

初始化接口

    在这个扩展中,我们的IShellExtInit::Initialize()实现将会相当不同。有两个原因,第一我们将列举所有选中的文件;第二,我们将测试我们选中的文件是否有注册()和卸载(unregister)函数的出口。我们将仅仅考虑那些同时有DllRegisterServer()DllUnregisterServer()出口的文件。其他的都将被忽略掉。

    我们将使用列表控件和STL的字符串与列表类,所以你必须首先在stdafx.h 文件中添加如下行:

#include <commctrl.h>#include <string>#include <list>#include <atlwin.h>typedef std::list<std::basic_string<TCHAR> > string_list;

    我们的CDllRegShlExt类也将需要一些成员变量:

protected:    HBITMAP     m_hRegBmp;    HBITMAP     m_hUnregBmp;    string_list m_lsFiles;    TCHAR       m_szDir[MAX_PATH];

    CDllRegShlExt的构造函数中将加载两幅上下文菜单中使用的位图:

CDLLRegShlExt::CDLLRegShlExt(){    m_hRegBmp = LoadBitmap ( _Module.GetModuleInstance(),                             MAKEINTRESOURCE(IDB_REGISTERBMP) );    m_hUnregBmp = LoadBitmap ( _Module.GetModuleInstance(),                               MAKEINTRESOURCE(IDB_UNREGISTERBMP) );}

    在你将IShellExtInit添加到了被CDllRegShlExt实现的接口列表中之后(关于这个操作请参见第一部分),我们开始写Initialize()函数。

  Initialize()将执行如下步骤:

  1. 改变当前目录为在资源浏览器窗口中正在被查看的目录;
  2. 列举所有被选中的文件;
  3. 对每一个文件,试图使用LoadLibrary()加载它;
  4. 如果LoadLibrary()成功,看该文件是否有DllRegisterServer()DllUnregisterServer()的函数出口;
  5. 如果两个出口都被找到了,添加该文件的的文件名到我们能操作的文件列表中,m_lsFiles
HRESULT CDllRegShlExt::Initialize (     LPCITEMIDLIST pidlFolder,    LPDATAOBJECT pDataObj,    HKEY hProgID ){TCHAR     szFile    [MAX_PATH];TCHAR     szFolder  [MAX_PATH];TCHAR     szCurrDir [MAX_PATH];TCHAR*    pszLastBackslash;UINT      uNumFiles;HDROP     hdrop;FORMATETC etc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };STGMEDIUM stg = { TYMED_HGLOBAL };HINSTANCE hinst;bool      bChangedDir = false;HRESULT (STDAPICALLTYPE* pfn)();

    非常多的局部变量!第一步是从传进去的pDataObj参数中获得一个HDROP。这和第一部分的扩展有些相似。

    // Read the list of folders from the data object.  They're stored in HDROP    // format, so just get the HDROP handle and then use the drag 'n' drop APIs    // on it.    if ( FAILED( pDO->GetData ( &etc, &stg )))        return E_INVALIDARG;    // Get an HDROP handle.    hdrop = (HDROP) GlobalLock ( stg.hGlobal );    if ( NULL == hdrop )        {        ReleaseStgMedium ( &stg );        return E_INVALIDARG;        }    // Determine how many files are involved in this operation.    uNumFiles = DragQueryFile ( hdrop, 0xFFFFFFFF, NULL, 0 );

    接下来要做的就是一个获取下一个文件名的for循环(使用DragQueryFile())并试图用LoadLibrary()装载它。在该文的实际例子工程当中预先做了一些目录改变的工作。在这儿我把它忽略了,因为它有点儿太长了。

    for ( UINT uFile = 0; uFile < uNumFiles; uFile++ )        {        // Get the next filename.        if ( 0 == DragQueryFile ( hdrop, uFile, szFile, MAX_PATH ))            continue;        // Try & load the DLL.        hinst = LoadLibrary ( szFile );                if ( NULL == hinst )            continue;

    下一步,我们将看看它是否有两个必要函数的出口。

        // Get the address of DllRegisterServer();        (FARPROC&) pfn = GetProcAddress ( hinst, "DllRegisterServer" );        // If it wasn't found, skip the file.        if ( NULL == pfn )            {            FreeLibrary ( hinst );            continue;            }        // Get the address of DllUnregisterServer();        (FARPROC&) pfn = GetProcAddress ( hinst, "DllUnregisterServer" );        // If it was found, we can operate on the file, so add it to        // our list of files (m_lsFiles).        if ( NULL != pfn )            {            m_lsFiles.push_back ( szFile );            }        FreeLibrary ( hinst );        }   // end for

    最后一步就是(在最后一个if块中)添加文件名到m_lsFiles中。m_lsFiles是一个保存字符串的STL列表集。这个列表在稍后,就是当我们遍历所有文件注册和卸载的时候将被用到。

    在Initialize()的最后要做的就是释放资源并返回恰当的值给Explorer。

    // Release resources.    GlobalUnlock ( stg.hGlobal );    ReleaseStgMedium ( &stg );    // If we found any files we can work with, return S_OK.  Otherwise,    // return E_INVALIDARG so we don't get called again for this right-click    // operation.    return ( m_lsFiles.size() > 0 ) ? S_OK : E_INVALIDARG;}

    如果你看一眼例子项目代码的话,你会发现我不得不都过查看文件的文件名来计算出哪个目录正在被查看。你也许会纳闷为什么我不用pidlFolder参数。虽然在pidlFolder的文档中,它被解释为“包含正在显示上下文菜单的项目的文件夹的项目标志列表(the item identifier list for the folder that contains the item whose context menu is being displayed)(很抱歉我不能把这句话翻译好,贴出原文供参考)”。可是,我在Windows 98 上测试的时候,这个参数总是为NULL,所以它毫无用处。

添加我们的菜单项

    接下来的是IContextMenu方法。和以前一样,你需要添加IContextMenuCDllRegShlExt实现的接口列表中。再一次的,做这些的步骤在第一部分中。

    我们将添加两个菜单项到菜单中,一个为注册选中的文件,一个是卸载它们。这些项看起来如下:

    我们的QueryContextMenu()实现和第一部分一样开始。我们检查uFlags,如果CMF_DEFAULTONLY标志存在的话立即返回。 

HRESULT CDLLRegShlExt::QueryContextMenu (    HMENU hmenu,    UINT  uMenuIndex,    UINT  uidFirstCmd,    UINT  uidLastCmd,    UINT  uFlags ){UINT uCmdID = uidFirstCmd;    // If the flags include CMF_DEFAULTONLY then we shouldn't do anything.    if ( uFlags & CMF_DEFAULTONLY )        {        return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 0 );        }

    下面,我们添加"Register servers"菜单项。这儿有些新东西:我们给菜单项设置了位图。这和WinZip的菜单有点儿相似,也在菜单命令旁边显示一个小图标。

    // Add our register/unregister items.    InsertMenu ( hmenu, uMenuIndex, MF_STRING | MF_BYPOSITION, uCmdID++,                 _T("Register (s)") );    // Set the bitmap for the register item.    if ( NULL != m_hRegBmp )        {        SetMenuItemBitmaps ( hmenu, uMenuIndex, MF_BYPOSITION, m_hRegBmp, NULL );        }    uMenuIndex++;

    SetMenuItemBitmaps()API函数我们宰菜单旁显示一个小齿轮的方法。注意uCmdID是自增加的,这样下一次我们调用InsertMenu(),它的命令ID将会比前一个大1。在这步骤的最后,uMenuIndex 被增加是因为我们的第二个菜单项将被显示在第一个之后。

    接着说第二个菜单项。它和第一个非常相似。

    InsertMenu ( hmenu, uMenuIndex, MF_STRING | MF_BYPOSITION, uCmdID++,                 _T("Unregister server(s)") );    // Set the bitmap for the unregister item.    if ( NULL != m_hUnregBmp )        {        SetMenuItemBitmaps ( hmenu, uMenuIndex, MF_BYPOSITION, m_hUnregBmp, NULL );        }

    最后,我们告诉Explorer我们添加了几个菜单项。

    return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 2 );

提供敏感帮助和动词

    和前面的一样,当Explorer需要显示敏感帮助或者获得动词的时候,GetCommandString()方法被调用。和前一个稍稍不同的是我们添加了2个菜单项,所以我们必须首先检查uCmdID参数来知道Explorer正在呼叫哪个菜单项。

#include <atlconv.h>HRESULT CDLLRegShlExt::GetCommandString (     UINT  uCmdID,    UINT  uFlags,     UINT* puReserved,    LPSTR szName,    UINT  cchMax ){LPCTSTR szPrompt;    USES_CONVERSION;    if ( uFlags & GCS_HELPTEXT )        {        switch ( uCmdID )            {            case 0:                szPrompt = _T("Register all selected COM servers");            break;            case 1:                szPrompt = _T("Unregister all selected COM servers");            break;            default:                return E_INVALIDARG;            break;            }

    如果uCmdID为0,那么我们正在调用第一个(register)。如果为1,则在调用第二个(unregister)。在确定帮助字符串之后,我们将它拷贝到提供的缓存当中,必要的话,先将它转换成Unicode形式。

        // Copy the help text into the supplied buffer.  If the  wants        // a Unicode string, we need to case szName to an LPCWSTR.        if ( uFlags & GCS_UNICODE )            {            lstrcpynW ( (LPWSTR) szName, T2CW(szPrompt), cchMax );            }        else            {            lstrcpynA ( szName, T2CA(szPrompt), cchMax );            }        }

    在这个扩展中,我同样也写了代码提供一个动词。然而,我在Windows 98下测试的时候,Explorer从来不调用GetCommandString()来获取一个动词。我甚至写了一个在DLL中调用ShellExecute()的测试程序然后试图使用动词,但是它还是不工作。我不知道这个情况在Windows NT下是否会有不同。这儿,我忽略了那些动词相关的代码,如果你感兴趣的话,你可以在例子项目中找到。

执行用户的选择

    当用户点击我们菜单中的一个时,Explorer调用我们的InvokeCommand()方法。InvokeCommand()首先检查了lpVerb的高位字(high word)。如果它是非零的,则它是被调用的动词的名称,因为我们知道动词不能正常工作(至少在Win 98下),我们将它忽略了。否则,如果它的低位字(low word)是0或1,那么我们知道其中有一个菜单项目被点击了。

HRESULT CDllRegShlExt::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo ){    // If lpVerb really points to a string, ignore this function call and bail out.    if ( 0 != HIWORD( pInfo->lpVerb ))        return E_INVALIDARG;    // Check that lpVerb is one of our commands (0 or 1)    switch ( LOWORD( pInfo->lpVerb ))        {        case 0:        case 1:            {            CProgressDlg dlg ( &m_lsFiles, pInfo );            dlg.DoModal();            return S_OK;            }        break;        default:            return E_INVALIDARG;        break;        }}

    如果lpVerb是0或1,我们创建一个对话框(从ATL类CDialogImpl继承),然后向它传递文件名列表。

    所有实际的工作都在CProgressDlg类中完成。它的OnInitDialog()函数初始化列表控件,然后调用CProgressDlg::DoWork()DoWork()遍历在CDllRegShlExt::Initialize()中生成的文件名列表,然后调用文件中的合适的函数。基本代码如下;并不完全,因为我删掉了错误检查和填充列表控件的部分。不过这已经足够向大家演示如何遍历一个文件列表并执行每一个。

void CProgressDlg::DoWork(){HRESULT (STDAPICALLTYPE* pfn)();string_list::const_iterator it, itEnd;HINSTANCE hinst;LPCSTR    pszFnName;HRESULT   hr;WORD      wCmd;    wCmd = LOWORD ( m_pCmdInfo->lpVerb );    // We only support 2 commands, so check the value passed in lpVerb.    if ( wCmd > 1 )        return;    // Determine which function we'll be calling.  Note that these strings are    // not enclosed in the _T macro, since GetProcAddress() only takes an    // ANSI string for the function name.    pszFnName = wCmd ? "DllUnregisterServer" : "DllRegisterServer";    for ( it = m_pFileList->begin(), itEnd = m_pFileList->end();          it != itEnd;          it++ )        {        // Try to load the next file.        hinst = LoadLibrary ( it->c_str() );        if ( NULL == hinst )            continue;        // Get the address of the register/unregister function.        (FARPROC&) pfn = GetProcAddress ( hinst, pszFnName );        // If it wasn't found, go on to the next file.        if ( NULL == pfn )            continue;        // Call the function!        hr = pfn();

    我需要解释一下那个for循环,因为STL集合类是有点令人胆战心惊的,如果你不习惯使用它的话。m_pFileList是一个指向在CDllRegShlExt类中的m_lsFiles列表的指针。(这个指针被传递给了CProgressDlg的构造器。)STL列表集有一个类型叫做const_iterator,这是一个相似于MFC中POSITION类型的抽象实体。一个const_iterator变量像一个列表中常量对象的指针,所以这个遍历器(iterator)可以使用->访问它本身。遍历器可以使用++ 来自增实现在列表中前进。

    所以,这个for循环的初始化表达式调用list::begin()来获取一个遍历器“指向”列表中的第一个字符串,然后调用list::end()来获取一个遍历器“指向”列表的“末尾”,最后一个字符串的位置。 (我把这些词语放在引号里是为了强调指向,开始,和末尾的概念都是被const_iterator类型抽象了的,而且必须通过const_iterator的方法[像begin()]或者操作符[像++]。)这些遍历器被分别地赋给ititEnd。 这个循环一直进行着,直到it等于itEnd;意思是,当it还没有到达列表的“末尾”时。这个遍历器it将会在循环过程每次自增加,这样它可以每次到达一个列表中的字符串。

    在遍历其中,表达式it->c_str() 使用->操作符。因为it像一个指向string的指针(记住,m_pFileList是一个STL string的列表),it->c_str()it当前所指向的string中调用c_str()函数。c_str()返回一个C类型的字符串指针,既然这样,就是一个LPCTSTR

    DoWork()的余下部分是释放内存和错误检查。你可以从例子项目的ProgressDlg.cpp文件中获取所有代码。

    (我刚刚意识到叫一个变量为"it",是多么的奇怪。很抱歉!) :)

注册外壳扩展

    这个DllReg扩展对可执行文件进行操作,所以我们注册它被EXE,DLL和OCX文件调用。和第一部分中一样,我们可以在RGS脚本中做这些事,DllRegShlExt.rgs。下面是注册我们的扩展为一个对于上述扩展名文件的上下文菜单扩展必需的脚本。

HKCR{    NoRemove dllfile    {        NoRemove shellex        {            NoRemove ContextMenuHandlers            {                ForceRemove DLLRegSvr = s '{8AB81E72-CB2F-11D3-8D3B-AC2F34F1FA3C}'            }        }    }    NoRemove exefile    {        NoRemove shellex        {            NoRemove ContextMenuHandlers            {                ForceRemove DLLRegSvr = s '{8AB81E72-CB2F-11D3-8D3B-AC2F34F1FA3C}'            }        }    }    NoRemove ocxfile    {        NoRemove shellex        {            NoRemove ContextMenuHandlers            {                ForceRemove DLLRegSvr = s '{8AB81E72-CB2F-11D3-8D3B-AC2F34F1FA3C}'            }        }    }}

    RGS文件的格式,以及关键词NoRemoveForceRemove第一部分已经解释过了,如果你忘了他们的意思的话。 

    正如我们前一个扩展一样,在NT/2000下,我们需要添加我们的扩展到“认证的(approved)”扩展列表当中。实现这个过程的代码在 DllRegisterServer()DllUnregisterServer()函数中。我不想展示这些代码,因为它仅仅是一些简单的注册表访问,但是你可以从例子项目中中到这些代码。

看起来应该什么样呢?

    当你点击我们其中的一个菜单的时候,对话框就会出现,显示操作的结果:

    列表控件显示了每个文件的文件名和操作是否成功。当你选中一个项时,一个更详细的消息将被显示在下方。如果失败的话,将有调用失败的描述。

注册扩展的其他方法

    到现在为止,我们的扩展只被确定的文件类型调用。通过在HKCR\*下注册成为一个上下文菜单扩展使得被任何文件操作调用成为可能:

HKCR{    NoRemove *    {        NoRemove shellex        {            NoRemove ContextMenuHandlers            {                ForceRemove DLLRegSvr = s '{8AB81E72-CB2F-11D3-8D3B-AC2F34F1FA3C}'            }        }    }}

    HKCR\*键被所有文件调用的外壳扩展。注意文档中所说的该扩展也被其他外壳对象所调用(文件,目录,虚拟文件夹,控制面板项等等),但是我在测试中所见到的并不是这样。这些扩展仅仅被文件系统中的文件所调用。

    在外壳版本4.71以上,还有一个叫做HKCR\AllFileSystemObjects的键。如果我们在这个键下注册,我们的扩展将被文件系统中所有的文件和目录调用,根目录除外。(根目录调用的扩展在HKCR\Drive下注册。)然而,当我在这个键下注册的时候我看到一些奇怪的现象。“发送到”菜单也使用这个键,而它混在了DllReg菜单中间:

    你同样可以编写一个操作目录的上下文菜单扩展。要这样的例子的话,请看我的文章:
   A Utility to Clean Up Compiler Temp Files(一个清除编译临时文件的实用工具)。(译者:确实是个好玩艺 :))

    最后,在外壳版本4.71以上,你可以使你的上下文菜单在用户右键单击一个正在查看目录(包括桌面)的资源管理器窗口背景时被调用。为了使你的扩展被这么调用,你必须在HKCR\Directory\Background\shellex\ContextMenuHandlers键下注册。使用这个方法,你可以向你的桌面上下文菜单中添加菜单项目或者其他任何目录。传递给IShellExtInit::Initialize()的参数有点儿不同,我会在我以后的文章中加以说明。

待续……

    接着的第三部分我们将实践一种新的扩展,查询。它将显示一些外壳对象的弹出式描述。我还将向你展示如何在外壳扩展中使用MFC。

    你可以从下面的网址获得这个和其他文章的最新版本:http://home.inreach.com/mdunn/code/

0

Shell编程 – 傻瓜教程1


    外壳扩展( Extention)是一个能向外壳(资源管理器)添加一些功能的COM对象。这有很多的内容,但是却很少有关于它们的易懂的文档告诉我们如何去编写这些外壳(Shell)程序。如果你想做对外壳很深入的了解,我极力向你推荐Dino Esposito 的非常好的一本书《Visual C++ Windows Shell Programming》。但是对于那些没有这本书并且仅仅关心如何去编写外壳扩展的人,我写的一指南将会令你非常惊讶,如果并非如此的话也能给你理解如何编写外壳扩展提供很好的帮助。要阅读这一指南,确保你对COMATL要相当熟悉。

第一部分包括了对外壳扩展的概要的介绍,并提供了一个上下文菜单扩展的例程来使你对以后的部分中充满兴趣。

什么是外壳扩展呢?

这有两部分,外壳和扩展(extension)。外壳指的是资源管理器(Explorer),而扩展是指当一个预订的事件(如:右键单击一个.doc文档)发生时,被资源管理器调用的你写的代码。所以以个外壳扩展是一个向资源管理器添加特色的COM对象。

一个外壳扩展是一个进程中服务器,它实现了一些与资源管理器通信的借口。而在我看来,ATL是快速实现一个扩展并使它运行的最简单的方法,因为你不用为一遍又一遍的写QueryInterface()AddRef()而大伤脑筋。而且在Windows NT/2000下调试扩展也变得更为容易。

有很多种的扩展,每种扩展在不同的事件发生时被调用。下面是一些比较通用的类型和它们被调用的情况:

类型

什么时候被调用

可以做什么

上下文菜单

用户在文件或目录右键单击时。在外壳扩展4.71版本以上,在目录窗口的背景上右键单击也将被调用。

向上下文菜单添加项目。

属性单

文件的属性单被显示时。

向属性单添加一个属性页。

拖扔

用户右键拖动项目并把它扔在一个目录窗口活着桌面上时。

添加项目至上下文菜单。

用户拖一个项目并把它扔到一个文件上时。

任何你想做的事。

查询信息(外壳版本4.71+)

用户鼠标在一个文件或像我的电脑一样的其他外壳对象上悬停时。

返回一个资源管理器在工具条提示上的字符串。

到现在为止你可能为什么一个扩展看起来想在资源管理器里。如果你安装了WinZip(有谁没有吗?),它就包括了许多种的外壳扩展,其中一个就是上下文句柄。下面世WinZip 8 为了压缩文件添加到上下文菜单的截图:

WinZip包含了添加菜单项目的代码,并提供敏感帮助(显示在资源管理器状态条的文本),并在用户选择WinZip命令之一时起作用。

WinZip还包含了拖和扔的句柄。这个类型和上下文菜单扩展非常类似,但是它仅仅在用户通过鼠标右键拖动一个文件时才被调用。下面是WinZip的拖扔句柄如何添加上下文菜单:

还有很多种其他类型(微软一直往新版本的Windows里添加更多内容)。到现在,我们已经看到了上下文菜单扩展,因为它非常容易编写,我们将很容易的看到它的结果(很快就能满意)。

在我们开始编码以前,有一些提示,它将使我们做起来更加容易。当你促成一个外壳扩展被资源管理器调用的时候,它将在内存中呆上一小会儿,从而使它不能立即被重建(rebuild)。为了使资源管理器更加频繁的卸载这些扩展,创建这个注册表键:

HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\AlwaysUnloadDLL

并把它的默认值设为”1”。在Windows 9x系列中,这是最好的方法。在NT/2000,到如下的键:

HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer

创建一个叫做DesktopProcess的双字节值,使它的值为1。这使得桌面和任务栏运行在一个进程中,后发的资源管理器运行在它自己的进程里。这就意味着你可以使用一个单独的资源管理器窗口来调试,并且当你关掉它的时候,你的DLL也会自动的被卸载,避免了该文件仍然在使用得问题。要使得你的注册表修改生效的话,你必须注销并且重新登录。

我将稍晚一些解释如何在Win 9x下进行调试。

开始一个上下文菜单扩展它能做什么?

让我们开始简单的做一个扩展,它仅仅弹出一个消息框表示它已经在工作了。我们将对扩展名为.txt的文件设置一个钩子,这样当用户右键单击一个文本文件的时候,我们的扩展就能被调用了。

使用AppWizard 开始

好了,现在是我们开始的时候了。那是什么?我还没有告诉你如何使用神秘的外壳扩展接口?不要担心,我将在接下来的过程中为你解释。我发现如果一个概念被解释,有一个例子更容易明白,通过例子代码你很快就能理解。我将会先解释任何东西,然后给出代码,但是我发现还是不容易吸收。总之,启动你的MSVC吧,我们要开始了。

运行AppWizard ,做一个新的ATL COM wizard app。我们叫它SimpleExt 。在向导中保持所有默认选项,单击完成。我们现在就有了一个空的将会生成DLLATL项目,但是我们必须添加自己的外壳扩展COM对象。在ClassView树中,右键单击SimpleExt classes 项,选择New ATL Object

ATL Object 向导,第一面板已经选择了Simple Object ,只要单击下一步就行了。在第二面板中,在Short Name 编辑控件中输入SimpleShlExt ,然后单击确定(面板中的其它的编辑框将会自动完成)。这就创建了一个类名为CSimpleShlExt 的类,它包含了实现一个COM对象的基本代码。我们将向这个类添加我们的代码。

初始化接口

当我们的外壳扩展被装载的时候,资源管理器调用我们的QueryInterface()函数获取一个指向IShellExtInit 接口的指针。这个接口只有一个方法,Initialize(),它的原型如下:

HRESULT IShellExtInit::Initialize (
    LPCITEMIDLIST pidlFolder,
    LPDATAOBJECT pDataObj,
    HKEY hProgID );

资源管理器使用这个方法给我们不同的信息。pidlFolder 是包含有正在被作用的文件的文件夹的PIDL(PIDL[pointer to an ID list]是唯一标志外壳中任一对象(无论是否是文件系统对象)的数据结构。pDataObj 是一个IDataObject 接口指针,通过它我们可以获得被作用的文件的文件名。hProgID 是一个打开的HKEY ,通过它我们可以访问包含有我们的DLL注册数据的注册表键。在这个简单的扩展中,我们只需要用到pDataObj参数。

添加这个接口方法到我们的COM 对象中,先打开SimpleShlExt.h 文件,并添加如下用红色书写的代码行:

#include <shlobj.h>
#include <comdef.h>
 
 ATL_NO_VTABLE CSimpleShlExt : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CSimpleShlExt, &CLSID_SimpleShlExt>,
    public IDispatchImpl<ISimpleShlExt, &IID_ISimpleShlExt, &LIBID_SIMPLEEXTLib>,
    public IShellExtInit
{
BEGIN_COM_MAP(CSimpleShlExt)
    COM_INTERFACE_ENTRY(ISimpleShlExt)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(IShellExtInit)
END_COM_MAP()

这个COM_MAP是说明了ATL如何实现它的QueryInterface()。这个列表告诉ATL其他使用QueryInterface()的程序可以从我们这儿获得什么。

接着,在类的声明当中,添加Initialize()函数。此外,我们还需要一个保存文件名的变量:

protected:
    TCHAR m_szFile [MAX_PATH];
 
public:
    // IShellExtInit
    STDMETHOD(Initialize)(LPCITEMIDLIST, LPDATAOBJECT, HKEY);

接下来,在SimpleShlExt.cpp 文件中,添加该函数的定义:

HRESULT CSimpleShlExt::Initialize ( 
    LPCITEMIDLIST pidlFolder,
    LPDATAOBJECT pDataObj,
    HKEY hProgID )

我们所要做的就是获得被右键单击的文件的文件名,并把它显示在一个消息框中。如果有很多个文件被选中,你可以通过pDataObj 接口指针访问它们。但是为了保持该例子的简单性,我只要获得第一个文件的文件名。

文件名被保存为与你使用WS_EX_ACCEPTFILES样式拖和扔一个文件到窗口时用到的一个相同的格式。那就意味着,我们获得文件名使用了相同的API: DragQueryFile()。我们通过获得包含在IDataObject 中的数据的句柄开始这个函数:

{
FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stg = { TYMED_HGLOBAL };
HDROP     hDrop;
 
    // Look for CF_HDROP data in the data object.
    if ( FAILED( pDataObj->GetData ( &fmt, &stg )))
        {
        // Nope! Return an "invalid argument" error back to Explorer.
        return E_INVALIDARG;
        }
 
    // Get a pointer to the actual data.
    hDrop = (HDROP) GlobalLock ( stg.hGlobal );
 
    // Make sure it worked.
    if ( NULL == hDrop )
        {
        return E_INVALIDARG;
        }

要注意,错误检查是极其重要的,尤其是指针。因为我们的扩展运行在资源管理器的进程空间当中,如果我们的程序毁坏的话,同样会让资源管理器也毁坏的。在Win 9x下,这可能就意味着重新启动。

现在,我们有了一个HDROP 句柄,我们可以获得我们需要的文件名了。

    // Sanity check – make sure there is at least one filename.
UINT uNumFiles = DragQueryFile ( hDrop, 0xFFFFFFFF, NULL, 0 );
 
    if ( 0 == uNumFiles )
        {
        GlobalUnlock ( stg.hGlobal );
        ReleaseStgMedium ( &stg );
        return E_INVALIDARG;
        }
 
HRESULT hr = S_OK;
 
    // Get the name of the first file and store it in our member variable m_szFile.
    if ( 0 == DragQueryFile ( hDrop, 0, m_szFile, MAX_PATH ))
        {
        hr = E_INVALIDARG;
        }
 
    GlobalUnlock ( stg.hGlobal );
    ReleaseStgMedium ( &stg );
 
    return hr;
}

如果我们返回E_INVALIDAR,在右键单击事件发生时,资源管理器将不会再调用我们的扩展。如果我们返回S_OK ,那么资源管理器将会再次调用QueryInterface()来获得我们将要添加的另一个接口指针:IContextMenu

和上下文菜单交互的接口

一旦资源管理器初始化了我们的扩展,它将会调用IContextMenu 的方法让我们添加菜单项目、敏感帮助并完成用户的选择。

向我们的扩展中添加IContextMenu 接口和添加IShellExtInit相类似。打开SimpleShlExt.h并添加一下红颜色的代码:

class ATL_NO_VTABLE CSimpleShlExt : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CSimpleShlExt, &CLSID_SimpleShlExt>,
    public IDispatchImpl<ISimpleShlExt, &IID_ISimpleShlExt, &LIBID_SIMPLEEXTLib>,
    public IShellExtInit,
    public IContextMenu
{
BEGIN_COM_MAP(CSimpleShlExt)
    COM_INTERFACE_ENTRY(ISimpleShlExt)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(IShellExtInit)
    COM_INTERFACE_ENTRY(IContextMenu)
END_COM_MAP()

接着添加IContextMenu 方法的原型:

public:
    // IContextMenu
    STDMETHOD(GetCommandString)(UINT, UINT, UINT*, LPSTR, UINT);
    STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO);
    STDMETHOD(QueryContextMenu)(HMENU, UINT, UINT, UINT, UINT);

更改上下文菜单

IContextMenu 3个方法。第一个,QueryContextMenu(),让我们更改菜单。它的原型为:

HRESULT IContextMenu::QueryContextMenu (
    HMENU hmenu,
    UINT  uMenuIndex, 
    UINT  uidFirstCmd,
    UINT  uidLastCmd,
    UINT  uFlags );

hmenu 是上下文菜单的句柄。uMenuIndex 是我们开始添加我们的菜单项目的开始位置。 uidFirstCmd uidLastCmd 是我们可以给菜单项目使用的命令ID值的范围。uFlags 指出为什么资源管理器正在调用QueryContextMenu(),这我们将在以后看到。

有关它的返回值你将会得到不同的答案,如果你问不同的人的话。Dino Esposito 的书上说它使被QueryContextMenu()添加的菜单项目的号码。MSDN上关于VC 6部分说它是最后一个被添加的菜单项目的命令ID加上1。最新的MSDN文档有如下说明:

设置代码的值[HRESULT返回的]为被分配的最大的命令ID偏移加上1。例如,假定idCmdFirst被设置为5,你添加了3个菜单项目分别使用命令ID578。它的返回值将是MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 – 5 + 1)

到我现在所写的所有代码中,我接受了Dino的解释,这样工作的很好。事实上,他的制作返回值的方法和在线MSDN的方法是相同的,在你使用uidFirstCmd开始添加你的菜单项目时开始计数,每添加一个增加1

我们的简单的扩展将仅仅添加一个菜单项目,所以QueryContextMenu()函数相当简单:

HRESULT CSimpleShlExt::QueryContextMenu (
    HMENU hmenu,
    UINT  uMenuIndex, 
    UINT  uidFirstCmd,
    UINT  uidLastCmd,
    UINT  uFlags )
{
    // If the flags include CMF_DEFAULTONLY then we shouldn't do anything.
    if ( uFlags & CMF_DEFAULTONLY )
        {
        return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 0 );
        }
 
    InsertMenu ( hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd, _T("SimpleShlExt Test Item") );
 
    return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 1 );
}

我们首先要做的就是检查uFlags。你可以在MSDN查询所有的标志列表,但是对于上下文菜单扩展来说,只有一样是重要的:CMF_DEFAULTONLY。这个标志告诉名字空间扩展仅仅添加默认菜单项目。如果这个标志在的话,外壳扩展将不会添加任何菜单项目。这就是为什么当CMF_DEFAULTONLY存在的时候我们立即返回0的原因。如果该标志不存在,我们更改菜单(使用hmenu 句柄),然后返回1告诉外壳我们添加了一个菜单项目。

在状态条显示敏感帮助

IContextMenu 中下一个可以调用的方法是GetCommandString()。如果用户在资源管理器窗口中右键单击了一个文本文件的时候,或者选中一个文本文件,然后单击“文件”菜单,状态条上将显示敏感帮助。我们的GetCommandString() 函数将会返回一个让资源管理器显示得字符串。

GetCommandString()函数原型如下:

HRESULT IContextMenu::GetCommandString (
    UINT idCmd,
    UINT uFlags,
    UINT *pwReserved,
    LPSTR pszName,
    UINT cchMax );

idCmd 是一个基于0的指明哪个菜单项目被选中的数。因为我们仅仅添加了一个菜单项,idCmd 将总是为0。但是如果我们添加了,我是说,3个的话,idCmd 将会是0,1或者2uFlags是另一个标志组。我将会在后面进行描述。我们可以忽略pwReservedpszName 是一个指向一个被外壳所拥有的缓存的指针,该缓存保存被显示的帮助字符串。cchMax 是缓存的大小。返回值是HRESULT常量,例如S_OKE_FAIL

GetCommandString()同样能被用来获得菜单项的“动词”。“动词”是一个标志作用于文件的动作的字符串,它是独立于语言的。有关ShellExecute()的文档作了更多的说明,有关“动词”的主题更适合在另一篇文章说明,这儿简要说明的是列在注册表中的动词(比如说”open””print”),或者那些有上下文菜单扩展动态创建的“动词”。这使得在外壳扩展中实现的行为可以被ShellExecute()调用。

总之,我提及所有这些的原因是我们不得不确定为什么GetCommandString()被调用。如果资源管理器需要一个敏感帮助字符串的时候,我们就提供。如果资源管理器请求一个“动词”的话,我们将忽略它。这是uFlags起作用的地方。如果uFlags GCS_HELPTEXT 的位被设置的话,那么资源管理器将请求敏感帮助。附加的,如果GCS_UNICODE位被设置,我们必须返回一个Unicode字符串。

我们的GetCommandString() 的代码看起来应该像下面这样:

#include <atlconv.h>  // for ATL string conversion macros
 
HRESULT CSimpleShlExt::GetCommandString (
    UINT  idCmd,
    UINT  uFlags,
    UINT* pwReserved,
    LPSTR pszName,
    UINT  cchMax )
{
    USES_CONVERSION;
 
    // Check idCmd, it must be 0 since we have only one menu item.
    if ( 0 != idCmd )
        return E_INVALIDARG;
 
    // If Explorer is asking for a help string, copy our string into the
    // supplied buffer.
    if ( uFlags & GCS_HELPTEXT )
        {
        LPCTSTR szText = _T("This is the simple shell extension's help");
 
        if ( uFlags & GCS_UNICODE )
            {
            // We need to cast pszName to a Unicode string, and then use the
            // Unicode string copy API.
            lstrcpynW ( (LPWSTR) pszName, T2CW(szText), cchMax );
            }
        else
            {
            // Use the ANSI string copy API to return the help string.
            lstrcpynA ( pszName, T2CA(szText), cchMax );
            }
 
        return S_OK;
        }
 
    return E_INVALIDARG;
}

没什么奇特的;我只是把字符串编码并且把它转换为合适的字符集。如果你以前从来都没有使用过ATL变换宏,你干脆先看看它们,因为这将我使更容易理解传递一个Unicode字符串到COM方法和OLE函数中。在上面的代码中,我使用了T2CWT2CA 分别将TCHAR字符串转换为UnicodeANSI。在函数头部的USES_CONVERSION 宏声明了一个变换宏使用的局部变量。

一个需要注意的重要事项是lstrcpyn()API函数保证了目标字符串是以null结束的。这是它和CRT函数strncpy()的不同之处。如果源字符串的长度大于或等于cchMax时, strncpy() 并不添加结束符null。我建议你总是使用lstrcpyn(),这样你就不用不得不在strncpy()之后添加检查来保证字符串是null结束的。

执行用户的选择

最后一个IContextMenu方法是InvokeCommand()。这个方法将在用户单击我们添加的那个菜单项目时被调用。它的原型如下:

HRESULT IContextMenu::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo );

CMINVOKECOMMANDINFO 结构里有很多的信息,但是根据我们现在的意图,我们只需要关心lpVerbhwnd lpVerb 有双重的任务——它既可以是被调用的“动词”的名称,也可以是一个用以告诉我们哪个菜单项被选中地索引。hwnd 是资源管理器窗口的句柄,在那儿,用户调用了我们的扩展。

我们检查lpVerb ,因为我们只添加了一个菜单项,所以如果它为0,则我们的菜单被点击了。我能想到的最简单的事就是弹出一个消息框,所以我们就这么做。这个消息框显示了选中的文件的文件名,证明它的确是在工作。

HRESULT CSimpleShlExt::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo )
{
    // If lpVerb really points to a string, ignore this function call and bail out.
    if ( 0 != HIWORD( pCmdInfo->lpVerb ))
        return E_INVALIDARG;
 
    // Get the command index - the only valid one is 0.
    switch ( LOWORD( pCmdInfo->lpVerb ))
        {
        case 0:
            {
            TCHAR szMsg [MAX_PATH + 32];
 
            wsprintf ( szMsg, _T("The selected file was:\n\n%s"), m_szFile );
 
            MessageBox ( pCmdInfo->hwnd, szMsg, _T("SimpleShlExt"),
                         MB_ICONINFORMATION );
 
            return S_OK;
            }
        break;
 
        default:
            return E_INVALIDARG;
        break;
        }
}

注册外壳扩展

到现在为止,我们已经实现了我们所有的COM接口。但是……如何使资源管理器使用我们的扩展呢?ATL自动生成了注册我们的DLL为一个COM服务器的代码,但是它仅仅是让其它程序来使用我们的DLL。为了告诉资源管理器我们的扩展存在,我们必须在保持文本文件的注册表键下注册它:

HKEY_CLASSES_ROOT\txtfile

在那个键下面,一个叫做ShellEx 的键保存了一个对于文本文件将被调用的外壳扩展列表。在ShellEx下,ContextMenuHandlers 键保存了一个上下文菜单扩展的列表。每一个扩展在ContextMenuHandlers下创建一个字键,并把他的默认值设置为它的GUID。所以,为我们的扩展,我们创建如下键:

HKEY_CLASSES_ROOT\txtfile\ShellEx\ContextMenuHandlers\SimpleShlExt

并把它的默认值设置为我们的GUID

"{5E2121EE-0300-11D4-8D3B-444553540000}"

然而,你不用自己做这件事。如果你在FileView页查看你得文件列表时,你会发现SimpleShlExt.rgs。这是一个由ATL解析的文本文件,它告诉ATL当这个服务器被注册时该添加什么键,当被反注册时又该删除什么键。下面我们指定了要添加的注册表入口:

HKCR
{
    NoRemove txtfile
    {
        NoRemove ShellEx
        {
            NoRemove ContextMenuHandlers
            {
                ForceRemove SimpleShlExt = s '{5E2121EE-0300-11D4-8D3B-444553540000}'
            }
        }
    }
}

它以"HKCR"——HKEY_CLASSES_ROOT的缩写——开头,每一行是注册表键名称。关键词NoRemove 意味着当该服务器被反注册时该键不能被删除。最后一行有点复杂。关键词ForceRemove 意思是如果该键存在,那么在该键被写之前先删除它。这一行剩下的部分指定了一个将被保存在SimpleShlExt键的默认值中的字符串(那就是”s”的意思)

在这儿,我需要说明一点。我们注册扩展时的键是HKCR\txtfile。然而,这个名称"txtfile" 并不是一个永久的或预先知道的。如果你查看一下HKCR\.txt ,那个键的默认值是这个名称被保存的地方。这就两个侧面效果:

  • 我们将不能可靠的使用RGS脚本,因为"txtfile"可能不是正确的键名。
  • 其他的一些文本编辑器可能被安装,它们同.TXT文件相关联。如果它们改变了 HKCR\.txt键的默认值,所有存在的外壳扩展都将会停止工作。

看起来,这的确是我设计的缺陷。我想微软也在考虑同样的事,因为最近创建的扩展,像QueryInfo扩展,是在.txt键下注册的。

好了,说明到这儿。有一个最终的注册细节。在Win NT/2000下,我们必须自己将我们的扩展方到一个“被认可的”扩展列表当中。如果我们不这么做的话,那些非管理员用户将不会壮在我们的扩展。这个列表被保存在:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved

在这个键下,我们创建一个字符串值它的名称是我们的GUID。字符串的内容可以是任何东西。做这些事情的代码在我们的DllRegisterServer()DllUnregisterServer()函数当中。我并不想把这些呆马列在这儿,因为这只是简单的注册表访问。你可以从本文的例子项目当中找到它们。

调试外壳扩展

最终,你写成了这个相当不容易的扩展,然后你将会调试它。打开你的项目设置(Project->Settings),到栏,在"Executable for debug session"编辑框中输入资源管理器的全路径,例如:"C:\windows\explorer.exe"。如果你使用的是NT2000,而且你已经设置过了DesktopProcess 注册表项,那么在你按F5开始调试的时候,会有一个新的资源管理器窗口打开。只要你在那个窗口工作,以后重建DLL时你将不会有问题,因为当你关掉窗口时,你的扩展也被卸载了。

Windows 9x下,恐怕你不得不在调试之前关闭你的外壳。单击“开始”->“关闭系统”。按住Ctrl+Alt+Shift然后点击“取消”。这将关闭资源管理器,然后你看见任务栏消失了。切换到MSVC然后按F5开始调试。按Shift+F5关闭资源管理器停止调试。当你做完调试的时候,你可以运行Explorer重新正常启动你的外壳。

它看起来是什么样的?

下面是我们添加的项目看起来的样子:

这就是我们的菜单!

下面是有敏感帮助时资源管理器的状态栏的样子:

而下面是消息框的样子,它显示了被选中的文件的文件名:

      本例程代码下载地址(11K)http://www.codeproject.com/shell/ShellExtGuide1/ShellExtGuide1_demo.zip

下一部分……

接着的第二部分,一个新的上下文菜单扩展将会告诉你如何同时对多个文件进行操作。

你可以从下面的网址获得这个和其他文章的最新版本:http://home.inreach.com/mdunn/code/

 

关于翻译:

      这是我第一次翻译文章,文章来自著名的http://www.codeproject.com/,翻译之前我看了csdn的开发文档,发现还是空白,所以就像把它翻译了,也许有对它感兴趣的人。文章总共有9个部分。我没有太多的时间,只翻译了第一部分,也许能起到抛砖引玉的作用,让那些对外壳扩展不了解的人入个门,入了门的多个参考。更多的文章大家可以从http://www.codeproject.com/shell/ 找到。例子代码,原文也可以从那儿找到。我的emailmefish@163.net,头一次翻译,做得不好,任何意见、建议、鲜花、掌声、石头、带酒的啤酒瓶都将受到热烈欢迎……

0

PHP5 效率优化


静态调用的成员一定要定义成 static  (5 ONLY)

PHP 5 引入了静态成员的概念,作用和 PHP 4 的函数内部静态变量一致,但前者是作为类的成员来使用。静态变量和 的类变量( variable)差不多,所有类的实例共享同一个静态变量。

<?php
class foo
{
    function
bar
() {
        echo
‘foobar’
;
    }
}

$foo = new foo;

// instance way

$foo->bar();

// static way

foo::bar();
?>

静态地调用非 static 成员,效率会比静态地调用 static 成员慢 50-60%。主要是因为前者会产生 E_STRICT 警告,内部也需要做转换。

使用类常量 (PHP5 ONLY)

PHP 5 新功能,类似于 C++ 的 const。

使用类常量的好处是:

- 编译时解析,没有额外开销
- 杂凑表更小,所以内部查找更快
- 类常量仅存在于特定「命名空间」,所以杂凑名更短
- 代码更干净,使除错更方便

(暂时)不要使用 require/include_once

require/include_once 每次被调用的时候都会打开目标文件!

- 如果用绝对路径的话,PHP 5.2/6.0 不存在这个问题
- 新版的 APC 缓存系统已经解决这个问题

文件 I/O 增加 => 效率降低

如果需要,可以自行检查文件是否已被 require/include。

不要调用毫无意义的函数

有对应的常量的时候,不要使用函数。

<?php
php_uname
(‘s’) == PHP_OS
;
php_version() == PHP_VERSION
;
php_sapi_name() == PHP_SAPI
;
?>
虽然使用不多,但是效率提升大概在 3500% 左右。

最快的 Win32 检查

<?php
$is_win
= DIRECTORY_SEPARATOR == ‘\\’
;
?>

- 不用函数
- Win98/NT/2000/XP/Vista/Longhorn/Shorthorn/Whistler…通用
- 一直可用

时间问题 (PHP>5.1.0 ONLY)

你如何在你的软件中得知现在的时间?简单,「time() time() again, you ask me…」。

不过总归会调用函数,慢。

现在好了,用 $_['REQUEST_TIME'],不用调用函数,又省了。

加速 PCRE

 对于不用保存的结果,不用 (),一律用 (?:)

这样 PHP 不用为符合的内容分配内存,省。效率提升 15% 左右。

- 能不用正则,就不用正则,在分析的时候仔细阅读手册「字符串函数」部分。有没有你漏掉的好用的函数?

加速 strtr

如果需要转换的全是单个字符的时候,用字符串而不是数组来做 strtr:

<?php
$addr
= strtr($addr, "abcd", "efgh");
// good
$addr = strtr($addr, array(‘a’ => ‘e’
,
                           
// …
                           
));
// bad
?>

效率提升:10 倍。

不要做无谓的替换

即使没有替换,str_replace 也会为其参数分配内存。很慢!解决办法:

- 用 strpos 先查找(非常快),看是否需要替换,如果需要,再替换

效率:

- 如果需要替换:效率几乎相等,差别在 0.1% 左右。
- 如果不需要替换:用 strpos 快 200%。

邪恶的 @ 操作符

不要滥用 @ 操作符。虽然 @ 看上去很简单,但是实际上后台有很多操作。用 @ 比起不用 @,效率差距:3 倍。

特别不要在循环中使用 @,在 5 次循环的测试中,即使是先用 error_reporting(0) 关掉错误,在循环完成后再打开,都比用 @ 快。

善用 strncmp

当需要对比「前 n 个字符」是否一样的时候,用 strncmp/strncasecmp,而不是 substr/strtolower,更不是 PCRE,更千万别提 ereg。strncmp/strncasecmp 效率最高(虽然高得不多)。

慎用 substr_compare (PHP5 ONLY)

按照上面的道理,substr_compare 应该比先 substr 再比较快咯。答案是否定的,除非:

- 无视大小写的比较
- 比较较大的字符串

不要用常量代替字符串

为什么:

- 需要查询杂凑表两次
- 需要把常量名转换为小写(进行第二次查询的时候)
- 生成 E_NOTICE 警告
- 会建立临时字符串

效率差别:700%。

不要把 count/strlen/sizeof 放到 for 循环的条件语句中

贴士:我的个人做法

<?php
for ($i = 0, $max = count($array);$i < $max; ++$i
);
?>

效率提升相对于:

- count 50%
- strlen 75%

短的代码不一定快

<?php
// longest
if ($a == $b
) {
   
$str .= $a
;
} else {
   
$str .= $b
;
}

// longer
if ($a == $b
) {
   
$str .= $a
;
}
$str .= $b
;

// short
$str .= ($a == $b ? $a : $b
);
?>

你觉得哪个快?

效率比较:

- longest: 4.27
- longer: 4.43
- short: 4.76

不可思议?再来一个:
<?php
// original
$d = dir(‘.’
);
while ((
$entry = $d->read()) !== false
) {
    if (
$entry == ‘.’ || $entry == ‘..’
) {
        continue;
    }
}

// versus
glob(‘./*’
);

// versus (include . and ..)
scandir(‘.’
);
?>

哪个快?

效率比较:

- original: 3.37
- glob: 6.28
- scandir: 3.42
- original without OO: 3.14
- SPL (PHP5): 3.95

画外音:从此也可以看出来 PHP5 的面向对象效率提高了很多,效率已经和纯函数差得不太多了。

提高 PHP 文件访问效率

需要包含其他 PHP 文件的时候,使用完整路径,或者容易转换的相对路径。

<?php

include ‘file.php’; // bad approach

incldue ‘./file.php’; // good

include ‘/path/to/file.php’; // ideal

?>

物尽其用

PHP 有很多扩展和函数可用,在实现一个功能的之前,应该看看 PHP 是否有了这个功能?是否有更简单的实现?

<?php
$filename
= "./somepic.gif"
;
$handle = fopen($filename, "rb"
);
$contents = fread($handle, filesize($filename
));
fclose($handle
);

// vs. much simpler

file_get_contents(‘./somepic.gif’);
?>

关于引用的技巧

引用可以:

- 简化对复杂结构数据的访问
- 优化内存使用
<?php
$a
['b']['c'
] = array();

// slow 2 extra hash lookups per access
for ($i = 0; $i < 5; ++$i
)
   
$a['b']['c'][$i] = $i
;

// much faster reference based approach
$ref =& $a['b']['c'
];
for (
$i = 0; $i < 5; ++$i
)
   
$ref[$i] = $i
;
?>

<?php
$a
= ‘large string’
;

// memory intensive approach
function a($str
)
{
    return
$str.‘something’
;
}

// more efficient solution
function a(&$str
)
{
   
$str .= ‘something’
;
}
?>

==============================================
参考资料
http://ilia.ws

Ilia 的个人网站,,他参与的开发以及出版的一些稿物链接等等。
http://ez.no

eZ components 官方网站,eZ comp 是针对 PHP5 的开源通用库,以效率为己任,Ilia 也参与了开发。
http://phparch.com

php|architect,不错的 php 出版商/培训组织。买不起或者买不到的话,网上可以下到很多经典的盗版。

http://talks.php.net

0

网页 Meta 标记全面介绍


META标签是HTML语言HEAD区的一个辅助性标签,它位于HTML文档头部的<HEAD>标记和<TITLE>标记之间,它提供用户不可见的信息。meta标签通常用来为搜索引擎robots定义页面主题,或者是定义用户浏览器上的cookie;它可以用于鉴别作者,设定页面格式,标注内容提要和关键字;还可以设置页面使其可以根据你定义的时间间隔刷新自己,以及设置RASC内容等级,等等。
下面介绍一些有关 标记的例子及解释。
  META标签分两大部分:HTTP标题信息(HTTP-EQUIV)和页面描述信息(NAME)。
※ HTTP-EQUIV
  HTTP-EQUIV类似于HTTP的头部协议,它回应给浏览器一些有用的信息,以帮助正确和精确地显示网页内容。常用的HTTP-EQUIV类型有:
  1、Content-Type和Content-Language (显示字符集的设定)
  说明:设定页面使用的字符集,用以说明主页制作所使用的文字已经语言,浏览器会根据此来调用相应的字符集显示page内容。
  用法:<Meta http-equiv="Content-Type" Content="text/; Charset=gb2312">
      <Meta http-equiv="Content-Language" Content="zh-CN">
  注意: 该META标签定义了HTML页面所使用的字符集为GB2132,就是国标汉字码。如果将其中的“charset=GB2312”替换成“BIG5”,则该页面所用的字符集就是繁体中文Big5码。当你浏览一些国外的站点时,IE浏览器会提示你要正确显示该页面需要下载xx语支持。这个功能就是通过读取HTML页面META标签的Content-Type属性而得知需要使用哪种字符集显示该页面的。如果系统里没有装相应的字符集,则IE就提示下载。其他的语言也对应不同的charset,比如日文的字符集是“iso-2022-jp ”,韩文的是“ks_c_5601”。
  Content-Type的Content还可以是:text/xml等文档类型;
  Charset选项:ISO-8859-1(英文)、BIG5、UTF-8、SHIFT-Jis、Euc、Koi8-2、us-ascii, x-mac-roman, iso-8859-2, x-mac-ce, iso-2022-jp, x-sjis, x-euc-jp,euc-kr, iso-2022-kr, gb2312, gb_2312-80, x-euc-tw, x-cns11643-1,x-cns11643-2等字符集;Content-Language的Content还可以是:EN、FR等语言代码。
  2、Refresh (刷新)
   说明:让网页多长时间(秒)刷新自己,或在多长时间后让网页自动链接到其它网页。
   用法:<Meta http-equiv="Refresh" Content="30">
      <Meta http-equiv="Refresh" Content="5; Url=http://www.xia8.net">
   注意:其中的5是指停留5秒钟后自动刷新到URL网址。
  3、Expires (期限)
   说明:指定网页在缓存中的过期时间,一旦网页过期,必须到服务器上重新调阅。
   用法:<Meta http-equiv="Expires" Content="0">
      <Meta http-equiv="Expires" Content="Wed, 26 Feb 1997 08:21:57 GMT">
   注意:必须使用GMT的时间格式,或直接设为0(数字表示多少时间后过期)。
  4、Pragma (cach模式)
   说明:禁止浏览器从本地机的缓存中调阅页面内容。
   用法:<Meta http-equiv="Pragma" Content="No-cach">
   注意:网页不保存在缓存中,每次访问都刷新页面。这样设定,访问者将无法脱机浏览。
  5、Set-Cookie (cookie设定)
  说明:浏览器访问某个页面时会将它存在缓存中,下次再次访问时就可从缓存中读取,以提高速度。

当你希望访问者每次都刷新你广告的图标,或每次都刷新你的计数器,就要禁用缓存了。通常HTML文件没有必要禁用缓存,对于ASP等页面,就可以使用禁用缓存,因为每次看到的页面都是在服务器动态生成的,缓存就失去意义。如果网页过期,那么存盘的cookie将被删除。
   用法:<Meta http-equiv="Set-Cookie" Content="cookievalue=xxx; expires=Wednesday, 21-Oct-98 16:14:21 GMT; path=/">
   注意:必须使用GMT的时间格式。
  6、Window-target (显示窗口的设定)
   说明:强制页面在当前窗口以独立页面显示。
   用法:<Meta http-equiv="Widow-target" Content="_top">
   注意:这个属性是用来防止别人在框架里调用你的页面。Content选项:_blank、_top、_self、_parent。
  7、Pics-label (网页RSAC等级评定)
   说明:在IE的Internet选项中有一项内容设置,可以防止浏览一些受限制的网站,而网站的限制级别就是通过该参数来设置的。
   用法:<META http-equiv="Pics-label" Contect="(PICS-1.1′http://www.rsac.org/ratingsv01.html’ I gen comment ‘RSACi North America Sever’ by ‘inet@microsoft.com’ for ‘http://www.microsoft.com’ on ’1997.06.30T14:21-0500′ r(n0 s0 v0 l0))">
   注意:不要将级别设置的太高。RSAC的评估系统提供了一种用来评价Web站点内容的标准。用户可以设置Microsoft Internet Explorer(IE3.0以上)来排除包含有色情和暴力内容的站点。上面这个例子中的HTML取自Microsoft的主页。代码中的(n 0 s 0 v 0 l 0)表示该站点不包含不健康内容。级别的评定是由RSAC,即美国娱乐委员会的评级机构评定的,如果你想进一步了解RSAC评估系统的等级内容,或者你需要评价自己的网站,可以访问RSAC的站点:http://www.rsac.org/
  8、Page-Enter、Page-Exit (进入与退出)
   说明:这个是页面被载入和调出时的一些特效。
   用法:<Meta http-equiv="Page-Enter" Content="blendTrans(Duration=0.5)">
      <Meta http-equiv="Page-Exit" Content="blendTrans(Duration=0.5)">
   注意:blendTrans是动态滤镜的一种,产生渐隐效果。另一种动态滤镜RevealTrans也可以用于页面进入与退出效果:
      <Meta http-equiv="Page-Enter" Content="revealTrans(duration=x,transition=y)">
      <Meta http-equiv="Page-Exit" Content="revealTrans(duration=x,ransition=y)">
       Duration  表示滤镜特效的持续时间(单位:秒)
       Transition 滤镜类型。表示使用哪种特效,取值为0-23。
       0 矩形缩小       1 矩形扩大
       2 圆形缩小       3 圆形扩大
       4 下到上刷新       5 上到下刷新
       6 左到右刷新       7 右到左刷新
       8 竖百叶窗       9 横百叶窗
       10 错位横百叶窗       11 错位竖百叶窗
       12 点扩散       13 左右到中间刷新
       14 中间到左右刷新       15 中间到上下
       16 上下到中间       17 右下到左上
       18 右上到左下       19 左上到右下
       20 左下到右上       21 横条
       22 竖条       23 以上22种随机选择一种
  9、MSThemeCompatible (XP主题)
   说明:是否在IE中关闭 xp 的主题
   用法:<Meta http-equiv="MSThemeCompatible" Content="Yes">
   注意:关闭 xp 的蓝色立体按钮系统显示样式,从而和win2k 很象。
  10、IE6 (页面生成器)
   说明:页面生成器generator,是ie6
   用法:<Meta http-equiv="IE6" Content="Generator">
   注意:用什么东西做的,类似商品出厂厂商。
  11、Content-Script-Type (脚本相关)
   说明:这是近来W3C的规范,指明页面中脚本的类型。
   用法:<Meta http-equiv="Content-Script-Type" Content="text/">
   注意:
※NAME变量
  name是描述网页的,对应于Content(网页内容),以便于搜索引擎机器人查找、分类(目前几乎所有的搜索引擎都使用网上机器人自动查找meta值来给网页分类)。
  name的value值(name="")指定所提供信息的类型。有些值是已经定义好的。例如description(说明)、keyword(关键字)、refresh(刷新)等。还可以指定其他任意值,如:creationdate(创建日期) 、document ID(文档编号)和level(等级)等。
  name的content指定实际内容。如:如果指定level(等级)为value(值),则Content可能是beginner(初级)、intermediate(中级)、advanced(高级)。
  1、Keywords (关键字)
   说明:为搜索引擎提供的关键字列表
   用法:<Meta name="Keywords" Content="关键词1,关键词2,关键词3,关键词4,……">
   注意:各关键词间用英文逗号“,”隔开。META的通常用处是指定搜索引擎用来提高搜索质量的关键词。当数个META元素提供文档语言从属信息时,搜索引擎会使用lang特性来过滤并通过用户的语言优先参照来显示搜索结果。例如:
      <Meta name="Kyewords" Lang="EN" Content="vacation,greece,sunshine">
      <Meta name="Kyewords" Lang="FR" Content="vacances,grè:ce,soleil">
  2、Description (简介)
   说明:Description用来告诉搜索引擎你的网站主要内容。
   用法:<Meta name="Description" Content="你网页的简述">
   注意:
  3、Robots (机器人向导)
   说明:Robots用来告诉搜索机器人哪些页面需要索引,哪些页面不需要索引。Content的参数有all、none、index、noindex、follow、nofollow。默认是all。
   用法:<Meta name="Robots" Content="All|None|Index|Noindex|Follow|Nofollow">
   注意:许多搜索引擎都通过放出robot/spider搜索来登录网站,这些robot/spider就要用到meta元素的一些特性来决定怎样登录。
    all:文件将被检索,且页面上的链接可以被查询;
    none:文件将不被检索,且页面上的链接不可以被查询;(和 "noindex, no follow" 起相同作用)
    index:文件将被检索;(让robot/spider登录)
    follow:页面上的链接可以被查询;
    noindex:文件将不被检索,但页面上的链接可以被查询;(不让robot/spider登录)
   nofollow:文件将不被检索,页面上的链接可以被查询。(不让robot/spider顺着此页的连接往下探找)
  4、Author (作者)
   说明:标注网页的作者或制作组
   用法:<Meta name="Author" Content="张三,abc@sohu.com">
   注意:Content可以是:你或你的制作组的名字,或Email
  5、Copyright (版权)
   说明:标注版权
   用法:<Meta name="Copyright" Content="本页版权归Zerospace所有。All Rights Reserved">
   注意:
  6、Generator (编辑器)
   说明:编辑器的说明
   用法:<Meta name="Generator" Content="PCDATA|FrontPage|">
   注意:Content="你所用编辑器"
  7、revisit-after (重访)
   说明:
   用法:<META name="revisit-after" CONTENT="7 days" >
   注意:
※Head中的其它一些用法
  1、scheme (方案)
   说明:scheme can be used when name is used to specify how the value of content should
      be interpreted.
   用法:<meta scheme="ISBN" name="identifier" content="0-14-043205-1" />
   注意:
  2、Link (链接)
   说明:链接到文件
   用法:<Link href="soim.ico" rel="Shortcut Icon">
   注意:很多网站如果你把她保存在收件夹中后,会发现它连带着一个小图标,如果再次点击进入之后还会发现地址栏中也有个小图标。现在只要在你的页头加上这段话,就能轻松实现这一功能。<LINK> 用来将目前文件与其它 URL 作连结,但不会有连结按钮,用於 <HEAD> 标记间, 格式如下:
       <link href="URL" rel="relationship">
       <link href="URL" rev="relationship">
  3、Base (基链接)
   说明:插入网页基链接属性
   用法:<Base href="http://www.csdn.net/" target="_blank">
   注意:你网页上的所有相对路径在链接时都将在前面加上“http://www.cn8cn.com/”。其中

target="_blank"是链接文件在新的窗口中打开,你可以做其他设置。将“_blank”改为“_parent”是链接文件将在当前窗口的父级窗口中打开;改为“_self”链接文件在当前窗口(帧)中打开;改为“_top”链接文件全屏显示。

<head>  
<title>文件头,显示在浏览器标题区</title>
<meta http-equiv="Content-Language" content="zh-cn">
 <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
 <meta name="GENERATOR" content="Microsoft FrontPage 4.0">
 <meta name="ProgId" content="FrontPage.Editor.Document">
 <meta name="制作人" content="Simonzy">
 <meta name="主题词" content="HTML 网页制作 C# .NET JavaScript JS">
</head>

以上是META标签的一些基本用法,其中最重要的就是:Keywords和Description的设定。为什么呢?道理很简单,这两个语句可以让搜索引擎能准确的发现你,吸引更多的人访问你的站点!根据现在流行搜索引擎(Google,Lycos,AltaVista等)的工作原理,搜索引擎先派机器人自动在WWW上搜索,当发现新的网站时,便于检索页面中的Keywords和Description,并将其加入到自己的数据库,然后再根据关键词的密度将网站排序。

Previous Page

Random Posts Recent Comments

  • 女友糖尿病害我蛀牙 Says:

    汗一个…...

  • Htj06 Says:

    zhenyouchuangyi...

  • 电商圈 Says:

    试图该怎么建立啊,,怎在程序中是吸纳...

  • edward Says:

    看得人心旷神怡,好文,情不自禁的顶一下...

  • Daniel Says:

    我也在处理这个问题,没有找到好的方法。我用了楼上兄弟的方法,还是可以的。不知道您找到好的方法了吗、我暂时楼上兄弟的方法。...

  • 卡,卡 Says:

    弱弱问一句:博主,你博客的模板这样设计pv高吗?...

  • 站长工具 Says:

    博主,兔年快乐!...

  • health Says:

    great post!!I hope I can read more in your website....

  • pdu Says:

    好博文,支持分享...

  • 站长工具 Says:

    博主的文章很不错,我是站长工具-站长精灵的作者,一款专业的SEO工具软件(可以帮您提高博客的流量),想跟您交换个链接,不知可否...

Tag Cloud

arm audio blog brew cache class debug flash google html j2me java javascript Joke linux lua mobile mtk php python ror ruby server shell stream unix web windows 优化 动态加载 女人 女生 平台 开发 手机 技术 流媒体 测试 漫画 生活 男人 男生 缓存 芯片