<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>H31家园</title>
<link>http://h31home.com/h312005</link>
<Description>欢迎光临使用H31家园</Description>
<language>zh-cn</language>
<copyright>Copyright 2004-2005 H31Home</copyright>
<webMaster>miao.whu@Gmail.com</webMaster>
<image>
	<title>H31家园</title> 
	<url>http://h31home.com/h312005/images/miao.gif</url> 
	<link>http://h31home.com/h312005</link> 
	<description>欢迎光临使用H31家园</description> 
</image>
<item><link>http://h31home.com/h312005/blogview.asp?ID=998</link><title><![CDATA[分布式基础学习]]></title><author>admin</author><category>Linux学习</category><pubDate>2009-2-23 15:14:31</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=998</guid><description><![CDATA[http://www.cnblogs.com/duguguiyu/archive/2009/02/22/1396034.html

所谓分布式，在这里，很狭义的指代以Google的三驾马车，GFS、Map/Reduce、BigTable为框架核心的分布式存储和计算系统。通常如我一样初学的人，会以Google这几份经典的论文作为开端的。它们勾勒出了分布式存储和计算的一个基本蓝图，已可窥见其几分风韵，但终究还是由于缺少一些实现的代码和示例，色彩有些斑驳，缺少了点感性。幸好我们还有Open Source，还有Hadoop。Hadoop是一个基于Java实现的，开源的，分布式存储和计算的项目。作为这个领域最富盛名的开源项目之一，它的使用者也是大牌如云，包括了Yahoo，Amazon，Facebook等等（好吧，还可能有校内，不过这真的没啥分量...）。Hadoop本身，实现的是分布式的文件系统HDFS，和分布式的计算（Map/Reduce）框架，此外，它还不是一个人在战斗，Hadoop包含一系列扩展项目，包括了分布式文件数据库HBase（对应Google的BigTable），分布式协同服务ZooKeeper（对应Google的Chubby），等等。。。
如此，一个看上去不错的黄金搭档浮出水面，Google的论文 + Hadoop的实现，顺着论文的框架看具体的实现，用实现来进一步理解论文的逻辑，看上去至少很美。网上有很多前辈们，做过Hadoop相关的源码剖析工作，我关注最多的是这里，目前博主已经完成了HDFS的剖析工作，Map/Reduce的剖析正火热进行中，更新频率之高，剖析之详尽，都是难得一见的，所以，走过路过一定不要错过了。此外，还有很多Hadoop的关注者和使用者贴过相关的文章，比如：这里，这里。也可以去Hadoop的中文站点（不知是民间还是官方...），搜罗一些学习资料。。。
我个人从上述资料中受益匪浅，而我自己要做的整理，与原始的源码剖析有些不同，不是依照实现的模块，而是基于论文的脉络和实现这样系统的基本脉络来进行的，也算，从另一个角度给出一些东西吧。鉴于个人对于分布式系统的理解非常的浅薄，缺少足够的实践经验，深入的问题就不班门弄斧了，仅做梳理和解析，大牛至此，可绕路而行了。。。
一. 分布式文件系统

分布式文件系统，在整个分布式系统体系中处于最低层最基础的地位，存储嘛，没了数据，再好的计算平台，再完善的数据库系统，都成了无水之舟了。那么，什么是分布式文件系统，顾名思义，就是分布式+文件系统。它包含这两个方面的内涵，从文件系统的客户使用的角度来看，它就是一个标准的文件系统，提供了一系列API，由此进行文件或目录的创建、移动、删除，以及对文件的读写等操作。从内部实现来看，分布式的系统则不再和普通文件系统一样负责管理本地磁盘，它的文件内容和目录结构都不是存储在本地磁盘上，而是通过网络传输到远端系统上。并且，同一个文件存储不只是在一台机器上，而是在一簇机器上分布式存储，协同提供服务，正所谓分布式。。。
因此，考量一个分布式文件系统的实现，其实不妨可以从这两方面来分别剖析，而后合二为一。首先，看它如何去实现文件系统所需的基本增删改查的功能。然后，看它如何考虑分布式系统的特点，提供更好的容错性，负载平衡，等等之类的。这二者合二为一，就明白了一个分布式文件系统，整体的实现模式。。。
I. 术语对照
说任何东西，都需要统一一下语言先，不然明明说的一个意思，却容易被理解到另一个地方去。Hadoop的分布式文件系统HDFS，基本是按照Google论文中的GFS的架构来实现的。但是，HDFS为了彰显其不走寻常路的本性，其中的大量术语，都与GFS截然不同。明明都是一个枝上长的土豆，它偏偏就要叫山药蛋，弄得水火不容的，苦了我们看客。秉承老好人，谁也不得罪的方针，文中，既不采用GFS的叫法，也不采用Hadoop的称谓，而是另辟蹊径，自立门户，搞一套自己的中文翻译，为了避免不必要的痛楚，特此先来一帖术语对照表，要不懂查一查，包治百病。。。

文中所用翻译 HDFS中的术语 GFS中的术语 术语解释 
主控服务器 NameNode Master 整个文件系统的大脑，它提供整个文件系统的目录信息，并且管理各个数据服务器。 
数据服务器 DataNode Chunk Server 分布式文件系统中的每一个文件，都被切分成若干个数据块，每一个数据块都被存储在不同的服务器上，此服务器称之为数据服务器。 
数据块 Block Chunk 每个文件都会被切分成若干个块，每一块都有连续的一段文件内容，是存储的基恩单位，在这里统一称做数据块。 
数据包 Packet 无 客户端写文件的时候，不是一个字节一个字节写入文件系统的，而是累计到一定数量后，往文件系统中写入一次，每发送一次的数据，都称为一个数据包。 
传输块 Chunk 无 在每一个数据包中，都会将数据切成更小的块，每一个块配上一个奇偶校验码，这样的块，就是传输块。 
备份主控服务器 SecondaryNameNode 无 备用的主控服务器，在身后默默的拉取着主控服务器 的日志，等待主控服务器牺牲后被扶正。 


*注：本文采用的Hadoop是0.19.0版本。
II. 基本架构
1. 服务器介绍
与单机的文件系统不同，分布式文件系统不是将这些数据放在一块磁盘上，由上层操作系统来管理。而是存放在一个服务器集群上，由集群中的服务器，各尽其责，通力合作，提供整个文件系统的服务。其中重要的服务器包括：主控服务器（Master/NameNode），数据服务器（ChunkServer/DataNode），和客户服务器。HDFS和GFS都是按照这个架构模式搭建的。个人觉得，其中设计的最核心内容是：文件的目录结构独立存储在一个主控服务器上，而具体文件数据，拆分成若干块，冗余的存放在不同的数据服务器上。
存储目录结构的主控服务器，在GFS中称为Master，在HDFS中称为NameNode。这两个名字，叫得都有各自的理由，是瞎子摸象各表一面。Master是之于数据服务器来叫的，它做为数据服务器的领导同志存在，管理各个数据服务器，收集它们的信息，了解所有数据服务器的生存现状，然后给它们分配任务，指挥它们齐心协力为系统服务；而NameNode是针对客户端来叫的，对于客户端而言，主控服务器上放着所有的文件目录信息，要找一个文件，必须问问它，由此而的此名。。。
主控服务器在整个集群中，同时提供服务的只存在一个，如果它不幸牺牲的话，会有后备军立刻前赴后继的跟上，但，同一时刻，需要保持一山不容二虎的态势。这种设计策略，避免了多台服务器间即时同步数据的代价，而同时，它也使得主控服务器很可能成为整个架构的瓶颈所在。因此，尽量为主控服务器减负，不然它做太多的事情，就自然而然的晋升成了一个分布式文件系统的设计要求。。。
每一个文件的具体数据，被切分成若干个数据块，冗余的存放在数据服务器。通常的配置，每一个数据块的大小为64M，在三个数据服务器上冗余存放（这个64M，不是随便得来的，而是经过反复实践得到的。因为如果太大，容易造成热点的堆叠，大量的操作集中在一台数据服务器上，而如果太小的话，附加的控制信息传输成本，又太高了。因此没有比较特定的业务需求，可以考虑维持此配置...）。数据服务器是典型的四肢发达头脑简单的苦力，其主要的工作模式就是定期向主控服务器汇报其状况，然后等待并处理命令，更快更安全的存放好数据。。。
此外，整个分布式文件系统还有一个重要角色是客户端。它不和主控服务和数据服务一样，在一个独立的进程中提供服务，它只是以一个类库（包）的模式存在，为用户提供了文件读写、目录操作等APIs。当用户需要使用分布式文件系统进行文件读写的时候，把客户端相关包给配置上，就可以通过它来享受分布式文件系统提供的服务了。。。
2. 数据分布
一个文件系统中，最重要的数据，其实就是整个文件系统的目录结构和具体每个文件的数据。具体的文件数据被切分成数据块，存放在数据服务器上。每一个文件数据块，在数据服务器上都表征为出双入队的一对文件（这是普通的Linux文件），一个是数据文件，一个是附加信息的元文件，在这里，不妨把这对文件简称为数据块文件。数据块文件存放在数据目录下，它有一个名为current的根目录，然后里面有若干个数据块文件和从dir0-dir63的最多64个的子目录，子目录内部结构等同于current目录，依次类推（更详细的描述，参见这里）。个人觉得，这样的架构，有利于控制同一目录下文件的数量，加快检索速度。。。
这是磁盘上的物理结构，与之对应的，是内存中的数据结构，用以表征这样的磁盘结构，方便读写操作的进行。Block类用于表示数据块，而FSDataset类是数据服务器管理文件块的数据结构，其中，FSDataset.FSDir对应着数据块文件和目录，FSDataset.FSVolume对应着一个数据目录，FSDataset.FSVolumeSet是FSVolume的集合，每一个FSDataset有一个FSVolumeSet。多个数据目录，可以放在不同的磁盘上，这样有利于加快磁盘操作的速度。相关的类图，可以参看这里 。。。
此外，与FSVolume对应的，还有一个数据结构，就是DataStorage，它是Storage的子类，提供了升级、回滚等支持。但与FSVolume不一样，它不需要了解数据块文件的具体内容，它只知道有这么一堆文件放这里，会有不同版本的升级需求，它会处理怎么把它们升级回滚之类的业务（关于Storage，可以参见这里）。而FSVolume提供的接口，都基本上是和Block相关的。。。
相比数据服务器，主控服务器的数据量不大，但逻辑更为复杂。主控服务器主要有三类数据：文件系统的目录结构数据，各个文件的分块信息，数据块的位置信息（就数据块放置在哪些数据服务器上...）。在GFS和HDFS的架构中，只有文件的目录结构和分块信息才会被持久化到本地磁盘上，而数据块的位置信息则是通过动态汇总过来的，仅仅存活在内存数据结构中，机器挂了，就灰飞烟灭了。每一个数据服务器启动后，都会向主控服务器发送注册消息，将其上数据块的状况都告知于主控服务器。俗话说，简单就是美，根据DRY原则，保存的冗余信息越少，出现不一致的可能性越低，付出一点点时间的代价，换取了一大把逻辑上的简单性，绝对应该是一个包赚不赔的买卖。。。
在HDFS中，FSNamespacesystem类就负责保管文件系统的目录结构以及每个文件的分块状况的，其中，前者是由FSDirectory类来负责，后者是各个INodeFile本身维护。在INodeFile里面，有一个BlockInfo的数组，保存着与该文件相关的所有数据块信息，BlockInfo中包含了从数据块到数据服务器的映射，INodeFile只需要知道一个偏移量，就可以提供相关的数据块，和数据块存放的数据服务器信息。。。
3、服务器间协议
在Hadoop的实现中，部署了一套RPC机制，以此来实现各服务间的通信协议。在Hadoop中，每一对服务器间的通信协议，都定义成为一个接口。服务端的类实现该接口，并且建立RPC服务，监听相关的接口，在独立的线程处理RPC请求。客户端则可以实例化一个该接口的代理对象，调用该接口的相应方法，执行一次同步的通信，传入相应参数，接收相应的返回值。基于此RPC的通信模式，是一个消息拉取的流程，RPC服务器等待RPC客户端的调用，而不会先发制人主动把相关信息推送到RPC客户端去。。。
其实RPC的模式和原理，实在是没啥好说的，之所以说，是因为可以通过把握好这个，彻底理顺Hadoop各服务器间的通信模式。Hadoop会定义一些列的RPC接口，只需要看谁实现，谁调用，就可以知道谁和谁通信，都做些啥事情，图中服务器的基本架构、各服务所使用的协议、调用方向、以及协议中的基本内容。。。

III. 基本的文件操作

基本的文件操作，可以分成两类，一个是对文件目录结构的操作，比如文件和目录的创建、删除、移动、更名等等；另一个是对文件数据流的操作，包括读取和写入文件数据。当然，文件读和写，是有本质区别的，尤其是在数据冗余的情况下，因此，当成两类操作也不足为过。此外，要具体到读写的类别，也是可以再继续分类下去的。在GFS的论文中，对于分布式文件系统的读写场景有一个重要的假定（其实是从实际业务角度得来的...）：就是文件的读取是由大数据量的连续读取和小数据量的随机读取组成，文件的写入则基本上都是批量的追加写，和偶尔的插入写（GFS中还有大量的假设，它们构成了分布式文件系统架构设计的基石。每一个系统架构都是搭建在一定假设上的，这些假设有些来自于实际业务的状况，有些是因为天生的条件约束，不基于假设理解设计，肯定会有失偏颇...）。在GFS中，对文件的写入分成追加写和插入写都有所支持，但是，在HDFS中仅仅支持追加写，这大大降低了复杂性。关于HDFS与GFS的一些不同，可以参看这里。。。
1. 文件和目录的操作
文件目录的信息，全部囤积在主控服务器上，因此，所有对文件目录的操作，只会直接涉及到客户端和主控服务器。整个目录相关的操作流程基本都是这样的：客户端DFSClient调用ClientProtocol定义的相关函数，该操作通过RPC传送到其实现者主控服务器NameNode那里，NameNode做相关的处理后（很少...），调用FSNamesystem的相关函数。在FSNamesystem中，往往是做一些验证和租约操作，具体的目录结构操作交由FSDirectory的相应函数来操作。最后，依次返回，经由RPC传送回客户端。具体各操作涉及到的函数和具体步骤，参见下表：


相关操作 ClientProtocol / NameNode FSNamesystem FSDirectory 关键步骤 
创建文件 create startFile addFile 1. 检查是否有写权限；
2. 检查是否已经存在此文件，如果是覆写，则先进行删除操作；
3. 在指定路径下添加INodeFileUnderConstruction的文件实例；
4. 写日志；
5. 签订租约。 
创建目录 mkdirs mkdirs mkdirs 1. 检查指定目录是否是目录；
2. 检查是否有相关权限；
3. 在指定路径的INode下，添加子节点；
4. 写日志。 
改名操作 rename renameTo renameTo 1. 检查相关路径的权限；
2. 从老路径下移除，在新路径下添加；
3. 修改相关父路径的修改时间；
4. 写日志；
5. 将租约从老路径移动到新路径下。 
删除操作 delete delete delete 1. 如果不是递归删除，确认指定路径是否是空目录；
2. 检查相关权限；
3. 在目录结构上移除相关INode；
4. 修改父路径的修改时间；
5. 将相关的数据块，放入到废弃队列中去，等待处理；
6. 写日志；
7. 废弃相关路径的租约。 
设置权限 setPermission setPermission setPermission 1. 检查owner判断是否有操作权限；
2. 修改指定路径下INode的权限；
3. 写日志。 
设置用户 setOwner setOwner setOwner 1. 检查是否有操作权限；
2. 修改指定路径下INode的权限；
3. 写日志。 
设置时间 setTimes setTimes setTimes 1. 检查是否有写权限；
2. 修改指定路径INode的时间信息；
3. 写日志。 



从上表可以看到，其实有的操作本质上还是涉及到了数据服务器，比如文件创建和删除操作。但是，之前提到，主控服务器只于数据服务器是一个等待拉取的地位，它们不会主动联系数据服务器，将指令传输给它们，而是放到相应的数据结构中，等待数据服务器来取。这样的设计，可以减少通信的次数，加快操作的执行速度。。。
另，上述步骤中，有些日志和租约相关的操作，从概念上来说，和目录操作其实没有任何联系，但是，为了满足分布式系统的需求，这些操作是非常有必要的，在此，按下不表。。。
2、文件的读取
不论是文件读取，还是文件的写入，主控服务器扮演的都是中介的角色。客户端把自己的需求提交给主控服务器，主控服务器挑选合适的数据服务器，介绍给客户端，让客户端和数据服务器单聊，要读要写随你们便。这种策略类似于DMA，降低了主控服务器的负载，提高了效率。。。
因此，在文件读写操作中，最主要的通信，发生在客户端与数据服务器之间。它们之间跑的协议是ClientDatanodeProtocol。从这个协议中间，你无法看到和读写相关的接口，因为，在Hadoop中，读写操作是不走RPC机制的，而是另立门户，独立搭了一套通信框架。在数据服务器一端，DataNode类中有一个DataXceiverServer类的实例，它在一个单独的线程等待请求，一旦接到，就启动一个DataXceiver的线程，处理此次请求。一个请求一个线程，对于数据服务器来说，逻辑上很简单。当下，DataXceiver支持的请求类型有六种，具体的请求包和回复包格式，请参见这里，这里，这里。在Hadoop的实现中，并没有用类来封装这些请求，而是按流的次序写下来，这给代码阅读带来挺多的麻烦，也对代码的维护带来一定的困难，不知道是出于何种考虑。。。
相比于写，文件的读取实在是一个简单的过程。在客户端DFSClient中，有一个DFSClient.DFSInputStream类。当需要读取一个文件的时候，会生成一个DFSInputStream的实例。它会先调用ClientProtocol定义getBlockLocations接口，提供给NameNode文件路径、读取位置、读取长度信息，从中取得一个LocatedBlocks类的对象，这个对象包含一组LocatedBlock，那里面有所规定位置中包含的所有数据块信息，以及数据块对应的所有数据服务器的位置信息。当读取开始后，DFSInputStream会先尝试从某个数据块对应的一组数据服务器中选出一个，进行连接。这个选取算法，在当下的实现中，非常简单，就是选出第一个未挂的数据服务器，并没有加入客户端与数据服务器相对位置的考量。读取的请求，发送到数据服务器后，自然会有DataXceiver来处理，数据被一个包一个包发送回客户端，等到整个数据块的数据都被读取完了，就会断开此链接，尝试连接下一个数据块对应的数据服务器，整个流程，依次如此反复，直到所有想读的都读取完了为止。。。
3、文件的写入
文件读取是一个一对一的过程，一个客户端，只需要与一个数据服务器联系，就可以获得所需的内容。但是，写入操作，则是一个一对多的流程。一次写入，需要在所有存放相关数据块的数据服务器都保持同步的更新，有任何的差池，整个流程就告失败。。。
在分布式系统中，一旦涉及到写入操作，并发处理难免都会沦落成为一个变了相的串行操作。因为，如果不同的客户端如果是任意时序并发写入的话，整个写入的次序无法保证，可能你写半条记录我写半条记录，最后出来的结果乱七八糟不可估量。在HDFS中，并发写入的次序控制，是由主控服务器来把握的。当创建、续写一个文件的时候，该文件的节点类，由INodeFile升级成为INodeFileUnderConstruction，INodeFileUnderConstruction是INodeFile的子类，它起到一个锁的作用。如果当一个客户端想创建或续写的文件是INodeFileUnderConstruction，会引发异常，因为这说明这个此处有爷，请另寻高就，从而保持了并发写入的次序性。同时，INodeFileUnderConstruction有包含了此时正在操作它的客户端的信息以及最后一个数据块的数据服务器信息，当追加写的时候可以更快速的响应。。。
与读取类似，DFSClient也有一个DFSClient.DFSOutputStream类，写入开始，会创建此类的实例。DFSOutputStream会从NameNode上拿一个LocatedBlock，这里面有最后一个数据块的所有数据服务器的信息。这些数据服务器每一个都需要能够正常工作（对于读取，只要还有一个能工作的就可以实现...），它们会依照客户端的位置被排列成一个有着最近物理距离和最小的序列（物理距离，是根据机器的位置定下来的...），这个排序问题类似于著名旅行商问题，属于NP复杂度，但是由于服务器数量不多，所以用最粗暴的算法，也并不会看上去不美。。。
文件写入，就是在这一组数据服务器上构造成数据流的双向流水线。DFSOutputStream，会与序列的第一个数据服务器建立Socket连接，发送请求头，然后等待回应。DataNode同样是建立DataXceiver来处理写消息，DataXceiver会依照包中传过来的其他服务器的信息，建立与下一个服务器的连接，并生成类似的头，发送给它，并等待回包。此流程依次延续，直到最后一级，它发送回包，反向着逐级传递，再次回到客户端。如果一切顺利，那么此时，流水线建立成功，开始正式发送数据。数据是分成一个个数据包发送的，所有写入的内容，被缓存在客户端，当写满64K，会被封装成DFSOutputStream.Packet类实例，放入DFSOutputStream的dataQueue队列。DFSOutputStream.DataStreamer会时刻监听这个队列，一旦不为空，则开始发送，将位于dataQueue队首的包移动到ackQueue队列的队尾，表示已发送但尚未接受回复的包队列。同时启动ResponseProcessor线程监听回包，直到收到相应回包，才将发送包从ackQueue中移除，表示成功。每一个数据服务器的DataXceiver收到了数据包，一边写入到本地文件中去，一边转发给下一级的数据服务器，等待回包，同前面建立流水线的流程。。。
当一个数据块写满了之后，客户端需要向主控服务器申请追加新的数据块。这个会引起一次数据块的分配，成功后，会将新的数据服务器组返还给客户端。然后重新回到上述流程，继续前行。。。
关于写入的流程，还可以参见这里。此外，写入涉及到租约问题，后续会仔细的来说。。。
IV. 分布式支持

如果单机的文件系统是田里勤恳的放牛娃，那么分布式文件系统就是刀尖上讨饭吃的马贼了。在分布式环境中，有太多的意外，数据随时传输错误，服务器时刻准备牺牲，很多平常称为异常的现象，在这里都需要按照平常事来对待。因此，对于分布式文件系统而言，仅仅是满足了正常状况下文件系统各项服务还不够，还需要保证分布式各种意外场景下健康持续的服务，否则，将一无是处。。。
1、服务器的错误恢复
在分布式环境中，哪台服务器牺牲都是常见的事情，牺牲不可怕，可怕的是你都没有时刻准备好它们会牺牲。作为一个合格的分布式系统，HDFS当然时刻准备好了前赴后继奋勇向前。HDFS有三类服务器，每一类服务器出错了，都有相应的应急策略。。。
a. 客户端

生命最轻如鸿毛的童鞋，应该就是客户端了。毕竟，做为一个文件系统的使用者，在整个文件系统中的地位，难免有些归于三流。而作为客户端，大部分时候，牺牲了就牺牲了，没人哀悼，无人同情，只有在在辛勤写入的时候，不幸辞世（机器挂了，或者网络断了，诸如此类...），才会引起些恐慌。因为，此时此刻，在主控服务器上对应的文件，正作为INodeFileUnderConstruction活着，仅仅为占有它的那个客户端服务者，做为一个专一的文件，它不允许别的客户端染指。这样的话，一旦占有它的客户端服务者牺牲了，此客户端会依然占着茅坑不拉屎，让如花似玉INodeFileUnderConstruction孤孤单单守寡终身。这种事情当然无法容忍，因此，必须有办法解决这个问题，办法就是：租约。。。
租约，顾名思义，就是当客户端需要占用某文件的时候，与主控服务器签订的一个短期合同。这个合同有一个期限，在这个期限内，客户端可以延长合同期限，一旦超过期限，主控服务器会强行终止此租约，将这个文件的享用权，分配给他人。。。
在打开或创建一个文件，准备追加写之前，会调用LeaseManager的addLease方法，在指定的路径下与此客户端签订一份租约。客户端会启动DFSClient.LeaseChecker线程，定时轮询调用ClientProtocol的renewLease方法，续签租约。在主控服务器一端，有一个LeaseManager.Monitor线程，始终在轮询检查所有租约，查看是否有到期未续的租约。如果一切正常，该客户端完成写操作，会关闭文件，停止租约，一旦有所意外，比如文件被删除了，客户端牺牲了，主控服务器都会剥夺此租约，如此，来避免由于客户端停机带来的资源被长期霸占的问题。。。
b. 数据服务器

当然，会挂的不只是客户端，海量的数据服务器是一个更不稳定的因素。一旦某数据服务器牺牲了，并且主控服务器被蒙在鼓中，主控服务器就会变相的欺骗客户端，给它们无法连接的读写服务器列表，导致它们处处碰壁无法工作。因此，为了整个系统的稳定，数据服务器必须时刻向主控服务器汇报，保持主控服务器对其的完全了解，这个机制，就是心跳消息。在HDFS中，主控服务器NameNode实现了DatanodeProtocol接口，数据服务器DataNode会在主循环中，不停的调用该协议中的sendHeartbeat方法，向NameNode汇报状况。在此调用中，DataNode会将其整体运行状况告知NameNode，比如：有多少可用空间、用了多大的空间，等等之类。NameNode会记住此DataNode的运行状况，作为新的数据块分配或是负载均衡的依据。当NameNode处理完成此消息后，会将相关的指令封装成一个DatanodeCommand对象，交还给DataNode，告诉数据服务器什么数据块要删除什么数据块要新增等等之类，数据服务器以此为自己的行动依据。。。
但是，sendHeartbeat并没有提供本地的数据块信息给NameNode，那么主控服务器就无法知道此数据服务器应该分配什么数据块应该删除什么数据块，那么它是如何决定的呢？答案就是DatanodeProtocol定义的另一个方法，blockReport。DataNode也是在主循环中定时调用此方法，只是，其周期通常比调用sendHeartbeat的更长。它会提交本地的所有数据块状况给NameNode，NameNode会和本地保存的数据块信息比较，决定什么该删除什么该新增，并将相关结果缓存在本地对应的数据结构中，等待此服务器再发送sendHeartbeat消息过来的时候，依照这些数据结构中的内容，做出相应的DatanodeCommand指令。blockReport方法同样也会返回一个DatanodeCommand给DataNode，但通常，只是为空（只有出错的时候不为空），我想，增加缓存，也许是为了确保每个指令都可以重复发送并确定被执行。。。
c. 主控服务器

当然，作为整个系统的核心和单点，含辛茹苦的主控服务器含泪西去，整个分布式文件服务集群将彻底瘫痪罢工。如何在主控服务器牺牲后，提拔新的主控服务器并迅速使其进入工作角色，就成了系统必须考虑的问题。解决策略就是：日志。。。
其实这并不是啥新鲜东西，一看就知道是从数据库那儿偷师而来的。在主控服务器上，所有对文件目录操作的关键步骤（具体文件内容所处的数据服务器，是不会被写入日志的，因为这些内容是动态建立的...），都会被写入日志。另外，主控服务器会在某些时刻，将当下的文件目录完整的序列化到本地，这称为镜像。一旦存有镜像，镜像前期所写的日志和其他镜像，都纯属冗余，其历史使命已经完成，可以报废删除了。在主控服务器不幸牺牲，或者是战略性的停机修整结束，并重新启动后，主控服务器会根据最近的镜像 + 镜像之后的所有日志，重建整个文件目录，迅速将服务能力恢复到牺牲前的水准。。。
对于数据服务器而言，它们会通过一些手段，迅速得知顶头上司的更迭消息。它们会立刻转投新东家的名下，在新东家旗下注册，并开始向其发送心跳消息，这个机制，可能用分布式协同服务来实现，这里不说也罢。。。
在HDFS的实现中，FSEditLog类是整个日志体系的核心，提供了一大堆方便的日志写入API，以及日志的恢复存储等功能。目前，它支持若干种日志类型，都冠以OP_XXX，并提供相关API，具体可以参见这里。为了保证日志的安全性，FSEditLog提供了EditLogFileOutputStream类作为写入的承载类，它会同时开若干个本地文件，然后依次写入，防止日志的损坏导致不可估量的后果。在FSEditLog上面，有一个FSImage类，存储文件镜像并调用FSEditLog对外提供相关的日志功能。FSImage是Storage类的子类，如果对数据块的讲述有所印象的话，你可以回忆起来，凡事从此类派生出来的东西，都具有版本性质，可以进行升级和回滚等等，以此，来实现产生镜像是对原有日志和镜像处理的复杂逻辑。。。
目前，在HDFS的日志系统中，有些地方与GFS的描述有所不同。在HDFS中，所有日志文件和镜像文件都是本地文件，这就相当于，把日志放在自家的保险箱中，一旦主控服务器挂了，别的后继而上的服务器也无法拿到这些日志和镜像，用于重振雄风。因此，在HDFS中，运行着一个SecondaryNameNode服务器，它做为主控服务器的替补，隐忍厚积薄发为篡位做好准备，其中，核心内容就是：定期下载并处理日志和镜像。SecondaryNameNode看上去像客户端一样，与NameNode之间，走着NamenodeProtocol协议。它会不停的查看主控服务器上面累计日志的大小，当达到阈值后，调用doCheckpoint函数，此函数的主要步骤包括：
首先是调用startCheckpoint做一些本地的初始化工作；

然后调用rollEditLog，将NameNode上此时操作的日志文件从edit切到edit.new上来，这个操作瞬间完成，上层写日志的函数完全感觉不到差别；

接着，调用downloadCheckpointFiles，将主控服务器上的镜像文件和日志文件都下载到此候补主控服务器上来；

并调用doMerge，打开镜像和日志，将日志生成新的镜像，保存覆盖；

下一步，调用putFSImage把新的镜像上传回NameNode；

再调用rollFsImage，将镜像换成新的，在日志从edit.new改名为edit；

最后，调用endCheckpoint做收尾工作。

整个算法涉及到NameNode和SecondaryNameNode两个服务器，最终结果是NameNode和SecondaryNameNode都依照算法进行前的日志生成了镜像。而两个服务器上日志文件的内容，前者是整个算法进行期间所写的日志，后者始终不会有任何日志。当主控服务器牺牲的时候，运行SecondaryNameNode的服务器立刻被扶正，在其上启动主控服务，利用其日志和镜像，恢复文件目录，并逐步接受各数据服务器的注册，最终向外提供稳定的文件服务。。。
同样的事情，GFS采用的可能是另外一个策略，就是在写日志的时候，并不局限在本地，而是同时书写网络日志，即在若干个远程服务器上生成同样的日志。然后，在某些时机，主控服务器自己，生成镜像，降低日志规模。当主控服务器牺牲，可以在拥有网络日志的服务器上启动主控服务，升级成为主控服务器。。。
GFS与HDFS的策略相比较，前者是化整为零，后者则是批量处理，通常我们认为，批量处理的平均效率更高一些，且相对而言，可能实现起来容易一些，但是，由于有间歇期，会导致日志的丢失，从而无法100%的将备份主控服务器的状态与主控服务器完全同步。。。
2、数据的正确性保证

在复杂纷繁的分布式环境中，我们坚定的相信，万事皆有可能。哪怕各个服务器都舒舒服服的活着，也可能有各种各样的情况导致网络传输中的数据丢失或者错误。并且在分布式文件系统中，同一份文件的数据，是存在大量冗余备份的，系统必须要维护所有的数据块内容完全同步，否则，一人一言，不同客户端读同一个文件读出不同数据，用户非得疯了不可。。。
在HDFS中，为了保证数据的正确性和同一份数据的一致性，做了大量的工作。首先，每一个数据块，都有一个版本标识，在Block类中，用一个长整型的数generationStamp来表示版本信息（Block类是所有表示数据块的数据结构的基类），一旦数据块上的数据有所变化，此版本号将向前增加。在主控服务器上，保存有此时每个数据块的版本，一旦出现数据服务器上相关数据块版本与其不一致，将会触发相关的恢复流程。这样的机制保证了各个数据服务器器上的数据块，在基本大方向上都是一致的。但是，由于网络的复杂性，简单的版本信息无法保证具体内容的一致性（因为此版本信息与内容无关，可能会出现版本相同，但内容不同的状况）。因此，为了保证数据内容上的一致，必须要依照内容，作出签名。。。
当客户端向数据服务器追加写入数据包时，每一个数据包的数据，都会切分成512字节大小的段，作为签名验证的基本单位，在HDFS中，把这个数据段称为Chunk，即传输块（注意，在GFS中，Chunk表达的是数据块...）。在每一个数据包中，都包含若干个传输块以及每一个传输块的签名，当下，这个签名是根据Java SDK提供的CRC算法算得的，其实就是一个奇偶校验。当数据包传输到流水线的最后一级，数据服务器会对其进行验证（想一想，为什么只在最后一级做验证，而不是每级都做...），一旦发现当前的传输块签名与在客户端中的签名不一致，整个数据包的写入被视为无效，Lease Recover（租约恢复）算法被触发。。。
从基本原理上看，这个算法很简单，就是取所有数据服务器上此数据块的最小长度当作正确内容的长度，将其他数据服务器上此数据块超出此长度的部分切除。从正确性上看，此算法无疑是正确的，因为至少有一个数据服务器会发现此错误，并拒绝写入，那么，如果写入了的，都是正确的；从效率上看，此算法也是高效的，因为它避免了重复的传输和复杂的验证，仅仅是各自删除尾部的一些内容即可。但从具体实现上来看，此算法稍微有些绕，因为，为了降低本已不堪重负的主控服务器的负担，此算法不是由主控服务器这个大脑发起的，而是通过选举一个数据服务器作为Primary，由Primary发起，通过调用与其他各数据服务器间的InterDatanodeProtocol协议，最终完成的。具体的算法流程，参见LeaseManager类上面的注释。需要说明的是此算法的触发时机和发起者。此算法可以由客户端或者是主控服务器发起，当客户端在写入一个数据包失败后，会发起租约恢复。因为，一次写入失败，不论是何种原因，很有可能就会导致流水线上有的服务器写了，有的没写，从而造成不统一。而主控服务器发起的时机，则是在占有租约的客户端超出一定时限没有续签，这说明客户端可能挂了，在临死前可能干过不利于数据块统一的事情，作为监督者，主控服务器需要发起一场恢复运动，确保一切正确。。。
3、负载均衡

负载的均衡，是分布式系统中一个永恒的话题，要让大家各尽其力齐心干活，发挥各自独特的优势，不能忙得忙死闲得闲死，影响战斗力。而且，负载均衡也是一个复杂的问题，什么是均衡，是一个很模糊的概念。比如，在分布式文件系统中，总共三百个数据块，平均分配到十个数据服务器上，就算均衡了么？其实不一定，因为每一个数据块需要若干个备份，各个备份的分布应该充分考虑到机架的位置，同一个机架的服务器间通信速度更快，而分布在不同机架则更具有安全性，不会在一棵树上吊死。。。
在这里说的负载均衡，是宽泛意义上的均衡过程，主要涵盖两个阶段的事务，一个是在任务初始分配的时候尽可能合理分配，另一个是在事后时刻监督及时调整。。。
在HDFS中，ReplicationTargetChooser类，是负责实现为新分配的数据块寻找婆家的。基本上来说，数据块的分配工作和备份的数量、申请的客户端地址（也就是写入者）、已注册的数据服务器位置，密切相关。其算法基本思路是只考量静态位置信息，优先照顾写入者的速度，让多份备份分配到不同的机架去。具体算法，自行参见源码。此外，HDFS的Balancer类，是为了实现动态的负载调整而存在的。Balancer类派生于Tool类，这说明，它是以一个独立的进程存在的，可以独立的运行和配置。它运行有NamenodeProtocol和ClientProtocol两个协议，与主控服务器进行通信，获取各个数据服务器的负载状况，从而进行调整。主要的调整其实就是一个操作，将一个数据块从一个服务器搬迁到另一个服务器上。Balancer会向相关的目标数据服务器发出一个DataTransferProtocol.OP_REPLACE_BLOCK消息，接收到这个消息的数据服务器，会将数据块写入本地，成功后，通知主控服务器，删除早先的那个数据服务器上的同一块数据块。具体的算法请自行参考源码。。。
4、垃圾回收
对于垃圾，大家应该耳熟能详了，在分布式文件系统而言，没有利用价值的数据块备份，就是垃圾。在现实生活中，我们提倡垃圾分类，为了更好的理解分布式文件系统的垃圾收集，搞个分类也是很有必要的。基本上，所有的垃圾都可以视为两类，一类是由系统正常逻辑产生的，比如某个文件被删除了，所有相关的数据块都沦为垃圾了，某个数据块被负载均衡器移动了，原始数据块也不幸成了垃圾了。此类垃圾最大的特点，就是主控服务器是生成垃圾的罪魁祸首，也就是说主控服务器完全了解有哪些垃圾需要处理。另外还有一类垃圾，是由于系统的一些异常症状产生的，比如某个数据服务器停机了一段，重启之后发现其上的某个数据块已经在其他服务器上重新增加了此数据块的备份，它上面的那个备份过期了失去价值了，需要被当作垃圾来处理了。此类垃圾的特点恰恰相反，主控服务器无法直接了解到垃圾状况，需要曲线救国。。。
在HDFS中，第一类垃圾的判定自然很容易，在一些正常的逻辑中产生的垃圾，全部被塞进了FSNamesystem的recentInvalidateSets这个Map中。而第二类垃圾的判定，则放在数据服务器发送其数据块信息来的过程中，经过与本地信息的比较，可以断定，此数据服务器上有哪些数据块已经不幸沦为垃圾。同样，这些垃圾也被塞到recentInvalidateSets中去。在与数据服务器进行心跳交流的过程中，主控服务器会将它上面有哪些数据块需要删除，数据服务器对这些数据块的态度是，直接物理删除。在GFS的论文中，对如何删除一个数据块有着不同的理解，它觉着应该先缓存起来，过几天没人想恢复它了再删除。在HDFS的文档中，则明确表示，在现行的应用场景中，没有需要这个需求的地方，因此，直接删除就完了。这说明，理念是一切分歧的根本：）。。。
V. 总结

整个分布式文件系统，计算系统，数据库系统的设计理念，基本是一脉相承的。三类服务器、作为单点存在的核心控制服务器、基于日志的恢复机制、基于租约的保持联系机制、等等，在后续分布式计算系统和分布式数据库中都可以看到类似的影子，在分布式文件系统这里，我详述了这些内容，可能在后续就会默认知道而说的比较简略了。而刨去这一些，分布式文件系统中最大特点，就是文件块的冗余存储，它直接导致了较为复杂的写入流程。当然，虽说分布式文件系统在分布式计算和数据库中都有用到，但如果对其机理没有兴趣，只要把它当成是一个可以在任何机器上使用的文件系统，就不会对其他上层建筑的理解产生障碍。。。]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=997</link><title><![CDATA[VC进程间通信消息]]></title><author>admin</author><category>VC学习</category><pubDate>2009-2-3 17:13:07</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=997</guid><description><![CDATA[进程间通信
void CH31Connect1Dlg::OnOK() 
{
CWnd *pCwnd； 
//用FindWindow函数找到想要关闭的应用程序的句柄的指针 
pCwnd=FindWindow(NULL,"H31CONNECT2")； 
//如果返回成功 
CString str="sendisok"；
COPYDATASTRUCT aa；
aa.dwData=1；
aa.cbData=str.GetLength()+1；
//memcpy(aa.lpData,str,aa.cbData)；
aa.lpData = (void*)str.GetBuffer(aa.cbData)；
if( pCwnd )
::SendMessage(pCwnd->m_hWnd,WM_COPYDATA,(WPARAM)this->m_hWnd,(LPARAM)&aa)；//给其发送关闭的消息 
}
程序2添加OnCopyData事件
BOOL CH31Connect2Dlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct) 
{
// TODO: Add your message handler code here and/or call default
CString aa = (LPCSTR)(pCopyDataStruct->lpData)； 
AfxMessageBox(aa)；
//return CDialog::OnCopyData(pWnd, pCopyDataStruct)；
return true；
}]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=996</link><title><![CDATA[C#测试委托+观察者模式]]></title><author>admin</author><category>VS and C#</category><pubDate>2008-12-5 11:22:43</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=996</guid><description><![CDATA[using System；
using System.Collections.Generic；
using System.Text；
namespace ConsoleApplication1
{
      class Program
    {
        
        static void Main(string[] args)
        {
    //测试委托+观察者模式
            HBoss aa = new HBoss()；
            HObserver bb = new HObserver("XXXX", aa)；
            aa.Update += new EventHandler(bb.CloseStock)；
            aa.SubjectState = "Boss comeback.."；
            aa.Notify()；
        }
    }
    interface Subject
    {
        void Notify()；
        string SubjectState
        {
            get；
            set；
        }
    }
    class HObserver
    {
        private string name；
        private Subject sub；
        public HObserver(string name,Subject sub)
        {
            this.name = name；
            this.sub = sub；
        }
        public void CloseStock()
        {
            Console.WriteLine("{0},{1}关闭股票行情，继续工作", sub.SubjectState,name)；
        }
    }
    delegate void EventHandler()；
    class HBoss:Subject
    {
        public event EventHandler Update；
        private string action；
        public void Notify()
        {
            Update()；
        }
        public string SubjectState
        {
            get { return action； }
            set { action = value； }
        }
    }；
}
--------------------------------------------
using System；
using System.Collections.Generic；
using System.Text；
namespace ConsoleApplication1
{
     public class A   
    {   
        public A()   
        {   
        Console.WriteLine('A')；   
        }   
        public virtual void Fun()   
        {   
        Console.WriteLine("A.Fun()")；   
        }   
    }
    public class B : A
    {
        public B()
        {
            Console.WriteLine('B')；
        }
        public new void Fun()
        {
            Console.WriteLine("B.Fun()")；
        }
    }
    class Class1 
    { 
        public static int Count = 0； 
        static Class1() 
        { 
        Count++； 
        } 
        public Class1() 
        { 
        Count++； 
        } 
    } 
    class Program
    {
        
        static void Main(string[] args)
        {
            //A a = new B()；   
            //a.Fun()；
            //Class1 o1 = new Class1()；
            //Class1 o2 = new Class1()； 
   //输出A         B           A.Fun()

        }
    }
 
}]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=995</link><title><![CDATA[DataTable使用技巧总结 ]]></title><author>admin</author><category>VS and C#</category><pubDate>2008-9-25 9:32:23</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=995</guid><description><![CDATA[       在项目中经常用到DataTable,如果DataTable使用得当，不仅能使程序简洁实用，而且能够提高性能，达到事半功倍的效果，现对DataTable的使用技巧进行一下总结。 


        一、DataTable简介     

          (1)构造函数 
          DataTable()   不带参数初始化DataTable 类的新实例。 
          DataTable(string tableName)  用指定的表名初始化DataTable 类的新实例。 
          DataTable(string tableName, string tableNamespace) 用指定的表名和命名空间初始化DataTable 类的新实例。 
          (2) 常用属性 
          CaseSensitive    指示表中的字符串比较是否区分大小写。 
          ChildRelations   获取此DataTable 的子关系的集合。 
          Columns             获取属于该表的列的集合。 
          Constraints        获取由该表维护的约束的集合。 
          DataSet               获取此表所属的DataSet。DataSet相关信息，可见我以前的一篇文章《数据访问(2)－DataSet》 
          DefaultView       获取可能包括筛选视图或游标位置的表的自定义视图。 
          HasErrors          获取一个值，该值指示该表所属的DataSet 的任何表的任何行中是否有错误。 
          MinimumCapacity  获取或设置该表最初的起始大小。该表中行的最初起始大小。默认值为 50。 
          Rows                  获取属于该表的行的集合。 
          TableName       获取或设置DataTable 的名称。 


          (3)常用方法 
          AcceptChanges()   提交自上次调用AcceptChanges() 以来对该表进行的所有更改。 
          BeginInit()         开始初始化在窗体上使用或由另一个组件使用的DataTable。初始化发生在运行时。 

          Clear()               清除所有数据的DataTable。 
          Clone()              克隆DataTable 的结构，包括所有DataTable 架构和约束。 
          EndInit()            结束在窗体上使用或由另一个组件使用的DataTable 的初始化。初始化发生在运行时。 
          ImportRow(DataRow row)    将DataRow 复制到DataTable 中，保留任何属性设置以及初始值和当前值。 
          Merge(DataTable table)  将指定的DataTable 与当前的DataTable 合并。 
          NewRow()         创建与该表具有相同架构的新DataRow。
 
        二、DataTable使用技巧

      （1）Create a DataTable
         DataTable dt = new DataTable("Table_AX")； 


      （2）Add columns for DataTable
        //Method 1
        dt.Columns.Add("column0", System.Type.GetType("System.String"))；
        //Method 2
        DataColumn dc = new DataColumn("column1", System.Type.GetType("System.Boolean"))；
        dt.Columns.Add(dc)； 

       （3）Add rows for DataTable
         //Initialize the row
         DataRow dr = dt.NewRow()；
         dr["column0"] = "AX"；
         dr["column1"] = true；
         dt.Rows.Add(dr)；
         //Doesn't initialize the row
         DataRow dr1 = dt.NewRow()；
         dt.Rows.Add(dr1)；  


        （4）Ｓelect row
         //Search the second row 如果没有赋值,则用is null来Ｓelect
         DataRow[] drs = dt.Ｓelect("column1 is null")；
         DataRow[] drss = dt.Ｓelect("column0 = 'AX'")； 


        （5）Copy DataTable include data
         DataTable dtNew = dt.Copy()； 

        （6）Copy DataTable only scheme
         DataTable dtOnlyScheme = dt.Clone()； 

        （7）Operate one row
         //对dt的操作
         //Method 1
         DataRow drOperate = dt.Rows[0]；
         drOperate["column0"] = "AXzhz"；
         drOperate["column1"] = false；
         //Method 2
         drOperate[0] = "AXzhz"；
         drOperate[1] = false；
         //Method 3
         dt.Rows[0]["column0"] = "AXzhz"；
         dt.Rows[0]["column1"] = false；
         //Method 4
         dt.Rows[0][0] = "AXzhz"；
         dt.Rows[0][1] = false； 

       （8）Evaluate another DataTable's row to current Datatable
         dtOnlyScheme.Rows.Add(dt.Rows[0].ItemArray)； 

       （9）Convert to string
         System.IO.StringWriter sw = new System.IO.StringWriter()；
         System.Xml.XmlTextWriter xw = new System.Xml.XmlTextWriter(sw)；
         dt.WriteXml(xw)；
         string s = sw.ToString()；

        （10）Filter DataTable
         dt.DefaultView.RowFilter = "column1 <> true"；
         dt.DefaultView.RowFilter = "column1 = true"；

        （11）Sort row
          dt.DefaultView.Sort = "ID ,Name ASC"；
          dt=dt.DefaultView.ToTable()；

         （12）Bind DataTable
           //绑定的其实是DefaultView
          gvTestDataTable.DataSource = dt；
          gvTestDataTable.DataBind()；

         （13）judge the DataTable’s Column name is a string
          //判断一个字符串是否为DataTable的列名
         dtInfo.Columns.Contains("AX")；

         （14）DataTable convert to XML and XML convert to DataTable
          protected void Page_Load(object sender, EventArgs e)
          {
             DataTable dt_AX = new DataTable()； 
             //dt_AX.Columns.Add("Sex", typeof(System.Boolean))；
             //DataRow dr = dt_AX.NewRow()；
             //dr["Sex"] = true；
             //dt_AX.Rows.Add(dr)； 
             string xml=ConvertBetweenDataTableAndXML_AX(dt_AX)；
             DataTable dt = ConvertBetweenDataTableAndXML_AX(xml)；
         } 
         public string ConvertBetweenDataTableAndXML_AX(DataTable dtNeedCoveret)
        {
             System.IO.TextWriter tw = new System.IO.StringWriter()；
             //if TableName is empty, WriteXml() will throw Exception.                  

dtNeedCoveret.TableName=dtNeedCoveret.TableName.Length==0?"Table_AX":dtNeedCoveret.TableName；
             dtNeedCoveret.WriteXml(tw)；
             dtNeedCoveret.WriteXmlSchema(tw)；
             return tw.ToString()；
        } 
         public DataTable ConvertBetweenDataTableAndXML_AX(string xml)
        {
             System.IO.TextReader trDataTable = new System.IO.StringReader(xml.Substring(0, xml.IndexOf("<?xml")))；
             System.IO.TextReader trSchema = new System.IO.StringReader(xml.Substring(xml.IndexOf("<?xml")))；
             DataTable dtReturn = new DataTable()；
             dtReturn.ReadXmlSchema(trSchema)；
             dtReturn.ReadXml(trDataTable)；
             return dtReturn；
        }

]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=994</link><title><![CDATA[SqlServer存储过程之简单入门 ]]></title><author>admin</author><category>Oralce</category><pubDate>2008-9-25 9:30:30</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=994</guid><description><![CDATA[一、简单实例

1.首先来一个最简单的存储过程吧
CREATE PROCEDURE dbo.testProcedure_AX
AS
Ｓelect userID from USERS order by userid desc
注:dbo.testProcedure_AX是你创建的存储过程名,可以改为:AXzhz等,别跟关键字冲突就行了,AS下面就是一条SQL语句.


2.如何在ASP.NET中调用这个存储过程?
        public static string GetCustomerCName(ref ArrayList arrayCName,ref ArrayList arrayID)
        {
            SqlConnection con=ADConnection.createConnection()；
            SqlCommand cmd=new SqlCommand("testProcedure_AX",con)；
            cmd.CommandType=CommandType.StoredProcedure；
            con.Open()；
            try
            {
                SqlDataReader dr=cmd.ExecuteReader()；
                while(dr.Read())
                {
                    if(dr[0].ToString()=="")
                    {
                        arrayCName.Add(dr[1].ToString())；
                    }
                }
                con.Close()； 
                return "OK!"；
            }
            catch(Exception ex)
            {
                con.Close()；
                return ex.ToString()；
            }
        }
注:其实就是把以前
SqlCommand cmd=new SqlCommand("Ｓelect userID from USERS order by userid desc",con)；
中的SQL语句替换为存储过程名,再把cmd的类型标注为CommandType.StoredProcedure(存储过程)


3.再来个带参数的存储过程吧.
CREATE PROCEDURE dbo.AXzhz
/*
这里写注释
*/
@startDate varchar(16),
@endDate varchar(16) 
AS
 Ｓelect id  from table_AX where commentDateTime>@startDate and commentDateTime<@endDate order by contentownerid DESC

注:@startDate varchar(16)是声明@startDate 这个变量,多个变量名间用【,】隔开.后面的SQL就可以使用这个变量了.


4.如何在ASP.NET中调用这个带参数的存储过程?
 public static string GetCustomerCNameCount(string startDate,string endDate,ref DataSet ds)
{
            SqlConnection con=ADConnection.createConnection()；
//-----------------------注意这一段--------------------------------------------------------------------------
            SqlDataAdapter da=new SqlDataAdapter("AXzhz",con)；

            para0=new SqlParameter("@startDate",startDate)；
            para1=new SqlParameter("@endDate",endDate)；
            da.ＳelectCommand.Parameters.Add(para0)；
            da.ＳelectCommand.Parameters.Add(para1)；
            da.ＳelectCommand.CommandType=CommandType.StoredProcedure；
//-----------------------------------------------------------------------------------------------------------
            try
            {
                con.Open()；
                da.Fill(ds)；
                con.Close()；
                return "OK"；
            }
            catch(Exception ex)
            {
                return ex.ToString()；
            }            
        }

注:把命令的参数添加进去,就OK了.

5.如何查看SQL命令执行成功了没有.
CREATE PROCEDURE dbo.AXzhz
/*
  @parameter1 用户名
  @parameter2 新密码
*/
@password nvarchar(20),
@userName nvarchar(20)
AS
declare @err0 int
update WL_user set password=@password where UserName=@userName
set @err0=@@error 
Ｓelect  @err0 as err0

注:先声明一个整型变量@err0,再给其赋值为@@error(这个是系统自动给出的语句是否执行成功,0为成功,其它为失败),最后通过Ｓelect把它选择出来.


6.如何从后台获得这个执行成功与否的值?
下面这段代码可以告诉你答案:
    public static string GetCustomerCName()
        {
            SqlConnection con=ADConnection.createConnection()；            
            SqlCommand cmd=new SqlCommand("AXzhz",con)；
            cmd.CommandType=CommandType.StoredProcedure；
            para0=new SqlParameter("@startDate","2006-9-10")；
            para1=new SqlParameter("@endDate","2006-9-20")；
            da.ＳelectCommand.Parameters.Add(para0)；
            da.ＳelectCommand.Parameters.Add(para1)； 
            con.Open()；
            try
            {
               Int32 re=(int32)cmd.ExecuteScalar()； 
                con.Close()； 
                if (re==0)
                 return "OK!"；
                else
                 return "false"；
            }
            catch(Exception ex)
            {
                con.Close()；
                return ex.ToString()；
            }
        }

7.如何根据传入的参数判断执行哪条SQL语句?
ALTER PROCEDURE dbo.ＳelectCustomerCNameCount
@customerID int
AS
if @customerID=-1
 begin
 Ｓelect contentownerid ,userCName,count(*) as countAll from view_usercomment group by contentownerid,userCName order by contentownerid DESC
 end
else
 begin
 Ｓelect contentownerid ,userCName,count(*) as countAll from view_usercomment where contentownerid=@customerID group by contentownerid,userCName order by contentownerid DESC
 end

二、C＃中使用带返回值的存储过程

例如在向数据库添加新数据时，需要检测是否有重复
本例介绍如何把这个检测的过程放在存储过程中，并用程序调用检测的结果做出反应。
存储过程如下：
CREATE PROCEDURE DInstitute_Insert
@InstituteNO nvarchar(6),@InstituteName nvarchar(40)
 AS
declare @return int,@count int
if(ltrim(rtrim(@InstituteName))='' or ltrim(rtrim(@InstituteNO))='')
 Ｓelect @return=3--返回3表示提交的数据有空值
else
begin
 Ｓelect @count=count(1) from DInstitute where InstituteNO=@InstituteNO
 if(@count>0)
  Ｓelect @return=1--返回1表示编号有重复
 else
 begin 
  insert into DInstitute (InstituteNO,InstituteName) values  (@InstituteNO,@InstituteName) 
  if(@@error>0)
   Ｓelect @return=2--返回2表示数据操作错误
  else
   Ｓelect @return=0--返回0表示数据操作成功
 end
end
return @return
GO 

其中DInstitute 是一个学院信息表。只有InstituteNO（学院编号）、InstituteName（学院名称）两个字段。

在C＃中调用本存储过程的代码如下：
//执行插入操作
            SqlCommand com1 = new SqlCommand("DInstitute_Insert", DBcon)；
            if (com1.Connection.State == ConnectionState.Closed)
                com1.Connection.Open()；
            com1.CommandType = CommandType.StoredProcedure；
            com1.Parameters.Add(new SqlParameter("@InstituteNO",SqlDbType.NVarChar,6))；
            com1.Parameters.Add(new SqlParameter("@InstituteName", SqlDbType.NVarChar, 40))；
            com1.Parameters.Add(new SqlParameter("@return", SqlDbType.Int))；
            com1.Parameters["@return"].Direction = ParameterDirection.Returnvalue；
            com1.Parameters["@InstituteNO"].value = t_NO.Text；
            com1.Parameters["@InstituteName"].value = t_name.Text；
            try
            {
                com1.ExecuteScalar()；
            }
            catch(SqlException ee)
            {
                DB.msgbox("操作失败！"+ee.Message.ToString())；
                return；
            }
            finally
            {
                com1.Connection.Close()；
            }
            string temp = com1.Parameters["@return"].value.ToString()；
            //返回0表示数据操作成功
            //返回1表示编号有重复   
            //返回2表示数据操作错误 
            //返回3表示提交的数据有空值
            switch (temp)
            {
                case "0":
                    DB.msgbox("添加成功！")；
                    break；
                case "1":
                    DB.msgbox("编号有重复！")；
                    break；
                case "2":
                    DB.msgbox("数据操作错误！")；
                    break；
                case "3":
                    DB.msgbox("提交的数据有空值！")；
                    break；
            }
            Binding()； //刷新datagrid 

三、SqlServer存储过程的事务处理

方法一：
--测试的表   
  create   table   tb(  id   int     not   null     constraint   PK_sys_zj_fielddict   primary   key   ,aa   int)   
  --事务处理   
  begin   tran   
  insert   into   tb   values(1,1)   
  if   @@error<>0   goto   lb_rollback   
  insert   into   tb   values(1,1)   
  if   @@error<>0   goto   lb_rollback   
  insert   into   tb   values(2,1)   
  if   @@error<>0   goto   lb_rollback   
  insert   into   tb   values(2,1)   
  if   @@error<>0   goto   lb_rollback   
  insert   into   tb   values(3,1)   
  if   @@error<>0   goto   lb_rollback   
  lb_commit:   
  commit   tran   
  goto   lb_ok   
  lb_rollback:   
  rollback   tran       
  --显示结果   
  lb_ok:   
  Ｓelect   *   from   tb   
  drop   table   tb


方法二：
--创建测试表   
  create   table   tb(id   int     not   null     constraint   PK_sys_zj_fielddict   primary   key  ,aa   int)       
  --设置选项   
  SET   XACT_ABORT   on       
  --事务处理   
  begin   tran   
  insert   into   tb   values(1,1)   
  insert   into   tb   values(1,1)   
  insert   into   tb   values(2,1)   
  commit   tran       
  --显示结果   
  /*--------注意   
          如果这样写的话,后面的语句不会被执行,如果要执行后面的语句,要在这句后面加上GO,仅查询分析分析器支持,所以如果是在存储过程中,要保证commit   tran后面没有其他语句,否则出错时,其他语句不会被执行   
  -----------*/   
  Ｓelect   *   from   tb   
  drop   table   tb   

四、.Net中使用事务处理


SqlConnection myConnection = new SqlConnection("Data Source=localhost；Initial Catalog=Northwind；Integrated Security=SSPI；")； 
myConnection.Open()； 

SqlTransaction myTrans = myConnection.BeginTransaction()； //使用New新生成一个事务 
SqlCommand myCommand = new SqlCommand()； 
myCommand.Transaction = myTrans； 

try 
{ 
myCommand.CommandText = "Update Address set location='23 rain street' where userid='0001'"； 
myCommand.ExecuteNonQuery()； 

myCommand.CommandText = "Update table2 set dd='23 rain street' where userid='0001'"； 
myCommand.ExecuteNonQuery()； 

myTrans.Commit()； 
Console.WriteLine("Record is udated.")； 
} 
catch(Exception e) 
{ 
myTrans.Rollback()； 
Console.WriteLine(e.ToString())； 
Console.WriteLine("Sorry, Record can not be updated.")； 
} 
finally 
{ 
myConnection.Close()； 
}

说明：在SqlServer中，每条Sql语句都作为一个事务来执行，所以无论在存储过程，还是在.net代码中使用，执行单条Sql语句没有必要使用事务处理。

信息来源：http://www.cnblogs.com/tuyile006/

]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=993</link><title><![CDATA[C#中取得程序當前工作目錄和執行目錄的一些方法 ]]></title><author>admin</author><category>VS and C#</category><pubDate>2008-9-20 8:44:26</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=993</guid><description><![CDATA[总结C#中得到程序当前工作目录和执行目录的一些方法 

1.   System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName
     获取模块的完整路径。
2.   System.Environment.CurrentDirectory
     获取和设置当前目录(该进程从中启动的目录)的完全限定目录。
3.   System.IO.Directory.GetCurrentDirectory() 
     获取应用程序的当前工作目录。这个不一定是程序从中启动的目录啊，有可能程序放在C:\www里,这个函数有可能返回C:\Documents and Settings\ZYB\,或者C:\Program Files\Adobe\,有时不一定返回什么东东，我也搞不懂了。
4.  System.AppDomain.CurrentDomain.BaseDirectory
     获取程序的基目录。
5.  System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase
     获取和设置包括该应用程序的目录的名称。
6.  System.Windows.Forms.Application.StartupPath 
     获取启动了应用程序的可执行文件的路径。效果和2、5一样。只是5返回的字符串后面多了一个"\"而已
7.  System.Windows.Forms.Application.ExecutablePath
     获取启动了应用程序的可执行文件的路径及文件名，效果和1一样。

8.进程对象在.NET中表现为System.Diagnostics.Process类，通过调用Process.GetCurrentProcess().MainModule.FileName可获得当前执行的exe的文件名。


//正在运行的程序路径和文件名 
string _file = Application.ExecutablePath；
//正在运行的程序路径 
string _path = Application.StartupPath；
//正在运行的程序名称 
string _name = _file.Replace(string.Format("{0}\\", _path), string.Empty)； 
]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=992</link><title><![CDATA[如何將程序的Access数据库嵌入到资源中发布 ]]></title><author>admin</author><category>VS and C#</category><pubDate>2008-9-20 8:42:19</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=992</guid><description><![CDATA[ 1        private string AccessPath = Application.StartupPath.ToString() + "\\" + "SAP_Words.dll"；
 2        private string ResourcesPath = Application.StartupPath.ToString() + "\\" + "Resources.dll"；
 3
 4       調用 WriteEmbeddedFile("SAP_Words.dll", AccessPath)；
 5        private Stream GetStream(string name)
 6        {
 7            return GetResourceAssembly().GetManifestResourceStream("資源類的命名空間."+name)；
 8        }
 9
10        private Assembly GetResourceAssembly()
11        {
12            return Assembly.LoadFrom(ResourcesPath)；
13        }
14
15        private void WriteEmbeddedFile(string name, string fileName)
16        {
17            using (Stream stream = GetStream(name))
18            {
19                FileInfo file = new FileInfo(fileName)；
20                using (FileStream fileStream = file.Create())
21                {
22                    byte[] buf = new byte[1024]；
23                    int size；
24                    while ((size = stream.Read(buf, 0, 1024)) > 0)
25                    {
26                        fileStream.Write(buf, 0, size)；
27                    }
28                }
29            }
30        }
]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=991</link><title><![CDATA[用反射方法使用户控件动态调用父页面的方法 ]]></title><author>admin</author><category>VS and C#</category><pubDate>2008-9-20 8:41:32</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=991</guid><description><![CDATA[下面演示了用户控件调用父页面SetLaeble方法。
父页面类型用反射的方法获取，这避免了不同页面调用同样时，需要类型转换的问题（不用写很多case了:)）。

当然还有一种方法是父页面去实现一个接口，即去实现SetLabel方法，uc把this.Page转成这个接口就可以了。


用户控件:

using System.Reflection；
private void Button1_Click(object sender, System.EventArgs e)
        {
            //用反射方法动态调用父页面的方法
            System.Web.UI.Page p = this.Page；
            Type pageType = p.GetType()；
            MethodInfo mi = pageType.GetMethod("SetLabel")；
            mi.Invoke(p,new object[]{"你这个大猪猪!"})；
            
        } 

父页面： 

public void SetLabel(string str)
        {
            this.Label1.Text = str；
        } 
]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=990</link><title><![CDATA[用C#代码生成一个简单的PDF文件 ]]></title><author>admin</author><category>VS and C#</category><pubDate>2008-9-20 8:33:00</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=990</guid><description><![CDATA[using System；
using System.IO；
using System.Text；
using System.Collections；

namespace PDFGenerator
{
     /// <summary>
     /// Application : Generation of PDF file from text
     /// Author         : Pramod Kumar Singh 
     /// Date         : 25th July 2001
     ///</summary>
        
     public class PDFGenerator
     {
         static float pageWidth = 594.0f；
         static float pageDepth = 828.0f；
         static float pageMargin = 30.0f；
         static float fontSize = 10.0f；
         static float leadSize = 10.0f；
        
         //Create a PDF file.
         //PDF on Disk
         static StreamWriter pPDF=new StreamWriter("F:\\Temp\\myPDF.pdf")；
         //PDF in Memory
         static MemoryStream mPDF= new MemoryStream()；
        
         //Convert the Text Data to PDF format and write back to
         //Memory Stream
         static void ConvertToByteAndAddtoStream(string strMsg)
         {
             Byte[] buffer=null；
             buffer=ASCIIEncoding.ASCII.GetBytes(strMsg)；
             mPDF.Write(buffer,0,buffer.Length)；  
             buffer=null；
         }
        
         //Format the data length in xRef Format
         static string xRefFormatting(long xvalue)
         {
             string strMsg =xvalue.ToString()；
             int iLen=strMsg.Length；
             if (iLen<10)
             {
                 StringBuilder s=new StringBuilder()；
                 //string s=null；
                 int i=10-iLen；
                 s.Append('0',i)；
                 strMsg=s.ToString() + strMsg；
             }
             return strMsg；
         }

         //Entry Point
         static void Main(string[] args)
         {
             //Create a ArrayList for xRefs of PDF Document
             ArrayList xRefs=new ArrayList()；
             Byte[] buffer=null；
             float yPos =0f；
             long streamStart=0；
             long streamEnd=0；
             long streamLen =0；
             string strPDFMessage=null；
             //PDF Header Message
             strPDFMessage="%PDF-1.1\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
            
             //ID 1 For Containt
             //ID 2 For Length of the Stream
             //write the Text
            
             //1> Start a new Page
             xRefs.Add(mPDF.Length)；
             strPDFMessage="1 0 obj\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
             strPDFMessage="<< /Length 2 0 R >>\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
             strPDFMessage="stream\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
            
             //Get the start of the stream
             streamStart=mPDF.Length；
             strPDFMessage="BT\n/F0 " + fontSize +" Tf\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
             yPos = pageDepth - pageMargin；
             strPDFMessage=pageMargin + " " + yPos +" Td\n" ；
             ConvertToByteAndAddtoStream(strPDFMessage)；
             strPDFMessage= leadSize+" TL\n" ；
             ConvertToByteAndAddtoStream(strPDFMessage)；
            
             //Add the text data to the PDF memory stream
             strPDFMessage= "(Pramod Kumar Singh)Tj\n" ；
             ConvertToByteAndAddtoStream(strPDFMessage)；
             strPDFMessage= "ET\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
             //Get the End of the stream
             streamEnd=mPDF.Length；
             //Get the Length of the stream
             streamLen=streamEnd-streamStart；
             strPDFMessage= "endstream\nendobj\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
                    
             //Add 2 object to xRef
             xRefs.Add(mPDF.Length)；
             strPDFMessage="2 0 obj\n"+ streamLen + "\nendobj\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
                        
             //Add Page to xRefs
             xRefs.Add(mPDF.Length)；
             strPDFMessage="3 0 obj\n<</Type/Page/Parent 4 0 R/Contents 1 0 R>>\nendobj\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
            
             //Build the Pages
             xRefs.Add(mPDF.Length)；
             strPDFMessage="4 0 obj\n<</Type /Pages /Count 1\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
             strPDFMessage="/Kids[\n3 0 R\n]\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
             strPDFMessage="/Resources<</ProcSet[/PDF/Text]/Font<</F0 5 0 R>> >>\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
             strPDFMessage="/MediaBox [ 0 0 "+ pageWidth + " " + pageDepth + " ]\n>>\nendobj\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
            
             //Add font to xRefs
             xRefs.Add(mPDF.Length)；
             strPDFMessage="5 0 obj\n<</Type/Font/Subtype/Type1/BaseFont/Courier/Encoding/WinAnsiEncoding>>\nendobj\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
                    
             //Add the catalog to xRefs
             xRefs.Add(mPDF.Length)；
             strPDFMessage="6 0 obj\n<</Type/Catalog/Pages 4 0 R>>\nendobj\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
                        
             //xRefs Entry 
             streamStart=mPDF.Length；
             strPDFMessage="xref\n0 7\n0000000000 65535 f \n"；
             for(int i=0；i<xRefs.Count；i++)
             {
                 strPDFMessage+=xRefFormatting((long) xRefs[i])+" 00000 n \n"；
             }
             ConvertToByteAndAddtoStream(strPDFMessage)；
             //Trailer for the PDF
             strPDFMessage="trailer\n<<\n/Size "+ (xRefs.Count+1)+"\n/Root 6 0 R\n>>\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
             //xRef location entry
             strPDFMessage="startxref\n" + streamStart+"\n%%EOF\n"；
             ConvertToByteAndAddtoStream(strPDFMessage)；
             //Write the PDF from Memory Stream to File Stream
             mPDF.WriteTo(pPDF.BaseStream)；
             //Close the Stream
             mPDF.Close()；
             pPDF.Close()；
         }
     }
} ]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=989</link><title><![CDATA[SQL 中 Ｓelect 语句的执行顺序 ]]></title><author>admin</author><category>Oralce</category><pubDate>2008-9-20 8:31:02</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=989</guid><description><![CDATA[    好像自已在书写 SQL 语句时由于不清楚各个关键字的执行顺序, 往往组织的 SQL 语句缺少很好的逻辑, 凭感觉 "拼凑" ( 不好意思, 如果您的 SQL 语句也经常 "拼凑", 那您是不是得好好反省一下呢?, 呵呵). 确实是爽了自己, 可苦了机器, 服务器还需要在我们的杂乱无章的 SQL 语句中寻找它下一句需要执行的关键字在哪里. 效率嘛, 由于我们的感觉神经对秒以下的变化实在不敏感, 暂且就认为自已写的 SQL 顺序无关紧要, "反正没什么变化!", 呵呵.其实服务器对每句 SQL 解析时间都会有详细记录的, 大家可以看一下自已按习惯写的 SQL 和按标准顺序写的SQL解析时间差别有多大.
   因此, 建议大家在平时工作中 SQL 语句按标准顺序写, 一是专业, 二是实用, 呵呵, 不过我觉得最主要的是心里感觉舒服.
   标准的 SQL 的解析顺序为:
   (1).FROM 子句, 组装来自不同数据源的数据
   (2).WHERE 子句, 基于指定的条件对记录进行筛选
   (3).GROUP BY 子句, 将数据划分为多个分组
   (4).使用聚合函数进行计算
   (5).使用 HAVING 子句筛选分组
   (6).计算所有的表达式
   (7).使用 ORDER BY 对结果集进行排序

   举例说明: 在学生成绩表中 (暂记为 tb_Grade), 把 "考生姓名"内容不为空的记录按照 "考生姓名" 分组, 并且筛选分组结果, 选出 "总成绩" 大于 600 分的.
   标准顺序的 SQL 语句为: 
   Ｓelect 考生姓名, max(总成绩) as max总成绩
   from tb_Grade
   where 考生姓名 is not null
   group by 考生姓名
   having max(总成绩) > 600
   order by max总成绩
   
   在上面的示例中 SQL 语句的执行顺序如下:
   (1). 首先执行 FROM 子句, 从 tb_Grade 表组装数据源的数据
   (2). 执行 WHERE 子句, 筛选 tb_Grade 表中所有数据不为 NULL 的数据
   (3). 执行 GROUP BY 子句, 把 tb_Grade 表按 "学生姓名" 列进行分组
   (4). 计算 max() 聚集函数, 按 "总成绩" 求出总成绩中最大的一些数值
   (5). 执行 HAVING 子句, 筛选课程的总成绩大于 600 分的.
   (7). 执行 ORDER BY 子句, 把最后的结果按 "Max 成绩" 进行排序.
]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=988</link><title><![CDATA[Git使用说明-Git入门教程]]></title><author>admin</author><category>工作学习</category><pubDate>2008-9-11 9:52:30</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=988</guid><description><![CDATA[Git和 CVS、SVN不同，是一个分布式的源代码管理工具。Linux内核的代码就是用Git管理的，它很强，也很快：
1.傻瓜都会的初始化，git init, git commit -a, 就完了。对于随便写两行代码就要放到代码管理工具里的人来说，再合适不过。也可以拿git做备份系统，或者同步两台机器的文档，都很方便。 
2.绝大部分操作在本地完成，不用和集中的代码管理服务器交互，终于可以随时随地大胆地check in代码了。可以经常的做commit操作且不会影响他人。 而且即使不在线也可以进行开发。 只有最终完成的版本才需要向一个中心的集中的代码管理服务器提交。
3.每次提交都会对所有代码创建一个唯一的commit id。不像CVS那样都是对单个文件分别进行版本的更改。所以你可以一次性将某次提交前的所有代码check出来，而不用考虑到底提交过那些文件。（其实SVN也可以做到这点）
4.branch管理容易多了，无论是建立新的branch，还是在branch之间切换都一条命令完成，不需要建立多余的目录。 
5.branch之间merge时，不仅代码会merge在一起，check in历史也会保留，这点非常重要。 
6.如果CVS管理代码，由于我们会常常做commit操作。但是在commit之前cvs update时常会遇到将中心库上的其它最新代码checkout下来的情况，此时，一旦出现问题，就很难确认到底是自己开发的bug还是其它用户的代码带来了影响。 而使用git则避免了用户间的开发互相影响。
7.更有利于在代码提交前做code review。 以往用cvs， 都是代码提交后才做code view。如果发生问题， 也无法避免服务器上有不好的代码。 但是用git， 真正向中心库commit前，都是在本地开发，可以方便的进行code review， 然后才提交到中心库。更有利于代码质量。而且， 大家应该可以感到，使用git的过程中，更容易对代码进行code review，因为影响因素更小。
8.创建多分支，更容易在开发中进行多种工作，而使工作间不会互相影响。 比如user2对user1的代码进行code review时，就可以非常方便的保留当时的开发现场，并切换到user1的代码分支，在code review完毕后，也可以非常方便的切换会曾经被中断的工作现场。
Git带来这些好处的同时,确实也使得操作比CVS复杂了一些,但和前面所能获得的好处相比，这些麻烦是值得的。 
当大家用惯了之后会发现，这并不增加多大的复杂性， 而且开发流程会更加自然。
###################################Git操作基本步骤#################################################
Git对大小写区别很敏感
1.git init 创建工程
2.git add . 添加所有文件到工程
3.git-update-index --force-remove res/Thumbs.db 删除res/Thumbs.db索引，因为这个文件没有用
  or git rm res/Thumbs.db (可以通过修改.git\info\exclude文件来添加忽略文件，不要以#开头，直接输入换行就行了)
4.git ls-files 列出此工程所有文件
5.git-status 查看版本库状态
6.git-commit 向版本库提交变化
7.git-branch 分支管理
8.git-merge master dev1 合并分支
9.git-clone [ssh://]username@ipaddr:path 远程获取一个git库 
10.git-pull username@ipaddr: 远端repository名 远端分支名:本地分支名 从远程获取一个git分支 
11.git-push username@ipaddr: 远端repository名 本地分支名:远端分支名 将本地分支内容提交到远端分支
12.git-reset [--mixed | --soft | --hard] [<commit-ish>]  库的逆转与恢复
服务器工程中添加文件和删除文件，在复制工程中要使用合并功能，不然数据到不了复制工程。
##################################开发者请其它同事进行code review##################################
当本地开发完毕，可以请其它同事进行code review：
1. user2通过git-pull命令，将开发者(user1)的开发分支（dev）pull到user2本地的一个tmp分支，并切换工作分支到该分支上进行code review。
2. 完成code review后， user2切换回其原有开发分支继续开发，并告知user1已经修改完毕。
3. User1将user2的tmp分支git-pull到本地tmp分支，并和dev分支进行merge。最终得到一个code review后的dev分支。
##################################Git-SVN###################################
服务器的项目都是使用SVN，在git-svn上工作时：
1.git svn clone -s https://svn.xxx.com/svn/xxx  从svn clone出项目，加上-s参数以标记识别svn标准的目录分支结构：
2.git checkout -b work 建立本地工作分支
3.git commit -a 修改内容直接commit，加上-a开头以省略git add操作 
4.git checkout master 提交回svn的过程
5.git merge work 合并work到master分支
6.git svn rebase 查找更新
7.git svn dcommit 向SVN服务器提交
----------------------------------
git svn fetch 取得更新后的文件
git svn rebase  
git svn fetch -r 625  不复制整个svn 版本仓库，只抓最新的revision 625
##########################Git-SVN更换URL方法还没有成功###########################
1.rm -r .git/svn //Clearing git-svn metadata
2.git config svn-remote.svn.url 'http://191.0.0.31:8080/svn/h31net/H31NetClient'
3.git svn init http://191.0.0.31:8080/svn/h31net/H31NetClient
4.git svn fetch
The solution to this problem is as follows:
1.edit the svn-remote url URL in .git/config to point to the new domain name 
2.run git svn fetch 
3.change svn-remote url back to the original url 
4.run git svn rebase -l to do a local rebase (with the changes that came in with the last fetch operation) 
5.change svn-remote url back to the new url 
6.git svn rebase should now work again! 
---------------------------------]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=987</link><title><![CDATA[屏蔽控件右键菜单(C#版)]]></title><author>admin</author><category>VS and C#</category><pubDate>2008-8-25 18:44:21</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=987</guid><description><![CDATA[其实就是调用一下API而已：  
1、安装钩子：  
　　SetWindowsHookEx  
函数原形： 
HHOOK SetWindowsHookEx( 
    int idHook, // 钩子类型， 
    HOOKPROC lpfn, // 钩子函数地址 
    INSTANCE hMod, // 钩子所在的实例的句柄， 
    DWORD dwThreadId // 钩子所监视的线程的线程号 
)
hMod: 对于线程序钩子，参数传NULL；  
对于系统钩子：参数为钩子DLL的句柄  
　　dwThreadId：对于全局钩子，该参数为NULL。  
钩子类型用WH_CALLWNDPROC=4（发送到窗口的消息。由SendMessage触发）  
返回：成功：返回SetWindowsHookEx返回所安装的钩子句柄；  
失败：NULL；  
2、回调，你要截获消息就在这里进行： 
LRESULT WINAPI MyHookProc( 
    int nCode , // 指定是否需要处理该消息 
    WPARAM wParam, // 包含该消息的附加消息 
    LPARAM lParam // 包含该消息的附加消息 
)
3、调用下一个钩子 
LRESULT CallNextHookEx( 
    HHOOK hhk, // 是您自己的钩子函数的句柄。用该句柄可以遍历钩子链 
    int nCode, // 把传入的参数简单传给CallNextHookEx即可 
    WPARAM wParam, // 把传入的参数简单传给CallNextHookEx即可 
    LPARAM lParam // 把传入的参数简单传给CallNextHookEx即可 
)；
4、用完后记得卸载钩子哦，要不然你的系统会变得奇慢无比！ 
BOOL UnhookWindowsHookEx( 
    HHOOK hhk // 要卸载的钩子句柄。 
)
把上面这些API用C#封装一下，就可以直接用了！  
首先在form界面放一个realG2控件。然后设定CtlControls的属性值为imagewindow,all 
这样我们就可以在form界面上看到一个realplay的播放器界面了。 
然后我们再在form界面上加一个ContextMenu的菜单。自己随便加几个菜单项。 
(我这里面的contextMenu的名字为contextMenu1。你们根据自己的情况 
可以改变名字。但底下的名字也要相应改变）
------------------------------代码区------------------------------------
using System.Runtime.InteropServices；
        //定义委托（钩子函数,用于回调） 
        public delegate int HookProc(int code, IntPtr wparam, ref CWPSTRUCT cwp)；
        //安装钩子的函数 
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SetWindowsHookEx(int type, HookProc hook, IntPtr instance, int threadID)；
        //调用下一个钩子的函数 
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        public static extern int CallNextHookEx(IntPtr hookHandle, int code, IntPtr wparam, ref CWPSTRUCT cwp)；
        //卸载钩子 
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        public static extern bool UnhookWindowsHookEx(IntPtr hookHandle)；
        //获取窗体线程ID 
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        public static extern int GetWindowThreadProcessId(IntPtr hwnd, int ID)；
        private HookProc hookProc；
        private IntPtr hookHandle = IntPtr.Zero；
        //开始拦截 
        private bool StartHook()
        {
            SetWindowsHookEx(7, hookProc, IntPtr.Zero, GetWindowThreadProcessId(Handle, 0))；
            return ((int)this.hookHandle != 0)；
        }
        //停止拦截 
        private bool StopHook()
        {
            return UnhookWindowsHookEx(this.hookHandle)；
        }
        //钩子处理函数，在这里拦截消息并做处理 
        private int myhookproc(int code, IntPtr wparam, ref CWPSTRUCT cwp)
        {
            System.Drawing.Point lp_p = new System.Drawing.Point()；
            if (code > 0)
            {
                if (wparam.ToInt32() == 516)//判断鼠标右击
                {
                    lp_p = Form1.MousePosition；
                    //MessageBox.Show("show menu")；
                    MyMenu.Show(lp_p)；
                    return (1)；
                }
            }
            return CallNextHookEx(hookHandle, code, wparam, ref cwp)；
        }
        [StructLayout(LayoutKind.Sequential)]
        public struct CWPSTRUCT
        {
            public IntPtr lparam；
            public IntPtr wparam；
            public int message；
            public IntPtr hwnd；
        }
        public Form1()
        {
            InitializeComponent()；
            this.hookProc = new HookProc(myhookproc)；
            StartHook()；
        }
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            StopHook()；
        }
 
]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=986</link><title><![CDATA[C#执行Sql事务处理]]></title><author>admin</author><category>VS and C#</category><pubDate>2008-8-22 14:39:06</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=986</guid><description><![CDATA[//执行事务处理
public void DoTran()
{ 　//建立连接并打开
　SqlConnection myConn=GetConn()；
　myConn.Open()；
　SqlCommand myComm=new SqlCommand()；
　//SqlTransaction myTran=new SqlTransaction()；
　//注意，SqlTransaction类无公开的构造函数
　SqlTransaction myTran；
　//创建一个事务
　myTran=myConn.BeginTransaction()；
　try
　{
　　//从此开始，基于该连接的数据操作都被认为是事务的一部分
　　//下面绑定连接和事务对象
　　myComm.Connection=myConn；
　　myComm.Transaction=myTran； //定位到pubs数据库
　　myComm.CommandText="USE pubs"；
　　myComm.ExecuteNonQuery()；//更新数据
　　//将所有的计算机类图书
　　myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'"；
　　myComm.ExecuteNonQuery()；
　　　//提交事务
　　myTran.Commit()；
　}
　catch(Exception err)
　{
　　throw new ApplicationException("事务操作出错，系统信息："+err.Message)；
　 }
　finally
　{
　　myConn.Close()；
　 }
}

　private SqlConnection GetConn()
　　{
　　　string strSql="Data Source=localhost；Integrated Security=SSPI；user id=sa；password="；
　　　SqlConnection myConn=new SqlConnection(strSql)；
　　　return myConn；
　　}
　}
　public class Test
　{
　　public static void Main()
　　{
　　　DbTranSql tranTest=new DbTranSql()；
　　　tranTest.DoTran()；
　　　Console.WriteLine("事务处理已经成功完成。")；
　　　Console.ReadLine()；
　　}
　}]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=985</link><title><![CDATA[SQL事务处理]]></title><author>admin</author><category>Oralce</category><pubDate>2008-8-22 14:38:10</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=985</guid><description><![CDATA[USE master    
GO    
IF EXISTS (Ｓelect * FROM sysdatabases Where NAME = 'Bank')    
    Drop DATABASE Bank    
GO    
Create DATABASE Bank    
ON(    
    NAME = 'Bank_Data',    
     FILENAME = 'D:\data\Bank_Data.mdf',    
    SIZE = 20,    
     FILEGROWTH =5    
)    
LOG ON   
(    
    NAME = 'Bank_Log',    
     FILENAME = 'D:\data\Bank_Log.ldf',    
    SIZE = 10,    
     FILEGROWTH = 2    
)    
GO    
   
IF EXISTS (Ｓelect * FROM sysobjects Where NAME = 'bank')    
    Drop TABLE bank    
Create TABLE bank    
(    
     currentName CHAR(8) NOT NULL,    
     cardID CHAR(10) NOT NULL,    
     currentMoney MONEY NOT NULL   
)    
GO    
   
IF EXISTS (Ｓelect * FROM sysobjects Where NAME = 'transInfo')    
    Drop TABLE transInfo    
Create TABLE transInfo    
(    
     cardID CHAR(10) NOT NULL,    
     transType CHAR(4) NOT NULL,    
     transMoney MONEY NOT NULL,    
     transDate DATETIME NOT NULL   
)    
GO    
   
   
Alter TABLE bank    
    ADD CONSTRAINT CK_currentMoney CHECK(currentMoney>=1)    
Alter TABLE transInfo    
    ADD CONSTRAINT DF_transDate DEFAULT (getDate()) FOR transDate,    
        CONSTRAINT CK_transType CHECK (transType IN ('存入','支取'))    
GO    
   
Insert bank(currentName,cardID,currentMoney)    
Ｓelect '张三',1000,1000 UNION   
Ｓelect '李四',2000,1000    
   
--转帐前余额    
Ｓelect * FROM bank    
   
--开始事务    
BEGIN TRAN    
DECLARE @errorSum INT   
SET @errorSum = 0    
Update bank SET currentMoney=currentMoney-900 Where cardID='1000'   
SET @errorSum=@errorSum+@@error    
Update bank SET currentMoney=currentMoney+900 Where cardID='2000'   
SET @errorSum=@errorSum+@@error    
Insert transInfo(cardID,transType,transMoney,transDate)    
Ｓelect '1000','支取',900,getDate() UNION   
Ｓelect '2000','存入',900,getDate()    
SET @errorSum=@errorSum+@@error    
IF (@errorSum<>0)    
BEGIN   
     PRINT '交易失败'   
    ROLLBACK TRAN    
END   
ELSE   
BEGIN   
     PRINT '交易成功'   
    COMMIT TRAN    
END   
   
Ｓelect * FROM bank    
Ｓelect * FROM transInfo 
]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=984</link><title><![CDATA[GPRS开发系列文章之进阶篇]]></title><author>admin</author><category>网络通信</category><pubDate>2008-8-8 10:10:26</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=984</guid><description><![CDATA[1.       客户端建立GPRS连接 API

客户端开发采用 EVC4.0进行开发，主要讲解的类库为Connection Manager 系列API，客户端在进行GPRS拨号连接时将使用下文所介绍的API。 

Connection Manager(本人译为 连接管理器，此对象为一系列API的集合) 系列API的主要目的是为了集中管理基于Windows Mobile系列的设备网络连接的建立与维护。移动应用程序使用 连接管理器API去建立或规划一个网络连接，而连接管理器则掌控连接过程中的所有状态信息.应用程序在要发起一个连接(比如Internet)，只要简单的告知连接管理器就OK了。

当一个应用程序发起一个网络连接的请求时，连接管理器首先从连接服务提供商（CSPS）处获取所有可能的连接信息，然后连接管理器会从这一系列连接信息中根据开销，延迟、带宽等因素来选择一个最佳的连接，最后连接管理器将被请求的连接排入队列，然后在一个适合的时间使用CSPS来建立连接。     

【API函数】：

a)       ConnMgrApiReadyEvent（）函数
函数原型：HANDLE WINAPI ConnMgrApiReadyEvent()；
利用此函数来我们可以返回一个连接事件的句柄 ，注意在的得到句柄后要记得及时释放

b)       ConnMgrConnectionStatus（）函数
函数原型：
HRESULT WINAPI ConnMgrConnectionStatus(HANDLE hConnection,DWORD * pdwStatus )；利用此函数的返回值pdwStatus，我们可以得到很多的关于连接的信息，例如如果我们连接成功将返回CONNMGR_STATUS_CONNECTED，断开连接将返回CONNMGR_STATUS_DISCONNECTED,他的返回状态信息非常丰富，有14中之多，完全可以满足我们的应用需要；

c)        ConnMgrEnumDestinations()函数
函数原型：
HRESULT WINAPI ConnMgrEnumDestinations(int nIndex,CONNMGR_DESTINATION_INFO * pDestInfo )；
一般我们的PDA在连接GPRS时都有好几个连接，利用此函数我们可以枚举出所有可用的连接，然后再对挑选的连接进行筛选得到一个最佳连接

接下来讲两个很重要的函数，我们将利用两个函数中的一个来发起连接，他们是：

d)       ConnMgrEstablishConnection()和ConnMgrEstablishConnectionSync()函数，这两个函数一个用于发起一个异步连接请求，一个用于同步请求，使用异步连接请求我们可以在发起连接后立即返回，而使用同步请求客户端将一直被阻塞知道函数返回确认连接，关于异步和同步我就不再叙述；
它们的原型依次为：
HRESULT WINAPI ConnMgrEstablishConnection( CONNMGR_CONNECTIONINFO * pConnInfo, HANDLE * phConnection )；
HRESULT WINAPI ConnMgrEstablishConnectionSync(   CONNMGR_CONNECTIONINFO * pConnInfo, HANDLE * phConnection,    DWORD dwTimeout, DWORD * pdwStatus )；
      可以看到两个函数的第一个参数都为一个CONNMGR_CONNECTIONINFO对象，此对象为一个结构体，它保存了客户端发起连接请求的一系列信息，因此，在我们调用此函数时必须构造一个该对象，然后将其作为参数传入连接函数中。这里很有必要讲一下该结构体，该结构体的原型如下：
typedef struct _CONNMGR_CONNECTIONINFO 
{ 
     DWORD cbSize；   DWORD dwParams；DWORD dwFlags；

           DWORD dwPriority；BOOL bExclusive；BOOL bDisabled；GUID guidDestNet；

           HWND hWnd；  UINT uMsg； LPARAM lParam；ULONG ulMaxCost；  ULONG ulMinRcvBw；   
      ULONG ulMaxConnLatency；

        } CONNMGR_CONNECTIONINFO；
其中参数dwFlags用于指定我们的接入点，比如我们常说的CMNER和CMWAP，而参数GUID则标志了我们对应于每个接入点的全球唯一标志符，关于如何得到或者查看GUID，我们可以在“\Program Files\Windows CE Tools\wce420\POCKET PC 2003\Include\Armv4”目录下查看connmgr.h文件，里面包含了各个接入点的GUID，例如：

CMNET 为：(0x436ef144, 0xb4fb, 0x4863, 0xa0, 0x41, 0x8f, 0x90, 0x5a, 0x62, 0xc5, 0x72)

 CMWAP为：0x7022e968, 0x5a97, 0x4051, 0xbc, 0x1c, 0xc5, 0x78, 0xe2, 0xfb, 0xa5, 0xd9

如果想更进一步了解，我们还可以通过查看注册表方式来查看PDA上连接管理器的相关连接信息，在PPC 2003中注册表路径为：

[HKEY_LOCAL_MACHINE\Comm\ConnMgr]，如下图所示：

 



在Destinations一项中就对应我们所有可用的网络连接，这个跟用ConnMgrEnumDestinations()方法得到的是一样的效果，在默认Internet设置中我们将看到CMNET的GUID，如下所示：

 



在这里里面有DestId一项，就对应着我们久违的GUID

e)       ConnMgrReleaseConnection函数
我们在上一步中建立连接后我们将得到一个连接句柄，在重新开始一个新的连接或者断开连接都要调用此函数来释放掉之前创建的连接，它的原型为：
HRESULT WINAPI ConnMgrReleaseConnection( HANDLE hConnection,BOOL bCache )；

【连接管理API大致使用步骤】：

首先我们利用ConnMgrApiReadyEvent()函数来确认是否有可用连接，如果有可用连接我们则利用ConnMgrEnumDestinations()函数枚举所有可用连接，然后遍历所有连接调用我们的同步或异步连接方法ConnMgrEstablishConnectionSync()与ConnMgrEstablishConnection()来发起连接，一旦连接成功后我们就可以进行我们伟大的下一步了，就是和我们的服务器进行通信。
【GPRS demo效果图】




  
 

【GPRSDemo介绍】

GPRSDemo主要利用了上述的几个重要的API函数来获取当前可用连接，并自动选择一个最佳的连接途径，然后启用这个连接，在连接启动成功以后再用socket 进行网络连接，与公网服务器进行通信。

首先检查是否有可用连接

 

BOOL CConnectManager::GetConnMgrAvailable()
{
    HANDLE hConnMgr = ConnMgrApiReadyEvent ()；
    BOOL bAvailbale = FALSE；
    DWORD dwResult = ::WaitForSingleObject ( hConnMgr, 2000 )；
    if ( dwResult == WAIT_OBJECT_0 )
    {
        bAvailbale = TRUE；
    }
    // 关闭
    if ( hConnMgr ) CloseHandle ( hConnMgr )；

    return bAvailbale；
}
                 然后枚举所有可用连接：
                 
void CConnectManager::EnumNetIdentifier ( OUT CStringArray &StrAry )
{
    CONNMGR_DESTINATION_INFO networkDestInfo = {0}；

    // 得到网络列表
    for ( DWORD dwEnumIndex=0； ； dwEnumIndex++ )
    {
        memset ( &networkDestInfo, 0, sizeof(CONNMGR_DESTINATION_INFO) )；
        if ( ConnMgrEnumDestinations ( dwEnumIndex, &networkDestInfo ) == E_FAIL )
        {
            break；
        }
        StrAry.Add ( networkDestInfo.szDescription )；
    }
}

接下来找到“Internet”这个连接，可用远程URL映射的方式来完成，这样可以让系统自动选取一个最好的连接。 


int CConnectManager::MapURLAndGUID ( LPCTSTR lpszURL, OUT GUID &guidNetworkObject, OUT CString *pcsDesc/*=NULL*/ )
{
    if ( !lpszURL || lstrlen(lpszURL) < 1 )
        return FALSE；

    memset ( &guidNetworkObject, 0, sizeof(GUID) )；
    int nIndex = 0；
    HRESULT hResult = ConnMgrMapURL ( lpszURL, &guidNetworkObject, (DWORD*)&nIndex )；
    if ( FAILED(hResult) )
    {
        nIndex = -1；
        DWORD dwLastError = GetLastError ()；
        AfxMessageBox ( _T("Could not map a request to a network identifier") )；
    }
    else
    {
        if ( pcsDesc )
        {
            CONNMGR_DESTINATION_INFO DestInfo = {0}；
            if ( SUCCEEDED(ConnMgrEnumDestinations(nIndex, &DestInfo)) )
            {
                *pcsDesc = DestInfo.szDescription；
            }
        }
    }

    return nIndex；
}





最后启用指定编号的连接并检查连接状态

 

BOOL CConnectManager::EstablishConnection ( DWORD dwIndex )
{
    // 释放之前的连接
    ReleaseConnection ()；

    // 得到正确的连接信息
    CONNMGR_DESTINATION_INFO DestInfo = {0}；
    HRESULT hResult = ConnMgrEnumDestinations(dwIndex, &DestInfo)；
    BOOL bRet = FALSE；
    if(SUCCEEDED(hResult))
    {
        // 初始化连接结构
        CONNMGR_CONNECTIONINFO ConnInfo；

        ZeroMemory(&ConnInfo, sizeof(ConnInfo))；
        ConnInfo.cbSize = sizeof(ConnInfo)；
        ConnInfo.dwParams = CONNMGR_PARAM_GUIDDESTNET；
        ConnInfo.dwFlags = CONNMGR_FLAG_PROXY_HTTP | CONNMGR_FLAG_PROXY_WAP | CONNMGR_FLAG_PROXY_SOCKS4 | CONNMGR_FLAG_PROXY_SOCKS5；
        ConnInfo.dwPriority = CONNMGR_PRIORITY_USERINTERACTIVE；
        ConnInfo.guidDestNet = DestInfo.guid；
        ConnInfo.bExclusive    = FALSE； 
        ConnInfo.bDisabled = FALSE；

        DWORD dwStatus = 0；
        hResult = ConnMgrEstablishConnectionSync(&ConnInfo, &m_hConnection, 10*1000, &dwStatus )；
        if(FAILED(hResult))
        {
            m_hConnection = NULL；
        }
        else bRet = TRUE；
    }

    return bRet；
}

 

检测连接状态

 


BOOL CConnectManager::WaitForConnected ( int nTimeoutSec, DWORD *pdwStatus/*=NULL*/ )
{
    DWORD dwStartTime = GetTickCount ()；
    BOOL bRet = FALSE；
    while ( GetTickCount ()-dwStartTime < (DWORD)nTimeoutSec * 1000 )
    {
        if ( m_hConnection )
        {
            DWORD dwStatus = 0；
            HRESULT hr = ConnMgrConnectionStatus ( m_hConnection, &dwStatus )；
            if ( pdwStatus ) *pdwStatus = dwStatus；
            if ( SUCCEEDED(hr) )
            {
                if ( dwStatus == CONNMGR_STATUS_CONNECTED )
                {
                    bRet = TRUE；
                    break；
                }
            }
        }
        Sleep ( 100 )；
    }

    return bRet；
}
                   最后要记得释放连接
void CConnectManager::ReleaseConnection ()
{
    if ( m_hConnection )
    {
        ConnMgrReleaseConnection(m_hConnection, FALSE)；
        m_hConnection = NULL；
    }
}
 

2.       客户端与服务器端进行socket通信API
socket通信相关开发API在Winsock2.h.文件中定义，因为SOCKET通信不是本文的重点但是又是必须要涉及的 

a)       WSAStartup函数。在应用程序进行Windows Sockets通信时，必须首先调用此函数来指定应用程序要加载的Windows Scoket版本等信息，应用程序结束前我们应该调用WSACleanup去释放掉所用的系统资源

b)       Connect 函数。此函数用来建立连接

c)        Ｓelect 函数。建立连接后，用来侦听是否有数据传输

d)       Send函数。用于给服务器发送消息

3.       服务器端与客户端进行socket通信 
服务器端主要涉及到的库为：
System.Net，System.Net.Sockets，System.IO；
System.Net 命名空间为当前网络上使用的多种协议提供了简单的编程接口，System.Net.Sockets 命名空间为需要严密控制网络访问的开发人员提供了 Windows Sockets (Winsock) 接口的托管实现。 
System.IO 命名空间包含允许读写文件和数据流的类型以及提供基本文件和目录支持的类型。
需要了解的技术有：多线程，事件与委托，SOCKET通信等

http://www.cnblogs.com/jsjkandy/archive/2008/08/06/1262445.html]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=983</link><title><![CDATA[TCP/IP协议和IP组播的视频传输]]></title><author>admin</author><category>网络通信</category><pubDate>2008-7-28 13:48:51</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=983</guid><description><![CDATA[1 TCP／IP协议 

TCP／IP是一组完整的数据协议集，得名于两个最重要的协议：传输控制协议（Transmission Control Protocol .缩写TCP）和网络协议（Internet Protocol.缩写IP）。 

目 前TCP／IP协议已成为占主导地位的通讯协议，它能使各种业务在不同的网上实现互联互通，从技术上为网络融合奠定了基础。它之所以能够获得如此成就，是 因为它具有如下的特点： 开放的协议标准，可以独立于特定计算机和操作系统的开放方式。 独立于特定的物理网络硬件，可以运行在多种网络传输介质上。 

共同的地址规划方案使得整个网络中的任何TCP／IP设备都具有一个唯一的地址。 标准化的高层协议提供了多种可靠的用户服务。 

2 IP技术的发展 

TCP／IP是Internet网络中的重要的技术基础。1989年 WWW的出现，使Internet发生了根本的变化，使其从单纯的数据通信网络发展成为能够在世界范围内共享和发送信息的分布式文件系统，其利用价值飞速 提高，TCP／IP协议也因此得到广泛应用。特别是1993年因特网商业化后，网络规模平均以半年翻一番的速度发展，使人们认识到应该在网络层用网络互联 协议实现不同种类网络的互联，形成一个独立于网络基础设施的平台，这就是IP网。普通IP网传送实时业务不能保证服务质量，因此，宽带IP网应运而生。随 着一些关键术如IP Over DWDM, MPLS流量工程等出现。它可以提供能够和ATM网相当的服务质量，可以实现无缝连接多种不同网络的综合业务网。宽带IP网为开拓新的宽带数据通信和宽带 交互式媒体新业务提供了平台。IP网络对多媒体通信的需求也在日溢膨胀。为此，国际电联（ITU）制定了H.323标准，使得在现有通信网络上进行视频传 输成为可能。 以TCP／IP协议为基础的宽带IP网必将融合三大网络即：有线电视网、电信网、计算机网的先进技术和网络资源，成为未来信息公路的主流。 

3 IP视频传输 

3．1 网络多媒体的应用要求 

因为多媒体信号是交互的互动的，它对网络提出了以下的应用要求： 

（1） 吞吐(throughtput)的要求，是指对高传输带宽，大存储缓冲带宽的要求，和对流量的控制。 

（2） 可靠性的要求：在这里对可靠性的要求不是重点。适当的数据丢失不会过多影响视频播出的实际效果。 

（3） 网络延时要求：对网络延时，抖动要求较高，因为多媒体视频流对网络传输延时和抖动比较敏感。如传输的视频信号与音频信号必须同步等。 

3．2 IP组播 

IP组播，英语原文IP Multicast ，也可译为“成组通信”。1999年IP组播具有了发展的三个关键条件：①支持IP组播的路由协议；②使用开放标准的可测试管理协议；③发展的商业机遇。 从而使其进入高速发展阶段。 IPv4定义了3种IP数据通信方式：①点点通信；②全网广播；③组播。点点通信是指两个IP地址间进行的数据通信；全网广播是指在IP子网内向所有网内 IP地址以广播的方式发送数据包，所有子网内的IP站都能收到全网广播；组播是指在IP网上对一组特定IP地址进行数据传送，是居于以上①与②之间的通信 方式。 对IPv4定义的3种数据通信方式比较我们发现，IP组播具有明显区别于①、②方式的特点：某个IP站点向IP网多个站点发送数据时，可减少不必要的重叠 发送，可有效地利用网络的带宽，可减少网络延时与抖动。IP成组通信的可靠性较之①、②方式差些。 

3．3 IP视频应用要求 

因为网上信息的交互性和互动性，使网络中的信息传输量日益剧增，网络传输的瓶径问题是突出的！在多媒体应用中，视频传输带来的网络带宽问题更突出 些。当n个IP地址同时接收网络多媒体视频流时，设每个视频流所需传输带宽为1.5M，按现在网络结构，所需带宽为n*1.5M，同时会带来无法忍受的网 络延时和抖动。现有的大部分网络多是使用TCP／IP点到点的协议构置，因此我们研究的重点是如何在现有网络条件下不作过多的改变来实现视频的传输，即 IP组播解决方案要与现有网络兼容。 如前所述：多媒体视频流对数据可靠性要求不高，适当的数据丢失不会过多影响视频播出的实际效果。虽然多媒体视频流对网络传输延时和抖动比较敏感，而IP组 播在网络中延时与抖动是很少的。所以用IP成组通信来传输IP视频信号是可行的。 

4 用IP组播实现视频传输的一种方法和它的特点 

目前在IP网上提供视频服务的方式主要有两种: 

1)完全利用路由器的Multicast技术，不需另加服务器转发，但会增加路由器负担，有“广播风暴”危险，网络路由协议也需调整。 

2）利用软件和服务器，在整个IP宽带网上迭加一个处理流媒体的迭加网，由迭加网实现点到多点组播、媒体流路由和多点注入等功能。 我们所用的视频服务方式为2)方案。具体的说就是：计算机配合专用软件组成服务器，实现实时控制。控制的目是：对于多媒体视频服务器端，必须具有最大效率 发送机制，也就是说，系统能够最大限度地在最短时间内响应和满足从多媒体视频接收端送来的视频请求，一次完成指向需求用户所有地址的数据发送，计算机实时 控制系统随时监控视频传输的质量，同时自动调整带宽等。当然传输方法的实现能与目前的网络设施兼容。 

以上方案实施过程中，计算机（服务器）时刻监控着系统，达到尽可能好的广播质量和高效率，绝不用发生辟如“广播风暴” 等危险。 根据以上要求我们设计了如下系统，它由四部分组成它们是：视频发送、视频转发、视频接收、视频控制。以下具体说明各部份的功能： 

① 视频发送 

视频发送为预制视频或者称为实时视频，它可以是独立的计算机，也可以与第一级“视频转发”单元共用一台计算机。具体地说，我们先将视频按MPEG－ 1 编码技术进行实时视频压缩，此格式的数码率为1.5Mbit/s，图象采用SIF格式（352х288），每秒30帧，两路立体声伴音。之所以按MPEG －1 编码技术进行实时视频压缩，因为通过它压缩后的视频信号质量令人满意，而数码率带宽相对比较窄，有利于IP组播。（当然也可以用其它编码技术）。然后我们 将压缩后的讯号送到视频转发端。讯号从视频发送连接到视频转发是点到点的传输（此单元属于IPv4的通信方式①）。 

②视频转发 

视频转发主要作用，是将从视频发送端发送来的视频信号，通过IP网络转发给视频接收端或下一级的视频转发端。它是IP组播传输视频信号的核心，视频信号用IP组播方式转发，即对一组特定IP地址（同一类请求的用户）进行数据传送。视频转发，由转发计算机（服务器）完成。 

③视频接收 

视频接收是用户的多媒体终端。当然要求用户的多媒体终端设备，须能支持IP组播。 

④视频控制 

其主要功能是对转发站点进行控制，用来建立和管理转发站点上的IP组播数据组的传输。控制系统要最大限度地满足，完成指向需求用户的数据发送，同时 密切注意视频传输的质量。具体的说就是，要尽可能多地为同类请求用户发送数据，但要在允许的带宽范围之内。这个带宽，是通过计算机实时控制的，计算机实时 控制系统随时监控视频传输的质量，自动调整带宽；同时对网络其它各项参数也实现实时监控。可见视频控制，实质上也就是计算机的实时控制。计算机实时控制的 好环真接决定了IP组播效果。 

此方案的关键技术是合理的视频转发，寻求最大的效益，又有满意的传输质量。此方案的实现，得益于计算机的实时控制技术。 我们用此方法在100MEthernetLAN网络，实现实时网络IP组播视频传输的试播，传输MPEG－1视频信号（每个视频流带宽1.5M），视频信 号传输效果良好。证明以上方案是可行的。 5 结束语 以上设计方案兼容现有网络，有满意的效果，具有推广应用价值。 
]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=982</link><title><![CDATA[局域网QQ（C#版）]]></title><author>admin</author><category>VS and C#</category><pubDate>2008-7-12 19:47:02</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=982</guid><description><![CDATA[本通讯程序没有服务端和客户端之分，局域网的计算机运行同一程序即可通信。
只可以实现基本的聊天功能和显示在线用户功能。
准备添加传送文件、聊天日志、可选参数等功能。	
using System；
using System.Drawing；
using System.Collections；
using System.ComponentModel；
using System.Windows.Forms；
using System.Data；
using System.Net；
using System.Net.Sockets；
using System.Text；
using System.Threading；

namespace myQQ
{
 /// <summary>
 /// Form1 的摘要说明。
 /// </summary>
 public class Form1 : System.Windows.Forms.Form
 {
  public bool runing=false； //标志
  public UdpClient listen=new UdpClient(2525)；
  public IPEndPoint End；
  public IPAddress groupAddress=IPAddress.Parse("255.255.255.255")； //广播地址
  public int groupPort=2525；  //广播端口
  public string machineName；
  public string machineIP；
  public string romeName；
  public string romeIP；
  public string romeCon；
 
  private System.Windows.Forms.ListBox box；
  private System.Windows.Forms.Label label1；
  private System.Windows.Forms.Label label2；
  private System.Windows.Forms.Button button1；
  private System.Windows.Forms.Button button3；
  private System.Windows.Forms.Label label3；
  private System.Windows.Forms.TextBox t_rec；
  private System.Windows.Forms.TextBox t_send；
  private System.Windows.Forms.Button button2；
  private System.Windows.Forms.Button newmsg；
  /// <summary>
  /// 必需的设计器变量。
  /// </summary>
  private System.ComponentModel.Container components = null；

  public Form1()
  {
   //
   // Windows 窗体设计器支持所必需的
   //
   InitializeComponent()；

   //
   // TODO: 在 InitializeComponent 调用后添加任何构造函数代码
   //
  }

  /// <summary>
  /// 清理所有正在使用的资源。
  /// </summary>
  protected override void Dispose( bool disposing )
  {
   if( disposing )
   {
    if (components != null) 
    {
     components.Dispose()；
    }
   }
   base.Dispose( disposing )；
  }

  #region Windows 窗体设计器生成的代码
  /// <summary>
  /// 设计器支持所需的方法 - 不要使用代码编辑器修改
  /// 此方法的内容。
  /// </summary>
  private void InitializeComponent()
  {
   this.box = new System.Windows.Forms.ListBox()；
   this.t_rec = new System.Windows.Forms.TextBox()；
   this.label1 = new System.Windows.Forms.Label()；
   this.label2 = new System.Windows.Forms.Label()；
   this.t_send = new System.Windows.Forms.TextBox()；
   this.button1 = new System.Windows.Forms.Button()；
   this.button3 = new System.Windows.Forms.Button()；
   this.label3 = new System.Windows.Forms.Label()；
   this.button2 = new System.Windows.Forms.Button()；
   this.newmsg = new System.Windows.Forms.Button()；
   this.SuspendLayout()；
   // 
   // box
   // 
   this.box.ItemHeight = 12；
   this.box.Location = new System.Drawing.Point(0, 32)；
   this.box.Name = "box"；
   this.box.Size = new System.Drawing.Size(200, 352)；
   this.box.TabIndex = 0；
   // 
   // t_rec
   // 
   this.t_rec.BackColor = System.Drawing.SystemColors.InactiveBorder；
   this.t_rec.ForeColor = System.Drawing.Color.FromArgb(((System.Byte)(192)), ((System.Byte)(64)), ((System.Byte)(0)))；
   this.t_rec.Location = new System.Drawing.Point(208, 56)；
   this.t_rec.Multiline = true；
   this.t_rec.Name = "t_rec"；
   this.t_rec.ScrollBars = System.Windows.Forms.ScrollBars.Vertical；
   this.t_rec.Size = new System.Drawing.Size(328, 136)；
   this.t_rec.TabIndex = 1；
   this.t_rec.Text = ""；
   // 
   // label1
   // 
   this.label1.Location = new System.Drawing.Point(208, 40)；
   this.label1.Name = "label1"；
   this.label1.Size = new System.Drawing.Size(120, 16)；
   this.label1.TabIndex = 2；
   this.label1.Text = "接收："；
   // 
   // label2
   // 
   this.label2.Location = new System.Drawing.Point(208, 200)；
   this.label2.Name = "label2"；
   this.label2.Size = new System.Drawing.Size(120, 16)；
   this.label2.TabIndex = 2；
   this.label2.Text = "发送："；
   // 
   // t_send
   // 
   this.t_send.Location = new System.Drawing.Point(208, 216)；
   this.t_send.Multiline = true；
   this.t_send.Name = "t_send"；
   this.t_send.ScrollBars = System.Windows.Forms.ScrollBars.Vertical；
   this.t_send.Size = new System.Drawing.Size(328, 112)；
   this.t_send.TabIndex = 1；
   this.t_send.Text = ""；
   // 
   // button1
   // 
   this.button1.Location = new System.Drawing.Point(440, 344)；
   this.button1.Name = "button1"；
   this.button1.Size = new System.Drawing.Size(75, 32)；
   this.button1.TabIndex = 3；
   this.button1.Text = "发  送"；
   this.button1.Click += new System.EventHandler(this.button1_Click)；
   // 
   // button3
   // 
   this.button3.FlatStyle = System.Windows.Forms.FlatStyle.Popup；
   this.button3.Location = new System.Drawing.Point(88, 3)；
   this.button3.Name = "button3"；
   this.button3.Size = new System.Drawing.Size(56, 23)；
   this.button3.TabIndex = 4；
   this.button3.Text = "刷 新"；
   this.button3.Click += new System.EventHandler(this.button3_Click)；
   // 
   // label3
   // 
   this.label3.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(134)))；
   this.label3.ForeColor = System.Drawing.Color.Blue；
   this.label3.Location = new System.Drawing.Point(8, 8)；
   this.label3.Name = "label3"；
   this.label3.Size = new System.Drawing.Size(80, 16)；
   this.label3.TabIndex = 5；
   this.label3.Text = "在线用户："；
   // 
   // button2
   // 
   this.button2.Location = new System.Drawing.Point(360, 344)；
   this.button2.Name = "button2"；
   this.button2.Size = new System.Drawing.Size(75, 32)；
   this.button2.TabIndex = 6；
   this.button2.Text = "清  空"；
   this.button2.Click += new System.EventHandler(this.button2_Click)；
   // 
   // newmsg
   // 
   this.newmsg.Location = new System.Drawing.Point(208, 56)；
   this.newmsg.Name = "newmsg"；
   this.newmsg.Size = new System.Drawing.Size(328, 136)；
   this.newmsg.TabIndex = 7；
   this.newmsg.Text = "有新消息了！"；
   this.newmsg.Click += new System.EventHandler(this.newmsg_Click)；
   // 
   // Form1
   // 
   this.AutoScaleBaseSize = new System.Drawing.Size(6, 14)；
   this.ClientSize = new System.Drawing.Size(544, 389)；
   this.Controls.Add(this.button2)；
   this.Controls.Add(this.label3)；
   this.Controls.Add(this.button3)；
   this.Controls.Add(this.button1)；
   this.Controls.Add(this.label1)；
   this.Controls.Add(this.box)；
   this.Controls.Add(this.label2)；
   this.Controls.Add(this.t_send)；
   this.Controls.Add(this.t_rec)；
   this.Controls.Add(this.newmsg)；
   this.Name = "Form1"；
   this.Text = "小y"；
   this.Closing += new System.ComponentModel.CancelEventHandler(this.Form1_Closing)；
   this.Load += new System.EventHandler(this.Form1_Load)；
   this.ResumeLayout(false)；

  }
  #endregion

  /// <summary>
  /// 应用程序的主入口点。
  /// </summary>
  [STAThread]
  static void Main() 
  {
   Application.Run(new Form1())；
  }

  private void Form1_Load(object sender, System.EventArgs e)
  { 
   
   
   End=new IPEndPoint(groupAddress,groupPort)；
   //初始化计算机名和端口
   IPHostEntry myentry=Dns.GetHostByName(Dns.GetHostName())；
   IPAddress myaddress=new IPAddress(myentry.AddressList[0].Address)；
   machineName=Dns.GetHostName()；
   machineIP=myaddress.ToString()；

   box.Items.Add("IP            主机名")；

   //开启监听线程
   runing=true；
   Thread myThread=new Thread(new ThreadStart(this.ListenPort))；
   myThread.Start()；

   //发送上线信息
   string sends="0&"+machineIP+"&"+machineName+"&"+machineIP；
   SendPack(sends)；
  }

  //侦听指定端口的广播地址UDP包
  public void ListenPort()
  { 
   IPEndPoint tempEnd=new IPEndPoint(IPAddress.Any,2525)；
   while(runing)
   {
    Application.DoEvents()；
    try
    {
     byte[] recb=listen.Receive(ref tempEnd)；

     // 检查所接收到的信息
     checkMessage(recb)；
    }
    catch(Exception e)
    {
     MessageBox.Show(e.Message.ToString())；
     break；
    }
    
   }
   listen.Close()；
   box.Items.Add("线程已经退出！")；
   runing=false；
  }

  //循环接收包
  public void checkMessage(byte[] recbb)
  {
   string recStr=Encoding.Default.GetString(recbb)；
   string[] rec=recStr.Split('&')；
   switch(rec[0])
   {
    case "0": //刷新
     if(rec[3]==machineIP)
     {
      if(box.FindString(rec[1]+"  "+rec[2])<=0)
           box.Items.Add(rec[1]+"  "+rec[2])；
     }
     else  if(rec[1]==rec[3])
     {
      //从其他机器发送过来的刷新请求
      //返回自己的信息
      string sendstr="0&"+machineIP+"&"+machineName+"&"+rec[3]；
      this.SendPack(sendstr)；
      //如果不存在则添加该用户
      if(box.FindString(rec[1]+"  "+rec[2])<=0)
       box.Items.Add(rec[1]+"  "+rec[2])；
     }
     break；
    case "1": //发言
     if(rec[3]==machineIP)
     {
      romeName=rec[2]；
      romeIP=rec[1]；
      romeCon=rec[4]；
      showNe()；
     }
     break；
    case "2": //传文件
     break； 
    case "9": //确认包
     if(rec[3]==machineIP)
      MessageBox.Show("信息来自：rec[2]("+rec[1]+")\r\n"+rec[4],"消息")；
     break；
   }
   
  }
       
  //发送数据包到广播地址
  public void SendPack(string sendStr)
  {
   
   byte[] sendb=Encoding.Default.GetBytes(sendStr)；
   try
   {
    listen.Send(sendb,sendb.Length,End)；
   }
   catch(Exception e)
   {
    MessageBox.Show(e.Message.ToString())；
   }
  }


  //刷新
  private void button3_Click(object sender, System.EventArgs e)
  {
   box.Items.Clear()；
   box.Items.Add("IP            主机名")；
   string temp2="0&"+machineIP+"&"+machineName+"&"+machineIP；
   this.SendPack(temp2)；
  }

  //关闭循环
  private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
  {
   runing=false；
   UdpClient mm=new UdpClient()；
   IPEndPoint tempIPEnd=new IPEndPoint(IPAddress.Parse("127.0.0.1"),2525)；
   string temps="0&0&0&0&0"；
   byte[] sendb=Encoding.Default.GetBytes(temps)；
   mm.Send(sendb,sendb.Length,tempIPEnd)；
  }
  //发送
  private void button1_Click(object sender, System.EventArgs e)
  {
   if(t_send.Text=="")
   {
    MessageBox.Show("不能发送空信息！")；
    return；
   }
   if(box.ＳelectedItem==null||box.ＳelectedIndex==0)
   {
    MessageBox.Show("请先选择一个用户！")；
    return；
   }
   string send="1&"+machineIP+"&"+machineName+"&"+box.ＳelectedItem.ToString().Split(' ')[0]+"&"+t_send.Text；
   this.SendPack(send)；
  }
  //清空
  private void button2_Click(object sender, System.EventArgs e)
  {
   t_send.Text=string.Empty；
   t_rec.Text=string.Empty；
  }
  //单击显示接收的信息并隐藏自身
  private void newmsg_Click(object sender, System.EventArgs e)
  {
   t_rec.Text="消息来自"+romeName+"("+romeIP+")"+DateTime.Now.ToShortDateString()；
   t_rec.Text+="\r\n> "+romeCon；
   string back="9&"+machineIP+"&"+machineName+"&"+romeIP+"&信息被打开！"；
   this.SendPack(back)；
   newmsg.SendToBack()；

  }
  //当有信息时显示提示按钮
  public void showNe()
  {
   //窗体显示
   if(this.WindowState!=FormWindowState.Normal)
   {
    this.Visible=true；
    this.WindowState=FormWindowState.Normal；
   }
   newmsg.BringToFront()；
  }
 }
}]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=981</link><title><![CDATA[将文件加入到图形文件里；]]></title><author>admin</author><category>VS and C#</category><pubDate>2008-7-12 19:42:14</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=981</guid><description><![CDATA[要做的就是把源码用rar打个包，然后找个bmp文件，打开它，在尾部增加几个特征字符串，再把rar的数据增加上去，ok了。带出去后，打开bmp文件，找到特征字符串，把尾部记录复制出来，保存到一个新文件内； 
该方法同样可以用于EXE文件. 

为了简便操作，用C#编写了一个工具软件，以下是部分代码，(本人菜鸟臭作、高手勿笑)： 


     private bool EncodeDataToBitmap(string srcBmpFile,string srcFile,string destBmpFile){ 
      //加入到文件尾部 
      System.IO.FileStream SBF= null；  
      System.IO.FileStream SF= null；  
      System.IO.FileStream DBF= null； 
      byte[] srcBmpByte； 
      byte[] srcFileByte； 
      try { 
        SBF = new System.IO.FileStream(srcBmpFile,System.IO.FileMode.Open, System.IO.FileAccess.Read)； 
        SF = new System.IO.FileStream(srcFile,System.IO.FileMode.Open, System.IO.FileAccess.Read)； 
        DBF = new System.IO.FileStream(destBmpFile,System.IO.FileMode.CreateNew, System.IO.FileAccess.Write)； 
         
        srcBmpByte = new byte[SBF.Length]； 
        SBF.Read(srcBmpByte,0,(int)SBF.Length)； 
        srcFileByte = new byte[SF.Length]；//取得该数据可以进一步加密一下或压缩一下 
        SF.Read(srcFileByte,0,(int)SF.Length)； 
        DBF.Write(srcBmpByte,0,srcBmpByte.Length)； 
        DBF.Write(System.Text.Encoding.Default.GetBytes("abcdefg"),0,System.Text.Encoding.Default.GetBytes("abcdefg").Length)； 
        DBF.Write(srcFileByte,0,srcFileByte.Length)； 
         
        return true； 
      }catch{ 
        return false； 
      }finally{ 
        if(SBF!=null) 
          SBF.Close()； 
        if(SF!=null) 
          SF.Close()； 
        if(DBF!=null) 
          DBF.Close()； 
      }       
    } 


代码就和上面所说的一样 
1、读bmp数据 
2、读文件数据 
3、创建新bmp文件 
4、写bmp数据 
5、写特征字符串 
6、写文件数据 
7、完毕。 

下面是拆开文件的代码： 
    private bool DecodeDataFromBitmap(string srcBmpFile,string destFile){ 
      System.IO.FileStream SBF = null； 
      System.IO.FileStream DF = null； 
      byte[] srcBmpByte； 
      try{ 
        SBF = new System.IO.FileStream(srcBmpFile,System.IO.FileMode.Open,System.IO.FileAccess.Read)； 
        DF = new System.IO.FileStream(destFile,System.IO.FileMode.CreateNew,System.IO.FileAccess.Write)； 
         
        srcBmpByte = new byte[SBF.Length]； 
        SBF.Read(srcBmpByte,0,(int)SBF.Length)； 
 
        string f = ""； 
        int offset = 0； 
 
        for(int i=0；i<srcBmpByte.Length- 7；i++){ 
          f = ""； 
          for(int j=i；j<i+7；j++){ 
            f+=(char)srcBmpByte[j]； 
          } 
          if(f=="abcdefg"){ 
            offset = i+7； 
            break； 
          } 
        } 
        if(offset==0){ 
          f =""； 
          for(int i=srcBmpByte.Length-7；i<srcBmpByte.Length；i++){ 
            f+=(char)srcBmpByte[i]； 
          } 
          if(f=="abcdefg"){ 
            offset = srcBmpByte.Length-7； 
          }else{ 
            MessageBox.Show("该文件未被加入数据！")； 
            return false； 
          } 
        } 
 
        DF.Write(srcBmpByte,offset,srcBmpByte.Length-offset)； 
        return true； 
      }catch{ 
        return false； 
      }finally{ 
        if(SBF!=null)SBF.Close()； 
        if(DF!=null)DF.Close()； 
      } 
    } 

过程是 
1、读bmp文件 
2、建立新文件 
3、查找特征字符串 
4、写新文件（特征字符串偏移位置+特征字符串长度） 
5、完成。]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=980</link><title><![CDATA[.NET软件保护与破解浅析[转]]]></title><author>admin</author><category>VS and C#</category><pubDate>2008-6-21 10:53:07</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=980</guid><description><![CDATA[<P>.NET软件保护与破解浅析&nbsp;&nbsp;&nbsp;&nbsp; 网上很少看到有关.NET软件保护与破解的文章，刚好分析了几款有一定代表性的.NET软件，于是便将他们的保护措施和如何破解方法记录下来，以便和大家交流。在开始之前，首先申明：本文中反编译和破解的软件只是为学习和研究的目的，请勿非法使用。 </P><P><BR>&nbsp;&nbsp;&nbsp; .NET平台下的软件(exe,dll文件)叫做程序集。它使用一种扩展的PE格式文件来保存。.NET程序集与以往的应用程序不同，它保存的是Microsoft中间语言指令(MSIL)和元数据(Metadata)，而不是机器指令和数据。.NET程序集在运行的时候才会动态将Microsoft中间语言编译成机器指令执行。所以我们不能简单的使用反汇编来解读程序逻辑。初学者明白这点很重要。不过幸运的是，.NET程序集是一种自描述的组件，可以用它自描述的特性反编译出高级程序代码(如c#,vb.net)，这比汇编代码更容易读懂得多。即使不反编译成高级程序代码，Microsoft中间语言本身也是一种抽象、基于堆栈的面向对象伪汇编语言。它本身也比汇编代码更容易读懂。在代码易阅读这点上，.NET程序更容易遭到破解。不过，有矛必有盾，现在有很多产品都可以混淆.NET代码，使得反编译出来后的结果也同样没有可读性。</P><P>&nbsp;&nbsp;&nbsp; .NET程序集与以往的应用程序另一个不同点在于它可以使用强名称签名来防止自身被篡改。使用强名称签名的程序集包含公钥和数字签名信息。.NET在执行具有强名称的程序集前会对它进行安全检查，以防止它被非法篡改。这一点很厉害，它限制了我们通过修改程序代码（爆破）来破解程序的方法。</P><P>&nbsp;&nbsp;&nbsp; .NET平台还提供了很多安全相关的类库，利用这些类库可以写成很强壮的注册算法（如使用RSA对注册文件签名，只有使用私钥才能产生正确的注册码）。这些算法破解者很难破解。</P><P>&nbsp;&nbsp;&nbsp; 另外，现在还出现了很多其他的保护措施，如流程混淆，元数据加密，加密壳，虚拟机技术，编译为本地代码等保护手段。综合使用这些保护手段，会使破解难道大幅度提高。</P><P>&nbsp;&nbsp;&nbsp; 软件破解一般有两种方式。一种方式为不对软件做出修改，只是分析软件注册算法，根据注册算法写出生成正确注册码的注册机程序完成注册过程。第二种方式需要对软件逻辑作出修改，使软件正常判断注册逻辑失效，总是认为软件已经注册成功。这种方式就是通常说的“爆破”。由于第二种方法需要修改软件逻辑，不能保证软件修改后的行为和原来一样，故一般只有在第一种方式尝试失败后才使用第二种方式。</P><P>&nbsp;&nbsp;&nbsp; 下面，我将通过对两款软件的分析来讲解.NET平台下软件的保护措施和破解方法。</P><P>准备的工具： <BR>1，Reflector <A href="http://www.aisto.com/roeder/dotnet/">http://www.aisto.com/roeder/dotnet/</A> <BR>.NET平台下极好的反编译工具。 <BR>2，ManagedSpy <BR>可以查看.NET代码写的程序窗口。&nbsp; </P><P>实例一，GatherBird Copy Large Files 2.4</P><P>&nbsp;&nbsp;&nbsp; 这是个拷贝大文件的工具，它的注册算法不强，可以很容易写出注册机。我们可以通过破解这个软件来了解破解.NET软件的一般流程。</P><P>&nbsp;&nbsp;&nbsp; 首先运行Copy Large Files 2.4 (Windows .Net 2.0 version) 。发现界面有个“Register”的按钮。查看功能说明书，知道这是没有注册的标志，注册了界面上就不会多这个按钮。点击这个按钮，就弹出注册框，窗口标题为“Register”。然后运行ManagedSpy，可以看出DotNetCopyLargeFiles程序有两个窗体，一个“Form1_class”，另一个“RFLib_Forms_Registration_class”。很显然，第二个就是我们弹出的注册框。</P><P>&nbsp;&nbsp;&nbsp; 运行Reflactor，将DotNetCopyLargeFiles.exe拖进Reflactor，查找“RFLib_Forms_Registration_class”就是反编译好的注册框C#或VB源代码。读懂代码。发现在点击“Register”按钮后会触发“button3_Click”函数，在这个函数中将文本框中输入的注册码保存到了registerstring属性中。使用Reflactor查找哪些地方在调用registerstring属性。发现在Form1_class中点击“Register”按钮后将这个registerstring属性保持在Form1_class中的RegistryString字段中。用Reflactor查找谁在使用RegistryString，发现在Form1_class的OnTimerTick方法中将RegistryString传给RSF1_class.SF40Helper方法检查。</P><P>&nbsp;&nbsp;&nbsp; 读懂RSF1_class，这就注册算法所在的类。读代码的时候，可以借助Reflactor反编译成源文件，然后使用VS2005或VS2008编译后动态调试。这样，可以分析SF4就是注册码产出的方法，根据这个方法，我们不难写出注册机算法。下面就是我写的一个注册机算法。当然，你也可以自己写，方法中用到的其他方法和类都可以从Reflector反编译的源代码中得到。</P><P>GenerateKey <BR>1public static string GenerateKey(byte[] exename)&nbsp; <BR>2{&nbsp; <BR>3&nbsp;&nbsp;&nbsp; byte[] buffer = new byte[0x5f];&nbsp; <BR>4&nbsp;&nbsp;&nbsp; byte[] row = new byte[0x800];&nbsp; <BR>5&nbsp;&nbsp;&nbsp; DotNetRandom_class random = new DotNetRandom_class();&nbsp; <BR>6&nbsp;&nbsp;&nbsp; StringBuilder key = new StringBuilder();&nbsp; <BR>7&nbsp;&nbsp;&nbsp; if (!SF20())&nbsp; <BR>8&nbsp;&nbsp;&nbsp; {&nbsp; <BR>9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; random.RRandomSeed2(exename);&nbsp; <BR>10 <BR>11&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; 0x5f; i++)&nbsp; <BR>12&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; buffer[i] = (byte)(i + 0x20);&nbsp; <BR>14&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>15 <BR>16&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SF2(row, ref random);&nbsp; <BR>17&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Random r = new Random();&nbsp; <BR>18&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int numberIndex = GetNumberIndex(buffer,(byte)r.Next(50,57));&nbsp; <BR>19 <BR>20&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; 6; i ++)&nbsp; <BR>21&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; key.Append( Convert.ToChar(row[numberIndex * 2]));&nbsp; <BR>23&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; key.Append(Convert.ToChar( row[numberIndex * 2+1]));&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>24&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int j = 0; j &lt; buffer[numberIndex]; j++)&nbsp; <BR>25&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>26&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; random.RRandomUnsignedLong();&nbsp; <BR>27&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>28&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SF2b(row, ref random);&nbsp; <BR>29 <BR>30&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; numberIndex = GetNumberIndex(buffer, (byte)r.Next(48, 57));&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>31&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>32&nbsp;&nbsp;&nbsp; }&nbsp; <BR>33 <BR>34&nbsp;&nbsp;&nbsp; return key.ToString();&nbsp; <BR>35}&nbsp; <BR>36 <BR>37public static int GetNumberIndex(byte[] buffer, byte number)&nbsp; <BR>38{&nbsp; <BR>39&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; buffer.Length; i++)&nbsp; <BR>40&nbsp;&nbsp;&nbsp; {&nbsp; <BR>41&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (buffer[i] == number)&nbsp; <BR>42&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>43&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return i;&nbsp; <BR>44&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>45&nbsp;&nbsp;&nbsp; }&nbsp; <BR>46 <BR>47&nbsp;&nbsp;&nbsp; return 20;&nbsp; <BR>48}<BR>&nbsp;</P><P>实例二，ANTS Profiler</P><P>&nbsp;&nbsp;&nbsp; ANTS Profiler是一个检测基于.Net Framework的任何语言开发出的应用程序的代码性能的工具。它使用激活码和网络激活的方式进行双重验证。代码经过了混淆器混淆，程序集也经过强名称签名，注册算法也用的是成熟的RSA算法。所以整体安全性不错，是.NET平台下比较成熟的做法，很有借鉴意义。</P><P>&nbsp;&nbsp;&nbsp; 在安装完ANTS Profiler后，运行ANTS Profiler ，会弹出Trial界面，提示还有14天试用期。另外，有一个激活按钮。点击激活按钮，第一步会弹出提示输入激活码的窗口。这里需要先得到正确的激活码，才能进行下面的验证步骤。那么怎么才能得到正确的激活码呢？像分析第一款软件一样，我们先打开ManagedSpy。在ManagedSpy中我们发现Trial界面的窗体类叫_53，输入激活码的窗体类叫_3。很显然，都是经过代码混淆的，这是ANTS Profiler安全保护的第一关-“代码混淆关”。这一关只是增加过其他关的难度，不需要特别处理。</P><P>&nbsp;&nbsp;&nbsp; 在找到注册信息相关的类名_53后，打开Reflactor，搜索_53类，顺藤摸瓜，再找到_3类，这个类在RedGate.Licensing.Client.dll中(RedGate.Licensing.Client.dll是注册相关的程序集，需要重点关注)。读_3类的代码，发现输入的激活码被设置到_2类中SerialNumber属性中，_2类中_2(string text1)方法就是校验激活码的方法。代码如下： </P><P><BR>Code <BR>1private static bool _2(string text1) <BR>2{ <BR>3&nbsp;&nbsp;&nbsp; text1 = text1.ToUpper().Trim(); <BR>4&nbsp;&nbsp;&nbsp; Regex regex = new Regex(@"[A-Z]{2}-[0-9A-Z]{1}-[0-9A-Z]{1}-\d{5}-[0-9A-F]{4}"); <BR>5&nbsp;&nbsp;&nbsp; Regex regex2 = new Regex(@"\d{3}-\d{3}-\d{6}-[0-9A-F]{4}"); <BR>6&nbsp;&nbsp;&nbsp; if (regex.IsMatch(text1)) <BR>7&nbsp;&nbsp;&nbsp; { <BR>8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; string str = text1.Substring(0, 12); <BR>9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; string str2 = string.Format("{0:X4}", _7._1(str)); <BR>10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!text1.EndsWith(str2)) <BR>11&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>12&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <BR>13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>14&nbsp;&nbsp;&nbsp; } <BR>15&nbsp;&nbsp;&nbsp; else if (regex2.IsMatch(text1)) <BR>16&nbsp;&nbsp;&nbsp; { <BR>17&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; string str3 = text1.Substring(0, 14); <BR>18&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; string str4 = string.Format("{0:X4}", _7._1(str3)); <BR>19&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!text1.EndsWith(str4)) <BR>20&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>21&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <BR>22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>23&nbsp;&nbsp;&nbsp; } <BR>24&nbsp;&nbsp;&nbsp; else <BR>25&nbsp;&nbsp;&nbsp; { <BR>26&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <BR>27&nbsp;&nbsp;&nbsp; } <BR>28&nbsp;&nbsp;&nbsp; return true; <BR>29}<BR>&nbsp;&nbsp;&nbsp; 这段代码表明，激活码是符合regex和regex2这两种正则表达式的形式。同时，满足激活码使用_7._1(string text1)方法返回值结尾。很显然，前12为是激活码信息，后四位是激活码校验位。_7._1(string text1)就是计算校验位的方法，代码如下： </P><P><BR>Code <BR>1internal static uint _1(string text1) <BR>2{ <BR>3&nbsp;&nbsp;&nbsp; long num = 0L; <BR>4&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; text1.Length; i++) <BR>5&nbsp;&nbsp;&nbsp; { <BR>6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int num4 = text1[i]; <BR>7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int j = 7; j &gt;= 0; j--) <BR>8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bool flag = ((num &amp; 0x8000L) == 0x8000L) ^ ((num4 &amp; (((int) 1) &lt;&lt; j)) != 0); <BR>10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; num = (num &amp; 0x7fffL) &lt;&lt; 1; <BR>11&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (flag) <BR>12&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; num ^= 0x1021L; <BR>14&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>15&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>16&nbsp;&nbsp;&nbsp; } <BR>17&nbsp;&nbsp;&nbsp; return (uint) num; <BR>18} <BR>19<BR>&nbsp;&nbsp;&nbsp; 知道了激活码的合法形式和校验位的计算方法，我们就可以构建一个合法的激活码。以regex表示的合法激活码形式为例，选择前12位为最小值“AA-0-0-00000”的一个合法激活码，然后使用_7._1(string text1)方法计算校验码为：“DE33”。那么一个合法的激活码就这样得到了“AA-0-0-00000-DE33”。复制激活码到激活窗口，成功通过验证。至此，ANTS Profiler安全保护的第二关-“激活码验证关”通过了。在激活码验证通过后，点击下一步会进入到网络激活或Email激活界面。选择Email激活，我们可以看见ANTS Profiler收集了版本信息，激活码，session，和机器硬件等信息。这可以保障一个注册码只能用在一台电脑上，值得学习。激活请求信息如下：</P><P>activationrequest <BR>1&lt;activationrequest&gt;&nbsp; <BR>2&lt;version&gt;2&lt;/version&gt;&nbsp; <BR>3&lt;machinehash&gt;F6FB-285E-CD12-667D&lt;/machinehash&gt;&nbsp; <BR>4&lt;productcode&gt;5&lt;/productcode&gt;&nbsp; <BR>5&lt;majorversion&gt;3&lt;/majorversion&gt;&nbsp; <BR>6&lt;minorversion&gt;0&lt;/minorversion&gt;&nbsp; <BR>7&lt;serialnumber&gt;AA-0-0-00000-DE33&lt;/serialnumber&gt;&nbsp; <BR>8&lt;session&gt;689fae08-2fdb-485e-9df7-28e2888cfbff&lt;/session&gt;&nbsp; <BR>9&lt;locale&gt;zh-CN&lt;/locale&gt;&nbsp; <BR>10&lt;/activationrequest&gt; <BR>11<BR>&nbsp;&nbsp;&nbsp; 接下来，就是输入激活响应信息，验证激活响应信息的界面了。这是激活界面的第四步，同样代码在_3类中。跟踪代码，发现验证逻辑在RedGate.Licensing.Client.Licence类中的_2(XmlDocument document1, ref _2 _Ref1)方法中，代码如下： </P><P><BR>Code <BR>&nbsp; 1private bool _2(XmlDocument document1, ref _2 _Ref1) <BR>&nbsp; 2{ <BR>&nbsp; 3&nbsp;&nbsp;&nbsp; XmlNodeList elementsByTagName = document1.GetElementsByTagName("data"); <BR>&nbsp; 4&nbsp;&nbsp;&nbsp; XmlNodeList list2 = document1.GetElementsByTagName("signature"); <BR>&nbsp; 5&nbsp;&nbsp;&nbsp; if ((elementsByTagName.Count != 1) || (list2.Count != 1)) <BR>&nbsp; 6&nbsp;&nbsp;&nbsp; { <BR>&nbsp; 7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._3 = _6._1(_6._16); <BR>&nbsp; 8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <BR>&nbsp; 9&nbsp;&nbsp;&nbsp; } <BR>10&nbsp;&nbsp;&nbsp; string outerXml = elementsByTagName[0].OuterXml; <BR>11&nbsp;&nbsp;&nbsp; string innerXml = list2[0].InnerXml; <BR>12&nbsp;&nbsp;&nbsp; if (innerXml.Length == 0) <BR>13&nbsp;&nbsp;&nbsp; { <BR>14&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._3 = _6._1(_6._17); <BR>15&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <BR>16&nbsp;&nbsp;&nbsp; } <BR>17&nbsp;&nbsp;&nbsp; RSACryptoServiceProvider provider = new RSACryptoServiceProvider(); <BR>18&nbsp;&nbsp;&nbsp; string xmlString = "&lt;RSAKeyvalue&gt;&lt;Modulus&gt;zLizNmLUd4VlIWee1GXgn/KxEwcghPASQ+NUzZhbY2fTGzpW64T6yEOdHlIbhX1DX6yAz2gMZKfnpQL2aFqxh5ACFV9dONSTzuQzkqeXwFEARsMxGP3eTQSWMpwVhEcraSn1zOqMb3CRDeQpgasq0lv4HRFhbwalOifKarjEL/8=&lt;/Modulus&gt;&lt;Exponent&gt;AQAB&lt;/Exponent&gt;&lt;/RSAKeyvalue&gt;"; <BR>19&nbsp;&nbsp;&nbsp; provider.FromXmlString(xmlString); <BR>20&nbsp;&nbsp;&nbsp; byte[] signature = Convert.FromBase64String(innerXml); <BR>21&nbsp;&nbsp;&nbsp; byte[] bytes = Encoding.UTF8.GetBytes(outerXml); <BR>22&nbsp;&nbsp;&nbsp; if (!provider.VerifyData(bytes, new SHA1Managed(), signature)) <BR>23&nbsp;&nbsp;&nbsp; { <BR>24&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._3 = _6._1(_6._18); <BR>25&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <BR>26&nbsp;&nbsp;&nbsp; } <BR>27&nbsp;&nbsp;&nbsp; _Ref1._1 = false; <BR>28&nbsp;&nbsp;&nbsp; foreach (XmlNode node in elementsByTagName[0].ChildNodes) <BR>29&nbsp;&nbsp;&nbsp; { <BR>30&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; string name = node.Name; <BR>31&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == null) <BR>32&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>33&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>34&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>35&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name = string.IsInterned(name); <BR>36&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name != "productcodes") <BR>37&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>38&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "serialnumber") <BR>39&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>40&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_0294; <BR>41&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>42&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "productcode") <BR>43&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>44&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_02A7; <BR>45&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>46&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "majorversion") <BR>47&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>48&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_02BF; <BR>49&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "minorversion") <BR>51&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>52&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_02D7; <BR>53&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>54&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "edition") <BR>55&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>56&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_02EF; <BR>57&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>58&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "machinehash") <BR>59&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>60&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_02FF; <BR>61&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>62&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "extension") <BR>63&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>64&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_030F; <BR>65&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>66&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name == "session") <BR>67&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>68&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto Label_0319; <BR>69&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>70&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>71&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>72&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; foreach (XmlNode node2 in node.ＳelectNodes("product")) <BR>73&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>74&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _1 _ = new _1(); <BR>75&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try <BR>76&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>77&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _._1 = node2.ＳelectSingleNode("productname").InnerText; <BR>78&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _._1 = Convert.ToInt32(node2.ＳelectSingleNode("productcode").InnerText); <BR>79&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _._2 = Convert.ToInt32(node2.ＳelectSingleNode("majorversion").InnerText); <BR>80&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _._3 = Convert.ToInt32(node2.ＳelectSingleNode("minorversion").InnerText); <BR>81&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _._2 = node2.ＳelectSingleNode("edition").InnerText; <BR>82&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._1.Add(_); <BR>83&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>84&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>85&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch (Exception) <BR>86&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>87&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>88&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>89&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>90&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>91&nbsp;&nbsp;&nbsp; Label_0294: <BR>92&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._2 = node.InnerText; <BR>93&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>94&nbsp;&nbsp;&nbsp; Label_02A7: <BR>95&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try <BR>96&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>97&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._1 = Convert.ToInt32(node.InnerText); <BR>98&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>99&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch <BR>100&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>101&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>102&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>103&nbsp;&nbsp;&nbsp; Label_02BF: <BR>104&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try <BR>105&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>106&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._2 = Convert.ToInt32(node.InnerText); <BR>107&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>108&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch <BR>109&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>110&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>111&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>112&nbsp;&nbsp;&nbsp; Label_02D7: <BR>113&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try <BR>114&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>115&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._3 = Convert.ToInt32(node.InnerText); <BR>116&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>117&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch <BR>118&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <BR>119&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <BR>120&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>121&nbsp;&nbsp;&nbsp; Label_02EF: <BR>122&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._5 = node.InnerText; <BR>123&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>124&nbsp;&nbsp;&nbsp; Label_02FF: <BR>125&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._1 = node.InnerText; <BR>126&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>127&nbsp;&nbsp;&nbsp; Label_030F: <BR>128&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._1 = true; <BR>129&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <BR>130&nbsp;&nbsp;&nbsp; Label_0319: <BR>131&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _Ref1._4 = node.InnerText; <BR>132&nbsp;&nbsp;&nbsp; } <BR>133&nbsp;&nbsp;&nbsp; return true; <BR>134} <BR>135<BR>&nbsp;&nbsp;&nbsp; 细读这段代码可以发现，激活响应信息已经使用RSA算法进行过数字签名，这样可以防止激活响应信息被篡改。要篡改或伪造激活响应信息，必需破解RSA私钥公钥对。公钥已经在方法体中给出，所以需要破解私钥。从公钥可以看出，它使用的1024位密钥长度，理论攻破时间1011MIPS年。虽然破解的可能性存在，但破解几率很小。这就是ANTS Profiler安全保护的第三关-“RSA数字签名关”。这使得我们不得不放弃伪造激活响应信息的办法。通常碰到RSA等成熟算法，我们只能选择“爆破”这款软件了。</P><P>通过前面的分析，我们发现程序中判断软件是否注册的逻辑放在RedGate.Licensing.Client.dll程序集中的Licence类里面。我们需要做的就是打开Reflactor软件，加载RedGate.Licensing.Client.dll文件，将RedGate.Licensing.Client.dll文件Export成C#工程源文件。然后将源文件中Licence类中所有公共方法都返回注册成功的信息，再重新编译生成新的修改后的RedGate.Licensing.Client.dll文件，用新生成的文件替换原RedGate.Licensing.Client.dll文件。这样，注册判断逻辑就被非法篡改了，使软件误认为已经注册。修改后的Licence类就像下面这样： </P><P><BR>Code <BR>1namespace RedGate.Licensing.Client&nbsp; <BR>2{&nbsp; <BR>3&nbsp;&nbsp;&nbsp; using System;&nbsp; <BR>4 <BR>5&nbsp;&nbsp;&nbsp; public class Licence&nbsp; <BR>6&nbsp;&nbsp;&nbsp; {&nbsp; <BR>7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public bool DisplayUI()&nbsp; <BR>8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return true;&nbsp; <BR>10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>11 <BR>12&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public static Licence GetLicence(int productCode, string productName, int majorVersion, int minorVersion)&nbsp; <BR>13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>14&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new Licence();&nbsp; <BR>15&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>16 <BR>17&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public static Licence GetLicence(int productCode, string productName, int majorVersion, int minorVersion, string path)&nbsp; <BR>18&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>19&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new Licence();&nbsp; <BR>20&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>21 <BR>22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public static void InitializeAtInstall(string productName, int productCode, int majorVersion, int minorVersion, string guid)&nbsp; <BR>23&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>24&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>25 <BR>26&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public bool Activated&nbsp; <BR>27&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>28&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get&nbsp; <BR>29&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>30&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return true;&nbsp; <BR>31&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>32&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>33 <BR>34&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public int DaysLeftInTrial&nbsp; <BR>35&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>36&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get&nbsp; <BR>37&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>38&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0x7fffffff;&nbsp; <BR>39&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>40&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>41 <BR>42&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public string Edition&nbsp; <BR>43&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>44&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get&nbsp; <BR>45&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>46&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return "professional";&nbsp; <BR>47&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>48&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>49 <BR>50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public string LicenceFilePath&nbsp; <BR>51&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>52&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get&nbsp; <BR>53&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>54&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return string.Empty;&nbsp; <BR>55&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>56&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>57 <BR>58&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public string SerialNumber&nbsp; <BR>59&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>60&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get&nbsp; <BR>61&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>62&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return "AA-0-0-00000-DE33";&nbsp; <BR>63&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>64&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; set&nbsp; <BR>65&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>66&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>67&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>68 <BR>69&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public RedGate.Licensing.Client.TrialStatus TrialStatus&nbsp; <BR>70&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>71&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get&nbsp; <BR>72&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <BR>73&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return RedGate.Licensing.Client.TrialStatus.InTrial;&nbsp; <BR>74&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>75&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp; <BR>76&nbsp;&nbsp;&nbsp; }&nbsp; <BR>77}&nbsp; <BR>78</P><P>&nbsp;&nbsp;&nbsp; 爆破虽然是一种好方法，不过.NET中有专门对付这种方法的杀手锏-“强名称签名”。不幸的是，ANTS Profiler使用了强名称签名机制，是我们在爆破软件这条路上受阻。这就是ANTS Profiler软件保护的第四关-“强名称签名”。强名称签名会在软件被加入GAC时验证。如果软件没在GAC中，那么运行软件的时候也会验证。如果软件被篡改，在运行软件的时候软件会直接抛出异常，使得软件无法正常运行。</P><P>&nbsp;&nbsp;&nbsp; 那么如何破解这种强名称签名验证机制呢？</P><P>&nbsp;&nbsp;&nbsp; 有几种办法可以解除强名称的验证机制。第一种方法适合单个文件的软件，对单个文件的软件我们可以移除强名称签名信息，使软件变成弱名称的程序，这样在软件运行的时候就不会验证是否被篡改了。对于引用关系复杂的多文件软件，我们可以采取第二种方法。这种方法需要理由.NET平台上设计的“后门”才能完成。这个“后门”是什么呢？原来，为了使软件开发后能够使用混淆工具混淆，微软允许延迟签名程序集，被延迟签名的程序集可以在混淆之后再重新签名。而为了测试方便，只需要在注册表中加入一条记录就可以使混淆修改后的程序在没有被重新签名前也能运行，即不进行强名称验证。</P><P>&nbsp;&nbsp;&nbsp; 利用这个“后门”，我们只需要将修改后的修改完Licence类编译成新的RedGate.Licensing.Client.dll文件，编译选项中使用任意一对公钥私钥对强名称签名，并选择延迟签名，然后篡改编译后的public key为原始RedGate.Licensing.Client.dll文件的public key。再在注册表中加入一条记录就可以是强名称签名验证机制失效，达到破解的目的。具体做法如下：</P><P>1，我们用sn命令生成一个新的公钥私钥对，用于签名</P><P>sn -k my.key</P><P>2，将反编译的RedGate.Licensing.Client.dll工程文件中Licence类修改成上面的代码，并在编译选项中使用第一步得到的my.key签名程序集。注意，一定要选择延迟签名选项。编译生成新的RedGate.Licensing.Client.dll文件。</P><P>3，使用sn命令得到原始RedGate.Licensing.Client.dll文件的public key和新RedGate.Licensing.Client.dll文件的public key。</P><P>sn -Tp RedGate.Licensing.Client.dll</P><P>4，使用二进制编辑软件，如WinHex打开新的RedGate.Licensing.Client.dll文件，替换掉它的public key为老文件中的public key。</P><P>5，在注册表“Software\Microsoft\StrongName\Verification\”下加一条子健“RedGate.Licensing.Client,7F465A1C156D4D57”即可完成破解。这步也可以使用sn命令代替。</P><P>sn -Vr RedGate.Licensing.Client.dll</P><P>&nbsp;&nbsp;&nbsp; 当然，你可以将修改后的RedGate.Licensing.Client.dll文件打包到一个Patch文件中，并在Patch文件中自动完成替换文件和修改注册表的操作。至此，ANTS Profiler就被破解了。 </P><P>&nbsp;&nbsp;&nbsp; 通过对以上两款软件的分析，我们可以看出，ANTS Profiler的保护手段还是比较成熟的，值得借鉴。它使用了名称混淆，激活码+网络验证，RSA数字签名，强名称签名等保护手段，整体安全系数较高。而GatherBird Copy Large Files则没有充分运用上.NET平台提供的保护措施，还处于比较低的保护级别。如果我们综合运用流程混淆，元数据加密，加密壳，虚拟机技术，编译为本地代码等保护手段，软件保护强度将更高。</P><P>&nbsp;</P>]]></description></item><item><link>http://h31home.com/h312005/blogview.asp?ID=979</link><title><![CDATA[关于CAB在ASP内的调用与使用[转]]]></title><author>admin</author><category>ASP学习</category><pubDate>2008-6-21 10:19:09</pubDate><guid>http://h31home.com/h312005/blogview.asp?logID=979</guid><description><![CDATA[具体代码如下: <BR><FONT face=Verdana><FONT face=Verdana>&lt;%@LANGUAGE="VBSCRIPT" CODEPAGE="936"%&gt;<BR>&lt;% on error resume next%&gt;<BR>&lt;!--#include file="../Connections/ds.asp" --&gt;<BR>&lt;!--#include file="../Connections/informix.asp" --&gt;</FONT><BR>&lt;html&gt;<BR>&lt;head&gt;<BR>&lt;title&gt;*********title&gt;<BR>&lt;meta http-equiv="Content-Type" content="text/html; charset=gb2312"&gt;<BR>&lt;link rel="stylesheet" href="../css/css1.css" type="text/css"&gt;<BR>&lt;/head&gt;<BR>&lt;body&gt;<BR><SPAN style="COLOR: red">&lt;OBJECT id=htactx name=htactx <BR>classid=clsid:FB4EE423-43A4-4AA9-BDE9-4335A6D3C74E codebase="../js/HTActX.cab#version=1,0,0,1" style="HEIGHT: 0px; WIDTH: 0px"&gt;&lt;/OBJECT&gt;<BR><SPAN style="COLOR: #000000"><SPAN style="COLOR: #008000">'在此对CAB包进行应用以后,在下面的代码中进行调用,但总是有问题出现,错误代号为:424(缺少对象)<BR></SPAN>&lt;%<BR><P><FONT face=Verdana>'启用USBkey进行验证</FONT></P><P><FONT face=Verdana>dim SubmitTm<BR>dim ReceiveTm<BR>dim y<BR>dim m<BR>dim d<BR>dim h<BR>dim n<BR>dim se<BR>Dim FirstDigest<BR>Dim Digest<BR>dim EnData<BR>Digest= "01234567890123456"<BR>'dim htactx<BR>'set htactx = CreateObject("HTSrvActX.HTSrvActXCtrl")<BR>dim LibVer<BR>&nbsp;LibVer = htactx.GetLibVersion<BR>&nbsp;If Err.number &lt;&gt; 0 Then<BR>&nbsp;&nbsp;&nbsp;' response.Write(err.number)<BR>&nbsp;&nbsp;&nbsp; response.write "&lt;script language=javascript&gt; alert('加载客户端控件失败！');history.go(-1);&lt;/script&gt;"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.end<BR>&nbsp;else<BR>&nbsp;dim hCard<BR>&nbsp;&nbsp;hCard = 0&nbsp;<BR>&nbsp;&nbsp;hCard = htactx.OpenDevice(1)'打开设备<BR>&nbsp;&nbsp;If Err.number&lt;&gt;0 or hCard = 0 then<BR>&nbsp;&nbsp; response.Write(err.number)<BR>&nbsp;&nbsp;response.write "&lt;script language=javascript&gt; alert('打开硬件锁失败！');history.go(-1);&lt;/script&gt;"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.end<BR>&nbsp;&nbsp;end if<BR>&nbsp;&nbsp;dim UserName<BR>&nbsp;&nbsp;UserName = htactx.GetUserName(hCard)'获取用户名<BR>&nbsp;&nbsp;if id&lt;&gt;UserName then<BR>&nbsp;&nbsp;response.write "&lt;script language=javascript&gt; alert('您输入的登录编号与锁不一致,请核查！');history.go(-1);&lt;/script&gt;"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.end<BR>&nbsp;&nbsp;end if<BR>&nbsp;&nbsp;Digest = htactx.HTSHA1(Rnddata,lRndLen)'SHA1数据<BR>&nbsp;&nbsp;if Err.number&lt;&gt;0 then<BR>&nbsp;&nbsp;response.write "&lt;script language=javascript&gt; alert('USB安全锁数据加密失败！');history.go(-1);&lt;/script&gt;"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.end<BR>&nbsp;&nbsp;htactx.CloseDevice hCard<BR>&nbsp;&nbsp;end if<BR>&nbsp;&nbsp;Digest = Digest&amp;"04040404"'对SHA1数据进行补码<BR>&nbsp;&nbsp;EnData = htactx.HTCrypt(hCard,0 ,0,Digest, len(Digest))'DES3加密SHA1后的数据<BR>&nbsp;&nbsp;If Err.number&lt;&gt;0 Then <BR>&nbsp;&nbsp;response.write "&lt;script language=javascript&gt; alert('HashToken compute！');history.go(-1);&lt;/script&gt;"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.end<BR>&nbsp;&nbsp;htactx.CloseDevice hCard<BR>&nbsp;&nbsp;end if<BR>&nbsp;&nbsp;htactx.CloseDevice hCard<BR>&nbsp;&nbsp;&nbsp; end if<BR>&nbsp;dim obj<BR>&nbsp;dim PasswordInFile,UserNameInFile<BR>&nbsp;PasswordInFile=rs1("usbkey")<BR>&nbsp;UserNameInFile=rs1("gys")<BR>&nbsp;&nbsp;&nbsp;set obj = CreateObject("HTSrvActX.HTSrvActXCtrl")<BR>&nbsp;&nbsp;&nbsp;Digest = obj.HTSrvSHA1(Rnddata, len(Rnddata))<BR>&nbsp;&nbsp;&nbsp;Digest = Digest&amp;"04040404"<BR>&nbsp;&nbsp;&nbsp;ServerEncData = obj.HTSrvCrypt(0, PasswordInFile,len(PasswordInFile),0, Digest, len(Digest))<BR>&nbsp;&nbsp;&nbsp;if UCase(ServerEncData)= UCase(EndData) and UCase(UserNameInFile) = UCase(UserName) Then <BR>&nbsp;&nbsp;&nbsp;session("userID")=rssql2("gys")<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; session("name")=rssql2("gysname")<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; session("userclass")=1<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.redirect "../default.asp"<BR>&nbsp;&nbsp;&nbsp;else<BR>&nbsp;&nbsp;&nbsp;response.write "&lt;script language=javascript&gt; alert('Anknow Error Happend！');history.go(-1);&lt;/script&gt;"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.end<BR>&nbsp;&nbsp;&nbsp;end if<BR></FONT></P>&nbsp; end if<BR>%&gt;<BR><FONT face=Verdana>&lt;/body&gt;<BR>&lt;/html&gt;</FONT></SPAN></SPAN></FONT>]]></description></item>
</channel>
</rss>
