﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-Tommy的技术博客-文章分类-P2P</title><link>http://www.cppblog.com/tommyyan/category/1337.html</link><description>&lt;br&gt;C++/web技术/设计模式/LINUX/MYSQL/P2P交流/嵌入式系统&lt;br&gt;
个人相关：&lt;a href="http://3965743.qzone.qq.com/" id = "MyLinks1_HomeLink" class="listitem" &gt;http://3965743.qzone.qq.com/&lt;/a&gt;</description><language>zh-cn</language><lastBuildDate>Fri, 22 May 2009 03:13:12 GMT</lastBuildDate><pubDate>Fri, 22 May 2009 03:13:12 GMT</pubDate><ttl>60</ttl><item><title>emule损坏的处理</title><link>http://www.cppblog.com/tommyyan/articles/83526.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 20 May 2009 16:44:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/83526.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/83526.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/83526.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/83526.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/83526.html</trackback:ping><description><![CDATA[<p align=left>eMule 使用各种的方式来确保文件在网络共享及下载没有错误. 万一错误发生, 称为损坏, eMule 有进阶功能以最小的额外重新下载资料量来修正这个损坏.</p>
<p><strong>文件哈希值和 ICH - 智慧型损坏处理</strong></p>
<p><u>文件哈希值, 部分哈希值 &amp; 片段哈希值</u><br>在网络共享的每个文件有一个独一无二的识别值是由 MD4 密码数学运算所建立. 这个值称为文件哈希值并且每个标准的 eD2k 链接都有包含, 例如<br><br>ed2k://|file|name|12043984|6744FC42EDA527B27F0B2F2538728B3E|/</p>
<p>其中 6744FC42EDA527B27F0B2F2538728B3E 是文件哈希值以确定这个文件在整个网络是独一无二的被识别出.<br>这个 <em>文件哈希值</em> 是将文件划分为 9.28 MB 为一个部分所计算出来. 每个部分的部分哈希值也是使用相同的 MD4 运算方式计算出来. 那些 <em>部分哈希值</em>, 称为 <em>片段哈希值</em>, 并且它是使用来计算出最终的文件哈希值. 例如一个 600 MB 文件被划分为 65 个部分每个部分都有它自己的 <em>部分哈希值</em> 而它是用来建立最终的 <em>文件哈希值</em>.<br>为确保 eMule 总是接收到正确的一个特别的链接能包含片段哈希值, 例如</p>
<p>ed2k://|file|name|12043984|6744FC42EDA527B27F0B2F2538728B3E| p=264E6F6B587985D87EB0157A2A7BAF40:17B9A4D1DCE0E4C2B672DF257145E98A|/</p>
<p>其中 p= 值表示 <em>片段哈希值</em>. 每个 <em>部分哈希值</em> 是由 ":" 来区隔. 这个文件大小为 12043984 位元组 (=11.49 MB) 这表示它有一个完整的 9.28 部分和剩下的到 11.49 MB 部分为二个 <em>部分切细片段</em>.</p>
<p><u>ICH 智慧型损坏处理</u><br>无论何时 eMule 完成某一个部分它将会被检查, 假如下载的资料和部分哈希值一样这个将成为已完成部分. 如果是, 这个部分会提供上传来帮助文件的散布.<br>假如不是, 一个损坏发生且这部分会再次下载. 为避免下载全部 9.28 MB, ICH 从这部分的开头 180 KB 重新下载并且再次检查部分是否完整. 假如不是, 下一个 180 KB 会再下载, 并再次检查. 直到部分哈希值正确为止. 最佳情况下假如损坏只在部分的开头 eMule 只再次下载 180 KB. 最差的情况可能会整个重新下载. 在部分的损坏 ICH 平均可节省 50%.</p>
<p><strong>AICH - 进阶智慧型损坏处理</strong><br>标准的 ICH 是相当有效的虽然它有它的限制只在整个 9.28 MB 能被验证并且没有完美的区块. 假如超过一个以上的位置是损坏或是恶意客户端一再的散布损坏的资料或甚至是假的 <em>部分哈希值</em>, ICH 再也没有能力去处理.<br>在这里 AICH 将会考虑完全的完整资料用一个最小重新下载量或者由建立非常完美的哈希值来管理.</p>
<p><u>根哈希值, 区块哈希值 &amp; AICH 片段哈希值</u></p>
<p><a href="http://www.emule.org.cn/faq/doc/img/AICHHash.png" target=_blank><img height=194 src="http://www.emule.org.cn/faq/doc/img/AICHHash_small.png" width=300 border=0></a><br><br>这次我们的起点是在一个文件的 9.28 MB 部分. 每个部分是被分割成 180 KB 的区块, 在每个部分将会产生 53 个区块并且每个区块使用 SHA1 切细运算方式计算出哈希值. 那些值称为 <em>区块哈希值</em> 并且根据一个低标准的一个完整 <em>AICH 片段哈希值</em>.<br>在上面的图片是显示一个完整的哈希值树状图如何建立在一个完整 4 部分文件的区块. 每个部分包含 53 个区块产生出 212 个 <em>区块哈希值</em> 其中建立在一个切细树状的第七层直到 <em>根哈希值</em> 到达时. 这整个树状称为 <em>AICH 片段哈希值</em>.<br>绿色和黄色点显示小型的 <em>区块哈希值</em> 到 <em>根哈希值</em> 之数学相关性. 这个表示假如我们有一个可信任的根哈希值整个树状能被逆向的来验证它.<br>eMule 能建立包含根哈希值的链接, 例如</p>
<p>ed2k://|file|name|12043984|6744FC42EDA527B27F0B2F2538728B3E| h=A2NWOTYURUU3P3GCUB6KCNW3FTYYELQB|/</p>
<p>其中 h= 是 <em>根哈希值</em>. 由提供一个可信赖的 <em>根哈希值<em> 并发布它应该能有明显的改善文件的损坏抵抗性. 阅读 <em>根哈希值的信任</em></em></em></p>
<p><em><em><u>从一个损坏还原</u><br>无论何时 eMule 在一个部分侦测到一个损坏它需要用一个完整 AICH 哈希值资料从随便一个客户端中取得一个还原封包. 这个还原封包包含在切细树状整体损坏部分的全部 53 个 <em>区块哈希值</em> 和一个 <em>验证哈希值</em> 的号码. 上面图片显示一个 4 部分文件的一个还原封包. <em>验证哈希值</em> 的号码是由文件的分割部分数量来决定 (2^x &gt;= '部分数量', 用 x = 验证哈希值号码).<br>接收还原封包之后 eMule 检查 <em>验证哈希值</em> 逆向确认它的根哈希值. 假如它们相符, eMule 从还原封包的 <em>区块哈希值</em> 逆向检查损坏部分的全部 53 个区块. AICH 能还原全部区块用它们的 <em>区块哈希值</em> 逆向相符来让只有损坏的区块重新下载.<br>在记录中一个成功的还原看起来会像下面列出的:</em></em></p>
<p><em><em>09.09.2004 02:43:43: 已下载部分 6 损坏了 :( ([file])<br>09.09.2004 02:43:46: AICH 成功的还原 8.22 于 9.28 从部分 6 于 [file]</em></em></p>
<p><em><em><u>根哈希值的信任</u><br>最佳的方式是从有 <em>根哈希值</em> 的链接来下载. 假定这个链接的来源是可信任的根哈希值而一但受信任将会把这个文件的根哈希值储存在磁盘.<br>假如不是由链接提供的根哈希值而是由文件的来源送出的 eMule 也会去信任这根哈希值. 它只会在一个 <em>根哈希值</em> 最少 10 个不同的来源送出相同的值和最少全部 92% 的来源同意这个值才会去相信它是真的. 因为这个 <em>根哈希值</em> 不是那么可靠它只有效于目前工作阶段并且不能储存也不能用 <em>根哈希值</em> 建链接.<br>一旦 eMule 建立整个 <em>AICH 片段哈希值</em>, 例如:文件已经完成, 它将开始传播 <em>根哈希值</em> 给其他的客户端.</em></em></p>
<table width="100%" border=0>
    <tbody>
        <tr vAlign=top>
            <td colSpan=2><strong>注意:</strong></td>
        </tr>
        <tr vAlign=top>
            <td width="3%">&#8226;</td>
            <td width="97%">新释放或罕见的文件将也许没有足够的完整来源来产生一个可信任的 <em>根哈希值</em>. 建议释放文件时包含这个哈希值.</td>
        </tr>
        <tr vAlign=top>
            <td>&#8226;</td>
            <td>在一般情况下假如在那里没有 <em>根哈希值</em> 或甚至是一个伪造的 eMule 将能够成功下载并且完成这个文件. 而 AICH 特性不能使用在这种情况.</td>
        </tr>
        <tr vAlign=top>
            <td>&#8226;</td>
            <td>如同 AICH 片段哈希值能非常大他们不储存在内存但存在 known2.met 并且只能做读取需求.</td>
        </tr>
        <tr vAlign=top>
            <td>&#8226;</td>
            <td>AICH 将只能在 eMule 客户端 v.44a 及更新版本有效但保留旧客户端的向下相容性.</td>
        </tr>
    </tbody>
</table>
<img src ="http://www.cppblog.com/tommyyan/aggbug/83526.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-21 00:44 <a href="http://www.cppblog.com/tommyyan/articles/83526.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> eMule协议文件校验分析</title><link>http://www.cppblog.com/tommyyan/articles/83525.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 20 May 2009 16:29:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/83525.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/83525.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/83525.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/83525.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/83525.html</trackback:ping><description><![CDATA[<p><font size="4"><strong><font size="2"><em><u>最近一直再从事eMule，BT，HTTP，FTP等下载协议的跨协议整合开发工作，对eMule的文件校验进行了仔细的分析，同大家分享一下相关的结果，有些结果来自与网络，向原作者表示感谢。</u></em></font>&nbsp;</strong></font></p>
<p><font size="4"><strong>1。首先介绍文件校验过程中的几个概念：</strong><br></font><strong>&nbsp;文件ID</strong><br>&nbsp;&nbsp;&nbsp;&nbsp;
文件ID用来惟一的标识网络中的文件和文件损坏侦测和修复。注意，eMule不依靠文件名来惟一标识和编目文件，通过哈希文件内容计算出的GUID标识文
件。有两种类型文件ID-一种主要用来产生惟一的文件ID，另一种是用来损坏侦测和修复。(就是说文件ID是2类HASH值的总称，本身是一个抽象的概
念，没有独立的实体)<br>&nbsp;<br><strong>&nbsp;&nbsp; 文件哈希</strong><br>&nbsp;&nbsp;&nbsp;
文件是用由客户端和基于文件内容计算出来的128位GUID哈希来标识的。GUID是应用MD4算法到文件数据中计算而来。当计算文件ID时，文件被分成
每段9.28MB长的部分。每部分单独计算出一个GUID，然后所有的哈希组合成一个惟一的文件ID。当下载的客户端完成一个文件部分下载时，它计算这部
分哈希，然后和发送过来的这部分哈希对比，如果这部分发现损坏了，客户端尝试通过逐渐替换这部分中的位（每个180kb）来修复损坏部分，直到哈希计算
OK。（具体修复过程参见后面的介绍）。<br>&nbsp;<br><strong>&nbsp; 根哈希</strong><br>&nbsp;用SHA1算法来为每部分计算根哈希，基于每块180kb大小。它提供了更高等级的可靠性和可修复性【具体在下面的文件校验方法中有详细说明】。<br><font size="4">2。&nbsp;下载文件的校验方法【详细介绍】</font><br>eMule使用各种方式来确保文件在网络共享及下载没有错误.万一错误发生, 称为损坏, eMule 有进阶功能以最小的额外重新下载资料量来修正这个损坏.校验方法分为<strong>ICH和AICH大类，或者说是侧重点不同</strong>的2个步骤</p>
<p><strong>2。1文件哈希值和 ICH - 智慧型损坏处理</strong></p>
<p><strong>这里涉及到：文件哈希值, 部分哈希值，片段哈希值</strong><br>在网络共享的每个文件有一个独一无二的识别值是由 MD4 密码数学运算所建立. 这个值称为文件哈希值并且每个标准的 eD2k 链接都有包含, 例如</p>
<p>ed2k://|file|name|12043984|6744FC42EDA527B27F0B2F2538728B3E|/</p>
<p>其中 6744FC42EDA527B27F0B2F2538728B3E 是文件哈希值以确定这个文件在整个网络是独一无二的被识别出.<br>这
个 文件哈希值 是将文件划分为 9.28 MB 为一个部分所计算出来.（这个值是基于文件内容的）每个部分的部分哈希值也是使用相同的 MD4
运算方式计算出来. 那些 部分哈希值, 称为 片段哈希值,
并且它是被用来计算出最终的文件哈希值的.（HASH运算是分块进行的，每个分片的部分HASH可以看作是计算最终文件内容HASH值的中间结果）
例如一个 600 MB 文件被划分为 65 个部分，每个部分都有它自己的 部分哈希值， 而它是用来建立最终的 文件哈希值的.<br>为确保 eMule 总是接收到正确的块，一个特别的链接能包含片段哈希值, 例如</p>
<p>ed2k://|file|name|12043984|6744FC42EDA527B27F0B2F2538728B3E|
p=264E6F6B587985D87EB0157A2A7BAF40:17B9A4D1DCE0E4C2B672DF257145E98A|/</p>
<p>其中 p= 值表示 片段哈希值. 每个 部分哈希值 是由 ":" 来区隔. 这个文件大小为 12043984 位元组 (=11.49 MB) 这表示它有一个完整的 9.28 部分和剩下的到 11.49 MB 部分为二个 部分切细片段.</p>
<p><font size="3"><strong><font size="1">2。2ICH 智慧型损坏处理详细过程：（这是基本的文件校验方式）</font><br></strong></font>无
论何时 eMule 完成某一个部分的下载它将会被检查,
假如下载的资料和部分哈希值一样，这个将成为已完成部分,同时这个部分会提供上传来帮助文件的散布.假如不是,
一个损坏发生，这部分会再次下载。为避免下载全部 9.28 MB文件块, ICH从这部分的开头180 KB重新下载并且再次检查部分是否完整.
假如不是, 下一个 180 KB 会再下载, 并再次检查. 直到部分哈希值正确为止. 最佳情况下假如损坏只在部分的开头
eMule只再次下载180 KB. 最差的情况可能会整个重新下载. 在部分的损坏 ICH 平均可节省50%.AICH - (Advanced
Intelligent Corruption Handling)进阶智慧型损坏处理详细过程：（这是从0。44版以后引入的损坏恢复机制） <br>标准ICH有缺陷,如只对9.28MB的完整文件块有效,但不失为一个高效的功能. 但如果文件块出错的部位多于一处,或有问题的客户端重复地上传错误文件块,又或者根本上连那个文件块的Hash值都是有问题的,ICH就没有办法对付了.<br>但AICH会料理整个文件数据,让重新下载的数据减少到最小,并且可以重新生成Hash值. </p>
<p><strong>根Hash, 区Hashes 和 AICH Hashset</strong><br>这里涉及到【中文】： 根哈希值, 区块哈希值 &amp;AICH 片段哈希值</p>
<p><br>这次处理的对象是文件中这些9.28 MB 的文件块. 每个文件块再细分成53个以180 KB为单位的区, 电骡再使用SHA1 方式为每个区生成一个Hash值. 这些Hash值叫做区Hash值,是AICH Hashset的最底层元素.<br>上图显示出的是一个由4个文件块组成的文件的Hash树形结构图.每个文件块包含 53 个区,这个文件共有 212 个区Hash值. 这些区hash值构成另7个层,至到最上面的 根Hash值. 这整个Hash树就叫做AICH Hashset.<br>图中那些绿点和红点表示从最底层的区Hash到最上面的根Hash的算数独立性. 这意味着一旦我们的根 Hash值无误,这树中的所有分枝都可以用它来校验. 电骡可以生成带有根Hash信息的链接, 如</p>
<p>ed2k://|file|文件名|12043984|6744FC42EDA527B27F0B2F2538728B3E|h=A2NWOTYURUU3P3GCUB6KCNW3FTYYELQB|/</p>
<p>其中h= 后面的就是根 Hash值. 在链接中提供这个数值,可以大大保证文件的防出错能力. 参考 可信的根 Hash值</p>
<p><strong><font size="4">3 修复坏文件块的详细过程：</font><br></strong>一旦电骡发现文件块不对,
它会使用完整的AICH Hash Set随机向一个客户申请一个修复包.
这个修复包里面包含当前文件块中所有53个区的区Hash数据和一个Hash树的校验码. 上面的图片显示一个由4个文件块构成的文件的修复包信息.
校验码的数量是由文件块的数量决定的.(2^x &gt;= '文件块数量', x = 检验码数量).<br>当接收到修复包之后,电骡用校验码检查根Hash值. 如果无误,电骡开始用修复包中提供的区Hash值检查所有53个区. AICH会重新下载不符合区Hash的区.<br>如果修复成功,在日志中会显示如下的信息:</p>
<p>09.09.2004 02:43:43: 文件块 6 发现问题 ([file])<br>09.09.2004 02:43:46: AICH 成功修复[文件]的第6文件块中 8.22 MB的数据</p>
<p>可信根Hash<br>最保险的做法是从带有根Hash值的链接下载文件.如果文件的链接是可靠的,这个根Hash值就会被接受,然后电骡将它保存
到硬盘中.如果链接中没有提供根 Hash值,电骡就只能接受来源发送过来的根Hash值.
电骡只相信最少10个来源发送过来的,并且一致的根Hash值,并至少92%的来源接受这个数值.
因为这个根Hash值的可靠性不能确定,所以它只在本次连接中有效,电骡也不会将它保存到硬盘,你也不能用它来生成带有根Hash值的链接.一旦
AICH Hashset生成完成, 如文件下载完成,
电骡就开始向其它客房端传播这个根Hash值.为了加快内容分发的速度，分块处理是一种简单有效的方法。emule中对每个文件都进行了分块处理。另外分
块还有一个好处就是如果保留了每一分块的hash值，就能在只下载到文件的一部分时判断出下载内容的有效性。emule在获取每个共享文件的信息时，就对
它进行了分块处理，因此如果要知道emule中的分块处理和恢复机制，看CKnownFile::CreateFromFile函数的实现就行了。结合
eMule的源代码分析，这个函数中牵涉到的和分块处理以及hash计算相关的类都在SHAHashSet.cpp和SHAHashSet.h中。下面介
绍其中几个主要的类：<br>CAICHHash类只负责一块hash值，提供两个CAICHHash类之间的直接赋值，比较等基本操作。
CAICHHashAlgo是一个hash算法的通用的接口，其它hash算法只要实现这种接口都能使用，这样，可以很方便得使用不同的hash算法来计
算hash值。CAICHHashTree则是一个树状的hash值组织方式，它有一个左子树和右子树成员变量，类型是指向CAICHHashTree的
指针，这是一个典型的实现树状结构的方法。CAICHHashSet中包含了一个CAICHHashTree类型的变量，它直接向CKnownFile负
责，代表的是一个文件的分块信息。<br>SHAHashSet.h文件的开始的注释部分向我们解释了它的分块的方式。这里要用到两个常量9728000
和184320，它们分别是9500k和180k。这是emule中两种不同粒度的分块方式，即首先把一个很大的文件分割成若干个9500k的块，把这些
块组织成一颗树状的结构，然后每一个这样的块又分解成若干个180k的块(52块，再加一个140k的块)，仍然按照树状的结构组织起来。最后总的结构还
是一颗树。<br>CKnownFile::CreateFromFile方法是在读取目标文件的内容时，逐步建立起这样一颗树的。
CAICHHashTree::FindHash能够根据读取到的目标文件的偏移量和下一块的大小，来找出对应的树枝节点(就是一个指向
CAICHHashTree的指针)。如果有必要的话，还会自动创建这些树枝节点。因此在进行分块操作的时候，把文件从头到尾读一边，整个
CAICHHashTree就建立起来了，对应的分块hash值也赋值好了。最后我们还需要注意的就是CKnownFile类中的hashlist变量。
就是说它还单独保留直接以9728000字节为单位的所有分块的MD4算法的hash值。这样对于一个文件就有了两套分块验证的机制，能够适应不同场合。</p>
<p><br>eMule数据文件校验的注意点:&nbsp;<br>(1) 新发布的文件或稀有文件可能会没有足够的来源数来产生一个可信的根Hash值. 建议在发布文件时带上这个数值. <br>(2)如果不存在根Hash值或这个数值是伪造的,电骡会以正常方式下载并完成这个文件.但在这种情况下不能使用AICH功能.&nbsp;<br>(3) 因为AICH Hashset比较大,它不被存在内存里,而被保存到known2.met文件中.只有在发现错误时才读取这个信息. <br>(4) AICH只对v.44a或以上版本的电骡有效,但它和更早版本的电骡兼容. </p>
<p><strong>附：emule实现文件校验的相关数据结构：</strong></p>
<p>CPartFile类是emule中用来表示一个下载任务的类。这就是一个还没有完成的文件。当一个下载任务被创建时，emule会在下载目录中创
建两个文件，以三位数字加后缀part的文件，例如001.part，002.part等。还有一个以同样的数字加上.part.met的文件，表示的是
对应文件的元信息。part文件会创建得和原始文件大小一样，当下载完成后，文件名会修改成它本来的名称。而事实上，诸如这个文件原来叫什么名称，修改日
期等等信息都在对应的.part.met元文件中。.part.met中还包含了该文件中那些部分已经下载完成的信息。CPartFile类中
Gap_Struct来表示文件的下载情况，一个Gap_Struct就是一个坑，它表示该文件从多少字节的偏移到多少字节偏移是一个坑。下载的过程就是
一个不断填坑的过程。CPartFile类中有个成员变量gaplist就是该文件目前的坑的状况列表。需要主要的是有时填了坑的中间部分后，会把一个坑
变成两个坑。坑的列表也会被存进.part.met中。</p>
<p>对数据的校验也是通过这个类完成的：主要数据结构<br>struct Requested_Block_Struct&nbsp;&nbsp; （请求块的结构）<br>{<br>&nbsp;uint64&nbsp;StartOffset;<br>&nbsp;uint64&nbsp;EndOffset;<br>&nbsp;uchar&nbsp;FileID[16];<br>&nbsp;uint64&nbsp; transferred; // Barry - This counts bytes completed<br>};<br>#pragma pack()</p>
<p>struct Gap_Struct&nbsp; （标识坑的结构，下载可以看作是填坑的过程）<br>{<br>&nbsp;uint64 start;<br>&nbsp;uint64 end;<br>};</p>
<p>struct PartFileBufferedData （写入文件的缓冲数据结构）<br>{<br>&nbsp;BYTE *data;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Barry - This is the data to be written<br>&nbsp;uint64 start;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Barry - This is the start offset of the data<br>&nbsp;uint64 end;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Barry - This is the end offset of the data<br>&nbsp;Requested_Block_Struct *block;&nbsp;// Barry - This is the requested block that this data relates to<br>};<br>在每个任务中又三个链表<br>CTypedPtrList&lt;CPtrList, Gap_Struct*&gt; gaplist;<br>&nbsp;CTypedPtrList&lt;CPtrList, Requested_Block_Struct*&gt; requestedblocks_list;<br>// Barry - Buffered data to be written<br>&nbsp;CTypedPtrList&lt;CPtrList, PartFileBufferedData*&gt; m_BufferedData_list;</p><img src ="http://www.cppblog.com/tommyyan/aggbug/83525.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-21 00:29 <a href="http://www.cppblog.com/tommyyan/articles/83525.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>eMue片选择算法</title><link>http://www.cppblog.com/tommyyan/articles/82068.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 08:58:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82068.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82068.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82068.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82068.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82068.html</trackback:ping><description><![CDATA[<p>由于从事eMule协议的相关开发已经有一段时间了，最近经常收到一些网友的邮件，探讨p2p网络中片选择的一些问题。&nbsp; 比如，在p2p假如一个文件被分为很多块<wbr>，当有很多个client请求时，谁向谁请求哪些文件块<wbr>，因为client和文件的提供者都是不断变化的啊<wbr>。不知道emule是怎样处理这个问题的。就某一个时刻而言<wbr>，client和文件的提供者是固定的，以什么样的规则请求文件块<wbr>呢？</p>
<div>对一个下载者来说，在选择下一个被下载的片断时，通常选择的是它的<wbr>peers们所拥有的最少的那个片断，也就是所谓的"最少优先"<wbr>。确保了每个下载者都拥有它的peers们最希望得到的那些片断<wbr>，从而一旦有需要，上载就可以开始。这也确保了那些越普通的片断越<wbr>放在最后下载，从而减少了这样一种可能性，即某个peer当前正提<wbr>供上载，而随后却没有任何的被别人感兴趣的片断了。也就说<wbr>，每个peer都优先选择整个系统中最少的那些片断去下载<wbr>，而那些在系统中相对较多的片断，放在后面下载，这样<wbr>，整个系统就趋向于一种更优的状态。如果不用这种算法<wbr>，大家都去下载最多的那些片断，那么这些片断就会在系统中分布的越<wbr>来越多，而那些在系统中相对较少的片断仍然很少，最后，某些 peer 就不再拥有其它 peer 感兴趣的片断了，那么系统的参与者越来越少，整个系统的性能就下降<wbr>。通常在下载的过程分为几个阶段，第一片选择，最后阶段模式，<strong>片选择要遵循的一个基本规则：一旦请求了某个片断的子片断<wbr>，那么该片断剩下的子片断优先被请求。这样，可以尽可能快的获得一<wbr>个完整的片断。</strong></div>
<div>&nbsp;</div>
<div><strong>具体对于emule来说，eMule仔细挑选选块的下载顺序<wbr>。下面是emule网络中片选择的规则，具体的实现可以参见源码的CpartFile.cpp文件</strong></div>
<div>每个文件被分成9.28M的块，每部分分成180KB的片。<br>块下载的顺序是由发送请求文件块消息（6.4.4节）的下载客户端决定。下载客户端可以在任何给定时刻从各个源中下载一<wbr>个单独的文件块，所有从相同源中请求的片都在同一个块中<wbr>。下面的原理（以这个顺序）应用于下载块等级：<br>1.（可获得的）大片的频率，尽可能快的下载非常稀少的大片来形成<wbr>一个新的源。<br>2.用来预览的块（最初+最后的大片），预览或检查文件（比如，电影、mp3）<br>3.请求状态（过程中下载），尝试向每个源询问其它的大片。在所有源之间扩散请求<wbr>。<br>4.完成（未到某种程度的完成），在开始下载另一个时应该完成获得部分的大片<br>&nbsp;<br><strong>频率标准定义了三个区域：非常稀少、稀少和一般。在每个区域里<wbr>，标准有特定的权重，用来计算块等级。较低等级的块先下载<wbr>。下面的列表根据上面的原理指定文件等级范围：<br>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0-9999 - 不请求和请求非常稀少的块<br>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span onmouseup="function anonymous()&#13;&#10;{&#13;&#10;skype_tb_imgOnOff(this,1,'0',true,16,'');return skype_tb_stopEvents();&#13;&#10;}" class=skype_tb_injection oncontextmenu="function anonymous()&#13;&#10;{&#13;&#10;skype_tb_SwitchDrop(this,'0','sms=0');return skype_tb_stopEvents();&#13;&#10;}" onmousedown="function anonymous()&#13;&#10;{&#13;&#10;skype_tb_imgOnOff(this,2,'0',true,16,'');return skype_tb_stopEvents();&#13;&#10;}" id=softomate_highlight_0 onmouseover="function anonymous()&#13;&#10;{&#13;&#10;skype_tb_imgOnOff(this,1,'0',true,16,'');&#13;&#10;}" title="Call this phone number in China (People's Republic) with Skype: +861000019999" onclick="function anonymous()&#13;&#10;{&#13;&#10;doRunCMD('call','0',null,0);return skype_tb_stopEvents();&#13;&#10;}" onmouseout="function anonymous()&#13;&#10;{&#13;&#10;skype_tb_imgOnOff(this,0,'0',true,16,'');&#13;&#10;}" context="10000-19999" durex="480"><span onmouseup="function anonymous()&#13;&#10;{&#13;&#10;doSkypeFlag(this,'0',1,1,16);return skype_tb_stopEvents();&#13;&#10;}" class=skype_tb_imgA onmousedown="function anonymous()&#13;&#10;{&#13;&#10;doSkypeFlag(this,'0',2,1,16);return skype_tb_stopEvents();&#13;&#10;}" id=skype_tb_droppart_0 onmouseover="function anonymous()&#13;&#10;{&#13;&#10;doSkypeFlag(this,'0',1,1,16);&#13;&#10;}" title="Change country code ..." style="BACKGROUND-IMAGE: url(C:
OCUME~1SINBAD~1LOCALS~1Temp__SkypeIEToolbar_Cache�847a8f5723cfca6b3fd9946506staticinactive_a.compat.flex.w16.gif)" onclick="function anonymous()&#13;&#10;{&#13;&#10;doHandleChdial(this,1,'0',1);return skype_tb_stopEvents();&#13;&#10;}" onmouseout="function anonymous()&#13;&#10;{&#13;&#10;doSkypeFlag(this,'0',0,1,16);&#13;&#10;}"><span class=skype_tb_imgFlag id=skype_tb_img_f0 style="BACKGROUND-IMAGE: url(C:
OCUME~1SINBAD~1LOCALS~1Temp__SkypeIEToolbar_Cache�847a8f5723cfca6b3fd9946506static&#250;mfamfam/CN.gif)"></span></span><span class=skype_tb_imgS id=skype_tb_img_s0></span><span class=skype_tb_injectionIn id=skype_tb_text0><span class=skype_tb_innerText id=skype_tb_innerText0>10000-19999</span></span><span class=skype_tb_imgR id=skype_tb_img_r0></span></span> - 不请求稀少和预览块<br>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span onmouseup="function anonymous()&#13;&#10;{&#13;&#10;skype_tb_imgOnOff(this,1,'1',true,16,'');return skype_tb_stopEvents();&#13;&#10;}" class=skype_tb_injection oncontextmenu="function anonymous()&#13;&#10;{&#13;&#10;skype_tb_SwitchDrop(this,'1','sms=0');return skype_tb_stopEvents();&#13;&#10;}" onmousedown="function anonymous()&#13;&#10;{&#13;&#10;skype_tb_imgOnOff(this,2,'1',true,16,'');return skype_tb_stopEvents();&#13;&#10;}" id=softomate_highlight_1 onmouseover="function anonymous()&#13;&#10;{&#13;&#10;skype_tb_imgOnOff(this,1,'1',true,16,'');&#13;&#10;}" title="Call this phone number in China (People's Republic) with Skype: +862000029999" onclick="function anonymous()&#13;&#10;{&#13;&#10;doRunCMD('call','1',null,0);return skype_tb_stopEvents();&#13;&#10;}" onmouseout="function anonymous()&#13;&#10;{&#13;&#10;skype_tb_imgOnOff(this,0,'1',true,16,'');&#13;&#10;}" context="20000-29999" durex="545"><span onmouseup="function anonymous()&#13;&#10;{&#13;&#10;doSkypeFlag(this,'1',1,1,16);return skype_tb_stopEvents();&#13;&#10;}" class=skype_tb_imgA onmousedown="function anonymous()&#13;&#10;{&#13;&#10;doSkypeFlag(this,'1',2,1,16);return skype_tb_stopEvents();&#13;&#10;}" id=skype_tb_droppart_1 onmouseover="function anonymous()&#13;&#10;{&#13;&#10;doSkypeFlag(this,'1',1,1,16);&#13;&#10;}" title="Change country code ..." style="BACKGROUND-IMAGE: url(C:&#220;UME~1SINBAD~1LOCALS~1Temp__SkypeIEToolbar_Cache�847a8f5723cfca6b3fd9946506staticinactive_a.compat.flex.w16.gif)" onclick="function anonymous()&#13;&#10;{&#13;&#10;doHandleChdial(this,1,'1',1);return skype_tb_stopEvents();&#13;&#10;}" onmouseout="function anonymous()&#13;&#10;{&#13;&#10;doSkypeFlag(this,'1',0,1,16);&#13;&#10;}"><span class=skype_tb_imgFlag id=skype_tb_img_f1 style="BACKGROUND-IMAGE: url(C:
OCUME~1SINBAD~1LOCALS~1Temp__SkypeIEToolbar_Cache�847a8f5723cfca6b3fd9946506static&#250;mfamfam/CN.gif)"></span></span><span class=skype_tb_imgS id=skype_tb_img_s1></span><span class=skype_tb_injectionIn id=skype_tb_text1><span class=skype_tb_innerText id=skype_tb_innerText1>20000-29999</span></span><span class=skype_tb_imgR id=skype_tb_img_r1></span></span> - 不请求大部分完成的一般的块<br>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 30000-39999 - 请求的稀少和预览的块<br>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 40000-49999 - 请求的没有完成的一般的块<br></strong>&nbsp;<br>这个算法通常选择第一个最稀少的块。然而，部分完成的块<wbr>，接近完成的，也可能被选中。对于一般的块，在不同的源之间扩散下<wbr>载。理论上是可以统计出所有文件块的拥有者的，但是在实际情况下只能达<wbr>到一个局部最优的效果，也就是小世界理论所说的。一个Peer通过<wbr>服务器或者KAD网络获得对方的Peer信息，然后交换Peer的<wbr>片信息，在他的邻居范围内就可以确定一个片的请求频率的。<wbr>在emule中具体的片选择策略的实现是在：（emule0.48a官方版本的算法，至于其他的修改版本不再次讨论之列，不过也大通小异）</div>
<div>
<div>bool CPartFile::GetNextRequestedBloc<wbr>k(CUpDownClient* sender, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<wbr>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Requested_Block_Struct** newblocks, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; uint16* count) 我们可以分析一下它的源代码的实现：</div>
<div></div>
<div>这个函数是在每次emule的一个Peer根据自己的Gaplist的分布状况，决定如何申请一个具体的片的时候调用的</div>
<div>bool CPartFile::GetNextRequestedBlock(CUpDownClient* sender,<br>Requested_Block_Struct** newblocks,<br>uint16* count) /<strong>const</strong>/<br>{<br>// The purpose of this function is to return a list of blocks (~180KB) to<br>// download. To avoid a prematurely stop of the downloading, all blocks that<br>// are requested from the same source must be located within the same<br>// chunk (=&gt; part ~9MB).<br>// 这个函数的功能是返回一组180Kb的块供下载，为了避免不必要的暂停，所有的请求块必须来自同一个源的一个9.28M的大块<br>// The selection of the chunk to download is one of the CRITICAL parts of the<br>// edonkey network. The selection algorithm must insure the best spreading<br>// of files.<br>// 片选择算法是edonkey 网络中非常重要的一部分，他必须保重文件能够最好地散布。<br>// The selection is based on several criteria:<br>// - Frequency of the chunk (availability), very rare chunks must be downloaded<br>// as quickly as possible to become a new available source.<br>// - Parts used for preview (first + last chunk), preview or check a<br>// file (e.g. movie, mp3)<br>// - Completion (shortest-to-complete), partially retrieved chunks should be<br>// completed before starting to download other one.<br>//<br>// <strong>The frequency criterion defines several zones: very rare, rare, almost rare,<br>// and common. Inside each zone, the criteria have a specific 憌eight? used<br>// to calculate the priority of chunks. The chunk(s) with the highest<br>// priority (highest=0, lowest=0xffff) is/are selected first.<br></strong>//<br>// This algorithm usually selects first the rarest chunk(s). However, partially<br>// complete chunk(s) that is/are close to completion may overtake the priority<br>// (priority inversion). For common chunks, it also tries to put the transferring<br>// clients on the same chunk, to complete it sooner.<br>//<br><br>// Check input parameters<br>if(count == 0)<br>return false;<br>if(sender-&gt;GetPartStatus() == NULL)<br>return false;<br><br>//AddDebugLogLine(DLP_VERYLOW, false, _T("Evaluating chunks for file: \"%s\" Client: %s"), GetFileName(), sender-&gt;DbgGetClientInfo());<br><br>// Define and create the list of the chunks to download<br>const uint16 partCount = GetPartCount();<br>CList<chunk> chunksList(partCount);<br><br>uint16 tempLastPartAsked = (uint16)-1;<br>if(sender-&gt;m_lastPartAsked != ((uint16)-1) &amp;&amp; sender-&gt;GetClientSoft() == SO_EMULE &amp;&amp; sender-&gt;GetVersion() &lt; MAKE_CLIENT_VERSION(0, 43, 1)){<br>tempLastPartAsked = sender-&gt;m_lastPartAsked; //最近请求的一个块<br>}<br><br>// Main loop<br>uint16 newBlockCount = 0;<br>while(newBlockCount != *count){<br>// Create a request block stucture if a chunk has been previously selected<br>if(tempLastPartAsked != (uint16)-1){<br>Requested_Block_Struct* pBlock = new Requested_Block_Struct;<br>if(GetNextEmptyBlockInPart(tempLastPartAsked, pBlock) == true){<br>//AddDebugLogLine(false, _T("Got request block. Interval %i-%i. File %s. Client: %s"), pBlock-&gt;StartOffset, pBlock-&gt;EndOffset, GetFileName(), sender-&gt;DbgGetClientInfo());<br>// Keep a track of all pending requested blocks<br>requestedblocks_list.AddTail(pBlock);<br>// Update list of blocks to return<br>newblocks[newBlockCount++] = pBlock;<br>// Skip end of loop (=&gt; CPU load)<br>continue;<br>}<br>else {<br>&nbsp;&nbsp; // All blocks for this chunk have been already requested<br>&nbsp;&nbsp;&nbsp; delete pBlock;<br>&nbsp;&nbsp; // =&gt; Try to select another chunk<br>&nbsp;&nbsp;&nbsp; sender-&gt;m_lastPartAsked = tempLastPartAsked = (uint16)-1;<br>&nbsp; }<br>}<br><br>// Check if a new chunk must be selected (e.g. download starting, previous chunk complete)<br>i f(tempLastPartAsked == (uint16)-1){<br><br>// Quantify all chunks (create list of chunks to download)<br>// This is done only one time and only if it is necessary (=&gt; CPU load)<br>if(chunksList.IsEmpty() == TRUE){<br>// Indentify the locally missing part(s) that this source has<br>&nbsp;&nbsp; for(uint16 i = 0; i &lt; partCount; i++){<br>if(sender-&gt;IsPartAvailable(i) == true &amp;&amp; GetNextEmptyBlockInPart(i, NULL) == true){<br>&nbsp; // Create a new entry for this chunk and add it to the list<br>Chunk newEntry;<br>&nbsp; newEntry.part = i;<br>&nbsp; newEntry.frequency = m_SrcpartFrequency[i];<br>&nbsp; chunksList.AddTail(newEntry);<br>}<br>}<br><br>// Check if any block(s) could be downloaded<br>if(chunksList.IsEmpty() == TRUE){<br>break; // Exit main loop while()<br>}<br><br>// Define the bounds of the zones (very rare, rare etc)<br>// more depending on available sources<br>uint16 limit = (uint16)ceil(GetSourceCount()/ 10.0);<br>if (limit&lt;3) limit=3;<br><br>const uint16 veryRareBound = limit;<br>const uint16 rareBound = 2*limit;<br>const uint16 almostRareBound = 4*limit;<br><br>// Cache Preview state (Criterion 2)<br>const bool isPreviewEnable = (thePrefs.GetPreviewPrio() || thePrefs.IsExtControlsEnabled() &amp;&amp; GetPreviewPrio()) &amp;&amp; IsPreviewableFileType();<br><br>// Collect and calculate criteria for all chunks<br>for(POSITION pos = chunksList.GetHeadPosition(); pos != NULL; ){<br>Chunk&amp; cur_chunk = chunksList.GetNext(pos);<br><br>// Offsets of chunk<br>UINT uCurChunkPart = cur_chunk.part; // help VC71...<br>const uint64 uStart = (uint64)uCurChunkPart * PARTSIZE;<br>const uint64 uEnd = ((GetFileSize() - (uint64)1) &lt; (uStart + PARTSIZE - 1)) ?<br>(GetFileSize() - (uint64)1) : (uStart + PARTSIZE - 1);<br>ASSERT( uStart &lt;= uEnd );<br><br>// Criterion 2. Parts used for preview 用于预览的片<br>// Remark: - We need to download the first part and the last part(s).<br>// - When the last part is very small, it's necessary to<br>// download the two last parts.<br>bool critPreview = false;<br>if(isPreviewEnable == true){<br>if(cur_chunk.part == 0){<br>critPreview = true; / First chunk<br>}<br>else if(cur_chunk.part == partCount-1){<br>critPreview = true; // Last chunk<br>}<br>else if(cur_chunk.part == partCount-2){<br>// Last chunk - 1 (only if last chunk is too small)<br>if( (GetFileSize() - uEnd) &lt; (uint64)PARTSIZE/3){<br>critPreview = true; // Last chunk - 1<br>}<br>}<br>}<br><br>// Criterion 3. Request state (downloading in process from other source(s))<br>//const bool critRequested = IsAlreadyRequested(uStart, uEnd);<br>bool critRequested = false; // &lt;--- This is set as a part of the second critCompletion loop below<br><br>// Criterion 4. Completion<br>uint64 partSize = uEnd - uStart + 1; //If all is covered by gaps, we have downloaded PARTSIZE, or possibly less for the last chunk;<br>ASSERT(partSize &lt;= PARTSIZE);<br>for(POSITION pos = gaplist.GetHeadPosition(); pos != NULL; ) {<br>const Gap_Struct* cur_gap = gaplist.GetNext(pos);<br>// Check if Gap is into the limit<br>if(cur_gap-&gt;start &lt; uStart) {<br>if(cur_gap-&gt;end &gt; uStart &amp;&amp; cur_gap-&gt;end &lt; uEnd) {<br>ASSERT(partSize &gt;= (cur_gap-&gt;end - uStart + 1));<br>partSize -= cur_gap-&gt;end - uStart + 1;<br>}<br>else if(cur_gap-&gt;end &gt;= uEnd) {<br>partSize = 0;<br>break; // exit loop for()<br>}<br>}<br>else if(cur_gap-&gt;start &lt;= uEnd) {<br>if(cur_gap-&gt;end &lt; uEnd) {<br>ASSERT(partSize &gt;= (cur_gap-&gt;end - cur_gap-&gt;start + 1));<br>partSize -= cur_gap-&gt;end - cur_gap-&gt;start + 1;<br>}<br>else {<br>ASSERT(partSize &gt;= (uEnd - cur_gap-&gt;start + 1));<br>partSize -= uEnd - cur_gap-&gt;start + 1;<br>}<br>}<br>}<br>//ASSERT(partSize &lt;= PARTSIZE &amp;&amp; partSize &lt;= (uEnd - uStart + 1));<br><br>// requested blocks from sources we are currently downloading from is counted as if already downloaded<br>// this code will cause bytes that has been requested AND transferred to be counted twice, so we can end<br>// up with a completion number &gt; PARTSIZE. That's ok, since it's just a relative number to compare chunks.<br>for(POSITION reqPos = requestedblocks_list.GetHeadPosition(); reqPos != NULL; ) {<br>const Requested_Block_Struct* reqBlock = requestedblocks_list.GetNext(reqPos);<br>if(reqBlock-&gt;StartOffset &lt; uStart) {<br>if(reqBlock-&gt;EndOffset &gt; uStart) {<br>if(reqBlock-&gt;EndOffset &lt; uEnd) {<br>//ASSERT(partSize + (reqBlock-&gt;EndOffset - uStart + 1) &lt;= (uEnd - uStart + 1));<br>partSize += reqBlock-&gt;EndOffset - uStart + 1;<br>critRequested = true;<br>} else if(reqBlock-&gt;EndOffset &gt;= uEnd) {<br>//ASSERT(partSize + (uEnd - uStart + 1) &lt;= uEnd - uStart);<br>partSize += uEnd - uStart + 1;<br>critRequested = true;<br>}<br>}<br>} else if(reqBlock-&gt;StartOffset &lt;= uEnd) {<br>if(reqBlock-&gt;EndOffset &lt; uEnd) {<br>//ASSERT(partSize + (reqBlock-&gt;EndOffset - reqBlock-&gt;StartOffset + 1) &lt;= (uEnd - uStart + 1));<br>partSize += reqBlock-&gt;EndOffset - reqBlock-&gt;StartOffset + 1;<br>critRequested = true;<br>} else {<br>//ASSERT(partSize + (uEnd - reqBlock-&gt;StartOffset + 1) &lt;= (uEnd - uStart + 1));<br>partSize += uEnd - reqBlock-&gt;StartOffset + 1;<br>critRequested = true;<br>}<br>}<br>}<br>//Don't check this (see comment above for explanation): ASSERT(partSize &lt;= PARTSIZE &amp;&amp; partSize &lt;= (uEnd - uStart + 1));<br><br>if(partSize &gt; PARTSIZE) partSize = PARTSIZE;<br><br>uint16 critCompletion = (uint16)ceil((double)(partSize*100)/PARTSIZE); // in [%]. Last chunk is always counted as a full size chunk, to not give it any advantage in this comparison due to smaller size. So a 1/3 of PARTSIZE downloaded in last chunk will give 33% even if there's just one more byte do download to complete the chunk.<br>if(critCompletion &gt; 100) critCompletion = 100;<br><br>// Criterion 5. Prefer to continue the same chunk<br>const bool sameChunk = (cur_chunk.part == sender-&gt;m_lastPartAsked);<br><br>// Criterion 6. The more transferring clients that has this part, the better (i.e. lower).<br>uint16 transferringClientsScore = (uint16)m_downloadingSourceList.GetSize();<br><br>// Criterion 7. Sooner to completion (how much of a part is completed, how fast can be transferred to this part, if all currently transferring clients with this part are put on it. Lower is better.)<br>uint16 bandwidthScore = 2000;<br><br>// Calculate criterion 6 and 7<br>if(m_downloadingSourceList.GetSize() &gt; 1) {<br>UINT totalDownloadDatarateForThisPart = 1;<br>for(POSITION downloadingClientPos = m_downloadingSourceList.GetHeadPosition(); downloadingClientPos != NULL; ) {<br>const CUpDownClient* downloadingClient = m_downloadingSourceList.GetNext(downloadingClientPos);<br>if(downloadingClient-&gt;IsPartAvailable(cur_chunk.part)) {<br>transferringClientsScore--;<br>totalDownloadDatarateForThisPart += downloadingClient-&gt;GetDownloadDatarate() + 500; // + 500 to make sure that a unstarted chunk available at two clients will end up just barely below 2000 (max limit)<br>}<br>}<br><br>bandwidthScore = (uint16)min((UINT)((PARTSIZE-partSize)/(totalDownloadDatarateForThisPart*5)), 2000);<br>//AddDebugLogLine(DLP_VERYLOW, false,<br>// _T("BandwidthScore for chunk %i: bandwidthScore = %u = min((PARTSIZE-partSize)/(totalDownloadDatarateForThisChunk*5), 2000) = min((PARTSIZE-%I64u)/(%u*5), 2000)"),<br>// cur_chunk.part, bandwidthScore, partSize, totalDownloadDatarateForThisChunk);<br>}<br><br>//AddDebugLogLine(DLP_VERYLOW, false, _T("Evaluating chunk number: %i, SourceCount: %u/%i, critPreview: %s, critRequested: %s, critCompletion: %i%%, sameChunk: %s"), cur_chunk.part, cur_chunk.frequency, GetSourceCount(), ((critPreview == true) ? _T("true") : _T("false")), ((critRequested == true) ? _T("true") : _T("false")), critCompletion, ((sameChunk == true) ? _T("true") : _T("false")));<br><br>// Calculate priority with all criteria<br>if(partSize &gt; 0 &amp;&amp; GetSourceCount() &lt;= GetSrcA4AFCount()) {<br>// If there are too many a4af sources, the completion of blocks have very high prio<br>cur_chunk.rank = (cur_chunk.frequency) + // Criterion 1<br>((critPreview == true) ? 0 : 200) + // Criterion 2<br>((critRequested == true) ? 0 : 1) + // Criterion 3<br>(100 - critCompletion) + // Criterion 4<br>((sameChunk == true) ? 0 : 1) + // Criterion 5<br>bandwidthScore; // Criterion 7<br>} else if(cur_chunk.frequency &lt;= veryRareBound){<br>// 3000..xxxx unrequested + requested very rare chunks<br>cur_chunk.rank = (75 * cur_chunk.frequency) + // Criterion 1<br>((critPreview == true) ? 0 : 1) + // Criterion 2<br>((critRequested == true) ? 3000 : 3001) + // Criterion 3<br>(100 - critCompletion) + // Criterion 4<br>((sameChunk == true) ? 0 : 1) + // Criterion 5<br>transferringClientsScore; // Criterion 6<br>}<br>else if(critPreview == true){<br>// 10000..10100 unrequested preview chunks<br>// 20000..20100 requested preview chunks<br>cur_chunk.rank = ((critRequested == true &amp;&amp;<br>sameChunk == false) ? 20000 : 10000) + // Criterion 3<br>(100 - critCompletion); // Criterion 4<br>}<br>else if(cur_chunk.frequency &lt;= rareBound){<br>// 10101..1xxxx requested rare chunks<br>// 10102..1xxxx unrequested rare chunks<br>//ASSERT(cur_chunk.frequency &gt;= veryRareBound);<br><br>cur_chunk.rank = (25 * cur_chunk.frequency) + // Criterion 1<br>((critRequested == true) ? 10101 : 10102) + // Criterion 3<br>(100 - critCompletion) + // Criterion 4<br>((sameChunk == true) ? 0 : 1) + // Criterion 5<br>transferringClientsScore; // Criterion 6<br>}<br>else if(cur_chunk.frequency &lt;= almostRareBound){<br>// 20101..1xxxx requested almost rare chunks<br>// 20150..1xxxx unrequested almost rare chunks<br>//ASSERT(cur_chunk.frequency &gt;= rareBound);<br><br>// used to slightly lessen the imporance of frequency<br>uint16 randomAdd = 1 + (uint16)((((uint32)rand()*(almostRareBound-rareBound))+(RAND_MAX/2))/RAND_MAX);<br>//AddDebugLogLine(LP_VERYLOW, false, _T("RandomAdd: %i, (%i-%i=%i)"), randomAdd, rareBound, almostRareBound, almostRareBound-rareBound);<br><br>cur_chunk.rank = (cur_chunk.frequency) + // Criterion 1<br>((critRequested == true) ? 20101 : (20201+almostRareBound-rareBound)) + // Criterion 3<br>((partSize &gt; 0) ? 0 : 500) + // Criterion 4<br>(5*100 - (5*critCompletion)) + // Criterion 4<br>((sameChunk == true) ? (uint16)0 : randomAdd) + // Criterion 5<br>bandwidthScore; // Criterion 7<br>}<br>else { // common chunk<br>// 30000..30100 requested common chunks<br>// 30001..30101 unrequested common chunks<br>cur_chunk.rank = ((critRequested == true) ? 30000 : 30001) + // Criterion 3<br>(100 - critCompletion) + // Criterion 4<br>((sameChunk == true) ? 0 : 1) + // Criterion 5<br>bandwidthScore; // Criterion 7<br>}<br><br>//AddDebugLogLine(DLP_VERYLOW, false, _T("Rank: %u"), cur_chunk.rank);<br>}<br>}<br><br>// Select the next chunk to download<br>if(chunksList.IsEmpty() == FALSE){<br>// Find and count the chunck(s) with the highest priority<br>uint16 count = 0; // Number of found chunks with same priority<br>uint16 rank = 0xffff; // Highest priority found<br>for(POSITION pos = chunksList.GetHeadPosition(); pos != NULL; ){<br>const Chunk&amp; cur_chunk = chunksList.GetNext(pos);<br>if(cur_chunk.rank &lt; rank){<br>count = 1;<br>rank = cur_chunk.rank;<br>}<br>else if(cur_chunk.rank == rank){<br>count++;<br>}<br>}<br><br>// Use a random access to avoid that everybody tries to download the<br>// same chunks at the same time (=&gt; spread the selected chunk among clients)<br>uint16 randomness = 1 + (uint16)((((uint32)rand()*(count-1))+(RAND_MAX/2))/RAND_MAX);<br>for(POSITION pos = chunksList.GetHeadPosition(); ; ){<br>POSITION cur_pos = pos;<br>const Chunk&amp; cur_chunk = chunksList.GetNext(pos);<br>if(cur_chunk.rank == rank){<br>randomness--;<br>if(randomness == 0){<br>// Selection process is over<br>sender-&gt;m_lastPartAsked = tempLastPartAsked = cur_chunk.part;<br>//AddDebugLogLine(DLP_VERYLOW, false, _T("Chunk number %i selected. Rank: %u"), cur_chunk.part, cur_chunk.rank);<br><br>// Remark: this list might be reused up to ?count?times<br>chunksList.RemoveAt(cur_pos);<br>break; // exit loop for()<br>}<br>}<br>}<br>}<br>else {<br>// There is no remaining chunk to download<br>break; // Exit main loop while()<br>}<br>}<br>}<br>// Return the number of the blocks<br>*count = newBlockCount;<br><br><br>// Return<br>return (newBlockCount &gt; 0);<br>}</chunk></div>
<div>上面的算法和注释已经对选择的实现过程做了详细的说明，有兴趣的可以查看源代码。对于BT和emule由于设计者的思路不太一样，所以具体实现有很多差别，但是核心思想是一样的：）</div>
<div></div>
<div></div>
</div>
<p>&#160;</p>
<p class=diaryFoot>【</p>
<img src ="http://www.cppblog.com/tommyyan/aggbug/82068.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 16:58 <a href="http://www.cppblog.com/tommyyan/articles/82068.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>eMule内网穿透原理</title><link>http://www.cppblog.com/tommyyan/articles/82059.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 08:02:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82059.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82059.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82059.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82059.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82059.html</trackback:ping><description><![CDATA[<a href="http://blog.verycd.com/dash/showentry=40155" target=_blank><font color=#222222><u>测试</u></font></a>开始以来，效果不错，Low2Low成功率颇高。<br><br>关注了一下大家的反馈，有人担心内网穿透会增加HighID负担，这个理解得有所偏差，关于内网穿透的原理，比较成熟的帖子有: <a href="http://dev.csdn.net/article/79/79799.shtm" target=_blank><font color=#222222><u>http://dev.csdn.net/article/79/79799.shtm</u></font></a><br><br>不过这个说的比较复杂，用我自己的浅薄理解，简单说来就是:<br><br>内网计算机(也就是LowID)，都通过至少一层网关连接互联网，没有自己的独立IP和端口(别人看到的你的IP是网关的)，所以别人无法主动与你建立连接，两个内网用户自然也就无法连通，更无法实现传输。<br><br>但是内网计算机可以主动连接其他有独立IP的外网计算机，再通过udp协议通讯的时候，因为udp是非持续连接的，所以网关那边会给你开一个临时端口，让你能够接受外网计算机返回给你的udp包，如果一段时间内没有传输，临时端口便会取消。<br><br>这个步骤就可有空子钻，比如A和B 两台内网计算机，都同时连接外网计算机C进行udp协议的传输，A和B分别用到了临时端口Ap和Bp，这个时候通过Ap就可以主动连接到A，Bp就可以主动连接到B，所以C所要做的，就是把Ap告诉B,把Bp告诉A。AB通过从C那里知道的Ap和Bp，即可实现UDP直连。只要连接不断，临时端口就一直有效，传输期间，C什么都不需要参与，这个过程，俗称打洞，C帮AB打好洞，AB就可以自己玩了。<br><br>当然我这个是最简单的讲法，根据不同的网关设备，还是有很多不同情况需要解决。<br><br>有人怕Low2Low会耗HighID资源，这个多虑了，不是说不耗，而是耗的根小，C只不过初期接受一下AB发来的UDP请求，并向双方返回一次数据，打洞成功之后就再也没事了。本身UDP传输就耗的资源很少，这一两次UDP传输相对于连接频繁的eMule，可以忽略不计了。<br><br>更要说明的一点就是，我们目前测试用的内网穿透eMule，都是连接的我们自己的一台服务器用来做&#8220;C&#8221;，帮LowID打洞，没有依赖任何其他HighID，而我们那台破PIII服务器，目前同时处理着几百个low2low的连接，也几乎没占多少服务器资源。<br><br>当然将来最好的方案，是可以让eMule的Low2Low基于Kad来进行，这样可以不依赖任何第三方的服务器，独立的发展下去。基本原理是LowID利用自己的buddy来做帮助打洞，每个HightID只会帮1个LowID做buddy，所以不会增加HighID的负担。这方面我们也作了研究，不日也准备进行测试。<br><br>内网穿透目前是一套成熟的方案，QQ，BC等都早已开始大规模使用。为什么eMule到现在为止才开始由我们开始测试内网穿透呢？主要是因为eMule的开发长期以来都由老外们主导，国外大都由公网IP，Low2Low对他们来说，太不重要。而我们自己也走了很多的弯路，去年尝试通过内置VNN来解决问题，但VNN相对eMule，是一套太大的解决方案，需要注册和安装虚拟网卡才能使用。虽然我们后来的版本自动完成了这2步，但是VNN的服务器还是无法拖起eMule这巨大的用户群进行这样复杂的应用。<br><br>所以这次痛定思痛，自己从头开始开发，主要就是让使用tcp协议传输数据的eMule可以利用到UDP直连，并且解决各种各样的细节问题(因为eMule之前都没考虑到low2low问题)。<br>国外也有个neo版本的eMule，尝试利用kad解决Low2Low的问题，但实际使用效果不好。我们在开发过程中也想参考，不过基本没参考成，代码太复杂太乱。最后还是根据自己的思路自己写的，会比neo的思路更清晰些。<br><br>这次的内网测试版本，是我们VeryCD软件开发组近几个月努力得来的一点小成绩，希望能够早日正式发布。我虽不是软件开发，但有幸参与这个过程。所以把自己所理解的东西向大家解释一下。虽然会有纰漏，但不熟悉相关技术的同志，应该更容易理解。<br><br>最后补充，eMule既然是开源软件，这项技术成熟之后，必然可以共用，对所有eMule用户，都能有所帮助。
<img src ="http://www.cppblog.com/tommyyan/aggbug/82059.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 16:02 <a href="http://www.cppblog.com/tommyyan/articles/82059.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>P2P网络架构调查</title><link>http://www.cppblog.com/tommyyan/articles/82058.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 08:00:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82058.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82058.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82058.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82058.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82058.html</trackback:ping><description><![CDATA[<span style="FONT-SIZE: 12pt">摘要：</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">网络实际上是一个分布式对象存储、查询及共享的网络架构。本文将展示其现状的一个调查。首先，我们将引入一个简单的</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">的定义，并定义了一些</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">常用操作流程。其次，为了理解和比较实际的</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">架构和协议，我们讨论了</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">运行效率。第三，对现有的</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">架构进行了分类和详细比较。第四，对相应的</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">架构中对象查询协议进行了仔细的讨论。我们也回顾了现有</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">模型的输出并列举了总体的模型解决方案。最后，简要研究了几个基于</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">技术的新应用和</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">网络架构将来的研究方向。这篇文章目的是根据</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">的发展和演变过程，给出一个清晰完整的</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">网络架构的调查研究。</span><span style="FONT-SIZE: 12pt"></span>
<p>&#160;</p>
<p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span style="FONT-SIZE: 12pt"><font face="Times New Roman">1 </font></span><span style="FONT-SIZE: 12pt">简介</span><span style="FONT-SIZE: 12pt"></span></p>
<p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span style="FONT-SIZE: 12pt"><font face="Times New Roman">Oram[1]</font></span><span style="FONT-SIZE: 12pt">给出了一个</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">简单的定义：&#8220;</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">是一类应用，它利用了网络中闲置的存储、周期、内容、人力的资源。因为使用这些分散资源就意味着要在一个不持续连接、未知</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">IP</font></span><span style="FONT-SIZE: 12pt">地址的环境中操作。端对端节点一定是在</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">DNS</font></span><span style="FONT-SIZE: 12pt">外操作，而且也在相对集中或完全自治的集中服务器之外&#8221;。简而言之，</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">是在应用层上的一个专有分布系统。在此，每对节点都可以通过路由协议在</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">层上直接通信。总体的</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P </font></span><span style="FONT-SIZE: 12pt">网络结构如图</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">1</font></span><span style="FONT-SIZE: 12pt">示。每个结点（节点）拥有一个对象（如文件，音乐，</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">MP3</font></span><span style="FONT-SIZE: 12pt">，</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">MPEG</font></span><span style="FONT-SIZE: 12pt">等）数据库。每个节点都可以通过</span><span style="FONT-SIZE: 12pt"><font face="Times New Roman">P2P</font></span><span style="FONT-SIZE: 12pt">层逻辑连接查询其它节点上它想要的对象。</span><span style="FONT-SIZE: 12pt"></span></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span>为了清晰地理解</span><span><font face="Times New Roman">P2P</font></span><span>，我们用</span><span><font face="Times New Roman">C/S</font></span><span>模型与</span><span><font face="Times New Roman">P2P</font></span><span>模型进行比较（表</span><span><font face="Times New Roman">1</font></span><span>）。在</span><span><font face="Times New Roman">C/S</font></span><span>模型中，每个节点扮演的角色或是客户端或是服务器。诸如存储或计算能力等资源可以在客户端和服务器之间共享。在</span><span><font face="Times New Roman">C/S</font></span><span>模型中，服务器事实上是一个集中控制点。在</span><span><font face="Times New Roman">P2P</font></span><span>模型中，每个节点都同时是客户端和服务器。作为客户端，它从其它节点查询或下载它想要的对象。同时作为服务器，它也为其它节点提供服务。</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span>根据生命周期，对于</span><span><font face="Times New Roman">P2P</font></span><span>节点来说有大致四个术语：连接，查询，下载和分离。首先，一个即将进入的节点必须活跃地连接到</span><span><font face="Times New Roman">P2P</font></span><span>系统。在这个过程中，它需要获取一些基本信息（如它的邻点）来启动，同时也要发布它拥有资源的信息。然后，这个节点可以为它需要的资源提交查询。这时，</span><span><font face="Times New Roman">P2P</font></span><span>定位协议会帮助这个节点来确定目标节点，同时</span><span><font face="Times New Roman">P2P</font></span><span>路由协议会传送查询消息到目标节点上。第三，如果查询成功（通常返回目标节点</span><span><font face="Times New Roman">IP</font></span><span>地址），节点可以直接从目标节点下载资源。最后，节点会在理想状态宣布分离。因此，</span><span><font face="Times New Roman">P2P</font></span><span>三个重要组成是：邻居发现，定位协议和路由协议。这些组成将会利用</span><span><font face="Times New Roman">P2P</font></span><span>拓扑结构，即使结构化或非结构化和</span><span><font face="Times New Roman">P2P</font></span><span>节点自治，而不会在开始下载资源时使用。</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span>本文目标是给出一个</span><span><font face="Times New Roman">P2P</font></span><span>当前优劣的调查。尽管已经有了一些</span><span><font face="Times New Roman">P2P</font></span><span>网络的概述文章</span><span><font face="Times New Roman">[2-4]</font></span><span>，但我们强调的是</span><span><font face="Times New Roman">P2P</font></span><span>架构，资源查询算法和</span><span><font face="Times New Roman">P2P</font></span><span>执行评价与模板化。我们尽量给出一个完整清晰的介绍与分析，设计到当今</span><span><font face="Times New Roman">P2P</font></span><span>网络及其执行比较、未解决问题和一些今后发展方向。文章后续部分是按如下组织的。第二部分列举了一些</span><span><font face="Times New Roman">P2P</font></span><span>执行公式。第三部分中，我们讨论比较了三代</span><span><font face="Times New Roman">P2P</font></span><span>架构。然后是资源查找算法，也是</span><span><font face="Times New Roman">P2P</font></span><span>网络中最重要组成之一，它在第四部分被认真描述了。第五部分将讨论</span><span><font face="Times New Roman">P2P</font></span><span>执行模型的一些发展。最后，在第六部分，我们综述了</span><span><font face="Times New Roman">P2P</font></span><span>重要应用和今后发展方向。第八部分给出了一些总结。</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><span><font face="Times New Roman" size=3></font></span></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman">2. P2P</font></span><span>执行模板</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span>为了评价比较现有</span><span><font face="Times New Roman">P2P</font></span><span>网络，我们首先定义一些执行模板。通常的</span><span><font face="Times New Roman">P2P</font></span><span>网络的特性一般包括：安全性，匿名性，可扩展性，弹性和查询效率。本文重点在后三者。弹性由查询字比率来确定。我们可以称提交查询的节点为获取者，称拥有被查询资源的节点为提供者（图</span><span><font face="Times New Roman">3</font></span><span>）。有两种情况将导致失败：</span><span><font face="Times New Roman">1</font></span><span>）提供者关闭。</span><span><font face="Times New Roman">2</font></span><span>）在到提供者的线路上有故障（如连接失败或节点超时失败）。但是，查询效率是依赖于平均查询消息数和平均查询路径长度。总的来说，资源查询算法涉及查询消息数，如果使用基于洪泛的转发，查询消息数一定会比使用单点路由转发多。但基于洪泛的转发会产生较短的查询路径长度。其它的一些公式：</span><span><font face="Times New Roman">1</font></span><span>）一个节点上通过的控制消息，</span><span><font face="Times New Roman">2</font></span><span>）一个节点上处理查询的平均数。</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman">3. P2P</font></span><span>架构</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman">3.1 P2P</font></span><span>分类</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span>当今的</span><span><font face="Times New Roman">P2P</font></span><span>网络有很多，每种</span><span><font face="Times New Roman">P2P</font></span><span>网络有着很多不同特性，而它们又没有统一分类标准。我们将思考几种方法来为它们分类，从而更好地理解它们。</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span>根据资源查询机制不同和</span><span><font face="Times New Roman">P2P</font></span><span>逻辑拓扑结构，现今</span><span><font face="Times New Roman">P2P</font></span><span>架构可以分为这样三类</span><span><font face="Times New Roman">[5]</font></span><span>：</span><span><font face="Times New Roman">1</font></span><span>）集中方式－所有资源索引项目被按照</span><span><font face="Times New Roman">&lt;</font></span><span>资源键值，节点地址</span><span><font face="Times New Roman">&gt;</font></span><span>形式保存在一个集中服务器上。每个进入的节点需要上传它拥有资源的信息到服务器，然后节点只需要在服务器上查询它所需要资源节点地址。这种类型的</span><span><font face="Times New Roman">P2P</font></span><span>架构是很简单的，易于开发。但是它有单点故障的问题，尽管我们可以使用多个并行服务器。这种架构</span><span><font face="Times New Roman">P2P</font></span><span>的典型例子是</span><span><font face="Times New Roman">Napster[6]</font></span><span>。</span><span><font face="Times New Roman">2</font></span><span>）非结构化分布方式－资源查询是分布的，而且逻辑</span><span><font face="Times New Roman">P2P</font></span><span>拓扑多是随机的非结构化网络。查询在网络中一跳一跳地执行直至成功、失败或是超时。这种类型，如</span><span><font face="Times New Roman">Gnutella[7]</font></span><span>，没有单点故障问题，但是查询效率是非常低的。</span><span><font face="Times New Roman">3</font></span><span>）结构化分布方式－资源查询是分布的，但是逻辑</span><span><font face="Times New Roman">P2P</font></span><span>拓扑是一定程度结构化的，如</span><span><font face="Times New Roman">mesh[8]~[10]</font></span><span>，</span><span><font face="Times New Roman">ring[11]</font></span><span>，</span><span><font face="Times New Roman">d-dimension torus[12]</font></span><span>，及</span><span><font face="Times New Roman">butterfly[13]~[15]</font></span><span>。这些结构化拓扑一般通过分布的哈希表－</span><span><font face="Times New Roman">DHT</font></span><span>技术来组织，如</span><span><font face="Times New Roman">[9]~[12]</font></span><span>。资源查询也是在结构化网络中一跳一跳地执行的，在理想情况下通过若干确定的跳转就可以确定成功。</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span>根据产生的时间和目的，我们可以将现有</span><span><font face="Times New Roman">P2P</font></span><span>网络分成三代：</span><span><font face="Times New Roman">1</font></span><span>）</span><st1:chmetcnv unitname="g" sourcevalue="1" hasspace="False" negative="False" numbertype="1" tcsc="0"><span><font face="Times New Roman">1G</font></span></st1:chmetcnv><span>－第一代是最早的</span><span><font face="Times New Roman">P2P</font></span><span>网络，如</span><span><font face="Times New Roman">Napster</font></span><span>和</span><span><font face="Times New Roman">Gnutella</font></span><span>，旨在开发简单快捷。它们过于简单，以致没有好的可测量性和好的查询效率。</span><span><font face="Times New Roman">2</font></span><span>）</span><st1:chmetcnv unitname="g" sourcevalue="2" hasspace="False" negative="False" numbertype="1" tcsc="0"><span><font face="Times New Roman">2G</font></span></st1:chmetcnv><span>－第二代：第二代</span><span><font face="Times New Roman">P2P</font></span><span>网络通常使用</span><span><font face="Times New Roman">DHT</font></span><span>技术来实现更好的可测量性和查询效率，提供负栽均衡，支持精确查询。但是可扩展性和容错能力不好，尤其是在恶意攻击下更差。</span><span><font face="Times New Roman">3</font></span><span>）</span><st1:chmetcnv unitname="g" sourcevalue="3" hasspace="False" negative="False" numbertype="1" tcsc="0"><span><font face="Times New Roman">3G</font></span></st1:chmetcnv><span>－第三代</span><span><font face="Times New Roman">[13]~[15]</font></span><span>：近来提出的</span><span><font face="Times New Roman">P2P</font></span><span>网络想要在假定节点会在某些失败的情况下关闭后提供高的弹性。通常在提供高容错性使用的技术包括资源复制，节点间增加连接数，还有一些特殊的结构化拓扑。</span><st1:chmetcnv unitname="g" sourcevalue="2" hasspace="False" negative="False" numbertype="1" tcsc="0"><span><font face="Times New Roman">2G</font></span></st1:chmetcnv><span>和</span><st1:chmetcnv unitname="g" sourcevalue="3" hasspace="False" negative="False" numbertype="1" tcsc="0"><span><font face="Times New Roman">3G</font></span></st1:chmetcnv><span>的</span><span><font face="Times New Roman">P2P</font></span><span>网络通常是分布式的结构化网络。在后面，我们将按照上述的两种分类方法分别详细讨论它们。</span></font></p>
<table style="TABLE-LAYOUT: fixed">
    <tbody>
        <tr>
            <td>
            <div class=cnt id=blog_text>
            <p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman">3.2 </font></span><span>第一代</span><span><font face="Times New Roman">P2P</font></span></font></p>
            <p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman">Mapster</font></span><span>－</span><span><font face="Times New Roman">CPP[6]</font></span></font></p>
            <p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman">Napster[6] </font></span><span>，一个音乐交互系统，和其它简单系统一样有一个不断更新的资源列表保存在集中的</span><span><font face="Times New Roman">Napster</font></span><span>服务器上。节点登录这个服务器并发送它能提供的文件列表，然后向服务器提交查询来查找其它的哪个节点持有它们需要的文件，最后直接从资源所在节点下载需要的文件。尽管</span><span><font face="Times New Roman">Napster</font></span><span>集中数据库先天地避免了查询路由和其它的</span><span><font face="Times New Roman">P2P</font></span><span>系统上存在的问题，但是很明显这样的集中式解决方案有着单点故障的问题而且有较差的可扩展性。这种架构的另一个特点是它支持模糊查询（例如，查询所有的标题包含两个或多个特殊词的资源）。</span></font></p>
            <p style="MARGIN: 0cm 0cm 0pt"><span><font face="Times New Roman" size=3>Gnutella-DUPP[7]</font></span></p>
            <p style="MARGIN: 0cm 0cm 0pt"><font size=3><span>在这种典型的如</span><span><font face="Times New Roman">Gnutella[7]</font></span><span>架构中，它在拓扑上或资源配置上既没有一个集中的方向也没有精确的控制。</span><span><font face="Times New Roman">Gnutella</font></span><span>实际上是一个分布式的文件共享系统，它的参与着自我组织成一个虚拟的网络来运行</span><span><font face="Times New Roman">P2P</font></span><span>方式的分布式的文件查询。为了进入</span><span><font face="Times New Roman">Gnutella</font></span><span>，一个节点必须连接到一个已知的</span><span><font face="Times New Roman">Gnutella</font></span><span>节点来获取一些</span><span><font face="Times New Roman">Gnutella</font></span><span>节点列表来启动。当要查询一个文件，节点要向它的邻点提交查询。典型的查询方式是洪泛，即查询在一个确定的半径内向所有邻点传播或由</span><span><font face="Times New Roman">P2P</font></span><span>的</span><span><font face="Times New Roman">TTL</font></span><span>机制控制。这种非结构化的架构对于节点进入和离开系统有很好的弹性。但是，现在的基于洪泛的查询机制是不可升级的，因为它给网络的参与者产生了很大的负栽。近来，</span><span><font face="Times New Roman">[5]</font></span><span>尽最大的努力去克服这个缺陷，提出了两个机制：&#8220;动态</span><span><font face="Times New Roman">TTL</font></span><span>设置或扩展环&#8221;和&#8220;</span><span><font face="Times New Roman">K</font></span><span>步的随机漫步&#8221;。但是&#8220;</span><span><font face="Times New Roman">K</font></span><span>步的随机漫步&#8221;可能导致大的查询时间（延时）。因此，资源复制机制</span><span><font face="Times New Roman">[16][17]</font></span><span>（如统一复制，部分复制，平方根复制和</span><span><font face="Times New Roman">Log</font></span><span>形式的复制）同时被提出来减少查找时间。我们考虑使用</span><span><font face="Times New Roman">cache</font></span><span>机制可以使查询消息和长度可以同时减少，如：在查询路由逆向缓存资源。而且，</span><span><font face="Times New Roman">Gnutella</font></span><span>也可以支持模糊匹配。</span></font></p>
            <p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman">3.3 </font></span><span>第二代</span><span><font face="Times New Roman">P2P</font></span></font></p>
            <p style="MARGIN: 0cm 0cm 0pt"><font size=3><span>这种类型的架构没有集中服务器，但是有着显著的结构。&#8220;结构&#8221;意味着</span><span><font face="Times New Roman">P2P</font></span><span>网络拓扑是严格控制的（如</span><span><font face="Times New Roman">Mesh[9][10][18]</font></span><span>，</span><span><font face="Times New Roman">Ring[11][20-22]</font></span><span>，</span><span><font face="Times New Roman">D-dimension Torus[12]</font></span><span>，</span><span><font face="Times New Roman">K-ary tree[19]</font></span><span>，</span><span><font face="Times New Roman">SkipList[23]</font></span><span>），而且文件也不是随机的而是特定的放置的，这样使得后续的查询很容易成功。这种结构的</span><span><font face="Times New Roman">P2P</font></span><span>系统经常支持一个类似</span><span><font face="Times New Roman">Hash</font></span><span>表的接口，这在当前文献中是很普遍的。它使用精确的配置算法和特殊的路由协议是查询高效。但是还不是很清楚在有相当多暂时性节点和多处节点失效情况下，这种结构效率如何（除了</span><span><font face="Times New Roman">Butterfly</font></span><span>）。这种拓扑结构不支持模糊查询，只支持精确查询。</span></font></p>
            <p style="MARGIN: 0cm 0cm 0pt"><span><font face="Times New Roman" size=3>Plaxton[18]</font></span></p>
            <p style="MARGIN: 0cm 0cm 0pt"><font size=3><span>在</span><span><font face="Times New Roman">Plaxton</font></span><span>中，每个节点或机器都可以扮演服务器角色（即存储资源），路由器角色（即转发消息），和客户端（请求方）。在我们的讨论中，我们用这些术语来与节点互换。同时，资源和节点在它们的定位和语义部分在名字上是独立的，以基于普通基数的随机定长比特序列的形式来描述的（如，</span><span><font face="Times New Roman">40</font></span><span>个十六进制数描述</span><span><font face="Times New Roman">160</font></span><span>比特）。系统假定进入者大约是在节点数和资源命名空间都均匀分布的，它们可以使用</span><span><font face="Times New Roman">hash</font></span><span>算法的输出来完成，如</span><span><font face="Times New Roman">SHA-1</font></span><span>（</span><span><font face="Times New Roman">RFC3174</font></span><span>）。</span><span><font face="Times New Roman">Plaxton</font></span><span>假定</span><span><font face="Times New Roman">Plaxton</font></span><span>网络是静态数据结构，没有节点或资源插入或删除。</span></font></p>
            <p style="MARGIN: 0cm 0cm 0pt"><font size=3><span>在</span><span><font face="Times New Roman">Plaxton</font></span><span>中，资源定位按照如下工作：</span><span><font face="Times New Roman">1</font></span><span>）服务器</span><span><font face="Times New Roman">S1</font></span><span>通过路由一个消息到</span><span><font face="Times New Roman">O1</font></span><span>的&#8220;根节点&#8221;来发布消息说明它拥有资源</span><span><font face="Times New Roman">O1</font></span><span>。根节点在网络中是唯一节点，这个节点被放置在</span><span><font face="Times New Roman">O1</font></span><span>的内嵌树的根部。发布的过程由向根节点发送一个消息组成，消息包含</span><span><font face="Times New Roman">&lt;</font></span><span>资源标识，服务器标识</span><span><font face="Times New Roman">&gt;</font></span><span>映射。这个映射将被自</span><span><font face="Times New Roman">S1</font></span><span>到</span><span><font face="Times New Roman">O1</font></span><span>根节点的路径上的节点记录下来。</span><span><font face="Times New Roman">2</font></span><span>）在资源定位过程中，一个目标为</span><span><font face="Times New Roman">O1</font></span><span>的查询消息最初被路由向</span><span><font face="Times New Roman">O1</font></span><span>的根节点。</span><span><font face="Times New Roman">3</font></span><span>）在每一步中，如果消息遇到一个包含</span><span><font face="Times New Roman">O1</font></span><span>位置映射的节点，它立即转向到包含</span><span><font face="Times New Roman">O1</font></span><span>的服务器上。否则，这个消息被转发到离根节点更近的节点。如果消息到达根节点，只要</span><span><font face="Times New Roman">O1</font></span><span>的根节点没有失效它就要确保找到一个</span><span><font face="Times New Roman">O1</font></span><span>位置的映射。</span></font></p>
            <p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman">Plaxton</font></span><span>路由协议执行如下：</span><span><font face="Times New Roman">1</font></span><span>）节点</span><span><font face="Times New Roman">N</font></span><span>有一个多层的邻点图，其中每一个层描述了一个</span><span><font face="Times New Roman">ID</font></span><span>数字的匹配的后缀。例如，节点</span><span><font face="Times New Roman">325AE</font></span><span>在第四层的第九个入口在网络上离</span><span><font face="Times New Roman">325AE</font></span><span>近，到</span><span><font face="Times New Roman">95AE</font></span><span>终止。</span><span><font face="Times New Roman">2</font></span><span>）</span><span><font face="Times New Roman">Plaxton</font></span><span>在每个节点上使用本地路由映射（邻点图），从而一个数字一个数字地逐步路由消息到目标</span><span><font face="Times New Roman">ID</font></span><span>上（例如，</span><span><font face="Times New Roman">****8=&gt;**98=&gt;*598=&gt;4598</font></span><span>，其中</span><span><font face="Times New Roman">*</font></span><span>代表通配符）。这种方案类似于在</span><span><font face="Times New Roman">CIDR IP</font></span><span>地址配置结构中的最长前缀路由方式。</span></font></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
<img src ="http://www.cppblog.com/tommyyan/aggbug/82058.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 16:00 <a href="http://www.cppblog.com/tommyyan/articles/82058.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Kademlia详解&amp;&amp;细说 Kademlia 【FreeXploiT整理文】</title><link>http://www.cppblog.com/tommyyan/articles/82057.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 07:58:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82057.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82057.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82057.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82057.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82057.html</trackback:ping><description><![CDATA[前两天在网上看到世界知名的电骡服务器Razorback 2被查封、4人被拘禁的消息，深感当前做eMule / BitTorrent等P2P文件交换软件的不易。以分布式哈希表方式(DHT，Distributed Hash Table)来代替集中索引服务器可以说是目前可以预见到的为数不多的P2P软件发展趋势之一，比较典型的方案主要包括：CAN、CHORD、 Tapestry、Pastry、Kademlia和Viceroy等，而Kademlia协议则是其中应用最为广泛、原理和实现最为实用、简洁的一种，当前主流的P2P软件无一例外地采用了它作为自己的辅助检索协议，如eMule、Bitcomet、Bitspirit和Azureus等。鉴于 Kademlia日益增长的强大影响力，今天特地在blog里写下这篇小文，算是对其相关知识系统的总结。
<div class=postText>
<p><strong><br>1. Kademlia简述</strong></p>
<p>Kademlia(简称Kad)属于一种典型的结构化P2P覆盖网络(Structured P2P Overlay Network)，以分布式的应用层全网方式来进行信息的存储和检索是其尝试解决的主要问题。在Kademlia网络中，所有信息均以的哈希表条目形式加以存储，这些条目被分散地存储在各个节点上，从而以全网方式构成一张巨大的分布式哈希表。我们可以形象地把这张哈希大表看成是一本字典：只要知道了信息索引的key，我们便可以通过Kademlia协议来查询其所对应的value信息，而不管这个value信息究竟是存储在哪一个节点之上。在eMule、 BitTorrent等P2P文件交换系统中，Kademlia主要充当了文件信息检索协议这一关键角色，但Kad网络的应用并不仅限于文件交换。下文的描述将主要围绕eMule中Kad网络的设计与实现展开。</p>
<p><strong><br>2. eMule的Kad网络中究竟存储了哪些信息?</strong></p>
<p>只要是能够表述成为字典条目形式的信息Kad网络均能存储，一个Kad网络能够同时存储多张分布式哈希表。以eMule为例，在任一时刻，其Kad网络均存储并维护着两张分布式哈希表，一张我们可以将其命名为关键词字典，而另一张则可以称之为文件索引字典。</p>
<p>a. <strong>关键词字典</strong>：主要用于根据给出的关键词查询其所对应的文件名称及相关文件信息，其中key的值等于所给出的关键词字符串的160比特SHA1散列，而其对应的value则为一个列表，在这个列表当中，给出了所有的文件名称当中拥有对应关键词的文件信息，这些信息我们可以简单地用一个3元组条目表示：(文件名，文件长度，文件的SHA1校验值)，举个例子，假定存在着一个文件 &#8220;warcraft_frozen_throne.iso&#8221;，当我们分别以&#8220;warcraft&#8221;、&#8220;frozen&#8221;、&#8220;throne&#8221;这三个关键词来查询 Kad时，Kad将有可能分别返回三个不同的文件列表，这三个列表的共同之处则在于它们均包含着一个文件名为 &#8220;warcraft_frozen_throne.iso&#8221;的信息条目，通过该条目，我们可以获得对应iso文件的名称、长度及其160比特的SHA1校验值。</p>
<p>b. <strong>文件索引字典</strong>：用于根据给出的文件信息来查询文件的拥有者(即该文件的下载服务提供者)，其中key的值等于所需下载文件的SHA1校验值(这主要是因为，从统计学角度而言，160比特的SHA1文件校验值可以唯一地确定一份特定数据内容的文件)；而对应的 value也是一个列表，它给出了当前所有拥有该文件的节点的网络信息，其中的列表条目我们也可以用一个3元组表示：(拥有者IP，下载侦听端口，拥有者节点ID)，根据这些信息，eMule便知道该到哪里去下载具备同一SHA1校验值的同一份文件了。</p>
<p><strong><br>3. 利用Kad网络搜索并下载文件的基本流程是怎样的?</strong></p>
<p>基于我们对eMule的Kad网络中两本字典的理解，利用Kad网络搜索并下载某一特定文件的基本过程便很明白了，仍以 &#8220;warcraft_frozen_throne.iso&#8221;为例，首先我们可以通过warcraft、frozen、throne等任一关键词查询关键词字典，得到该iso的SHA1校验值，然后再通过该校验值查询Kad文件索引字典，从而获得所有提供 &#8220;warcraft_frozen_throne.iso&#8221;下载的网络节点，继而以分段下载方式去这些节点下载整个iso文件。</p>
<p>在上述过程中，Kad网络实际上所起的作用就相当于两本字典，但值得再次指出的是，Kad并不是以集中的索引服务器(如华语P2P源动力、 Razorback 2、DonkeyServer 等，骡友们应该很熟悉吧)方式来实现这两本字典的存储和搜索的，因为这两本字典的所有条目均分布式地存储在参与Kad网络的各节点中，相关文件信息、下载位置信息的存储和交换均无需集中索引服务器的参与，这不仅提高了查询效率，而且还提高了整个P2P文件交换系统的可靠性，同时具备相当的反拒绝服务攻击能力；更有意思的是，它能帮助我们有效地抵制FBI的追捕，因为俗话说得好：法不治众&#8230;看到这里，相信大家都能理解&#8220;分布式信息检索&#8221;所带来的好处了吧。但是，这些条目究竟是怎样存储的呢?我们又该如何通过Kad网络来找到它们?不着急，慢慢来。</p>
<p><strong><br>4. 什么叫做节点的ID和节点之间的距离?</strong></p>
<p>Kad网络中的每一个节点均拥有一个专属ID，该ID的具体形式与SHA1散列值类似，为一个长达160bit的整数，它是由节点自己随机生成的，两个节点拥有同一ID的可能性非常之小，因此可以认为这几乎是不可能的。在Kad网络中，两个节点之间距离并不是依靠物理距离、路由器跳数来衡量的，事实上，Kad网络将任意两个节点之间的距离d定义为其二者ID值的逐比特二进制和数，即，假定两个节点的ID分别为a与b，则有：d=a XOR b。在Kad中，每一个节点都可以根据这一距离概念来判断其他节点距离自己的&#8220;远近&#8221;，当d值大时，节点间距离较远，而当d值小时，则两个节点相距很近。这里的&#8220;远近&#8221;和&#8220;距离&#8221;都只是一种逻辑上的度量描述而已；在Kad中，距离这一度量是无方向性的，也就是说a到b的距离恒等于b到a的距离，因为a XOR b==b XOR a</p>
<p><strong><br>5. 条目是如何存储在Kad网络中的?</strong></p>
<p>从上文中我们可以发现节点ID与条目中key值的相似性：无论是关键词字典的key，还是文件索引字典的key，都是160bit，而节点ID恰恰也是160bit。这显然是有目的的。事实上，节点的ID值也就决定了哪些条目可以存储在该节点之中，因为我们完全可以把某一个条目简单地存放在节点ID 值恰好等于条目中key值的那个节点处，我们可以将满足(ID==key)这一条件的节点命名为目标节点N。这样的话，一个查找条目的问题便被简单地转化成为了一个查找ID等于Key值的节点的问题。</p>
<p>由于在实际的Kad网络当中，并不能保证在任一时刻目标节点N均一定存在或者在线，因此Kad网络规定：任一条目，依据其key的具体取值，该条目将被复制并存放在节点ID距离key值最近(即当前距离目标节点N最近)的k个节点当中；之所以要将重复保存k份，这完全是考虑到整个Kad系统稳定性而引入的冗余；这个k的取值也有讲究，它是一个带有启发性质的估计值，挑选其取值的准则为：&#8220;在当前规模的Kad网络中任意选择至少k个节点，令它们在任意时刻同时不在线的几率几乎为0&#8221;；目前，k的典型取值为20，即，为保证在任何时刻我们均能找到至少一份某条目的拷贝，我们必须事先在Kad网络中将该条目复制至少20份。</p>
<p>由上述可知，对于某一条目，在Kad网络中ID越靠近key的节点区域，该条目保存的份数就越多，存储得也越集中；事实上，为了实现较短的查询响应延迟，在条目查询的过程中，任一条目可被cache到任意节点之上；同时为了防止过度cache、保证信息足够新鲜，必须考虑条目在节点上存储的时效性：越接近目标结点N，该条目保存的时间将越长，反之，其超时时间就越短；保存在目标节点之上的条目最多能够被保留24小时，如果在此期间该条目被其发布源重新发布的话，其保存时间还可以进一步延长。</p>
<p><strong><br>6. Kad网络节点需要维护哪些状态信息?</strong></p>
<p>在Kad网络中，每一个节点均维护了160个list，其中的每个list均被称之为一个k-桶(k-bucket)，如下图所示。在第i个 list中，记录了当前节点已知的与自身距离为2^i~2^(i+1)的一些其他对端节点的网络信息(Node ID，IP地址，UDP端口)，每一个list(k-桶)中最多存放k个对端节点信息，注意，此处的k与上文所提到的复制系数k含义是一致的；每一个 list中的对端节点信息均按访问时间排序，最早访问的在list头部，而最近新访问的则放在list的尾部。</p>
<p><img src="http://www.cppblog.com/images/cppblog_com/shenhuafeng/2600/r_k_bucket.png"></p>
<p>k-桶中节点信息的更新基本遵循Least-recently Seen Eviction原则：当list容量未满(k-桶中节点个数未满k个)，且最新访问的对端节点信息不在当前list中时，其信息将直接添入list队尾，如果其信息已经在当前list中，则其将被移动至队尾；在k-桶容量已满的情况下，添加新节点的情况有点特殊，它将首先检查最早访问的队首节点是否仍有响应，如果有，则队首节点被移至队尾，新访问节点信息被抛弃，如果没有，这才抛弃队首节点，将最新访问的节点信息插入队尾。可以看出，尽可能重用已有节点信息、并且按时间排序是k-桶节点更新方式的主要特点。从启发性的角度而言，这种方式具有一定的依据：在线时间长一点的节点更值得我们信任，因为它已经在线了若干小时，因此，它在下一个小时以内保持在线的可能性将比我们最新访问的节点更大，或者更直观点，我这里再给出一个更加人性化的解释：MP3文件交换本身是一种触犯版权法律的行为，某一个节点反正已经犯了若干个小时的法了，因此，它将比其他新加入的节点更不在乎再多犯一个小时的罪&#8230;&#8230;-_-b</p>
<p>由上可见，设计采用这种多k-bucket数据结构的初衷主要有二：a. 维护最近-最新见到的节点信息更新；b. 实现快速的节点信息筛选操作，也就是说，只要知道某个需要查找的特定目标节点N的ID，我们便可以从当前节点的k-buckets结构中迅速地查出距离N 最近的若干已知节点。</p>
<p><strong><br>7. 在Kad网络中如何寻找某特定的节点?</strong></p>
<p>已知某节点ID，查找获得当前Kad网络中与之距离最短的k个节点所对应的网络信息(Node ID，IP地址，UDP端口)的过程，即为Kad网络中的一次节点查询过程(Node Lookup)。注意，Kad之所以没有把节点查询过程严格地定义成为仅仅只查询单个目标节点的过程，这主要是因为Kad网络并没有对节点的上线时间作出任何前提假设，因此在多数情况下我们并不能肯定需要查找的目标节点一定在线或存在。</p>
<p>整个节点查询过程非常直接，其方式类似于DNS的迭代查询：<br>a. 由查询发起者从自己的k-桶中筛选出若干距离目标ID最近的节点，并向这些节点同时发送异步查询请求；<br>b .被查询节点收到请求之后，将从自己的k-桶中找出自己所知道的距离查询目标ID最近的若干个节点，并返回给发起者；<br>c. 发起者在收到这些返回信息之后，再次从自己目前所有已知的距离目标较近的节点中挑选出若干没有请求过的，并重复步骤1；<br>d. 上述步骤不断重复，直至无法获得比查询者当前已知的k个节点更接近目标的活动节点为止。<br>e. 在查询过程中，没有及时响应的节点将立即被排除；查询者必须保证最终获得的k个最近节点都是活动的。</p>
<p>简单总结一下上述过程，实际上它跟我们日常生活中去找某一个人打听某件事是非常相似的，比方说你是个Agent Smith，想找小李(key)问问他的手机号码(value)，但你事先并不认识他，你首先肯定会去找你所认识的和小李在同一个公司工作的人，比方说小赵，然后小赵又会告诉你去找与和小李在同一部门的小刘，然后小刘又会进一步告诉你去找和小李在同一个项目组的小张，最后，你找到了小张，哟，正好小李出差去了(节点下线了)，但小张恰好知道小李的号码，这样你总算找到了所需的信息。在节点查找的过程中，&#8220;节点距离的远近&#8221;实际上与上面例子中&#8220;人际关系的密切程度&#8221;所代表的含义是一样的。<br></p>
<p>最后说说上述查询过程的局限性：Kad网络并不适合应用于模糊搜索，如通配符支持、部分查找等场合，但对于文件共享场合来说，基于关键词的精确查找功能已经基本足够了(值得注意的是，实际上我们只要对上述查找过程稍加改进，并可以令其支持基于关键词匹配的布尔条件查询，但仍不够优化)。这个问题反映到eMule的应用层面来，它直接说明了文件共享时其命名的重要性所在，即，文件名中的关键词定义得越明显，则该文件越容易被找到，从而越有利于其在 P2P网络中的传播；而另一方面，在eMule中，每一个共享文件均可以拥有自己的相关注释，而Comment的重要性还没有被大家认识到：实际上，这个文件注释中的关键词也可以直接被利用来替代文件名关键词，从而指导和方便用户搜索，尤其是当文件名本身并没有体现出关键词的时候。</p>
<p><strong><br>8. 在Kad网络中如何存储和搜索某特定的条目?</strong></p>
<p>从本质上而言，存储、搜索某特定条目的问题实际上就是节点查找的问题。当需要在Kad网络中存储一个条目时，可以首先通过节点查找算法找到距离 key最近的k个节点，然后再通知它们保存条目即可。而搜索条目的过程则与节点查询过程也是基本类似，由搜索发起方以迭代方式不断查询距离key较近的节点，一旦查询路径中的任一节点返回了所需查找的value，整个搜索的过程就结束。为提高效率，当搜索成功之后，发起方可以选择将搜索到的条目存储到查询路径的多个节点中，作为方便后继查询的cache；条目cache的超时时间与节点-key之间的距离呈指数反比关系。</p>
<p><strong><br>9. 一个新节点如何首次加入Kad网络?</strong></p>
<p>当一个新节点首次试图加入Kad网络时，它必须做三件事，其一，不管通过何种途径，获知一个已经加入Kad网络的节点信息(我们可以称之为节点 I)，并将其加入自己的k-buckets；其二，向该节点发起一次针对自己ID的节点查询请求，从而通过节点I获取一系列与自己距离邻近的其他节点的信息；最后，刷新所有的k-bucket，保证自己所获得的节点信息全部都是新鲜的。</p>
</div>
<div class=cnt id=blog_text>
<p><font face=verdana,arial,helvetica,sans-serif color=#0000ff>Kad是Kademlia的简称，eMule的官方网站在2004年2月27日正式发布的 eMule v0.42b中，Kad开始正式内嵌成为eMule的一个功能模块，可以说从这个版本开始eMule便开始支持Kad网络了。<br><br>Kad的出现，结束了之前edonkey时代，在ed圈里只存在着ED2K一种网络的模式，它通过新的协议开创并形成了自己的kad网络，使之和ED2K 网络并驾齐驱，而且它还完全支持两种网络，可以在两种网络之间通用。Kad同样也属于开源的自由软件。它的程序和源代码可以在官方网站 http://www.emule-project.net上下载。 <br><br>Kad网络拓扑的最大特点在于它完全不需要服务器，我们都知道传统的ed2k网络需要服务器支持作为中转和存储hash列表信息，kad可以不通过服务器同样完成ed2k网络的一切功能，你唯一要做的就是连线上网，然后打开kad。Kad需要UDP端口的支持，之后Emule会自动按照客户端的要求，来判断它能否自由连线，然后同样也会分配给你一个id，这个过程和我们ed2k的高id和低id检查很像，不过这个id所代表的意义不同于ed2k网络，它代表一个是否&#8220;freely&#8221;的状态。<br><br>Kad和ed2k网络有着完全不同的观念但是相同的目的: 都是搜索和寻找文件的源。 Kad网络的主要的目标是做到不需要服务器和改善可量测性。相对于传统的ed2k服务器只能处理一定数量的使用者(我们在服务器列表也都看到了,每个服务器都有最大人数限制)，而且如果服务器比较大连接人数过多,还会严重的的拖垮网络。而Kad能够自我组织,并且自我调节最佳的使用者数量以及他们的连接效果。因此, 它更能使网络的损失达到最小。由于具备了以上所叙述的功能，Kad也被称之为Serverless network（无服务器网络）。虽然目前一直处于开发阶段(alpha stage) 。但毫无疑问，它无可比拟的优势,将会使它成为p2p的明天。<br><br>可能很多朋友会关注， kad网络没有高低id的计算原则，是否对于低id来言就畅通无阻了呢？<br><br>我们大家知道在ed2k网络里面，我们的id是通过ip进行如下的算法计算得出的<br>设我们的IP = A.B.C.D<br>那么我们的ID number= A + 256*B + 256*256*C + 256*256*256*D<br>low ID的产生是由于我们的ID计算结果小于16777216.<br>即 ID number= A + 256*B + 256*256*C + 256*256*256*D &lt; 16777216 <br><br>Kad的 id计算原则并不是象上面那样，他更关注我们是否open和freely。<br>但是kad里面是如何计算我们的id呢？<br>事实上它的计算方法是这样<br>ID number=256*256*256*A+256*256*B+256*C+D <br>所以kad其实也有高低id的分别。所以内网用户在使用的时候依旧无法达到内网用户完全穿透网络的效果，而且目前来看，还存在着kad模块引入,导致占用系统资源会变大以及会突然产生Memory Leak的问题，对于内存的控制，目前emule做的效果还是不好。<br><br>其实kad本身有一个nodes.dat文件，也叫做节点文件，这里面存放了我们在Kad网络中的邻居节点，我们都是通过这些节点来进入Kad网络的。其实kad的网络倒更像是overnet和Kazaa网络，有兴趣的朋友大家可以对比看看。Kad网络提供了帮助寻找节点以及记录节点的机制。 <br><br>下面我们来说说这个机制的原理：<br>Kad拥有一个160bit的ID，每一个节点送出的讯息都必须包含此ID。每一个节点都必须记录一个资料来保存已经存在的节点，资料的格式是 (IP address, UDP port, Node ID)，节点所必须负责的范围是2的i次方及2的i+1次方，i的范围是0 &lt; i &lt;160，这个结构叫做k-bucket，该结构会形成一个tree的形状，每一次接收到新的信息时，各个节点都必须更新k-bucket內的资料，透过k-bucket结构我们可以保证所有的节点状态都是新的，而且一定会知道这个节点在哪里。<br><br>Kademlia网络提供四种Potocol(RPC) <br>(1)PING 测试是否节点存在<br>(2)STORE存储通知的资料<br>(3)FIND_NODE 通知其他节点帮助寻找node<br>(4)FIND_VALUE 通知其他节点帮助寻找Value<br>而当每一个指令被接受到后，每一个节点都会到k-bucket上搜寻，通过这样的结构，kad提供一个方便快速且可以被保证在logN次数下找到所需的节点。<br><br>通俗的来讲就是在kad网络中，我们每个emule用户端只负责处理一小部分搜索和查找源的工作。分配这些工作的时候，通过我们每个用户端的唯一的ID和搜索文件的hash值之间的匹配来决定。比如像我猜我猜我猜猜.rm这个文件由用户小王来负责（通过该文件的hash值来决定），那么任何其他用户在下载这个文件的時候都会告诉其他用户,小王有这个文件，其他用户去下载这个文件的時候也会询问小王，小王也会告诉他们谁正在共享这个文件，这样kad找源的工作就完成了。搜索时候的方法也差不多，只不过是每个人负责一个关键字。<br><br>整个过程有点像在照线索循序问路而找到正确方向，而不是路上随便到处抓人在问路。而每个地方里的网络相关信息，则会随着电脑及文件的加入而持续更新。好处在于让你可以搜索整个网络，而不只是在某一地区。目前来讲，这个机制和算法是绝对领先而且非常优秀的。<br><br>如何找到用户小王则是通过将用户id异或的方式，两个id的二进位异或值决定他们之间的逻辑距离，如1100距离1101要比距离1001近。那么当一个用户加入kad后，首先通过一个已知的用户找到一批用户的id和ip地址和端口。当该用户要寻找一个特定用户A的时候，该用户先询问几个已知的逻辑距离较 A较近的用户，如B用户,C用户,D用户，B，C，D会告诉该用户他们知道的更加近的用户的id和ip地址和端口，同理类推，这个用户最终就能找到A。所以寻找的次数会在logN数量级，这里N代表询问的人数。<br><br>其实也就是一种分散式杂凑的方法，基本上是对网络上某一特定时刻的文件进行快照(snapshot)，然后将这些信息分散到整个网络里。为了找到特定的文件，搜索的要求先到达网络上的任何一台电脑上，然后这台电脑就会再将它转到另一台有更多文件信息的电脑。第三台电脑可能就拥有文件本身 ──或者也可能再继续转到其他有正确信息的电脑。采用这种方法，通常只需要跳转两到三次，便可以轻松查找到所需文件。<br><br>以上几个部分，便是对于kad作用原理以及算法的分析，可能好多人看了之后头大，那么我们普通用户到底该注意些什么呢？<br><br>很简单，你要作的就是再使用emule的时候打开kad，你会发现有两个明显的特点<br>(1)你的下载速度会加快<br>(2)你的下载文件的源会增加<br>以上两条对于lowid和经常下载源在国外的文件用户，效果就更为突出，特别对于在ed2k网络中只有几个源或者没有源的文件，在kad网络中，一般都能找到源，所以说你使用了emule下载文件，基本上不会出现没有源的请况，无论多长时间，差别只是源的多少个数问题，由于kad网络都是自动配置的，所以你丝毫不用分心，那么索性我们就打开它，何乐而不为呢？<br><br>另外对于我们搜索的时候，如果采用kad网络搜索,多数情况下找到的文件源会远远多于ed2k的全局搜索，对于大家都是一个明智的选择。</font></p>
<p><font face=verdana,arial,helvetica,sans-serif></font></p>
<p><font face=verdana,arial,helvetica,sans-serif></font></p>
<p><font face=verdana,arial,helvetica,sans-serif>1. ID and Key</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>Node ID：160 bit （每一个Node拥有一个ID，随机产生）<br>Key：160 bit （Key也许是某个很大的数据的SHA-1 hash值）</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>(Key,Value)这一对数据保存在ID最&#8220;接近&#8221;Key的Node上。<br>&#8220;接近&#8221;的意思是Key和ID之间的&#8220;距离&#8221;很短。<br>Kad网络中&#8220;距离&#8221;的定义是：d(x,y) = x XOR y，也就是x和y的异或值。</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>* 选择XOR作为衡量距离的尺度，是因为XOR有xxx,yyy,zzz,...等特性，具体请看Kademlia的paper [1] section 2.1。</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>2. k-bucket</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>每一个Node保持160个 list。对于第 i (0 &lt;= i &lt; 160) 个 list，此 list 中保存着和该 Node 距离为 2*i ~ 2*(i+1) 【2的 i 次方到 2的 i+1 次方】的一些 Node 的信息（Node ID，IP地址，UDP端口）。这些 list 叫做 k-bucket 。k是每一个 listk 中保存的 Node 的最大<br>个数（比如，20）。</font></p>
<p><font face=verdana,arial,helvetica,sans-serif><font face="courier new,courier,monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br>list 0: |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp; 距离为[1,2)的node <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br></font>（ID前159 bit相同，第160 bit一定不同的node: 1个）</font></p>
<p><font face=verdana,arial,helvetica,sans-serif><font face="courier new,courier,monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br>list 1: |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp; 距离为[2,4)的node <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br></font>（ID前158 bit相同，第159 bit一定不同的node: 2个）</font></p>
<p><font face=verdana,arial,helvetica,sans-serif><font face="courier new,courier,monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br>list 2: |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp; 距离为[4,8)的node <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br></font>（ID前157 bit相同，第158 bit一定不同的node: 4个）</font></p>
<p><font face=verdana,arial,helvetica,sans-serif><font face="courier new,courier,monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br>list 3: |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp; 距离为[8,16)的node <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br></font>（ID前156 bit相同，第157 bit一定不同的node: 8个）</font></p>
<p><font face=verdana,arial,helvetica,sans-serif><font face="courier new,courier,monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br>list 4: |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp; 距离为[16,32)的node <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br></font>（ID前155 bit相同，第156 bit一定不同的node: 16个）</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>。<br>。<br>。<br><font face="courier new,courier,monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br>list 159: |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp; 距离为[2*159,2*160)的node <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br></font>（ID第1 bit不同的node: 2*159个。此list中最多保存最近访问的k个）</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>每一个list （k-bucket）中的node按照访问的时间顺序排列，最先访问的在list头部，最近访问的在list的尾部：</font></p>
<p><font face="courier new,courier,monospace">Head&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Tail<br>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>V&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; V<br>+--+&nbsp;&nbsp;&nbsp;&nbsp; +--+&nbsp;&nbsp;&nbsp;&nbsp; +--+&nbsp;&nbsp;&nbsp;&nbsp; +--+&nbsp;&nbsp;&nbsp;&nbsp; +--+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +--+<br>|&nbsp;&nbsp;&nbsp; |--&gt;|&nbsp;&nbsp;&nbsp; |--&gt;|&nbsp;&nbsp;&nbsp; |--&gt;|&nbsp;&nbsp;&nbsp; |--&gt;|&nbsp;&nbsp;&nbsp; |--&gt; ... --&gt; |&nbsp;&nbsp;&nbsp; |<br>+--+&nbsp;&nbsp;&nbsp;&nbsp; +--+&nbsp;&nbsp;&nbsp;&nbsp; +--+&nbsp;&nbsp;&nbsp;&nbsp; +--+&nbsp;&nbsp;&nbsp;&nbsp; +--+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +--+<br>Node0&nbsp;&nbsp;&nbsp; Node1&nbsp;&nbsp;&nbsp; ...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Node[N]<br>A&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A<br>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>least-recently&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; most-recently</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>3.k-bucket的刷新</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>当一个Node收到另一个Node发来的消息的时候，它根据以下规则（least-recently seen eviction policy）来刷新相应的k-bucket:<br>1) 如果发送者已经在某一个k-bucket中，将它在该 k-bucket 中移动到尾部<br>2) 如果发送者不在其对应的k-bucket中，并且该 k-bucket 还不满 k 个Node，将这个发送者加入到 k-bucket 的尾部<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果该 k-bucket 已经满了（有k个Node），那么接受消息的 Node 向该 k-bucket 中最老的（也就是头部的）Node发送一个 ping 消息<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果这个最老的 Node 没有响应，则将它从 k-bucket 中删除，将新的发送消息的 Node 加入到 k-bucket 的尾部<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果这个最老的 Node 响应了，则将它移动到 k-bucket 的尾部，并抛弃新的 Node 的信息</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>* 为什么要采取这样的规则，请看Kademlia的paper [1] section 2.2</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>4. 基本协议(protocol)</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>四种基本的RPC：PING, STORE, FIND_NODE, FIND_VALUE</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>1) PING<br>PING 探测一个Node是否在线</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>2) STORE<br>STORE 以(Key,Value)为参数。指示另一个Node（消息接收者）保存一个(Key, Value)对，以供以后取用。</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>3) FIND_NODE<br>FIND_NODE 以一个160 bit的ID作为参数。接收者返回它所知道的最接近该ID的 k 个 Node 的 Contact 信息（ID,UPD Port,IP）。这些 Node 不一定要从同一个 k-bucket 中产生。除非它所有的 k-bucket 中所有的 Node 加在一起都不到 k 个，否则它必需凑足 k 个。</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>4) FIND_VALUE<br>FIND_VALUE 以一个160 bit的ID/Key作为参数。如果接收者先前已经收到一个 STORE RPC，而且 STORE 的参数之一 Key 就是当前 FIND_VALUE 的参数，那么，接收者将 STORE 的另一个参数 Value 返回给发送者。否则，FIND_VALUE 和 FIND_NODE 一样，返回它所知道的最接近该ID的 k 个 Node 的 Contact 信息（ID,UPD Port,IP）。<br>FIND_VALUE 的意思就是：如果接收者有发送着所要的 Value，就将该 Value 返回。否则，接收者就帮忙找更接近该 ID/Key 的 Node。</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>所有的 RPC 消息都带有一个 160 bit的随机 RPC ID，由发送者产生，接收者在响应每一个消息的时候，返回的消息里面都必需拷贝此 RPC ID。目的是防止地址伪造。PING 消息可以在 RPC 响应里使用捎带确认机制来获得发送者网络地址（原文：pings can also be piggy-backed on RPC replies for the RPC recipient to obtain additional assurance of the sender's network address.）。</font></p>
<p><font face=verdana,arial,helvetica,sans-serif>* piggyback<br>&nbsp;&nbsp;&nbsp; 捎带确认(法)<br>&nbsp;&nbsp;&nbsp; A technique used to return acknowledgement information across a full-duplex&nbsp;&nbsp;&nbsp; (two-way simultaneous) data link without the use of special (acknowledgement) message. The acknowledgement information relating to the flow of message in one direction is embedded (piggybacked) into normal data-carrying message flowing in the reverse direction.<br>&nbsp;&nbsp;&nbsp; 经全双工(双向同时)数据链路,不用专门(确认)报文返回确认信息所用的技术。与一个方向的报文流有关的确认信息钳在反方向正常携带数据的报文流中。</font></p>
<p><font face=verdana,arial,helvetica,sans-serif></font></p>
<p><font face=verdana,arial,helvetica,sans-serif></font></p>
<p><font face=verdana,arial,helvetica,sans-serif></font></p>
<p><font face=verdana,arial,helvetica,sans-serif></font></p>
<p><font face=verdana,arial,helvetica,sans-serif></font></p>
<p><font face=verdana,arial,helvetica,sans-serif>[1] Kadmelia的paper：</font><a href="http://www.cs.rice.edu/Conferences/IPTPS02/109.pdf"><font face=verdana,arial,helvetica,sans-serif color=#0000ff><u>http://www.cs.rice.edu/Conferences/IPTPS02/109.pdf</u></font></a></p>
<p><u><font color=#0000ff></font></u></p>
<p>5. 节点查找(node lookup)<br>node lookup:找到距离给定的 ID 最近的 k 个 node<br>定义 a:系统范围内的并发参数，比如3。<br>步骤：<br>1) 从最近的 k-bucket 里面取出 a 个最近的 node，然后向这 a 个 node 发送并行的、异步的 FIND_NODE RPC。<br>2) 再次发送 FIND_NODE RPC 给从前一步返回的 node（这一步可以不必等到前一步中所有 a 个 PRC 都返回之后才开始）：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 从发送者已知的 k 个最接近目标的 node 当中，取出 a 个还没有查询的 node，向这 a 个 node 发送 FIND_NODE RPC。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 没有迅速响应的 node 将被排除出考虑之列，直到其响应。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果一轮 FIND_NODE RPC 没有返回一个比已知的所有 node 更接近目标的 node，发送者将再向 k 个最接近目标的、还没有查询的 node 发送 FIND_NODE RPC。<br>3) 查找结束的条件：发送者已经向 k 个最近的 node 发送了查询，并且也得到了响应。</p>
<p><br>6. 存储&lt;Key, Value&gt;(store a &lt;Key,Value&gt; pair)<br>步骤：<br>1) 使用node lookup算法，找到距离 Key 最近的 k 个 node<br>2) 向这 k 个 node 发送 STORE RPC<br>3) 每一个 node 必要的时候重新发布(re-publish)所有的&lt;Key,Value&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; （对于当前的 Kademlia 应用(文件共享)，每一个&lt;Key,Value&gt;的原始发布者被要求每隔24小时重新发布一次，否则&lt;Key,Value&gt;将在发布之后的24小时之后过期。对于其他一些应用，比如digital certificates, cryptographic hash to value mapping，过期时间可以更长一些）</p>
<p>7. 搜索&lt;Key,Value&gt; （find a &lt;Key,Value&gt; pair)<br>步骤：<br>1) 使用 FIND_VALUE 代替 FIND_NODE 进行"node lookup"过程。一旦任何其他 node 返回了所要的 Value，搜索的过程就结束。<br>2) cache: 如果搜索成功，搜索的发起者将这个&lt;Key,Value&gt;对存储到已知的、最近的、但是在第一步中没有返回该 Value 的 node 上。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 显然，有了cache之后，以后对于该 &lt;Key,Value&gt; 的搜索很可能首先找到 cache，而不是直到找到最接近 Key 的那个 node。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果一个 &lt;Key,Value&gt; 被频繁的搜索，那么它很可能被缓存到很多 ID 不太接近 Key 的 node 中。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 为了避免过度缓存(over-caching)，每一个 &lt;Key,Value&gt; 都有一个过期时间，这个过期时间和当前node 和&#8220;最接近Key之node&#8221;之间的 node 的个数（这个数目可以从当前node的bucket接口推断出）的指数倒数成正比。（To avoid "over-caching", we make the expiration time of a &lt;key,value&gt; pair in any node's database exponentially inversely proportional to the number of nodes between the current node and the node whose ID is closest to the key ID. This number can be inferred from the bucket structure of the current node）<br><br>8. 刷新bucket (refresh bucket)<br>所有的 bucket 通过node之间的请求来刷新。<br>如果某一个bucket在过去一个小时之内没有任何的node lookup操作，那么这个node就随机搜索一个在这个bucket覆盖范围内的ID。<br><br>9. node加入网络<br>步骤：<br>1) 一个新加入的node（假设为u）必需首先获得一个已经在网络中的node（比如为w）的contact信息。<br>2) u 首先将 w 加入到其对应的 k-bucket 中<br>3) u 执行一个对自己ID的 node lookup 操作<br>4) u 刷新所有的 k-bucket</p>
</div>
<img src ="http://www.cppblog.com/tommyyan/aggbug/82057.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 15:58 <a href="http://www.cppblog.com/tommyyan/articles/82057.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>emule协议分析</title><link>http://www.cppblog.com/tommyyan/articles/82056.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 07:54:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82056.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82056.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82056.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82056.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82056.html</trackback:ping><description><![CDATA[Kulbak and Danny Bickson 《The eMule Protocol Specification》
<p>翻译：lzcx</p>
<p>QQ:402722857 </p>
<p>EMail: lzcx_cn@yahoo.com.cn </p>
<p>供学习用，转载请注明出处</p>
<p>&#160;</p>
<div style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: -21pt"><strong><span style="FONT-SIZE: 14pt">1<span style="FONT: 7pt Times New Roman; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></strong><strong><span style="FONT-SIZE: 14pt">简介</span></strong></div>
<div><strong><span style="FONT-SIZE: 14pt"></span></strong></div>
<div><strong><span style="FONT-SIZE: 12pt">1.1 </span></strong><strong><span style="FONT-SIZE: 12pt">目的和范围</span></strong></div>
<div><strong><span style="FONT-SIZE: 12pt"></span></strong></div>
<div>eMule是流行的文件共享程序，基于eDonkey协议。这份报告描述了eMule的网络行为和解释了理解该协议所需的基本术语。本报告也给出了eMule网络协议的完整规范，包括一个附录，它提供了消息格式。这份文档的信息是基于开源的eMule客户端。接下来的简介目的是提供基本的背景知识，让读者阅读和理解这份文档。关于eMule的更多消息在这里找到。</div>
<div></div>
<div><strong><span style="FONT-SIZE: 12pt">1.2 </span></strong><strong><span style="FONT-SIZE: 12pt">概述</span></strong></div>
<div><strong><span style="FONT-SIZE: 12pt"></span></strong></div>
<div>eMule网络是由上百个eMule服务器和几百万个eMule客户端组成。客户端必须连接到一个服务器来取得网络服务，只要该客户端在系统中，服务器连接保持打开状态。这些服务器主要执行集聚索引服务（好像在Napster），相互间不联系。</div>
<div></div>
<div>每个eMule客户端都预配置了一个服务器列表和当地文件系统的共享文件列表。客户端用单独的TCP连接到一个eMule服务器登录到网络中，获得想得到的文件信息和客户端。eMule客户端也用几百个TCP连接到其他客户端进行上传和下载文件。每个eMule客户端对它的每个共享文件都维护着一个上传队列。要下载的客户端先加入到队列的底部，然后逐渐前进直到到达队列的顶部并开始下载它的文件。一个客户端可以从几个不同的eMule客户端中下载同一个文件的不同的文件块。客户端也可以上传它还没有完成的文件的文件块。最后，eMule扩展了eDonkey的能力，允许客户端之间交换关于服务器、其他客户端和文件的信息。注意，客户端和服务器的交流都是基于TCP的。</div>
<div>服务器使用了一个内部数据库，用来存储关于客户端和文件的信息。一个eMule服务器不存储任何文件，它为关于文件位置的存储信息作集聚索引。服务器的另一个功能，开始变得被抗议，是连接由于通过防火墙连接而无法接收到连接的两个客户端。这个连接功能增加了服务器的负载。相对于服务器和其他客户端，eMule使用UDP来增强客户端的能力。客户端发送和接收UDP信息的能力在日常使用中不是强制使用的，当有防火墙阻止它收发UDP信息时也能无瑕疵的运行。</div>
<div></div>
<div><strong>1.2.1<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></strong><strong>客户端到服务器的连接</strong></div>
<div><strong></strong></div>
<div>在开始启动时，客户端用TCP连接到一个eMule服务器。服务器提供一个客户ID给客户端，在整个客户端-服务器连接的生命周期里，它是有效的（注意，如果客户端有一个高ID，它会从所有的服务器中接收到相同的ID，直到它的IP地址改变）。在连接建立之后，客户端发送它的共享文件列表到服务器中。服务器把这个列表存储到它的内部数据库中，这个数据库通常包含了成百上千有效的文件和活动的客户端。eMule客户端也发送它的下载列表，包含着它想下载的文件。第二章提供了eMule客户端和服务器TCP信息交换的详细描述。</div>
<div>建立连接之后，eMule服务器给客户端发送用有它想下载的文件的其他客户端列表（这些客户端称作&#8220;源&#8221;）。从这点起，eMule客户端开始与其他客户端建立连接，如1.2.2所述。<img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/121.jpg"></div>
<div>注意，在整个客户端会话期间，客户/服务TCP连接一直保持连接状态。初次握手后主要是用户活动激发事务：有时，客户端发送文件搜索需求，由搜索结果回应，一个搜索事务一般在对源中指定文件查询之后，用源（IP和端口）列表来回答这个查询，查询者可以从这列表中下载文件。</div>
<div>客户端和它没有连接的服务器的交流是用UDP。UDP信息的目的是增强文件搜索，增强源搜索，最后保持连接状态（确保客户端服务器列表中的eMule服务器有效）。在第三章中可找到更多的关于客户-服务UDP信息交换的细节。</div>
<div></div>
<div><strong>1.2.2<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></strong><strong>客户端到客户端的连接</strong></div>
<div></div>
<div>一个eMule客户端连接到另一个eMuel客户端（源）是为了下载文件。一个文件分成很多部分，进一步的碎片。客户端可以从几个（不同的）客户端中下载同一个文件来分别获得不同的文件碎片。</div>
<div>当两个客户端连接时，它们交换容量信息，然后协商一个下载（或者上传，根据看法）的开始。每个客户端有一个下载列表，记住一列等待下载文件的客户端。当eMule客户端下载队列空的时候，一个下载请求很可能会导致一个下载开始（除非，比如这个请求者被禁止）。当下载队列不是空的时候，就会将这个请求的客户端加入到队列中。在给定的时间内,不能为几个以上客户端各自提供最小带宽2.4k/s。一个下载的客户端可能被一个比它较高队列等级的等待的客户端抢占，在下载会话的最初15分钟内，正在下载的eMule客户端的队列等级会增加直到能防止被击溃。</div>
<div>当下载的客户端到达下载队列的头部时，上传的客户端初始化一个连接来给它发送需要的文件块。eMule客户端可以在几个其他客户端的等待队列中，都注册下载相同文件的块。当一个等待的客户端实际上完成了（从它们中的一个）下载文件块，它不会通知其他客户端在其队列中删除它，当它到达它们的队列头时只是简单的拒绝它们的上传意图。</div>
<div>EMuley用一个信用系统来鼓励上传，为了防止假冒用RSA公匙密码系统来保护信用系统。</div>
<div>客户端连接可能用一套eDonkey协议没有定义的信息，这信息称作扩展协议。扩展协议用来实施信用系统，一般信息的交换（像服务器和源列表的更新），通过收发压缩的文件块来改善性能。</div>
<div>当EMule客户端在等待开始下载文件时，有限地用UDP周期性检查在它对等的客户端上的上传队列客户端状态。</div>
<div></div>
<div><strong>1.3<span>&nbsp;&nbsp;&nbsp;</span></strong><strong>客户ID</strong></div>
<div><strong></strong></div>
<div>客户ID是服务器在它们连接握手时提供的一个4字节标识符。客户ID只在客户-服务器TCP连接的生命期中有效，尽管万一客户端有一个高ID，所有的服务器都会分配它同样的ID直到IP地址改变。客户端ID分为低ID和高ID。当一个客户端不能接收一个输入连接时，eMule服务器将特有地分配给客户端一个低ID。拥有一个低ID会限制客户端对eMule网络的使用，和可能导致服务器拒绝一个客户端连接。高ID的计算是以客户端IP地址为基础的，如下所述。本节从eMule协议观点描述了客户ID的分配和重要性。允许其它客户端自由地连接到其本机上的eMule的TCP端口（默认端口号是 4662）的客户端会分配给一个高ID。有高ID的客户端没限制使用eMule网络。当服务器无法打开一个TCP连接到客户端的eMule端口时，会分配一个低ID给该客户端。这主要发生在机器上装有防火墙的客户端，阻止了输入连接。当出现下面情况时，客户端也会接收到一个低ID：</div>
<div style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: -21pt">l<span style="FONT: 7pt Times New Roman; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>当客户端通过NAT或代理服务器连接</div>
<div style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: -21pt">l<span style="FONT: 7pt Times New Roman; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>当服务器繁忙（导致服务器重连接计时器超时）</div>
<div>高ID用下面的方法计算：假设主机IP是X.Y.Z.W，ID就是X+2^8*Y+2^16*Z+2^24*W。低ID总是小于16777216（0x1000000），关于它是怎样计算的，我找不到任何线索，在不同的服务器中得到不同的低ID。</div>
<div>低ID客户端没有其他客户端可以连接到的公网IP，这样所有的交流必须通过eMule服务器完成。这增加了服务器计算能力的负担，并且导致服务器勉强接收低ID客户端。这也意味着低ID客户端不能连接到不在同一个服务器上的其他低ID客户端，因为eMule不支持在服务器间管道连接。</div>
<div>为了支持低ID客户端，引入了回调机制。使用这机制，高ID客户端请求（通过eMule服务器）低ID客户端连接它来交换文件。</div>
<div></div>
<div><strong>1.4<span>&nbsp;&nbsp;&nbsp;</span></strong><strong>用户ID</strong></div>
<div></div>
<div>eMule支持信用系统来鼓励用户共享文件。用户上传越多的文件给其他客户端，它接收的信用越多，它在它们的等待队列中前进得越快。</div>
<div>用户ID是128位（16字节）、连接随机数字创建的GUID，第6和第15字节不是随机产生的，它们的值分别是14和111。在整个客户端和指定的服务器会话中，客户ID是有效的，然而用户ID（也叫用户哈希）是唯一的并且跨越会话时用来识别客户端（用户ID识别工作站）。用户ID在信用系统中扮演重要角色，这为&#8220;黑客&#8221;假冒其他用户来获得他们信用赋予的优先权提供了动机。Emule提供加密方案设计来阻止欺骗和冒名顶替。这个实施是简单的应答交换，依靠RSA公有/私有钥匙加密。</div>
<div></div>
<div><strong>1.5<span>&nbsp;&nbsp;&nbsp;</span></strong><strong>文件ID</strong></div>
<div><strong></strong></div>
<div>文件ID用来惟一的标识网络中的文件和文件损坏侦测和修复。注意，eMule不依靠文件名来惟一标识和编目文件，通过哈希文件内容计算出的GUID标识文件。有两种类型文件ID-一种主要用来产生惟一的文件ID，另一种是用来损坏侦测和修复。</div>
<div></div>
<div><strong>1.5.1<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></strong><strong>文件哈希</strong></div>
<div></div>
<div>文件是用由客户端和基于文件内容计算出来的128位GUID哈希来标识的。GUID是应用MD4算法到文件数据中计算而来。当计算文件ID时，文件被分成每段9.28MB长的部分。每部分单独计算出一个GUID，然后所有的哈希组合成一个惟一的文件ID。当下载的客户端完成一个文件部分下载时，它计算这部分哈希，然后和发送过来的这部分哈希对比，如果这部分发现损坏了，客户端尝试通过逐渐替换这部分中的位（每个180kb）来修复损坏部分，直到哈希计算OK。</div>
<div></div>
<div><strong>1.5.2<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></strong><strong>根哈希</strong></div>
<div></div>
<div>用SHA1算法来为每部分计算根哈希，基于每块180kb大小。它提供了更高等级的可靠性和可修复性，更多信息可在eMule官方网站得到。</div>
<div></div>
<div style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: -21pt"><strong>1.6<span style="FONT: 7pt Times New Roman; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></strong><strong>eMule</strong><strong>协议扩展</strong></div>
<div>
<div><strong><span style="FONT-SIZE: 14pt">2&nbsp;&nbsp;&nbsp;&nbsp;</span></strong><strong><span style="FONT-SIZE: 14pt">客户端服务器的</span></strong><strong><span style="FONT-SIZE: 14pt">TCP</span></strong><strong><span style="FONT-SIZE: 14pt">交流</span></strong></div>
<div></div>
<div>每个客户端用TCP精确地连接到一个服务器。服务器分配给客户端一个ID,在与服务器其余的会话中标识该客户端（高ID客户端总是根据它的IP 地址分配）。eMule GUI客户端需要建立一个服务器连接来用于操作。客户端不能同时与几个服务器连接，也不能在没有用户干涉的情况下动态更换服务器。</div>
<div></div>
<div><strong>2.1<span>&nbsp;&nbsp;&nbsp;&nbsp;</span></strong><strong>建立连接</strong></div>
<div><strong></strong></div>
<div>在准备建立与服务器的连接时，客户端会尝试并行地连接到几个服务器，根据成功的登陆顺序放弃其他的。</div>
<div>有下面几个可能的连接建立个案：</div>
<div>1、高ID连接-服务器分配一个高ID给正在连接的客户端</div>
<div>2、低ID连接-服务器分配一个低ID给正在连接的客户端</div>
<div>3、拒绝会话-服务器拒绝客户端</div>
<div>当然，也有不重要的个案-服务器崩溃或者不可连接。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/2-1.jpg"></div>
<div>图2.1描述了导致高ID连接的信息顺序。在这种情况下，客户端建立一个TCP连接到服务器，然后发送一个登录信息到服务器。服务器用另一个 TCP连接到客户端，执行一个客户端-客户端的握手来保证连接的客户端有能力接收来自其他eMule客户端的连接。在完成客户端握手后，服务器关闭第二个连接，通过发送ID更改信息来完成客户端-服务器的握手。你可能注意到eMule信息消息是灰色的。这是因为这个消息是eMule协议扩展的一个部分（1.6节）</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/2-2.jpg"></div>
<div>图2.2描述了导致低ID连接的信息顺序。在这种情况下，服务器不能连接到发送请求的客户端，分配一个低ID给客户端。服务器消息一般包含警告信息，就像&#8220;警告[服务器细节] - 你是低ID。请察看你的网络配置和/或你的设置&#8221;低ID和高ID握手都是通过随着ID更改消息完成的，这个ID更改消息分配客户端一个客户端ID，用在与服务器的下一个会话。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/2-3.jpg"></div>
<div>图2.3描述了被拒绝的会话顺序。因为客户端拥有一个低ID或者到达了服务器硬件的容量限制，服务器就可能拒绝会话。服务器消息会包含一个短字符串描述拒绝的理由。</div>
<div></div>
<div><strong>2.2<span>&nbsp;&nbsp;&nbsp;</span></strong><strong>连接启动时消息交换</strong></div>
<div></div>
<div>在建立成功的连接后，客户端和服务器交换几个设置消息。这些消息的目的是根据双方状态来双方更新。客户端通过提供它的共享文件列表（见 6.2.4节）给服务器来开始，然后要求更新它的服务器列表。服务器发送它的状态和版本（6.2.6节和6.2.2节），然后发送它所知的eMule服务器列表和提供更多一些自我认定的细节。最后客户端要求源（可以访问下载它下载列表中的文件的其它客户端）和服务器回应一系列的消息，客户端下载列表中的每个文件，直到下载所有的源列表到客户端。图2.4图解了这个顺序。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/2-4.jpg"></div>
<div></div>
<div><strong>2.3</strong><strong>文件搜索</strong></div>
<div></div>
<div>文件搜索是由用户发起的。这个操作简单，一个搜索要求（见6.2.9节）发送到服务器，然后服务器用一个搜索结果回应。当有很多结果时，搜索结果消息就会被压缩。接着，用户选择下载一个或多个文件，客户端就要求源为选中的文件和服务器返回每个要求文件的源队列（见6.2.12节）。就在回应发现的源之前，可以发送一个可选的服务器状态消息。这个状态消息（6.2.6节）包含关于当前用户数量和服务器支持的文件等信息。重要注意的是，UDP消息有个补充顺序事件，用来增强客户端为它搜索的文件定位源的能力，详细的细节见第3章。在检验出源是新的之后，eMule客户端开始尝试连接和把它们加入到它的源列表。源联系的顺序就是eMule客户端接收到它们的顺序。图2.5描述了文件搜索顺序。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/2-5.jpg"></div>
<div>eMule客户端根据源加入到它的列表中的顺序来连接源。没有优先机制来决定连接那个源。当可以要求同一个源来下载客户端下载列表中的几个文件时，有一种复杂的机制来解决这个局面（注意，载客户端之间eMule只允许一个单独的上传连接）。选择算法是基于用户优先规则，当没有指定优先时，默认是字母顺序。关于处理可以上传多于一个文件的源的详细描述，可以在网站中找到。</div>
<div></div>
<div><strong>2.4<span>&nbsp;&nbsp;&nbsp;</span></strong><strong>回调机制</strong></div>
<div><strong></strong></div>
<div>回调机制是设计来克服低ID客户端不能接收输入的连接的，这样客户端之间就能共享它们的文件。机制很简单：假如客户端A和B都连接到同一个 eMule服务器，A需要的文件在B上，但B是低ID的，A可以向服务器发送一个回调请求（见6.2.13节），请求服务器叫B呼叫回它。服务器，已经有一个与B的打开的TCP连接，发送一个回调请求消息（见6.2.14节）到B，为它提供A的IP和端口。B就能连接到A并发送文件，没有给服务器增加负担。很明显，只有高ID客户端可以要求低ID客户端回调（低ID客户端是没有接收输入连接的能力的）。图2.6图解了回调消息交换。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/2-6.jpg"></div>
<div>也有允许两个低ID客户端交换文件的能力，通过它们的服务器连接，用服务器接力。大部分服务器不再提供这个选项，因为它招致服务器的负担。</div>
<strong></strong></div>
<div>尽管eMule完全兼容eDonkey，它还是实行了几种扩展，允许eMule两个客户端为用户提供另外的功能。扩展只要集中在客户端与客户端的交流，特别是在安全和UDP使用领域上。在本文档中，所有信息流图标明的信息，是eMule扩展部分的，用灰色表示。</div>
<div></div>
<div><strong>1.7<span>&nbsp;&nbsp;&nbsp;</span></strong><strong>软件和硬件限制</strong></div>
<div><strong></strong></div>
<div>在活动用户数量的服务器配置中有两种限制-软件和硬件。硬件限制远大于软件限制。当活动用户的数量达到软件限制时，服务器停止接收新的低ID客户连接。当用户数量达到硬件限制时，服务器满了，不再接收任何客户端连接。</div>
<div class=cnt id=blog_text>
<div><strong><span style="FONT-SIZE: 14pt">3&nbsp;&nbsp;&nbsp;&nbsp;</span></strong><strong><span style="FONT-SIZE: 14pt">客户端服务器的</span></strong><strong><span style="FONT-SIZE: 14pt">UDP</span></strong><strong><span style="FONT-SIZE: 14pt">交流</span></strong></div>
<div>eMule客户端和服务器用不可靠的UDP服务来保持连接和增强搜索。eMule客户端产生UDP包的总量可以达到它发送包的总数目的5% - 这些根据客户端服务器列表中服务器的数目，客户端下载列表中每个文件的源数目和用户执行的搜索数目而定。UDP包通过计时器触发，计时器每100ms过期，有一个单独的线程负责发送UDP输送结果，以每秒10个UDP的最大速率。</div>
<div></div>
<div><strong>3.1 </strong><strong>服务器保持连接和状态信息</strong></div>
<div></div>
<div>客户端周期性验证它服务器列表中的服务器状态。验证是通过发送UDP服务器状态请求（见6.3.3节）和UDP服务器描述请求（见6.3.7 节）消息完成的。这里描述的简单保持连接计划每小时产生不超过几打包。任何情况下，包的最大速率是每秒0.2个包（或每5秒一个包）。当检查服务器的状态时，客户端会首先发送一个服务器状态请求消息，接着，每两次试图（发送一个服务器状态请求）中就发送一次服务器描述请求，见图3.1。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/3-1.jpg"></div>
<div></div>
<div>客户端发送的服务器状态请求中包括一个随机数字，在服务器回应中返回。在服务器返回的数字与客户端发送的要求中数字不同的情况下，回应的信息就会被丢弃。每次发送到服务器的包是状态请求，客户端就移动尝试计数器。任何来自服务器的消息（包括搜索结果）都重置尝试计数器。当尝试计数器达到一个可配置的限制时，服务器就认为是死机，从客户端的服务器列表中删除。服务器回应包括几个数据项：服务器状态回应（见6.3.4）包括服务器中当前用户数目和文件数目，也包括服务器的软件和硬件限制（见1.7节）。服务器描述回应（见6.3.8节）包括服务器名称和一个短的描述字符串。图3.2演示了客户端和活动服务器中满连接序列的消息流。</div>
<div></div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/3-2.jpg"></div>
<div></div>
<div><strong>3.2 </strong><strong>增强文件搜索</strong></div>
<div></div>
<div>eMule客户端可以设置用UDP来增强它的文件搜索。UDP搜索结果格式几乎与？？节描述的TCP搜索请求一样。服务器只在有了搜索结果才回应。UDP搜索结果消息有两种不同的opcdes，我也无法说清它们之间的不同。UDP搜索包发送到客户端服务器列表中的服务器上。更多信息见6.3.5 节和6.3.6节。</div>
<div></div>
<div><strong>3.3 </strong><strong>增强文件源搜索</strong></div>
<div></div>
<div>当客户端下载列表中的特定文件的源数目小于配置限制（100）时，客户端就周期性地发送获取源的UDP包到它的服务器列表中的服务器中为该文件寻找更多的源。可能每秒发送一个包，使得源搜索在客户端产生的UDP输送中成为可观的部分。消息的格式（6.3.1节描述）非常相似它的TCP计数器部分。注意，与TCP源搜索相反，UDP源搜索在文件搜索中减弱，对于指定的文件，只是依靠客户端拥有的源数目。</div>
</div>
<div class=cnt id=blog_text>
<div><strong><span style="FONT-SIZE: 14pt">4 </span></strong><strong><span style="FONT-SIZE: 14pt">客户端到客户端的</span></strong><strong><span style="FONT-SIZE: 14pt">TCP</span></strong><strong><span style="FONT-SIZE: 14pt">交流</span></strong></div>
<div></div>
<div>在eMule客户端注册到服务器和向服务器查询文件和源之后，为了下载文件，eMule客户端需要联系其它客户端。为每对[文件，客户端]创建一个专用的TCP连接。当特定的周期内（默认40秒）没有任何socket活动或者对方已经关闭了这个连接，那么这个连接就会关闭。</div>
<div>为了提供合理的下载速率，直到可能提供给它（和所有其它下载的客户端）至少最小允许速率（当前的硬编码常量设置为2.4k/s），eMule才允许客户端开始下载文件。</div>
<div></div>
<div><strong>4.1 </strong><strong>初始的握手</strong></div>
<div></div>
<div>初始的握手是对称的 - 双方都相互发送相同的信息给对方。客户端交换双方的信息，信息包括身份认证、版本和容量等。参与的有两种类型消息 - Hello消息（6.4.1节）和eMule信息消息（6.5.1节），第一种是eDonkey的一部分，兼容eDonkey客户端，第二种是eMule 独有的扩展客户端协议的一部分。图4.1图解了两个eMule客户端之间的握手。在扩展信息中包含的有UDP消息交换、安全身份证明和源交换能力。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/4-1.jpg"></div>
<div></div>
<div><strong>4.2 </strong><strong>安全的用户身份认证</strong></div>
<div></div>
<div>1.4节简单解释了关于用户ID和用户假冒其它用户的动机。安全用户认证是eMule扩展的一部分。如果客户端支持安全认证，就会在初始化握手之后立即执行。安全认证的目的是防止用户冒名顶替。当实施安全认证时，执行以下步骤：</div>
<div></div>
<div>1.在初始化握手中，B客户端指明它支持和希望使用安全认证。</div>
<div>2.通过发送安全认证消息（见6.5.8）来回应，指明A是否需要B的公匙，也包含了B发出的4字节的询问。</div>
<div>3.如果A指明它需要B的公匙，B就将它的公匙发送给A（6.5.9节）。</div>
<div>4.B发送一个签名消息（6.5.10节），签名消息是用发送过来的询问和额外的双字节创建的，双字节要么是A的IP地址如果B是低ID，要么是B的ID如果它有高ID。图4.2演示了这个顺序。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/4-2.jpg"></div>
<div></div>
<div><strong>4.2.1 </strong><strong>信用系统</strong></div>
<div></div>
<div>本节简要地描述了客户端的信用系统。信用系统的目的是鼓励用户共享文件。当客户端上传文件给它的对方，下载的客户端就根据数据传输的数量来更新它的信用。注意，信用系统不是全局的 - 传输的信用被下载的客户端局部保存，只有当上传的客户端（获得信用的那个）要求从这个特定的客户端下载时，信用才会被考虑。信用是用下面最小值计算的：</div>
<div></div>
<div>1. 上传的总量 * 2 / 下载的总量</div>
<div>当下载的总量是零时，这个表达式估值是10</div>
<div>2. 上传的总量 + 2 的和开方</div>
<div>当上传的总量小于1MB，这个表达式估值是1</div>
<div></div>
<div>上传/下载数量是以M为单位计算。任何情况下，信用不会高过10或者低于1。</div>
<div></div>
<div><strong>4.3 </strong><strong>请求文件</strong></div>
<div>正如已经提到的一样，每对[客户端，文件]都创建一个独立的连接。在连接建立之后，客户端立即发送几个关于它希望下载的文件的请求消息。4.3节描述了一个典型的、成功的情景。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/4-3.jpg"></div>
<div></div>
<div><strong>4.3.1 </strong><strong>基本消息交换</strong></div>
<div></div>
<div>基本消息交换是由四个消息构成：A发送一个文件请求消息，立即跟着的是请求文件ID消息（6.4.17节）。B用文件请求回答回应文件请求，用文件状态（6.4.18节）来回应请求文件的ID消息。我找不到任何理由来把这些发送过来的消息中的信息分成四个消息，它可以容易地用两个消息（请求和回应）来处理。</div>
<div>扩展协议增加两个消息到这个顺序中，源请求消息（6.5.6节）和源回应消息（6.5.7节）。用这个扩展来传递B的源（假定B当前下载着文件）到A中。详细阐述就是，在它能发送文件块给其它客户端之前B完成下载一个文件是没有任何要求的，B可以发送任何它已经完成下载的文件块，甚至当它只是用文件的一小块。</div>
<div></div>
<div><strong>4.3.2 </strong><strong>没找到文件的情景</strong></div>
<div></div>
<div>当A向B请求一个文件，但是B的共享文件列表中没有这个文件。B忽略这个文件请求回应消息，在请求文件ID消息之后立即发送一个没有文件消息（6.4.16节），如图4.4所演示。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/4-4.jpg"></div>
<div></div>
<div><strong>4.3.3 </strong><strong>加入上传队列</strong></div>
<div></div>
<div>当B有被请求的文件但是它上传队列不为空，意味着有正在下载文件的客户端，也可能有客户端在上传列表中，在这种情况下，A和B执行满握手，如图 4.3所述，但是当A请求B开始下载文件时，B把A加入到它的上传队列中，回应一个队列等级消息，这个消息包含A在B地上传队列中的位置。图4.5演示了这个顺序。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/4-5.jpg"></div>
<div></div>
<div><strong>4.3.4 </strong><strong>上传对列管理</strong></div>
<div></div>
<div>对每个上传的文件，客户端维护着一个上传优先级队列。队列中的每个客户端的优先级是以客户端在队列中的时间和优先级修正为基础计算的。位于队列头部的客户端有一个最高级别的分数。分数是用以下的方程式计算的：分数 = （等级 * 队列中的秒数）/100或者无穷大，如果下载的客户端被定义为朋友。初始化的等级值是100，除开禁止的用户是0等级（这样阻止达到队列的前面）外。等级可以被下载客户端的信用（范围1-10）或上传文件优先级（0.2-1.8）修改，上传文件优先级是由上传客户端设置的。当一个客户端的分数比其它的客户端高时，它就开始下载文件。客户端可以继续下载文件直到产生以下任一个条件：</div>
<div>1.用户关闭了上传客户端。</div>
<div>2.下载的客户端得到了它所需文件的所有部分。</div>
<div>3.下载的客户端给其它拥有比它更高优先级的客户端抢占。</div>
<div></div>
<div>为了允许一个刚刚开始的客户端在它被抢占之前可以得到几M的数据，eMule在客户端下载的前15钟内增加初始等级到200。</div>
<div></div>
<div><strong>4.3.5 </strong><strong>到达上传队列的顶部</strong></div>
<div></div>
<div>当A到达B上传队列的顶部时，B连接A，执行初始握手，然后发送一个接收上传请求消息（6.4.11节）。A现在可以选择要么发送请求块消息来继续下载文件，要么发送取消传输消息来取消（如果它已经从别的源得到了这一块）。图4.6演示了这些选择。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/4-6.jpg"></div>
<div></div>
<div><strong>4.4 </strong><strong>数据传输</strong></div>
<div></div>
<div><strong>4.4.1 </strong><strong>数据包</strong></div>
<div></div>
<div>发送和接收文件块是eMule网络活动的主要部分。用FTP解释eMule可以推论出，当所有其它eMule可以控制，发送的文件块适合数据传输。发送的文件块大小可以是在5000到15000位（也根据压缩）范围内。为了避免出错，文件块消息在碎片中发送，每碎片在一个独立的TCP包中。在 eMule 0.30e版中，最大的碎片大小是1300位（注意，这个数字只与TCP有效负载有关）。换句话说，当每个控制消息在单独的TCP包中发送，有时和其它消息共享，数据消息被分成几个TCP包。第一个包包含发送文件块消息头部（6.4.3节）。剩下的包值包含数据。当被分成1300，如果发送块的大小有剩余，和第一个包（这个包带有头部）一起发送。图4.7演示了文件块消息。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/4-7.jpg"></div>
<div><strong>4.4.2 </strong><strong>数据传输顺序</strong></div>
<div></div>
<div>在文件请求回应之后立即开始块传输顺序。下载客户端A发送一个开始上传请求（6.4.10节），然后一个接收上传请求消息（6.4.11节）回应这个请求。A在这之后立即开始请求文件块（6.4.4节），B通过发送被请求块（6.4.3节）来回应。注意，单独的文件块请求可能请求可达3块之多，所以每个文件块请求可能被可达3个发送的块顺序回应。</div>
<div>当两个客户端都支持扩展的客户端协议，文件块可能压缩发送。扩展协议也支持可选的文件信息消息（6.5.5节），该消息就在接收上传请求消息之前发送。图4.8演示了块传输消息顺序。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/4-8.jpg"></div>
<div></div>
<div><strong>4.4.3 </strong><strong>选择块下载</strong></div>
<div></div>
<div>为了最大化整个网络的吞吐量和共享，eMule仔细挑选选择块的下载顺序。每个文件被分成9.28M的块，每部分分成180KB的片。</div>
<div>块下载的顺序是由发送请求文件块消息（6.4.4节）的下载客户端决定。下载客户端可以在任何给定时刻从各个源中下载一个单独的文件块，所有从相同源中请求的片都在同一个块中。下面的原理（以这个顺序）应用于下载块等级：</div>
<div>1.（可获得的）大片的频率，尽可能快的下载非常稀少的大片来形成一个新的源。</div>
<div>2.用来预览的块（最初+最后的大片），预览或检查文件（比如，电影、mp3）</div>
<div>3.请求状态（过程中下载），尝试向每个源询问其它的大片。在所有源之间扩散请求。</div>
<div>4.完成（未到某种程度的完成），在开始下载另一个时应该完成获得部分的大片</div>
<div></div>
<div>频率标准定义了三个区域：非常稀少、稀少和一般。在每个区域里，标准有特定的权重，用来计算块等级。较低等级的块先下载。下面的列表根据上面的原理指定文件等级范围：</div>
<div style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: -21pt">l<span style="FONT: 7pt Times New Roman; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>0-9999 - 不请求和请求非常稀少的块</div>
<div style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: -21pt">l<span style="FONT: 7pt Times New Roman; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>10000-19999 - 不请求稀少和预览块</div>
<div style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: -21pt">l<span style="FONT: 7pt Times New Roman; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>20000-29999 - 不请求大部分完成的一般的块</div>
<div style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: -21pt">l<span style="FONT: 7pt Times New Roman; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>30000-39999 - 请求的稀少和预览的块</div>
<div style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: -21pt">l<span style="FONT: 7pt Times New Roman; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>40000-49999 - 请求的没有完成的一般的块</div>
<div></div>
<div>这个算法通常选择第一个最稀少的块。然而，部分完成的块，接近完成的，也可能被选中。对于一般的块，在不同的源之间扩散下载。</div>
<div></div>
<div></div>
<div><strong>4.5 </strong><strong>浏览共享的文件和文件夹</strong></div>
<div></div>
<div>有两个消息流程来处理每对客户端之间的共享文件和文件夹的浏览。第一个是浏览共享文件消息（6.4.21节），该消息在初始握手后立即发送。通常由一个浏览共享文件回应消息（6.4.22）来回应这个消息。当回应的客户端想隐藏它的共享文件列表时，回应就包含零个文件（而不是发送一个指示访问拒绝的消息）。图4.9演示了这个消息顺序。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/4-9.jpg"></div>
<div>第二个消息流程随着一个浏览共享的文件夹列（6.4.23节）表请求开始，通过共享文件夹列表来回应这个消息，然后，对于回应中的每个文件夹，发送浏览共享文件夹内容的消息。当消息到达时，回应的每个消息带有内容列表。图4.10演示了这个消息顺序。</div>
<div>如果接收的客户端设置了阻塞共享文件/文件夹请求，它用请求共享拒绝消息回应，如图4.11所示。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/4-1011.jpg"></div>
<div></div>
<div><strong>4.6 </strong><strong>交换片哈希集</strong></div>
<div>为了取得片的哈希，发送一个哈希集请求，这个请求通过一个包含文件中每块的哈希集回应来回答。图4.12演示了这个。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/4-12.jpg"></div>
<div></div>
<div><strong>4.7 </strong><strong>取得文件预览</strong></div>
<div>客户端可以请求对方来获得下载文件的预览。预览是种独立的、随着文件类型不同而不同的应用。eMule 0.30e只支持图像预览。这个消息交换如图4.13描述，只包含两种消息：预览请求（6.5.11节）和预览回应（6.5.12节）。</div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/4-13.jpg"></div>
<div class=cnt id=blog_text>
<p><strong>5&nbsp;&nbsp;&nbsp; 客户端到客户端的UDP连接</strong></p>
<div>eMule客户端周期性地用UDP协议发送消息。在eMule0.30e中，使用的UDP消息只是询问客户端在对方下载队列中的位置。这个简单的请求-应答方案是随着重复要求文件消息(6.6.1节)而开始的。对于这个请求，有三种可能的回应，如图5.1所示。</div>
<div></div>
<div>1. 队列等级 - 客户端在发送者队列中的等级</div>
<div>2. 队列满 - 发送者队列已满</div>
<div>3. 找不到文件 - 发送者在它的列表中没有被请求的文件</div>
<div></div>
<div>重复要求文件消息大约每20分钟的间歇发送到每个客户端，这些客户端把发送者加入到它的下载队列中。</div>
<div></div>
<div><img src="http://blog.csdn.net/images/blog_csdn_net/lzcx/5-1.jpg"></div>
</div>
</div>
<img src ="http://www.cppblog.com/tommyyan/aggbug/82056.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 15:54 <a href="http://www.cppblog.com/tommyyan/articles/82056.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>hash在emule中的重要作用的文章</title><link>http://www.cppblog.com/tommyyan/articles/82054.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 07:51:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82054.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82054.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82054.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82054.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82054.html</trackback:ping><description><![CDATA[<div class=cnt id=blog_text>
<div class=postText>
<p>来源：<a href="http://www.jfish2004.net/blog/?q=node/43"><font color=#000080><u>http://www.jfish2004.net/blog/?q=node/43</u></font></a></p>
<p><strong>转贴：关于hash在emule中的重要作用的文章</strong></p>
<p><strong></strong>从emule诞生到现在也已经有了两年左右时间了，随着emu<wbr>le的普及，喜欢他的人也越来越多，但是由于emule对技术相<wbr>应有一个门槛，不像bt那么容易上手，所以很多朋友很长时间以来<wbr>一直都有这样或那样的疑问，今天是周末我也献献丑，写一篇关于h<wbr>ash的文章。</p>
<p>大家天天都在使用emule，hash这个词是在emule里<wbr>面出现频率最高的，那么到底什么是hash呢？</p>
<p>让我们先来了解一些基本知识，作作预热只有这样才能更好的了解<wbr>hash。</p>
<p>Hash，一般翻译做&#8220;散列&#8221;，也有直接音译为"哈希"的，就<wbr>是把任意长度的输入（又叫做预映射， pre-image），通过散列算法，变换成固定长度的输出，该<wbr>输出就是散列值。这种转换是一种压缩映射，也就是，散列值的空间<wbr>通常远小于输入的空间，不同的输入可能会散列成相同的输出，而不<wbr>可能从散列值来唯一的确定输入值。</p>
<p>简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘<wbr>要的函数。</p>
<p>HASH主要用于信息安全领域中加密算法，他把一些不同长度的<wbr>信息转化成杂乱的128位的编码里,叫做HASH值. 也可以说，hash就是找到一种数据内容和数据存放地址之间的映<wbr>射关系</p>
<p>了解了hash基本定义，就不能不提到一些著名的hash算法<wbr>，MD5 和 SHA1 可以说是目前应用最广泛的Hash算法，而它们都是以 MD4 为基础设计的。那么他们都是什么意思呢？<br>这里简单说一下：</p>
<p>1) MD4<br>MD4(RFC 1320)是 MIT 的 Ronald L. Rivest 在 1990 年设计的，MD 是 Message Digest 的缩写。它适用在32位字长的处理器上用高速软件实现--它是基<wbr>于 32 位操作数的位操作来实现的。</p>
<p>2) MD5<br>MD5(RFC 1321)是 Rivest 于1991年对MD4的改进版本。它对输入仍以512位分组，其<wbr>输出是4个32位字的级联，与 MD4 相同。MD5比MD4来得复杂，并且速度较之要慢一点，但更安全<wbr>，在抗分析和抗差分方面表现更好</p>
<p>3) SHA1 及其他<br>SHA1是由NIST NSA设计为同DSA一起使用的，它对长度小于264的输入，产<wbr>生长度为160bit的散列值，因此抗穷举(brute-for<wbr>ce)性更好。SHA-1 设计时基于和MD4相同原理,并且模仿了该算法。</p>
<p>那么这些Hash算法到底有什么用呢？<br>Hash算法在信息安全方面的应用主要体现在以下的3个方面<wbr>： </p>
<p>1) 文件校验<br>我们比较熟悉的校验算法有奇偶校验和CRC校验，这2种校验<wbr>并没有抗数据篡改的能力，它们一定程度上能检测并纠正数据传输中<wbr>的信道误码，但却不能防止对数据的恶意破坏。<br>MD5 Hash算法的"数字指纹"特性，使它成为目前应用最广泛的一种<wbr>文件完整性校验和(Checksum)算法，不少Unix系统有<wbr>提供计算md5 checksum的命令。<br>2) 数字签名<br>Hash 算法也是现代密码体系中的一个重要组成部分。由于非对称算法的运<wbr>算速度较慢，所以在数字签名协议中，单向散列函数扮演了一个重要<wbr>的角色。 对 Hash 值，又称"数字摘要"进行数字签名，在统计上可以认为与对文件本<wbr>身进行数字签名是等效的。而且这样的协议还有其他的优点。<br>3) 鉴权协议<br>如下的鉴权协议又被称作"挑战--认证模式：在传输信道是可<wbr>被侦听，但不可被篡改的情况下，这是一种简单而安全的方法。 </p>
<p>以上就是一些关于hash以及其相关的一些基本预备知识。那么<wbr>在emule里面他具体起到什么作用呢？</p>
<p>什么是文件的hash值呢？</p>
<p>大家都知道emule是基于P2P （Peer-to-peer的缩写，指的是点对点的意思的软件）<wbr>， 它采用了&#8220;多源文件传输协议&#8221;(MFTP，the Multisource FileTransfer Protocol)。在协议中，定义了一系列传输、压缩和打包还<wbr>有积分的标准，emule 对于每个文件都有md5-hash的算法设置，这使得该文件独一<wbr>无二，并且在整个网络上都可以追踪得到。</p>
<p>MD5-Hash-文件的数字文摘通过Hash函数计算得到。<wbr>不管文件长度如何，它的Hash函数计算结果是一个固定长度的数<wbr>字。与加密算法不同，这一个Hash算法是一个不可逆的单向函数<wbr>。采用安全性高的Hash算法，如MD5、SHA时，两个不同的<wbr>文件几乎不可能得到相同的Hash结果。因此，一旦文件被修改，<wbr>就可检测出来。</p>
<p>当我们的文件放到emule里面进行共享发布的时候，emul<wbr>e会根据hash算法自动生成这个文件的hash值，他就是这个<wbr>文件唯一的身份标志，它包含了这个文件的基本信息,然后把它提交<wbr>到所连接的服务器。当有他人想对这个文件提出下载请求的时候， 这个hash值可以让他人知道他正在下载的文件是不是就是他所想<wbr>要的。尤其是在文件的其他属性被更改之后（如名称等）这个值就更<wbr>显得重要。而且服务器还提供了,这个文件当前所在的用户的地址,<wbr>端口等信息,这样emule就知道到哪里去下载了.</p>
<p>一般来讲我们要搜索一个文件，emule在得到了这个信息后，<wbr>会向被添加的服务器发出请求，要求得到有相同hash值的文件。<wbr>而服务器则返回持有这个文件的用户信息。这样我们的客户端就可以<wbr>直接的和拥有那个文件的用户沟通，看看是不是可以从他那里下载所<wbr>需的文件。</p>
<p>对于emule中文件的hash值是固定的，也是唯一的，它就<wbr>相当于这个文件的信息摘要，无论这个文件在谁的机器上，他的ha<wbr>sh值都是不变的，无论过了多长时间，这个值始终如一，当我们在<wbr>进行文件的下载上传过程中，emule都是通过这个值来确定文件<wbr>。</p>
<p>那么什么是userhash呢？</p>
<p>道理同上，当我们在第一次使用emule的时候，emule会<wbr>自动生成一个值，这个值也是唯一的，它是我们在emule世界里<wbr>面的标志，只要你不卸载，不删除config，你的userha<wbr>sh值也就永远不变，积分制度就是通过这个值在起作用，emul<wbr>e里面的积分保存，身份识别，都是使用这个值，而和你的id和你<wbr>的用户名无关，你随便怎么改这些东西，你的userhash值都<wbr>是不变的，这也充分保证了公平性。其实他也是一个信息摘要，只不<wbr>过保存的不是文件信息，而是我们每个人的信息。</p>
<p>那么什么是hash文件呢？</p>
<p>我们经常在emule日至里面看到，emule正在hash文<wbr>件，这里就是利用了hash算法的文件校验性这个功能了，文章前<wbr>面已经说了一些这些功能，其实这部分是一个非常复杂的过程，目前<wbr>在ftp,bt等软件里面都是用的这个基本原理，emule里面<wbr>是采用文件分块传输，这样传输的每一块都要进行对比校验，如果错<wbr>误则要进行重新下载，这期间这些相关信息写入met文件，直到整<wbr>个任务完成，这个时候part文件进行重新命名，然后使用mov<wbr>e命令，把它传送到incoming文件里面，然后met文件自<wbr>动删除，所以我们有的时候会遇到hash文件失败，就是指的是m<wbr>et里面的信息出了错误不能够和part文件匹配，另外有的时候<wbr>开机也要疯狂hash，有两种情况一种是你在第一次使用，这个时<wbr>候要hash提取所有文件信息，还有一种情况就是上一次你非法关<wbr>机，那么这个时候就是要进行排错校验了。</p>
<p>关于hash的算法研究，一直是信息科学里面的一个前沿，尤其<wbr>在网络技术普及的今天，他的重要性越来越突出，其实我们每天在网<wbr>上进行的信息交流安全验证，我们在使用的操作系统密钥原理，里面<wbr>都有它的身影，特别对于那些研究信息安全有兴趣的朋友，这更是一<wbr>个打开信息世界的钥匙，他在hack世界里面也是一个研究的焦点<wbr>.我是一个门外汉，利用这个周末找了一些资料，胡乱写了一点关于<wbr>hash的文章，也有不少是我自己的分析，这期间肯定还有不对的<wbr>地方，还请朋友们多多指出错误，我抛砖引玉希望大家批评指导。</p>
</div>
</div>
<img src ="http://www.cppblog.com/tommyyan/aggbug/82054.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 15:51 <a href="http://www.cppblog.com/tommyyan/articles/82054.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>eMule电驴使用从入门到精通&amp;&amp;常用的emule资源网站&amp;&amp;用emule发布资源</title><link>http://www.cppblog.com/tommyyan/articles/82053.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 07:50:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82053.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82053.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82053.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82053.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82053.html</trackback:ping><description><![CDATA[<div class=cnt>
<div class=cnt id=blog_text>
<p>　　回顾上网伊始，网民寻找网站都是沿着各网站提供链接，自主权、选择权相对受到限制。但是当 Yahoo、Lycos、Google、百度等建立了搜索引擎后，网友上网冲浪的方式有所改变，可以利用搜索引擎去查找获取自己需要的所有信息。类似于网站、网页的搜索引擎，电驴是文件的搜索引擎。可以说，电驴的推出开创了文件搜索新时代。 </p>
<p>　　何为电驴？英文名称edonkey。用户用电驴软件把各自的PC连接到电驴服务器上，而服务器的作用仅是收集连接到服务器的各电驴用户的共享文件信息（并不存放任何共享文件），并指导P2P下载方式。P2P 就是Point To Point，也可以理解为PC To PC或Peer To Peer，所以电驴用户既是client，同时也是server。可以说，电驴把控制权真正交与用户手中，用户通过电驴可以共享硬盘上的文件、目录甚至整个硬盘。那些费心收集存储在自己硬盘上的文件肯定是被认为最有价值的。所有用户都共享了他们认为最有价值的文件，这将使互联网上信息的价值得到极大的提升。看到这里，你是不是有一种第一次见到网络、搜索引擎时的激动呢？本傻瓜教程将本着循序渐进的原则，力图使大家快速掌握电驴技术。学完第一课，就可以使用电驴了，学完前三课，就可以熟练使用电驴了，学完本教程，就完全可以算是名副其实的电驴高手了！！！你不必一次学完，可一课一课仔细领会，逐步提高骑驴技术。 </p>
<p>　　本教程所使用的软件为eMule3.0c多国语言版，简单易学，该版本解决了以前emule软件不能支持代理的问题。 </p>
<p><strong>　　第一课——文件下载</strong> </p>
<p>　　第1步、下载软件（emule或edonkey） </p>
<p>　　电驴软件很多，我用的是emule，也就是电骡。该软件界面有简体中文及多种语言，为大多数电驴用户使用。本网站首页就有，欢迎下载！ </p>
<p>　　第2步、安装emule </p>
<p>　　运行安装程序，先选择英文（用繁体中文操作系统的朋友可以直接选择繁体中文），以下全部选择默认，安装完毕。运行emule后，点上面菜单的最右边的一项（英文名称Prefere），把语言改成简体中文，退出emule后重新运行emule。 </p>
<p>　　第3步、更改目录 </p>
<p>　　emule 功能强大，我先把更改目录说一下。点菜单&#8220;选项&#8221;，再点&#8220;目录&#8221;，把&#8220;下载的文件&#8221;和&#8220;临时文件&#8221;两个目录选择到不是系统盘（一般是C:盘）的分区，如 D:emuleincoming和D:emule emp。下面还有一个共享目录，你可以选择你想共享的分区、目录或者文件，在前面打上钩就可以共享给其他电驴用户了。 </p>
<p>　　第4步、文件下载 </p>
<p>　　运行emule后，它会自动连接服务器（也可以自己双击连接）。如果连接好了，点菜单&#8220;搜索&#8221;后，在右上方有一个&#8220;直接下载ed2k链接&#8221;窗口，你把找到的ed2k链接剪贴到此窗口中，点&#8220;开始&#8221;就可以下载了。可以用下面的片子试验（可下载）：</p>
<p>　　《大开眼戒》上集 </p>
<p>　　ed2k://　file　Eyes_Wide_Shut_(1999).CD1.SOUTHSiDE.ShareReactor.[VeryCD.com].avi　734076928　E98C261E91AA7D1C590BAC38CB2D9B71　/ </p>
<p>　　稍微解释一下以上的电驴文件ed2k链接，以&#8220;　&#8221;划分可以分成三部分： </p>
<p>　　文件名：虽然最直观醒目，但是最不关键，作用仅是便于搜索； </p>
<p>　　文件大小：也没有什么用，主要用来分别片子的清晰度，一般情况是越大越好； </p>
<p>　　文件ID：又叫做hash，这才是ed2k链接里面的关键。很多文件即使它们的文件名不一样，但是只要文件ID一致，电驴服务器就视同为同一个文件。如果想知道你欲下载的文件是否以前已经下载过了，唯一的操作办法就是将每次下载文件的文件ID保存到word文件里面（当然保存ed2k链接更简便），然后下载之前查找一下你要下载文件的文件ID（千万不可查找ed2k链接）是否在该文件中即可判定。 </p>
<p>　　最后做一点补充说明： </p>
<p>　　下载文件的同时你也在提供别人下载。下载完的朋友，如果还想帮助别人下载，可以将文件更名，但不要在你的下载目录或者共享目录中删除，这样可以保证大家下载速度越来越快。电驴的宗旨是&#8220;我为人人、人人为我&#8221;，同一个文件共享、下载的人越多，大家的速度就越快。这点和PUB、FTP有本质区别。 </p>
<p>　　电驴支持多文件下载，一般同时下载20个左右为宜。下载文件的大小建议都选择600M以上的，文件太小不光画质不好，共享的人也越少，下载速度反而不快。 </p>
<p>　　对了，对于电驴下载，文件删档是根本不可能了。</p>
<p><strong>　　第二课——上载文件</strong></p>
<p>　　这东西就更简单了，分两种情况。 </p>
<p>　　1、你通过电驴下载的文件 </p>
<p>　　首先你下载的同时也在提供上载，如果想公布此文件的ed2k链接。你点击菜单&#8220;共享&#8221;，如果文件没有在列表中，可以刷新一下，然后找到你要公布的文件。点鼠标右键，选择&#8220;复制ed2k链接到剪贴板&#8221;，然后在论坛公布即可。 </p>
<p>　　2、你独有的文件 </p>
<p>　　你先把该文件拷贝到你机器里面电驴下载的incoming目录里面（或你指定的共享目录），然后点击&#8220;共享&#8221;菜单，点&#8220;刷新&#8221;后，就可以看到你要上载到电驴服务器的文件了（其实文件并没有上载到服务器，还是在你自己的计算机里面）。然后按照布骤1中所说的方法公布。其实即使你不公布文件的 ed2k链接，大家如果可以搜索到，也都可以自行下载，公布了只是为了方便大家，提高大家的下载速度。 </p>
<p>　　最后再强调一句，提高文件下载速度的办法就是，共享、下载的朋友越多越好，即增大文件的下载源！！！</p>
<p><strong>　　 第三课——搜索文件</strong> </p>
<p>　　用电驴下载电影，你看一辈子电影都看不完。我计算一下，假如你1分钟看1部（太夸张了吧），从出生开始一直活到100岁，每天不吃不喝直到看电影看死，总共可以看大概5000万多部电影。而此时此刻我用电驴搜索到的可下载的文件数目超过9000多万个，如果10%是视频文件的话，也有900万部，这只是今天可以下载的数目。人的岁数有限，而每时每刻又有源源不断的视频文件加入，显然你永远赶不上此发展的脚步。搜索其实很简单，会用Google 等搜索引擎，就应该会用电驴搜索，只不过一个是搜索网页，一个是搜索文件。 </p>
<p>　　1、点&#8220;搜索&#8221;菜单，在&#8220;名字&#8221;里面输入关键字，&#8220;类别&#8221;可以选择任意（推荐方式）或者视频（无法搜索dat文件），&#8220;方法&#8221;最好选择&#8220;全部（服务器）&#8221;，然后点&#8220;开始&#8221;，你就会发现列出了n多可下载的符合你口味的视频文件。</p>
<p>　　2、最好选择&#8220;来源&#8221;多的片子，双击就可以下载了。 </p>
<p>　　3、要保存搜索的文件信息，可以在搜索结果窗口里面，同时按Ctrl和A键全选，然后点鼠标右键，选择&#8220;复制ed2k链接到剪贴板&#8221;，最后剪贴到一个文件中保存即可。 </p>
<p>　　其实有了电驴搜索，你基本都可以找到自己需要的片子，似乎可以完全抛弃此类的论坛。但是本着&#8220;我为人人，人人为我&#8221;的原则，在论坛公布对大家都有好处。因为虽然电驴搜索到片子很多，但是靠一个人的力量找到精品总没有众人的力量大吧。用电驴搜索下载视频文件不难，难的是如何筛选。茫茫草原，又有几棵灵芝草呢？另外好片下载的人越多，下载的速度也就越快啊！！！ </p>
<p>　　这里补充说明几点： </p>
<p>　　1、你提交电驴共享的文件最好能提供一些可查询的特征字，这样别人只要输入里面任何一个字符或者字段就可以搜索到了； </p>
<p>　　2、信息搜索的时候应全面，如分别用简体中文、繁体中文、英文查找，这样总可以找到你所需要的文件。 </p>
<p>　　如果要查找某个演员出演的片子，最好也是用简体中文，繁体中文，英文分别检索，演员的英文名可以用Google检索查询。若想检索到更多的文件信息，建议最好用英文查找，毕竟用电驴的还是以西方人居多。 </p>
<p>　　3、选择&#8220;来源&#8221;数目多的文件下载，这样不会因为提供来源的某1人关机而使你就下载不了了； </p>
<p>　　4、电驴是多点下载，不存在续传的问题，也就是登陆服务器中的任何一个人共享了此文件，都可以保证你能下载完全。 </p>
<p>　　我想大部分驴友访问论坛下载电影，操作方式大概都是直接拷贝文件的ed2k链接下载的，大家有没有想过这其中的不妥之处吗？因为你如此简单操作下载的文件版本也许存在以下两个问题： </p>
<p>　　1、不是最流行的，也就是源最多的版本； </p>
<p>　　2、不是容量最大，也就是最清晰的版本。 </p>
<p>　　所以正确的电驴文件下载操作方法是： </p>
<p>　　把论坛公布的文件下载ed2k链接中的文件名只是看成一个提供给你搜索关键字的素材。自行搜索下载此文件，这样你就可以选择来源最多，容量最大的版本。此法对于提高下载速度简单有效。</p>
<p><strong>　　第四课——电驴宗旨：我为人人，人人为我</strong></p>
<p>　　电驴其实就是一个文件检索器，其作用类似Google，只不过是用来搜索文件的。连接到电驴服务器上的PC上的共享文件相当于Google数据库中的网页，使用电驴就可以检索下载。和PUB不同，文件下载的人越多，下载的速度就越快！因为同时共享文件的人也越多。下面我举一个例子定性的说明一下（仅用来说明问题，实际情况与此不完全相同）： </p>
<p>　　假如我有一个文件（独有的）大小100M提供大家下载，由于文件并不需要上传到服务器上，所以也就没有让人厌烦的上载等待时间。电驴服务器接收到你提供到的文件信息后，会自动的进行分块（类似程序设计里面的指针），假如分成100个字段，每段1M，这时候同时有100人的团队连接你的机器请求文件下载。假定每人运行最大上载数连接是10，下载连接无限制，每个连接数下载速度是1M/分钟，下面分3个阶段来说明： </p>
<p>　　阶段1、由于只有我有这个文件，所以大家都要连到我这里下载，每次连进10人，每人下1个字段（大小1M）。假设10分钟后，每人都从我这里获得了1个字段。另假设大家获得的字段都不相同，第1号获得1字段，第2号获得2字段，依此类推。此阶段团队共下载了100M，历时10分钟，平均到每人的下载速度是100M/10分钟/100人＝0.1M/分钟。 </p>
<p>　　阶段2、假如我此后关机走人，大家还可以下载吗？如果是PUB、FTP方式肯定不行，但是电驴可以，因为100人中每人都有组装此文件的1个字段，而且所有的字段都齐备，所以在服务器的指导下，每个人都可以完成组装工作。下面计算速度，每人可以提供10个连接数，那么这100人之间总共有 1000个连接数。1分钟该团队可以下载1000M，平均到每个人就有1000M/1分钟/100人=10M/分钟！，比阶段1提高了100倍！ </p>
<p>　　阶段3、假如100人都下载完毕，此时又加入1人下载，那他的下载速度最大可以到多少呢？最理想的情况，他可以同时分别连接100人，从每人处下载不同的字段，1分钟后就可以下载完毕，下载速度是100M/分钟！又比阶段2提高了10倍！ </p>
<p>　　虽然以上例子只是用来说明问题，实际上也没有这么理想，带宽也没有如此快速，但是从中众位驴友也应该可以体会到一定的道理。 </p>
<p><strong>　　 第五课——emule菜单说明</strong> </p>
<p>　　再讲述电驴设置之前，先简要介绍一下emule菜单（从左到右）： </p>
<p>　　1、断开/连接：连上服务器就不要点击了； </p>
<p>　　2、服务器：个人认为，原有的各项参数排列次序不合理。建议将前八项排列顺序变为服务器名、文件、Ping、静态、用户、最大用户数、描述、 IP，这样在今后的使用中会比较方便。用鼠标点中想要移动的选项拖动到指定位置即可。从右边的&#8220;我的信息&#8221;窗口可以查看自己是高ID还是低ID（在后续课程中讲述区别）。下面的&#8220;服务器信息&#8221;和&#8220;日志&#8221;应该定期重置。如果老不清理，日志文件有时会上G！对于在下载过程日志中出现的&#8220;文件段已损坏&#8221;信息，不用去理会， emule会自动查找修复。 </p>
<p>　　3、传输：从这里可以查看各个文件的下载状态。鼠标点最上方的&#8220;全部&#8221;可以查看总体下载信息，比较重要的是下载文件的总量，查看后可以根据你的硬盘分区大小增减你的下载文件数目。有些文件名前面有红色或者绿色的&#8220;i&#8221;，表示有人评分或者注释，可点右键查看。红色表示评分为&#8220;无效的/损坏的/假的 &#8221;，如果你相信别人就可以不下载了。建议点击&#8220;速度&#8221;排序下载文件。&#8220;进程&#8221;中最上面的浅蓝色线条表示已下载的比例，进程条快到终点时可将文件的优先级设高。来源中的三个数字分别表示当前连接数，最大连接数、当前上载数。最下面的客户排队中的黑名单不用去理会，原因是由于你的某一连接下载速度过快，对方将你加入黑名单并切断了你对他的连接。但由于源比较多，少一两个无所谓。 </p>
<p>　　4、搜索：见第三课。 </p>
<p>　　5、共享：可以查看你共享的文件，连正在下载的文件也计算在内。选中文件点鼠标右键可以更改文件注释。 </p>
<p>　　6、消息：和QQ类似。 </p>
<p>　　7、IRC：和消息一样，我从来不用，下电影也许应该此时无声胜有声。 </p>
<p>　　8、统计信息：这个对于你磨合电驴很有帮助。 </p>
<p>　　9、选项：下课详述。</p>
<p><strong>　　第六课——电驴设置</strong> </p>
<p>　　估计到今天不少朋友都已经初步掌握了电驴的使用，本课将讲解让很多初学者头疼的设置问题。所有的设置变更都要点&#8220;应用&#8221;或者&#8220;确定&#8221;项才能生效。 </p>
<p>　　1、先说说&#8220;统计&#8221;菜单，这个菜单很有用，你修改设置所造成的一切影响结果，就可以从这里得到体现。那三个曲线图就不说了，很直观，自己看看就明白了。重要的是那个文本窗口，里面的统计数据对你适当更改电驴设置会有帮助。最直观重要的是&#8220;连接&#8221;项和最前面的&#8220;本次运行上传:下载比例&#8221;。我的观点是，首先要努力提高&#8220;连接&#8221;项里面的平均下载速度，其次使&#8220;本次运行上传:下载比例&#8221;高于1，小于2（不宜太高），这样你对于电驴的奉献就和索取基本一致了，这和电驴的宗旨也是一致的。 </p>
<p>　　2、点&#8220;选项&#8221;，点&#8220;服务器&#8221;，将里面的选项除最后1项以外都选上，别的倒无所谓，尤其重要的是要把倒数第2项：仅自动连接到静态服务器选上。然后再点第3项后面的&#8220;列表&#8221;，会打开写字板，把以下的每日更新的服务器列表拷贝到里面： </p>
<p>　　<a href="http://www.srv1000.com/x1/server.met"><u><font color=#0000ff>http://www.srv1000.com/x1/server.met</font></u></a> </p>
<p>　　<a href="http://emule.945.cn/server.met"><u><font color=#0000ff>http://emule.945.cn/server.met</font></u></a> </p>
<p>　　<a href="http://www.edk-files.com/x1/server.met"><u><font color=#0000ff>http://www.edk-files.com/x1/server.met</font></u></a> </p>
<p>　　然后保存，退出写字板。这样启动emule的时侯就可以自动更新服务器了。连接服务器的数目决定你最大可以选择下载的文件数目，从理论上当然是越多越好了。但是你的电驴扫描服务器的时候要耗费时间和内存，而且有的服务器文件数目也不过几千，几万，没有太大的价值，所以建议数目不要太多。加入我上面列的三个列表后，你的服务器数目一般可以保证150—250之间，可以满足足够的需求。 </p>
<p>　　3、仍然在&#8220;选项&#8221;里面，点&#8220;连接&#8221;，如果最下面的三项没有打上勾请补上。再看上面各项： </p>
<p>　　每个文件的最大来源数—硬性限制：300-500是比较适中的数值。 </p>
<p>　　连接限制—最大连接数：填一个较大的数（统计里面可以提供参考意见），但是也别太大，建议输入是上一个数字的15倍左右，例如5000。 </p>
<p>　　最后说说连接能力和上限： </p>
<p>　　首先要知道自己用网络的最大上下载速度是多少k。以下我自己定义了三种模式，对应三种不同的设定（一般的用户用傻瓜模式就可以了，本人推荐使用下两种模式）： </p>
<p>　　傻瓜模式：在能力里面，下载输入高于你最大速度的数值，上传里面输入你想提供给别人下载的总速度，一般可以输入你下载速度的1/5－1/3，比如你的网络最大下载速度是200K，这两个数可以输入300（只要高过200都可以）和50，在上限里面，下载输入0（就是没有限制），上传则为和前面一个上传能力相同的数值。 </p>
<p>　　手动档模式：在能力里面，下载输入尽量接近你最大下载能力的数值，上传输入此数据的1/5－1/3。在上限里面两个数据都输入0。然后点右上的小点，将电驴窗口最小化。鼠标点中右下的驴头，点右键，就可以根据自己的不同情况和喜好选择下载、上传速度档位了。比如上班或者需要上网操作其他事情的时候，就把下载速度档设成2成或4成，上传速度也设成2成或4成，这样公司整体网速或者你自己的上网速度也不会因为电驴疯狂下载而受到很大影响了。下班的时候，将下载速度设成100%或者无限制，上传速度也适当放大，比如增大到4成或者6成。这样可以充分利用公司晚上网络的全部带宽。 </p>
<p>　　自动档模式：在能力里面，下载和上载都输入你平时希望分配的数值，比如手动档模式中你常用的档位数值，而不必考虑网络的能力。在上限里面两个数据都输入0。然后点右上的小点，将电驴窗口最小化。需要更改的时侯，鼠标点中右下的驴头，点右键，就可以根据自己的情况更换档位。比如上班或者需要上网操作其他事情的时候，上下载都设成100％速度，给其他同事或者你上网操作其它事情以足够带宽。下班的时候，将下载速度设成无限制。这样可以充分利用公司晚上网络的全部带宽。这样设置有两个好处，首先由于只有两档变化，比较容易操作；更重要的是由于你在能力中输入的数值相比前两种模式较小，所以驴头右边的下载速度显示条可以更直观准确地显示你的下载速度。比如我在能力里面输入的是150，这样显示条假如到达驴头的一半高度，不用还原emule窗口就知道当前下载速度约为 70K左右了，很方便。如果按照前两种模式，在能力中输入较大数值，则显示条几乎永远只是一个小黑点。另外，如果你在公司上网，最好在WinXP中把那个驴头设置成永远隐藏。 </p>
<p>　　最后说说为什么要将上传速度设定成适当的数值，比如我设定的是50k。若设成1k，即我光下载而基本不上传这样不是更好吗？这是对电驴的误解，电驴也会相应的惩罚你。因为这将直接影响你的连接数，因为你只下载，而不上传，所以电驴分配到你这里来下载的连接数目必然很少。由于电驴用户既是上传又是下载，人家虽然连到你这里下载，同时他也会给你上传。这就是你不要将上传速度设置过小的原因。以上的比例我经过观察统计，比较科学。 </p>
<p>　　当然，如果你对上传不设限制，这绝对是最能体现&#8220;我为人人，人人为我&#8221;电驴宗旨的！ </p>
<p>　　4、最后点主菜单&#8220;服务器&#8221; </p>
<p>　　可以看到n多服务器列表，如何选择呢？我教你一个万全之策。你最好按照上课中的方法将各项参数排序。然后按照以下方法操作： </p>
<p>　　首先点文件，将服务器按文件多少排序，同时观察它的ping值，如果数值比较小，表示你连到服务器的速度比较快。从第1个开始，如果ping值低于你心目中一定的数值，比如1000或者800（你自己看着定），你就点右键，将其添加到&#8220;静态服务器列表中&#8221;，这样无论如何变化，该服务器都不会从你的列表中丢失。从上往下依次选择10-20个左右。 </p>
<p>　　当然从使用的角度，你选择的连接服务器文件数目越多，总的静态服务器数目越少越好，但是也许会比较难连这些优质的服务器，一旦连接断了也很难再自动连接上去，所以推荐选择10—20个静态服务器为宜。 </p>
<p>　　由于你在&#8220;选项&#8221;里面设置成&#8220;仅自动连接静态服务器&#8221;，所以你必然可以连到一个文件数目很多的服务器！ </p>
<p>　　最后补充一点，等文件下载完了，别忘了在&#8220;传输&#8221;菜单里面，点右键选择&#8220;移除已完成文件&#8221;，这完全是为了显示简洁的需要，并不影响别人下载你被移除的文件，因为它仍然在你的incoming目录里面。我建议在另外一个分区建立一个专门的共享目录，把下载完的文件暂时寄存到此处，这样首先可以给你的驴儿腾出活动，另外也可以给别人的下载提供支持。我用于电驴下载的空间是20G，另外的文件共享空间为10G。 </p>
<p>　　以上的情况只是根据我个人情况所做的设置，大家可以根据你个人的情况，多看看你的统计，然后进行适当的调整，我相信不出1周，你就可以找到适合你的网速与机器的最佳电驴配置！</p>
<p><strong>　　第七课——驴儿快快跑</strong> </p>
<p>　　如果经过前一课的学习，你的驴儿还是慢悠悠的。那你一定要好好看看这一课了。如果说将前一课比喻成慢驴的西方手术疗法，那本课就是一味无任何副作用的中药，是本人独创的偏方。 </p>
<p>　　首先要调整驾驴的心态，绝对不能心急，你越是猛赶驴，驴越是原地打转，驴的脾气很犟，要顺着它。首次驾驴的朋友原先一定是用PUB或者FTP下载的，看到一个好片，总恨不得半个小时以内就能看到，这个用PUB或者FTP下载或许可以，但是电驴肯定不行，你也只能将片子加入你的下载列表，进一步能做的也就是将其的优先级设高，但是这不等于电驴的下载速度就比不上PUB或者FTP。我来做个计算。比如你的驴速是30K，这个速度在用宽带驾驴用户中算中下的了。你今天上午9点骑驴找了20个片子，然后就可以不用理它，可以去***想干的。一天后，你的驴儿到底吃了多少呢？简单计算一下，是2.6G！如果按一部大片 500M来算，应该能下载5部了。不过因为各个片子下载进程不一样，最终完成的应该有2－3部吧。用PUB下载作比较，且不说你每天是否能找到如此大量的片子，再说了，你整天忙着下载也耽误功夫啊。那位看官说了，PUB也可以片子放到任务列表中等待啊，然后你自己忙别的去。其实这种方法并不好，因为你后面擦屁股的工作太可怕了，无数的文件，你还要去查合并密码，无异于自寻烦恼。 </p>
<p>　　说了这么多，主要是说一个道理，就是不要因为习惯PUB的方式就对驾驴有畏惧心态。当然PUB方式也有它的优点，就是短平快，想看单个片子短时间就可以解决。不过要是删档了也只有干着急了，电驴下载几乎可以保证永远不删档。 </p>
<p>　　言归正传，下面说说我偏方的配方： </p>
<p>　　1、选择一个优质的服务器 </p>
<p>　　衡量优质的服务器要素主要有四个：稳定、文件多、ping值小，用户多。 </p>
<p>　　选择优质服务器的直接好处是什么呢？那就是在它的服务器用户里面，本身就可以快速查找提供给你一定数量的连接，因为扫描别的服务器查找比较耗费时间。如果同时下载20个文件，用优质服务器也许用几分钟就可以达到稳定速度了，而用劣质服务器可能要耗费半个小时以上进行速度提升，并且一旦某个连接中断，又需要重新去别的服务器查找进行补充。注意，电驴下载都是慢慢加速的。 </p>
<p>　　2、同时下载多个文件 </p>
<p>　　推荐数目是20个左右，这样可以减少等待时间，毕竟东边不亮西边亮。很难想象你只下载1个文件，速度能快到哪里去。 </p>
<p>　　3、搜索下载源数大的文件 </p>
<p>　　不要被某些文件的名称所诱惑，名不副实的片子多的是！我就上过当。只要相信一点，源多的片子就是热门的片子，是好片！有注释的文件要看看注释。源数当然越大越好了，西方电影一般源数多，下载速度很快，毕竟西方人用电驴的还是占大多数，喜欢外文片的朋友有福啊。</p>
<p>　　以上3点你如果做到了，如果你用宽带驾驴还是那么慢，我把我的驴子给你骑！ </p>
<p>　　最后再说说空间的问题。驾驴由于是海量下载，硬盘还是要足够大的，10G的驴圈应该是小户型了，incoming和temp目录要在同一个分区，并且不要放到windows系统所在的分区。我公司的驴圈是20G，并且我有40G的移动硬盘，家里的机器是120G并带刻录机，这应该算金鞍银镫铜掌了。对文件也不要太留恋，该删还是要删的，需要的时候再下不迟嘛！ </p>
<p>　　再提醒一遍，每天查看驴的时候，看看统计，同时将服务器信息和日志重置一下，有时候你老不清理，日志文件都会上G！ </p>
<p><strong>　　第八课——代理和高ID、低ID</strong> </p>
<p>　　前一版本的电驴傻瓜教程中本课讲解比较详细，但是由于新版本的emule支持代理，这一问题似乎变得很简单了。以下说明几点： </p>
<p>　　1、最佳方式－搞定网管 </p>
<p>　　对于局域网用户，这同时也是最有效的方法，但是比较难于操作。就是让网管在网关服务器修改一下设置，把4662端口指向你的计算机。你自然就可以升级成高ID用户了。 </p>
<p>　　2、使用代理获得高ID </p>
<p>　　首先使用代理猎手等软件搜索一些可用的代理，推荐使用Socks 5代理。然后在emule（或edonkey）中设定代理，最后把&#8220;连接&#8221;中的客户端口设成代理的端口数即可。多准备一些代理，由于电驴是海量下载，很容易被代理服务器的网管察觉。这也是本人不推荐使用代理的重要原因。 </p>
<p>　　3、高ID和低ID </p>
<p>　　有很多网友非常介意这点，其实完全没有必要。我本人就是低ID，但是同样可以保证日均50K以上的下载速度，算一算吓死人，每天可以下载5G以上！你的驴儿有如此食量当然也要化上一段时间来消化，不必贪多求全。 </p>
<p>　　高ID 和低ID在使用方面最大的区别就是，高ID用户可以和任意电驴用户建立连接，而低ID用户只能和高ID用户建立连接。也就是说，两个低ID用户如果想要交换文件，必须借助于高ID用户来中转。其实由于大多数用户都是高ID，所以此方面的影响并没有想象中的那么大。同样，如果不关注前几讲中的要点，即使是高 ID用户，也可能寸步难行。</p>
<p><strong>　　 第九课——养成良好的骑驴习惯</strong> </p>
<p>　　推荐电驴首先是电驴的宗旨很好：&#8220;我为人人，人人为我&#8221;。相比于PUB，用句时髦的话，电驴应该是&#8220;绿色环保&#8221;，因为所有的电驴用户文件都是自己主动希望共享的，而不用去窃取其他用户的空间，因而也不会造成大量的网络垃圾。 </p>
<p>　　其次使用电驴可以搜索到几乎你想搜索到的所有文件，我近来就将目标逐步转移到音乐、软件和英文电影方面了。我也建议大家多使用电驴和google，有了这两部海量百科全书，有什么问题不能自己解决呢？ </p>
<p>　　对比FTP和PUB，使用电驴更有利于培养良好的网络习惯。你完全可以定期选好片子，然后安心去工作、学习、上网。让驴儿自己去吃草，你全力去做其他更有价值的事情，何乐而不为呢？ </p>
<p>　　最后祝愿众位驴友骑驴愉快！</p>
</div>
<p><br>常用的emule资源网站<a href="http://www.verycd.com/" target=_blank><font color=#0000ff><u><br>http://www.verycd.com/</u></font></a></p>
<p><a href="http://emule.ppcn.net/" target=_blank><font color=#0000ff><u>http://emule.ppcn.net/</u></font></a></p>
<p><a href="http://www.15y.net/" target=_blank><font color=#0000ff><u>http://www.15y.net/</u></font></a></p>
<p><a href="http://p2p.down.com.cn/" target=_blank><font color=#0000ff><u>http://p2p.down.com.cn/</u></font></a></p>
<p><a href="http://popgo.net/bbs/" target=_blank><u><font color=#0000ff>http://popgo.net/bbs/</font></u></a></p>
<br>请问如何用emule发布资源？<br><br>1 将您准备发布的文件准备好。 <br><br>2 您需要在您的 eMule 里面添加共享目录，默认目录为 C:/Program Files/eMule/incoming，您可以把您要发布的资源的所在文件夹添加为共享。步骤：选项目录 &#8594; 在共享文件夹前面打勾 &#8594; 确定。 <br><br>3 添加好共享文件之后，在emule的共享菜单下可以看到您已经添加的共享文件。（没看见的话多刷新几次）。然后，对要发布的文件使用鼠标右健菜单，把发布文件的优先级改为发布。步骤：共享 &#8594; 察看已共享文件 &#8594; 改变优先级为发布。 <br><br>4 察看共享文件的ed2k链接，并且复制。步骤：鼠标右健点击共享文件 &#8594; 察看ed2k链接 &#8594; 复制。 <br><br>5 到 <a href="http://www.verycd.com/" target=_blank><u><font color=#0000ff>www.VeryCD.com</font></u></a> 上面，点击提交资源，填写发布资源的详细介绍，信息，图片请引用 URL（网上找到的图片鼠标右健属性可以查看图片的URL）。填写好发布资料以后，粘帖之前复制的 ed2k 链接。步骤：填写资源分类 &#8594; 中文名称 &#8594; 其他必要信息 &#8594; 资源内容介绍 &#8594; 粘贴ed2k链接。<br></div>
<img src ="http://www.cppblog.com/tommyyan/aggbug/82053.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 15:50 <a href="http://www.cppblog.com/tommyyan/articles/82053.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Emule与bt协议小议 [转]</title><link>http://www.cppblog.com/tommyyan/articles/82050.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 07:47:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82050.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82050.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82050.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82050.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82050.html</trackback:ping><description><![CDATA[<div class=cnt id=blog_text><font size=3><span><span><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>很久以前就一直想写个东西来总结一下我对</span><span><font face="Times New Roman">p2p</font></span><span>下载模式的认识</span><span><font face="Times New Roman">,</font></span><span>可一直不能静下心来</span><span><font face="Times New Roman">,</font></span><span>不知道是不是由于没喝静心口服液的原因</span><span><font face="Times New Roman">,</font></span><span>最近终于成文</span><span><font face="Times New Roman">,</font></span><span>拿出来与大家共享</span><span><font face="Times New Roman">,</font></span><span>并欢迎拍砖</span><span><font face="Times New Roman">,</font></span><span>共同进步</span><span><font face="Times New Roman">.</font></span><span>闲话少话</span><span><font face="Times New Roman">,</font></span><span>转入正题</span><span><font face="Times New Roman">.</font></span><span>简单的说</span><span><font face="Times New Roman">,emule</font></span><span>与</span><span><font face="Times New Roman">bt </font></span><span>协议两者各有千秋</span><span><font face="Times New Roman">,</font></span><span>下面就两种协议的异同及性能作一比较</span><span><font face="Times New Roman">.</font></span></font>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>1.</font></span><span>传统连接方式</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>bt</font></span><span>使用统一的</span><span><font face="Times New Roman">torrent</font></span><span>文件先作一个原下载文件的信息记录</span><span><font face="Times New Roman">,</font></span><span>然后客户下载后通过</span><span><font face="Times New Roman">torrent</font></span><span>的信息与服务器连接并下载</span><span><font face="Times New Roman">,</font></span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>emule</font></span><span>仅有一个文件</span><span><font face="Times New Roman">ID,</font></span><span>客户自行与服务器连接再下载</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>2.</font></span><span>底层传输协议比较</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>bt</font></span><span>只使用</span><span><font face="Times New Roman">TCP</font></span><span>协议进行下载</span><span><font face="Times New Roman">,</font></span><span>协议简单有效</span><span><font face="Times New Roman">,</font></span><span>但是功能比较单一</span><span><font face="Times New Roman">,</font></span><span>有的功能不完整</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>emule</font></span><span>使用</span><span><font face="Times New Roman">TCP</font></span><span>和</span><span><font face="Times New Roman">UDP</font></span><span>两种协议进行通信</span><span><font face="Times New Roman">,</font></span><span>更加有效的利用了网络资源</span><span><font face="Times New Roman">,</font></span><span>功能完整强大</span><span><font face="Times New Roman">,</font></span><span>但这也同时使主机的负荷加大</span><span><font face="Times New Roman">,</font></span><span>程序编写难度提高</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>3.</font></span><span>文件组织方式和数据验证方式</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>bt</font></span><span>会在开始前对文件进行一次完全的</span><span><font face="Times New Roman">HASH,</font></span><span>就是将文件首尾相联然后按固定块取</span><span><font face="Times New Roman">SHA</font></span><span>值</span><span><font face="Times New Roman">,</font></span><span>这些值最终被放入</span><span><font face="Times New Roman">torrent</font></span><span>文件编码中</span><span><font face="Times New Roman">,</font></span><span>客户从网上一次下载完全</span><span><font face="Times New Roman">,</font></span><span>高效简单</span><span><font face="Times New Roman">,</font></span><span>一般情况下</span><span><font face="Times New Roman">bt</font></span><span>软件会在每小块下载完成后就对其进行</span><span><font face="Times New Roman">HASH</font></span><span>测试</span><span><font face="Times New Roman">,</font></span><span>检查其正确性</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>emule </font></span><span>在连接字符串中只存放了整体文件的</span><span><font face="Times New Roman">HASH</font></span><span>值</span><span><font face="Times New Roman">,</font></span><span>通过将这个</span><span><font face="Times New Roman">HASH</font></span><span>到服务器上取出文件的相关信息</span><span><font face="Times New Roman">,</font></span><span>在实际的操作中</span><span><font face="Times New Roman">,</font></span><span>会将文件分解成</span><st1:chmetcnv unitname="m" sourcevalue="9.28" hasspace="False" negative="False" numbertype="1" tcsc="0"><span><font face="Times New Roman">9.28M</font></span></st1:chmetcnv><span>大小的块并进行</span><span><font face="Times New Roman">HASH</font></span><span>用于对块的完整性测试</span><span><font face="Times New Roman">.</font></span><span>新版的</span><span><font face="Times New Roman">emul</font></span><span>会用一种叫</span><span><font face="Times New Roman">AICH</font></span><span>的技术</span><span><font face="Times New Roman">,</font></span><span>就是说将文件分成</span><span><font face="Times New Roman">120K</font></span><span>大小的块然后</span><span><font face="Times New Roman">HASH</font></span><span>再将</span><span><font face="Times New Roman">HASH</font></span><span>值进行二进迭代式（具体的看</span><span><font face="Times New Roman">emule</font></span><span>协议</span><span><font face="Times New Roman">)</font></span><span>的</span><span><font face="Times New Roman">HASH</font></span><span>最终组成一个</span><span><font face="Times New Roman">HASH</font></span><span>二叉树这种方式的好处是可以在连接时只加入根结节的</span><span><font face="Times New Roman">HASH</font></span><span>值而不用加入叶子节点</span><span><font face="Times New Roman">,</font></span><span>减小了连接时的字符串大小</span><span><font face="Times New Roman">,</font></span><span>如果在最终文件下载完毕后</span><span><font face="Times New Roman">,</font></span><span>测试出的根节点</span><span><font face="Times New Roman">HASH</font></span><span>与得到的根节点的</span><span><font face="Times New Roman">HASH</font></span><span>值不同</span><span><font face="Times New Roman">,</font></span><span>则可以通过协议与网络上的其它主机的树进行比较快速得出错误的块</span><span><font face="Times New Roman">.</font></span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>4.</font></span><span>流量控制方式</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>bt</font></span><span>采用有名的针锋相对的方式处理上传下载平衡的控制</span><span><font face="Times New Roman">,</font></span><span>这种方式会记录短期内与客户连接的所有节点的上传下载流量</span><span><font face="Times New Roman">,</font></span><span>通过在固定时间内对下载流量的比较</span><span><font face="Times New Roman">,</font></span><span>得出允许上传的客户</span><span><font face="Times New Roman">;</font></span><span>为防止新客户长时间得不到其它客户的认同</span><span><font face="Times New Roman">,bt</font></span><span>会在一断时间停止他的上传作为对他的警告；对于已经下载完毕的客户</span><span><font face="Times New Roman">,bt</font></span><span>会简单的使上传流量最的客户得到更多的时间完成上传</span><span><font face="Times New Roman">;</font></span><span>为了防止在文件的最后阶段下载速度下降</span><span><font face="Times New Roman">,bt</font></span><span>会在最后时向所有连接的客户发送请求迅速完成下载</span><span><font face="Times New Roman">;</font></span><span>在整个下载过程中</span><span><font face="Times New Roman">,bt</font></span><span>会对文件块在整个网络中的存在复本的多少进行跟踪</span><span><font face="Times New Roman">,</font></span><span>那些存在的比较少的复本总是会得到优先的下载权</span><span><font face="Times New Roman">,</font></span><span>以使整个网络的文件冗余度提高</span><span><font face="Times New Roman">.</font></span><span>简单的说</span><span><font face="Times New Roman">bt</font></span><span>使用的是针对文件的流量控制方式</span><span><font face="Times New Roman">.</font></span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>Emule</font></span><span>采用的是客户积分的方式</span><span><font face="Times New Roman">,</font></span><span>就是对所有用户的上传和下载量进行一个运算</span><span><font face="Times New Roman">,</font></span><span>从而得出一个客户的积分值</span><span><font face="Times New Roman">,</font></span><span>那些积分比较高的用户总是可以得到优先的下载权</span><span><font face="Times New Roman">,</font></span><span>甚至可以不进行排队直接下载</span><span><font face="Times New Roman">,</font></span><span>这样就在一个比较长的时间内对用户对其它用户的整体贡献有了一个评估</span><span><font face="Times New Roman">.</font></span><span>简单的说</span><span><font face="Times New Roman">emule</font></span><span>采用的是针对用户的流量控制方式</span><span><font face="Times New Roman">.</font></span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>5.kad</font></span><span>与</span><span><font face="Times New Roman">dht</font></span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>kad</font></span><span>和</span><span><font face="Times New Roman">dht</font></span><span>两者都是基于</span><span><font face="Times New Roman">kademlia</font></span><span>技术的分布式</span><span><font face="Times New Roman">HASH</font></span><span>表查找技术</span><span><font face="Times New Roman">,</font></span><span>可惜的是由于协议上的区别</span><span><font face="Times New Roman">,</font></span><span>两者不能互通</span><span><font face="Times New Roman">.</font></span><span>简单介绍下</span><span><font face="Times New Roman">kad,</font></span><span>它首先给每个客户分配一个唯一的</span><span><font face="Times New Roman">ID</font></span><span>值</span><span><font face="Times New Roman">,</font></span><span>然后对不同的</span><span><font face="Times New Roman">ID</font></span><span>值进行异或来得到两个客户之间的</span><span><font face="Times New Roman">"</font></span><span>距离</span><span><font face="Times New Roman">",kad</font></span><span>会维护一个桶</span><span><font face="Times New Roman">,"</font></span><span>距离</span><span><font face="Times New Roman">"</font></span><span>越近的用户桶里的数量会越多</span><span><font face="Times New Roman">,kad</font></span><span>定其的对桶里的用户进行清理</span><span><font face="Times New Roman">,</font></span><span>以保持其有效性</span><span><font face="Times New Roman">.</font></span><span>对于文件和用户</span><span><font face="Times New Roman">emule</font></span><span>会有两个这个东西</span><span><font face="Times New Roman">,</font></span><span>所以我们可以通过</span><span><font face="Times New Roman">kad</font></span><span>来查找文件和文件相关的用户信息</span><span><font face="Times New Roman">;</font></span><span>同样为了考虑冗余的问题</span><span><font face="Times New Roman">,kad</font></span><span>会将其自身的信息复制一份给</span><span><font face="Times New Roman">"</font></span><span>距离</span><span><font face="Times New Roman">"</font></span><span>它最近的一定数量的用户</span><span><font face="Times New Roman">,</font></span><span>这样就算在它下线后</span><span><font face="Times New Roman">,</font></span><span>这些信息也不会丢失</span><span><font face="Times New Roman">.bt</font></span><span>的</span><span><font face="Times New Roman"> dht</font></span><span>不太了解</span><span><font face="Times New Roman">,</font></span><span>呵呵</span><span><font face="Times New Roman">,</font></span><span>不过估计差不多</span><span><font face="Times New Roman">.</font></span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>6.</font></span><span>功能比较</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>emule</font></span><span>具有查找功能</span><span><font face="Times New Roman">,</font></span><span>而这在</span><span><font face="Times New Roman">bt </font></span><span>只能通过网站来实现</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><span><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font></span></span><span>新版的</span><span><font face="Times New Roman">emule</font></span><span>在对防火墙的支持上采用的代理的方式</span><span><font face="Times New Roman">,</font></span><span>就是如果一个用户处在内网</span><span><font face="Times New Roman">,</font></span><span>那么它会找到一个在公网的用户作为它的朋友</span><span><font face="Times New Roman">.bt</font></span><span>在这方面没有明显的变化</span><span><font face="Times New Roman">,</font></span><span>但是不同的</span><span><font face="Times New Roman">bt</font></span><span>客户端实现方式有些不同的支持</span><span><font face="Times New Roman">.</font></span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><font face="Times New Roman"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>7.</font></span><span>总体性能比较</span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><span><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font></span></span><span>个人感觉</span><span><font face="Times New Roman">bt</font></span><span>的方式更注重于简单高效的快速传输</span><span><font face="Times New Roman">,</font></span><span>而</span><span><font face="Times New Roman">emule</font></span><span>更注重于整个网络状态的变化及用户体验</span><span><font face="Times New Roman">.</font></span><span>单从下载效率上说</span><span><font face="Times New Roman">bt</font></span><span>占优</span><span><font face="Times New Roman">,</font></span><span>而从网络状态及完整强大的协议支持上说</span><span><font face="Times New Roman">,emule</font></span><span>作了更多的事情</span><span><font face="Times New Roman">.</font></span><span>从性能上考虑</span><span><font face="Times New Roman">,</font></span><span>在相同网络状态下</span><span><font face="Times New Roman">,bt</font></span><span>下载单文件的能力比较强</span><span><font face="Times New Roman">, emule</font></span><span>比较适合于长时间的多文件下载</span><span><font face="Times New Roman">,</font></span><span>这源于两者对网络均衡及</span><span><font face="Times New Roman">p2p</font></span><span>模式的不同理解</span><span><font face="Times New Roman">.</font></span></font></p>
<p style="MARGIN: 0cm 0cm 0pt"><font size=3><span><span><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font></span></span><span>总结</span><span><font face="Times New Roman">:</font></span><span>无论是</span><span><font face="Times New Roman">BT</font></span><span>还是</span><span><font face="Times New Roman">EMULE</font></span><span>都是优秀的</span><span><font face="Times New Roman">P2P</font></span><span>下载方式</span><span><font face="Times New Roman">,</font></span><span>对于他们的学习</span><span><font face="Times New Roman">,</font></span><span>最终还是要深入到其源码内部进行研究</span><span><font face="Times New Roman">,</font></span><span>而对于网络均衡分布式群集模式的学习</span><span><font face="Times New Roman">,</font></span><span>它们起到了抛砖引玉的作用</span><span><font face="Times New Roman">,</font></span><span>还是那句老话</span><span><font face="Times New Roman">,</font></span><span>学无止境</span><span><font face="Times New Roman">,</font></span><span>让我们多多交流</span><span><font face="Times New Roman">,</font></span><span>共同进步</span><span><font face="Times New Roman">.</font></span></font></p>
</div>
<img src ="http://www.cppblog.com/tommyyan/aggbug/82050.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 15:47 <a href="http://www.cppblog.com/tommyyan/articles/82050.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>迅雷协议分析笔记_20071118 (转)</title><link>http://www.cppblog.com/tommyyan/articles/82051.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 07:47:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82051.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82051.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82051.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82051.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82051.html</trackback:ping><description><![CDATA[<div class=cnt id=blog_text>
<div class=cnt>
<p>发信人: RunningOn (挥着翅膀的男孩), 信区: Python<br>标 题: 迅雷协议分析笔记_20071118<br>发信站: 水木社区 (Sun Nov 18 22:28:58 2007), 转信</p>
<p>前几天想出个主意想做个Linux下的迅雷。今天抽了点时间搞了一下下，分析出了迅雷的索引服务器地址与使用的端口，算是完成了第一步吧。发在这里，也算是笔记。如果有人也感兴趣，不妨看看。<br><br>参考文献：<br>http://student.bnu.edu.cn/bbs/archiver/tid-3086.html<br>写得很不错，原文在btbbs.org，不过我打不开这个BBS。但文章是06年12月的，文中的IP地址与端口现在迅雷都已经换了，所以只能作为参考。同理，本文也可能随时过时，所以读者请注意发文时间为2007年11月18日，转载请注明来源水木社区，作者RunningOn。<br><br>迅雷的原理，就是在下载文件时，向服务器询问会有哪些地方还有相同的文件，如果有，有同时在这些地方一起下载，以达到加速的目的。上面提到的服务器，不妨称为索引服务器。迅雷使用的是被称为p2sp的技术。<br><br>索引服务器有三个：58.254.39.4, 58.254.39.6 和 58.254.39.8。当这三个服务器都无法连接时，就不能搜索到其它有相同文件的站点，因此无法加速下载。<br>迅雷索引服务器直接使用80端口(根据参考文献，迅雷以前曾用其它端口，80端口以前仅作为备用端口)，因为80端口是浏览网页的端口，封它的代价太大，所以要禁迅雷一般不能用封端口的办法，只能封IP。<br>当三个索引服务器的IP都被封时，用迅雷下载会依次发生以下现象：<br>1.迅雷尝试连接58.254.39.4，会尝试三次，三次的timeout分别(精确)是3, 6, 12秒。<br>2.尝试连接58.254.39.6，也是三次，方法同1<br>3.尝试连接58.254.39.8，同1<br>4.如果以上都失败，则休息120秒，然后返回1。<br>在连接索引服务器的同时，如果原始连接(就是在网页上要用迅雷下载的链接)可以下载，就会并行地下载。<br><br><br>迅雷使用的端口和IP地址众多，有论坛和各种网页(广告)的地址，垃圾信息众多，没有好的网络工具进行过滤。我在分析时利用了python帮助分析。方法是：<br>准备九个要下载的文件的地址<br>对每个要下载的文件，做以下工作：<br>1.打开ethereal，将缓冲设为100M，开始抓包<br>2.用迅雷下载文件<br>3.不必等2进行完毕，停止抓包，将数据导出为文本文件。<br>4.停止下载<br>这样得到九个文本文件。用python抓出每个文本文件中出现过的IP，重复的不计，分别存到九个新的文本文件ip1.txt~ip9.txt中<br>再用python分析这九个新的文本文件，统计每个IP出现的次数，将出现了多次的IP打印出来，去掉那些肯定不是迅雷服务器的IP(比如自己的IP以及DNS的IP等)，得到大约十个"可疑"的IP地址。<br>打开防火墙(我用的天网)，封掉这些IP地址，发现迅雷不能使用，说明所有的服务器地址都在那十个中。然后再手动排除，最后得到迅雷索引服务器的地址。<br>IP的分析只需要简单的正则表达式，数据结构用字典，很容易，代码就不贴了，相信也没人想看代码。</p>
</div>
</div>
<img src ="http://www.cppblog.com/tommyyan/aggbug/82051.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 15:47 <a href="http://www.cppblog.com/tommyyan/articles/82051.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>迅雷协议分析</title><link>http://www.cppblog.com/tommyyan/articles/82048.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 07:45:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82048.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82048.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82048.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82048.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82048.html</trackback:ping><description><![CDATA[作 者: xee<br>时 间: 2008-02-23,22:13<br>链 接: http://bbs.pediy.com/showthread.php?t=60110<br>【文章标题】: 迅雷协议分析<br>【文章作者】: vessial<br>【邮件地址】: vessial@hotmail.com<br>【作者主页】: http://blog.csdn.net/xee<br>【生产日期】: 20071122<br>【软件名称】: Thunder 5.7.4.404<br>【使用工具】: OD+Wireshark<br>【作者声明】: 本文仅供研究学习，本人对因这篇文章而导致的一切后果，不承担任何法律责任。本文中的不足之处请各位多多指教,欢迎转载,但转载请保留文章的完整性.<br>----------------------------------------------------------------------------------------------------------<br>分析背景: 本文基于迅雷版式本5.7.4.404<br>分析目的: 通过分析研究得出迅雷客户端与服务器通信,获取下载资源的链接地址,以及它们通信的加&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 密方式,以及附带的源码,欢迎大家讨论.<br>涉及算法: MD5, 128 bit AES<br>----------------------------------------------------------------------------------------------------------<br>&nbsp;&nbsp; 大家都知道迅雷下载为什么这么快,因为它是通过P2SP下载的,就是可以从多个具有相同下载资源的服务器上进行下载,这样下载速度就会很快了,问题是你要从一个指定的下载链接下载文件,它是怎么知道其它的服务器也有相同的资源了,这就是本文讨论的重点,我就不废话了.<br>&nbsp;&nbsp; 迅雷客户端与服务器通信获取多个下载资源的一个方式就是通过http协议,通过80端口进行加密传输,类似下面<br>这个就是客户端向服务端58.254.39.10发送资源查询的包<br>0x0000&nbsp;&nbsp; 50 4F 53 54 20 2F 20 48-54 54 50 2F 31 2E 31 0D&nbsp;&nbsp; POST / HTTP/1.1.<br>0x0010&nbsp;&nbsp; 0A 48 6F 73 74 3A 20 35-38 2E 32 35 34 2E 33 39&nbsp;&nbsp; .Host: 58.254.39<br>0x0020&nbsp;&nbsp; 2E 31 30 3A 38 30 0D 0A-43 6F 6E 74 65 6E 74 2D&nbsp;&nbsp; .10:80..Content-<br>0x0030&nbsp;&nbsp; 74 79 70 65 3A 20 61 70-70 6C 69 63 61 74 69 6F&nbsp;&nbsp; type: applicatio<br>0x0040&nbsp;&nbsp; 6E 2F 6F 63 74 65 74 2D-73 74 72 65 61 6D 0D 0A&nbsp;&nbsp; n/octet-stream..<br>0x0050&nbsp;&nbsp; 43 6F 6E 74 65 6E 74 2D-4C 65 6E 67 74 68 3A 20&nbsp;&nbsp; Content-Length:<br>0x0060&nbsp;&nbsp; 33 39 36 0D 0A 43 6F 6E-6E 65 63 74 69 6F 6E 3A&nbsp;&nbsp; 396..Connection:<br>0x0070&nbsp;&nbsp; 20 4B 65 65 70 2D 41 6C-69 76 65 0D 0A 0D 0A 34&nbsp;&nbsp;&nbsp; Keep-Alive....4<br>0x0080&nbsp;&nbsp; 00 00 00 96 00 00 00 80-01 00 00 02 3A A0 8A 5E&nbsp;&nbsp; ...?..€....:爦^<br>0x0090&nbsp;&nbsp; 52 22 AC 5E FA C8 F6 54-E8 DC 9A BC E6 78 11 D9&nbsp;&nbsp; R"琟 鯰柢毤鎥.?<br>0x00A0&nbsp;&nbsp; 59 C3 E8 64 8E B8 93 EA-E7 43 28 BA 16 FF C4 A9&nbsp;&nbsp; Y描d幐撽鏑(?末<br>0x00B0&nbsp;&nbsp; DC AB 26 7C 56 08 47 D9-A9 37 F6 C1 3A 7B 68 C8&nbsp;&nbsp; 塬&amp;|V.G侃7隽:{h?<br>0x00C0&nbsp;&nbsp; 11 74 9D 62 6D 4C 6C E7-AD 08 46 70 31 AC 97 34&nbsp;&nbsp; .t漛mLl绛.Fp1瑮4<br>0x00D0&nbsp;&nbsp; AE 15 18 37 B3 97 32 91-13 F8 FB AA 30 75 10 02&nbsp;&nbsp; ?.7硹2? ?u..<br>0x00E0&nbsp;&nbsp; 78 8E F6 38 1D 43 6B B9-F4 DE C4 09 23 3A 27 8B&nbsp;&nbsp; x庼8.Ck刽弈.#:'?<br>0x00F0&nbsp;&nbsp; E6 2C 5D 87 BF 4C BF BF-54 15 4E DB 8F 77 95 C0&nbsp;&nbsp; ?]嚳L靠T.N蹚w暲<br>0x0100&nbsp;&nbsp; 67 EE 1E B4 B4 36 F6 EF-CF 96 77 1A EA 9E 63 11&nbsp;&nbsp; g?创6鲲蠔w.隇c.<br>0x0110&nbsp;&nbsp; 40 FC E1 23 81 90 92 5E-FE 23 36 FB 1A 23 37 9A&nbsp;&nbsp; @ #亹抆?6?#7?<br>0x0120&nbsp;&nbsp; 7D 20 95 CA 47 C2 DA E9-E8 FE 30 4C A0 FE 4F 6E&nbsp;&nbsp; } 暿G纶殍?L狛On<br>0x0130&nbsp;&nbsp; A0 A5 81 45 BA AF 68 EE-60 A1 D5 00 A8 DC CC 80&nbsp;&nbsp; 牓丒函h頯≌.ㄜ虁<br>0x0140&nbsp;&nbsp; 84 0C 19 CF 81 B9 13 C0-13 07 E8 70 05 79 15 F5&nbsp;&nbsp; ?.蟻??.鑠.y.?<br>0x0150&nbsp;&nbsp; D5 2B 05 A1 DD 34 D8 D9-C3 E7 05 70 05 79 15 F5&nbsp;&nbsp; ?.&#8805;4刭苗.p.y.?<br>0x0160&nbsp;&nbsp; D5 2B 05 A1 DD 34 D8 D9-C3 E7 05 70 05 79 15 F5&nbsp;&nbsp; ?.&#8805;4刭苗.p.y.?<br>0x0170&nbsp;&nbsp; D5 2B 05 A1 DD 34 D8 D9-C3 E7 05 10 3A CC 2F 13&nbsp;&nbsp; ?.&#8805;4刭苗..:?.<br>0x0180&nbsp;&nbsp; E1 E1 8C 7B C9 C5 48 B3-85 73 55 87 EE 99 14 67&nbsp;&nbsp; 後寋膳H硡sU囶?g<br>0x0190&nbsp;&nbsp; B2 1B 01 1B 56 01 2F FB-47 07 88 BD 4C D2 1A 08&nbsp;&nbsp; ?..V./鸊.埥L?.<br>0x01A0&nbsp;&nbsp; 14 42 F3 F5 C2 7C 26 9E-24 00 A4 EA 5F 20 FC CA&nbsp;&nbsp; .B篚聕&amp;?.り_ <br>0x01B0&nbsp;&nbsp; 80 F6 9B C9 28 5B 55 22-94 33 4F 3E 1B C6 31 23&nbsp;&nbsp; €鰶?[U"?O&gt;.?#<br>0x01C0&nbsp;&nbsp; 82 B1 97 3E C1 00 2F EF-CE 06 7B AA CD A6 61 F5&nbsp;&nbsp; 偙??/镂.{ ?<br>0x01D0&nbsp;&nbsp; C9 59 8E DB F6 49 73 9C-B9 08 05 C3 1E EB A6 D3&nbsp;&nbsp; 蒠庅鯥s湽..?毽?<br>0x01E0&nbsp;&nbsp; 0F BB 86 FD FC CC 99 89-61 A9 B1 F9 30 C7 48 B1&nbsp;&nbsp; .粏 虣塧┍?荋?<br>0x01F0&nbsp;&nbsp; 79 6C 75 26 8C F5 46 F4-7F 04 ED D1 2B 16 2D 94&nbsp;&nbsp; ylu&amp;岝F?.硌+.-?<br>0x0200&nbsp;&nbsp; 2F 2C DE 6E 7B 97 E7 28-8B DA 0D<br>很明显从上面你看不出你熟悉的东西,通过分析,我发现了一些特征,<br>发现这些包的特征和结构如下:<br>0--3字节为命令请求<br>4--7字节我猜想为包序号:)<br>8--11字节为加密包体长度<br>12--最后为了加密的包体<br>拿上面的包为例<br>&nbsp;&nbsp;&nbsp; |&lt;--cmd--&gt;| |&lt;--seq--&gt;| |&lt;-length-&gt;|<br>&nbsp;&nbsp;&nbsp; 34 00 00 00 96 00 00 00 80-01 00 00接下来的数据就是AES加过密的数据了.<br>注意上面的数据来自于http的content数据.<br>既然是通过AES加密了,那密钥是什么了,它是怎么生成的了,不会是DHE吧,那我估计就歇菜了,<br>功能不负有心人啊,这个AES的密钥是通过包的前8个字节生成的,也就是命令请求字和序列号<br>和56个填充字组成的64个字节通过MD5计算出来的,刚好是16个字节.<br>但是这个填充的56个字节和标准的MD5填充的不一样.该填充数据如下:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 80 00 00 00 00 00 00 00<br>00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br>00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br>00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00<br>它们组合到一起就是:<br>34 00 00 00 96 00 00 00 80 00 00 00 00 00 00 00<br>00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br>00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br>00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00<br>经过MD5计算得到的HASH值如下:<br>f5 26 32 d9 0b 36 f0 58 25 53 71 a2 ae 2f 3e d3<br>这个就是数据包的AES加密解密的密钥.<br>于是上面的数据包解密出来就是<br>94 01 05 00 00 00 c1 0b 10 00 00 00 30 30 31 36&nbsp;&nbsp;&nbsp;&nbsp; ?&nbsp;&nbsp; ?&nbsp;&nbsp; 0016<br>36 46 35 41 45 45 44 33 30 30 30 30 14 00 00 00&nbsp;&nbsp;&nbsp;&nbsp; 6F5AEED30000&nbsp;&nbsp;<br>7f 2f 32 dc d5 76 bc 1e 37 ef 83 30 0f 45 80 80&nbsp;&nbsp;&nbsp;&nbsp; /2苷v?7飪0E€€<br>6b 83 48 91 2b 00 00 00 68 74 74 70 3a 2f 2f 64&nbsp;&nbsp;&nbsp;&nbsp; k僅?&nbsp;&nbsp; http://d<br>6f 77 6e 2e 73 61 6e 64 61 69 2e 6e 65 74 2f 54&nbsp;&nbsp;&nbsp;&nbsp; own.sandai.net/T<br>68 75 6e 64 65 72 35 2e 37 2e 34 2e 34 30 34 2e&nbsp;&nbsp;&nbsp;&nbsp; hunder5.7.4.404.<br>65 78 65 00 00 00 00 00 00 00 00 e0 86 6e 00 00&nbsp;&nbsp;&nbsp;&nbsp; exe&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 鄦n <br>00 00 00 7d 7d 14 00 00 00 00 00 7a 65 13 00 00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }}&nbsp;&nbsp;&nbsp;&nbsp; ze <br>00 00 00 e9 a3 46 00 00 00 00 00 00 00 00 00 50&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 椋F&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; P<br>00 00 00 03 00 00 00 65 78 65 0b 06 01 05 02 00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exe <br>20 05 00 00 00 00 00 00 00 00 00 00 00 00 00 05&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>02 80 d1 10 00 00 00 00 00 00 00 00 00 00 00 00&nbsp;&nbsp;&nbsp;&nbsp; €?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>00 00 00 00 00 00 00 02 00 00 00 04 00 00 00 09&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>00 00 00 35 2e 37 2e 34 2e 34 30 34 04 00 00 00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 5.7.4.404&nbsp;&nbsp;<br>30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 00&nbsp;&nbsp;&nbsp;&nbsp; 0000&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>00 00 00 00 00 00 00 00 da 3d 00 c2 c0 a8 b7 01&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ? 吕ǚ<br>01 80 0c 00 00 00 00 00 14 00 00 00 c6 76 99 e7&nbsp;&nbsp;&nbsp;&nbsp; €&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 苬欑<br>6e 66 10 4d 7c be c2 bc 40 3e 6f c2 30 9a 44 65&nbsp;&nbsp;&nbsp;&nbsp; nfM|韭粿&gt;o?欴e<br>00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>00 14 00 00 00 54 68 75 6e 64 65 72 35 2e 37 2e&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Thunder5.7.<br>34 2e 34 30 34 2e 65 78 65 07 07 07 07 07 07 07&nbsp;&nbsp;&nbsp;&nbsp; 4.404.exe<br>这就是构造的加密前的包,简单的说一下这个包的结构,你可以看到那个链接地址,<br>那是我下载这个程序的原始链接地址,我是用迅雷从<br>http://down.sandai.net/Thunder5.7.4.404.exe下载Thunder5.7.4.404.exe<br>那看看服务器回复的包有些什么了,<br>34 00 00 00 0c 00 00 00 f0 07 . n...4.........<br>0040&nbsp;&nbsp; 00 00 66 2b 99 1a af ed 82 56 af b2 93 c2 03 84 ..f+.....V......<br>0050&nbsp;&nbsp; 54 4d 1e 13 6a 65 7c 37 31 32 92 2c 7f 31 b5 32 TM..je|712.,.1.2<br>0060&nbsp;&nbsp; 8c 1e 5f b9 b9 10 f8 63 a1 45 a8 e1 76 f8 5b 2d .._....c.E..v.[-<br>0070&nbsp;&nbsp; 1d 07 7a 1d 8d e9 82 d6 b8 34 ef f2 ec 5d 1b eb ..z......4...]..<br>0080&nbsp;&nbsp; a1 24 96 c4 ad 96 3e 55 0e 73 df 75 c2 9d 8b cc .$....&gt;U.s.u....<br>0090&nbsp;&nbsp; 1e db dc b2 dc 7c 56 3a e8 01 d8 a1 a2 21 05 31 .....|V:.....!.1<br>00a0&nbsp;&nbsp; b0 90 a2 40 8f 86 31 da c8 ee 85 c1 3c 5b 40 1b ...@..1.....&lt;[@.<br>00b0&nbsp;&nbsp; ef d5 5f a4 7d 96 8a 5f d3 38 7f b1 f2 bd b5 95 .._.}.._.8......<br>00c0&nbsp;&nbsp; f7 15 a5 39 1a 1d 73 56 b0 12 cd 2e cf d9 fa 62 ...9..sV.......b<br>00d0&nbsp;&nbsp; e3 d8 08 6c 93 68 02 15 4e ca 34 d8 9c 09 fa 6a ...l.h..N.4....j<br>00e0&nbsp;&nbsp; 62 35 43 5e de d4 52 f8 2b 61 0c 64 c4 bd d1 0a b5C^..R.+a.d....<br>00f0&nbsp;&nbsp; fc 95 3f 22 e8 68 4d 1c 65 82 93 43 24 e7 55 5e ..?".hM.e..C$.U^<br>0100&nbsp;&nbsp; f2 db 7e 07 3b bc bc ad 30 54 78 be f2 45 1e 2d ..~.;...0Tx..E.-<br>0110&nbsp;&nbsp; 2a 6b 11 9b 9e c7 2d 31 d9 e6 d8 3b 33 c9 26 b5 *k....-1...;3.&amp;.<br>0120&nbsp;&nbsp; 41 e3 61 a1 ba 90 1d 70 55 d0 93 3f a4 f9 6a 55 A.a....pU..?..jU<br>0130&nbsp;&nbsp; f9 19 43 e2 6c 38 a1 57 15 aa 2e d4 18 f1 c6 fe ..C.l8.W........<br>0140&nbsp;&nbsp; fe bf e3 e3 62 1a 9e 6f 3b ee c1 44 b1 f8 d8 23 ....b..o;..D...#<br>0150&nbsp;&nbsp; 2c 66 f1 c4 43 a6 9f 0b a7 d5 5c 8c e5 68 19 9f ,f..C.....\..h..<br>0160&nbsp;&nbsp; db aa 7c fa 6e 3a dd 4e f0 53 ce 45 51 25 18 8d ..|.n:.N.S.EQ%..<br>0170&nbsp;&nbsp; a0 0d f0 8f e0 b0 cb 12 6d 92 80 f4 4f eb a9 c0 ........m...O...<br>0180&nbsp;&nbsp; f4 27 4e 34 c0 8d 96 8e 3b 20 57 b0 fb df 5a 4b .'N4....; W...ZK<br>0190&nbsp;&nbsp; 18 e7 2d 54 6f ad da be a6 1e 94 1e f9 2b 9f d7 ..-To........+..<br>01a0&nbsp;&nbsp; 03 8d de c6 16 0b f4 a1 07 d2 15 85 7c fc 78 df ............|.x.<br>01b0&nbsp;&nbsp; 26 3d a7 eb 2f 0b 5f fa 60 4a 73 a5 5a 7e 4a 4e &amp;=../._.`Js.Z~JN<br>01c0&nbsp;&nbsp; 80 a3 9a ad ae 53 b4 dc 6d a8 04 35 96 e5 93 70 .....S..m..5...p<br>01d0&nbsp;&nbsp; 7d 26 07 07 62 cc ce 3f ee 87 5e c4 b2 e5 0e b0 }&amp;..b..?..^.....<br>01e0&nbsp;&nbsp; b3 c5 ef dd 9b 2d ef 4b 13 2a ad 39 13 59 25 55 .....-.K.*.9.Y%U<br>01f0&nbsp;&nbsp; c2 76 1b 95 74 66 2d 1c 3a 2f f6 f5 4e a4 dd 09 .v..tf-.:/..N...<br>0200&nbsp;&nbsp; c8 36 66 bd cd c2 d6 ff 29 cd 20 a3 19 ab 3f d4 .6f.....). ...?.<br>0210&nbsp;&nbsp; 75 67 b5 d4 37 18 24 c0 57 67 f4 8d 06 33 95 1b ug..7.$.Wg...3..<br>0220&nbsp;&nbsp; 03 89 16 f0 b8 e5 52 4f a3 d4 be 38 c9 cc 89 65 ......RO...8...e<br>0230&nbsp;&nbsp; e7 ef 32 df 2e 9f 87 a4 2f 8f c3 a3 41 77 7b cd ..2...../...Aw{.<br><br>服务器回复包如下:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 34 00 00 00 0c 00 00 00 f0 07 . n...4.........<br>0040&nbsp;&nbsp; 00 00 66 2b 99 1a af ed 82 56 af b2 93 c2 03 84 ..f+.....V......<br>0050&nbsp;&nbsp; 54 4d 1e 13 6a 65 7c 37 31 32 92 2c 7f 31 b5 32 TM..je|712.,.1.2<br>0060&nbsp;&nbsp; 8c 1e 5f b9 b9 10 f8 63 a1 45 a8 e1 76 f8 5b 2d .._....c.E..v.[-<br>0070&nbsp;&nbsp; 1d 07 7a 1d 8d e9 82 d6 b8 34 ef f2 ec 5d 1b eb ..z......4...]..<br>0080&nbsp;&nbsp; a1 24 96 c4 ad 96 3e 55 0e 73 df 75 c2 9d 8b cc .$....&gt;U.s.u....<br>0090&nbsp;&nbsp; 1e db dc b2 dc 7c 56 3a e8 01 d8 a1 a2 21 05 31 .....|V:.....!.1<br>00a0&nbsp;&nbsp; b0 90 a2 40 8f 86 31 da c8 ee 85 c1 3c 5b 40 1b ...@..1.....&lt;[@.<br>00b0&nbsp;&nbsp; ef d5 5f a4 7d 96 8a 5f d3 38 7f b1 f2 bd b5 95 .._.}.._.8......<br>00c0&nbsp;&nbsp; f7 15 a5 39 1a 1d 73 56 b0 12 cd 2e cf d9 fa 62 ...9..sV.......b<br>00d0&nbsp;&nbsp; e3 d8 08 6c 93 68 02 15 4e ca 34 d8 9c 09 fa 6a ...l.h..N.4....j<br>00e0&nbsp;&nbsp; 62 35 43 5e de d4 52 f8 2b 61 0c 64 c4 bd d1 0a b5C^..R.+a.d....<br>00f0&nbsp;&nbsp; fc 95 3f 22 e8 68 4d 1c 65 82 93 43 24 e7 55 5e ..?".hM.e..C$.U^<br>0100&nbsp;&nbsp; f2 db 7e 07 3b bc bc ad 30 54 78 be f2 45 1e 2d ..~.;...0Tx..E.-<br>0110&nbsp;&nbsp; 2a 6b 11 9b 9e c7 2d 31 d9 e6 d8 3b 33 c9 26 b5 *k....-1...;3.&amp;.<br>0120&nbsp;&nbsp; 41 e3 61 a1 ba 90 1d 70 55 d0 93 3f a4 f9 6a 55 A.a....pU..?..jU<br><br>解密如下:<br>058B2378 91 01 05 00 00 00 D2 07 01 B8 F7 6C 00 00 00 00 ?...?各l....<br>058B2388 00 14 00 00 00 90 4B 81 47 A5 0F 1E F6 6C 85 FA ....怟丟?鰈咜<br>058B2398 16 13 91 76 8A 91 C8 84 1A 00 00 00 00 00 00 00 憊姂葎.......<br>058B23A8 00 0A 00 00 00 8B 00 00 00 44 00 00 00 68 74 74 .....?..D...htt<br>058B23B8 70 3A 2F 2F 64 6F 77 6E 6C 6F 61 64 2E 7A 6F 6C p://download.zol<br>058B23C8 2E 63 6F 6D 2E 63 6E 2F 64 6F 77 6E 2E 70 68 70 .com.cn/down.php<br>058B23D8 3F 73 6F 66 74 69 64 3D 31 33 35 33 37 33 26 73 ?softid=135373&amp;s<br>058B23E8 75 62 63 61 74 69 64 3D 33 33 26 73 69 74 65 3D ubcatid=33&amp;site=<br>058B23F8 38 2F 00 00 00 68 74 74 70 3A 2F 2F 64 6F 77 6E 8/...http://down<br>058B2408 6C 6F 61 64 2E 7A 6F 6C 2E 63 6F 6D 2E 63 6E 2F load.zol.com.cn/<br>058B2418 6C 69 6E 6B 2F 31 34 2F 31 33 35 33 37 33 2E 73 link/14/135373.s<br>058B2428 68 74 6D 6C D0 42 0B 00 00 A0 00 00 00 5A 00 00 html蠦 ..?..Z..<br>058B2438 00 00 00 00 D6 00 00 00 7F 00 00 00 68 74 74 70 ....?.. ...http<br>058B2448 3A 2F 2F 72 65 64 69 72 65 63 74 2E 6D 79 64 6F ://redirect.mydo<br>058B2458 77 6E 2E 63 6F 6D 2F 6D 79 64 6F 77 6E 2F 70 72 wn.com/mydown/pr<br>058B2468 65 64 6F 77 6E 2E 6A 73 70 3F 69 64 3D 34 30 38 edown.jsp?id=408<br>058B2478 37 32 39 26 70 3D 30 26 6A 3D 31 32 26 6D 3D 31 729&amp;p=0&amp;j=12&amp;m=1<br>058B2488 26 75 72 6C 3D 68 74 74 70 3A 2F 2F 6A 73 31 2E &amp;url=http://js1.<br>058B2498 6D 79 64 6F 77 6E 2E 63 6F 6D 2F 73 6F 66 74 2F mydown.com/soft/<br>058B24A8 32 30 30 37 31 30 2F 54 68 75 6E 64 65 72 35 2E 200710/Thunder5.<br>058B24B8 37 2E 34 2E 34 30 31 2E 65 78 65 3F 00 00 00 68 7.4.401.exe?...h<br>058B24C8 74 74 70 3A 2F 2F 77 77 77 2E 6D 79 64 6F 77 6E ttp://www.mydown<br>058B24D8 2E 63 6F 6D 2F 73 6F 66 74 2F 6E 65 74 77 6F 72 .com/soft/networ<br>058B24E8 6B 2F 64 6F 77 6E 6C 6F 61 64 2F 32 32 39 2F 34 k/download/229/4<br>058B24F8 30 38 37 32 39 5F 64 73 2E 73 68 74 6D 6C D8 82 08729_ds.shtml貍<br>058B2508 0E 00 00 49 22 00 00 5A 00 00 00 00 00 00 5F 00 ..I"..Z......_.<br>058B2518 00 00 26 00 00 00 68 74 74 70 3A 2F 2F 64 2E 35 ..&amp;...http://d.5<br>058B2528 32 70 6B 2E 63 6F 6D 2F 64 6F 77 6E 2E 61 73 70 2pk.com/down.asp<br>058B2538 3F 69 64 3D 31 35 32 26 6E 6F 3D 33 21 00 00 00 ?id=152&amp;no=3!...<br>058B2548 68 74 74 70 3A 2F 2F 64 6F 77 6E 2E 35 32 70 6B http://down.52pk<br>058B2558 2E 63 6F 6D 2F 73 6F 66 74 2F 31 35 32 2E 68 74 .com/soft/152.ht<br>058B2568 6D 30 92 10 00 FF 95 00 00 00 5A 00 00 00 00 00 m0?.?..Z.....<br>058B2578 00 AA 00 00 00 3D 00 00 00 68 74 74 70 3A 2F 2F .?..=...http://<br>058B2588 36 31 2E 31 34 35 2E 31 31 33 2E 31 31 37 2F 62 61.145.113.117/b<br>058B2598 35 2F 64 6F 77 6E 2E 73 61 6E 64 61 69 2E 6E 65 5/down.sandai.ne<br>058B25A8 74 2F 54 68 75 6E 64 65 72 35 2E 37 2E 34 2E 34 t/Thunder5.7.4.4<br>058B25B8 30 31 2E 65 78 65 55 00 00 00 68 74 74 70 3A 2F 01.exeU...http:/<br>058B25C8 2F 36 31 2E 31 34 35 2E 31 31 33 2E 31 31 37 2F /61.145.113.117/<br>058B25D8 62 35 2F 64 6C 2E 70 63 6F 6E 6C 69 6E 65 2E 63 b5/dl.pconline.c<br>058B25E8 6F 6D 2E 63 6E 2F 68 74 6D 6C 5F 32 2F 31 2F 38 om.cn/html_2/1/8<br>058B25F8 39 2F 69 64 3D 34 32 34 34 33 26 70 6E 3D 30 26 9/id=42443&amp;pn=0&amp;<br>058B2608 6C 69 6E 6B 50 61 67 65 3D 31 2E 68 74 6D 6C 68 linkPage=1.htmlh<br>058B2618 77 0C 00 FF 81 00 00 00 5A 00 00 00 00 00 00 7A w..?..Z......z<br>058B2628 00 00 00 3E 00 00 00 68 74 74 70 3A 2F 2F 77 77 ...&gt;...http://ww<br>058B2638 77 2E 39 39 37 2E 63 6E 2F 73 6F 66 74 2F 64 6F w.997.cn/soft/do<br>058B2648 77 6E 6C 6F 61 64 2E 61 73 70 3F 73 6F 66 74 69 wnload.asp?softi<br>058B2658 64 3D 37 36 36 26 64 6F 77 6E 69 64 3D 30 26 69 d=766&amp;downid=0&amp;i<br>058B2668 64 3D 37 39 30 24 00 00 00 68 74 74 70 3A 2F 2F d=790$...http://<br>058B2678 77 77 77 2E 39 39 37 2E 63 6E 2F 73 6F 66 74 2F www.997.cn/soft/<br>058B2688 31 2F 31 38 2F 37 36 36 2E 68 74 6D 6C 68 FA 0B 1/18/766.htmlh?<br>058B2698 00 00 3C 01 00 00 5A 00 00 00 00 00 00 80 00 00 ..&lt;..Z......€..<br>058B26A8 00 33 00 00 00 68 74 74 70 3A 2F 2F 64 6F 77 6E .3...http://down<br>058B26B8 38 2E 7A 6F 6C 2E 63 6F 6D 2E 63 6E 2F 78 69 61 8.zol.com.cn/xia<br>058B26C8 7A 61 69 2F 54 68 75 6E 64 65 72 35 2E 37 2E 34 zai/Thunder5.7.4<br>058B26D8 2E 34 30 31 2E 65 78 65 35 00 00 00 68 74 74 70 .401.exe5...http<br>058B26E8 3A 2F 2F 64 6F 77 6E 6C 6F 61 64 2E 77 77 77 2E ://download.www.<br>058B26F8 66 65 6E 67 6E 69 61 6F 2E 63 6F 6D 2F 6C 69 6E fengniao.com/lin<br>058B2708 6B 2F 31 34 2F 31 33 35 33 37 33 2E 73 68 74 6D k/14/135373.shtm<br>058B2718 6C F8 F4 08 00 00 8F 00 00 00 5A 00 00 00 00 00 l ..?..Z.....<br>058B2728 00 97 00 00 00 4A 00 00 00 68 74 74 70 3A 2F 2F .?..J...http://<br>058B2738 64 6F 77 6E 6C 6F 61 64 2E 77 77 77 2E 66 65 6E download.www.fen<br>058B2748 67 6E 69 61 6F 2E 63 6F 6D 2F 64 6F 77 6E 2E 70 gniao.com/down.p<br>058B2758 68 70 3F 73 6F 66 74 69 64 3D 31 33 35 33 37 33 hp?softid=135373<br>058B2768 26 73 75 62 63 61 74 69 64 3D 33 33 26 73 69 74 &amp;subcatid=33&amp;sit<br>058B2778 65 3D 38 35 00 00 00 68 74 74 70 3A 2F 2F 64 6F e=85...http://do<br>058B2788 77 6E 6C 6F 61 64 2E 77 77 77 2E 66 65 6E 67 6E wnload.www.fengn<br>058B2798 69 61 6F 2E 63 6F 6D 2F 6C 69 6E 6B 2F 31 34 2F iao.com/link/14/<br>058B27A8 31 33 35 33 37 33 2E 73 68 74 6D 6C 68 00 0B 00 135373.shtmlh. .<br>058B27B8 00 9D 00 00 00 5A 00 00 00 00 00 00 93 00 00 00 .?..Z......?..<br>058B27C8 48 00 00 00 68 74 74 70 3A 2F 2F 64 6F 77 6E 6C H...http://downl<br>058B27D8 6F 61 64 2E 77 77 77 2E 78 69 79 75 69 74 2E 63 oad.www.xiyuit.c<br>058B27E8 6F 6D 2F 64 6F 77 6E 2E 70 68 70 3F 73 6F 66 74 om/down.php?soft<br>058B27F8 69 64 3D 31 33 35 33 37 33 26 73 75 62 63 61 74 id=135373&amp;subcat<br>058B2808 69 64 3D 33 33 26 73 69 74 65 3D 38 33 00 00 00 id=33&amp;site=83...<br>058B2818 68 74 74 70 3A 2F 2F 64 6F 77 6E 6C 6F 61 64 2E http://download.<br>058B2828 77 77 77 2E 78 69 79 75 69 74 2E 63 6F 6D 2F 6C www.xiyuit.com/l<br>058B2838 69 6E 6B 2F 31 34 2F 31 33 35 33 37 33 2E 73 68 ink/14/135373.sh<br>058B2848 74 6D 6C 60 31 0A 00 00 90 00 00 00 5A 00 00 00 tml`1...?..Z...<br>058B2858 00 00 00 46 00 00 00 2E 00 00 00 68 74 74 70 3A ...F.......http:<br>058B2868 2F 2F 64 6F 77 6E 2E 73 61 6E 64 61 69 2E 6E 65 //down.sandai.ne<br>058B2878 74 2F 54 68 75 6E 64 65 72 35 2E 37 2E 34 2E 34 t/Thunder5.7.4.4<br>058B2888 30 31 2E 65 78 65 3F 32 30 00 00 00 00 FF FF FF 01.exe?20....<br>058B2898 FF 00 FF FF FF FF 5A 00 00 00 00 00 00 46 00 00 .Z......F..<br>058B28A8 00 2E 00 00 00 68 74 74 70 3A 2F 2F 64 6F 77 6E .....http://down<br>058B28B8 2E 73 61 6E 64 61 69 2E 6E 65 74 2F 54 68 75 6E .sandai.net/Thun<br>058B28C8 64 65 72 35 2E 37 2E 34 2E 34 30 31 2E 65 78 65 der5.7.4.401.exe<br><br>看见了吗,回复包解密后,里面带着的链接地址就是P2SP的多个可供下载的服务器的链接地址.<br>而且回复里面包含一些文件相关的信息,比如SHA-1 HASH值之类的,大家有兴趣的话,可以自<br>已分析它的包的结构,我下篇文章分析它的包结构,呵呵:)<br>注意,上面的发送包和回复包不是关联的,因为我调试的时候没有把它们关取在一起,送了不同的包进行分析的.<br>好了,客户端与服务器之间的获取多个下载源的加密通信过程就到此结束了,这儿我主要的只介绍<br>它们通信的加密算法而已,具体其它的协议以后有时间再发.<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 时间仓促,如有不足之处,还请多多指教.<br>最后附上加解密的源代码.<br>#include &lt;stdio.h&gt;<br>#include &lt;string.h&gt;<br>#include &lt;openssl/aes.h&gt;<br>#include "thunder-md5.h"<br>unsigned char thunder[]={<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0x34, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00,0x80,0x00,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00};<br>unsigned char thunder_md5_pad[]={<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00};<br>unsigned char thunder_AES_key[16];//thunder MD5 padding data<br>unsigned char in[]={0x02,0x3A,0xA0,0x8A,0x5E<br>,0x52,0x22,0xAC,0x5E,0xFA,0xC8,0xF6,0x54,0xE8,0xDC,0x9A,0xBC,0xE6,0x78,0x11,0xD9<br>,0x59,0xC3,0xE8,0x64,0x8E,0xB8,0x93,0xEA,0xE7,0x43,0x28,0xBA,0x16,0xFF,0xC4,0xA9<br>,0xDC,0xAB,0x26,0x7C,0x56,0x08,0x47,0xD9,0xA9,0x37,0xF6,0xC1,0x3A,0x7B,0x68,0xC8<br>,0x11,0x74,0x9D,0x62,0x6D,0x4C,0x6C,0xE7,0xAD,0x08,0x46,0x70,0x31,0xAC,0x97,0x34<br>,0xAE,0x15,0x18,0x37,0xB3,0x97,0x32,0x91,0x13,0xF8,0xFB,0xAA,0x30,0x75,0x10,0x02<br>,0x78,0x8E,0xF6,0x38,0x1D,0x43,0x6B,0xB9,0xF4,0xDE,0xC4,0x09,0x23,0x3A,0x27,0x8B<br>,0xE6,0x2C,0x5D,0x87,0xBF,0x4C,0xBF,0xBF,0x54,0x15,0x4E,0xDB,0x8F,0x77,0x95,0xC0<br>,0x67,0xEE,0x1E,0xB4,0xB4,0x36,0xF6,0xEF,0xCF,0x96,0x77,0x1A,0xEA,0x9E,0x63,0x11<br>,0x40,0xFC,0xE1,0x23,0x81,0x90,0x92,0x5E,0xFE,0x23,0x36,0xFB,0x1A,0x23,0x37,0x9A<br>,0x7D,0x20,0x95,0xCA,0x47,0xC2,0xDA,0xE9,0xE8,0xFE,0x30,0x4C,0xA0,0xFE,0x4F,0x6E<br>,0xA0,0xA5,0x81,0x45,0xBA,0xAF,0x68,0xEE,0x60,0xA1,0xD5,0x00,0xA8,0xDC,0xCC,0x80<br>,0x84,0x0C,0x19,0xCF,0x81,0xB9,0x13,0xC0,0x13,0x07,0xE8,0x70,0x05,0x79,0x15,0xF5<br>,0xD5,0x2B,0x05,0xA1,0xDD,0x34,0xD8,0xD9,0xC3,0xE7,0x05,0x70,0x05,0x79,0x15,0xF5<br>,0xD5,0x2B,0x05,0xA1,0xDD,0x34,0xD8,0xD9,0xC3,0xE7,0x05,0x70,0x05,0x79,0x15,0xF5<br>,0xD5,0x2B,0x05,0xA1,0xDD,0x34,0xD8,0xD9,0xC3,0xE7,0x05,0x10,0x3A,0xCC,0x2F,0x13<br>,0xE1,0xE1,0x8C,0x7B,0xC9,0xC5,0x48,0xB3,0x85,0x73,0x55,0x87,0xEE,0x99,0x14,0x67<br>,0xB2,0x1B,0x01,0x1B,0x56,0x01,0x2F,0xFB,0x47,0x07,0x88,0xBD,0x4C,0xD2,0x1A,0x08<br>,0x14,0x42,0xF3,0xF5,0xC2,0x7C,0x26,0x9E,0x24,0x00,0xA4,0xEA,0x5F,0x20,0xFC,0xCA<br>,0x80,0xF6,0x9B,0xC9,0x28,0x5B,0x55,0x22,0x94,0x33,0x4F,0x3E,0x1B,0xC6,0x31,0x23<br>,0x82,0xB1,0x97,0x3E,0xC1,0x00,0x2F,0xEF,0xCE,0x06,0x7B,0xAA,0xCD,0xA6,0x61,0xF5<br>,0xC9,0x59,0x8E,0xDB,0xF6,0x49,0x73,0x9C,0xB9,0x08,0x05,0xC3,0x1E,0xEB,0xA6,0xD3<br>,0x0F,0xBB,0x86,0xFD,0xFC,0xCC,0x99,0x89,0x61,0xA9,0xB1,0xF9,0x30,0xC7,0x48,0xB1<br>,0x79,0x6C,0x75,0x26,0x8C,0xF5,0x46,0xF4,0x7F,0x04,0xED,0xD1,0x2B,0x16,0x2D,0x94<br>,0x2F,0x2C,0xDE,0x6E,0x7B,0x97,0xE7,0x28,0x8B,0xDA,0x0D};//Encrypt data<br>unsigned char out[4096];<br>int main(int argc, char *argv[])<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp; MD5_CTX c;<br>&nbsp;&nbsp;&nbsp;&nbsp; AES_KEY aes_key;<br>&nbsp;&nbsp;&nbsp;&nbsp; int i,j;<br>&nbsp;&nbsp;&nbsp;&nbsp; MD5Init(&amp;c);<br>&nbsp;&nbsp;&nbsp;&nbsp; Transform((unsigned long *)c.buf,(unsigned long*)thunder);<br>&nbsp;&nbsp;&nbsp;&nbsp; strncpy((char*)&amp;thunder_AES_key,(const char*)&amp;c.buf,16);<br>&nbsp;&nbsp;&nbsp;&nbsp; AES_set_decrypt_key((const unsigned char *)&amp;thunder_AES_key,128,&amp;aes_key);<br>&nbsp;&nbsp;&nbsp;&nbsp; for ( i=0;i&lt;sizeof(in)/16;i++)<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AES_decrypt((const unsigned char *)&amp;in[i*16],(unsigned char *)&amp;out[i*16],&amp;aes_key);<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp; for ( i=0;i&lt;sizeof(in)/16;i++)<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for ( j=0;j&lt;16;j++)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("%02x ",out[i*16+j]);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("&nbsp;&nbsp;&nbsp; ");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for ( j=0;j&lt;16;j++)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("%c",out[i*16+j]);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("\n");<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; return 0;<br>}
<img src ="http://www.cppblog.com/tommyyan/aggbug/82048.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 15:45 <a href="http://www.cppblog.com/tommyyan/articles/82048.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>我研究kademlia时搜集的一些资料 [转]</title><link>http://www.cppblog.com/tommyyan/articles/82049.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 07:45:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82049.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82049.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82049.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82049.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82049.html</trackback:ping><description><![CDATA[<p>研究kademlia有1年半了，中间搜集过很多的资料，只是都被我不小心删除掉了，这里发布一些经典的资料。</p>
<p><a href="http://kadc.sourceforge.net/"><u><font color=#0000ff>KadC</font></u></a>：一个比较经典的C实现的kademlia协议KadC：<a href="http://kadc.sourceforge.net/"><u><font color=#0000ff>http://kadc.sourceforge.net/</font></u></a> <a href="http://kadc.sourceforge.net/files/KadC-02Aug06.tgz" target=_blank><u><font color=#0000ff>下载链接</font></u></a>，不过目前这个库已经很久没有人更新了，这个库实现了kademlia协议几乎全部的内容，但是有一个关键的部分没有实现，就是存储发布信息，即发布信息是可以成功的，但是目标节点并没有存储这些信息，因此在成功发布之后不能搜索到发布的内容，这个实现运行于Linux平台，windows下可以用 cygwin或者mingw模拟运行。</p>
<p><a href="http://www.emule-project.net/"><u><font color=#0000ff>emule</font></u></a>:电骡是我研究时间比较长的一个东东，开源的，<a href="http://prdownloads.sourceforge.net/emule/eMule0.48a-Installer2.exe"><u><font color=#0000ff>软件下载地址</font></u></a>，<a href="http://prdownloads.sourceforge.net/emule/eMule0.48a-Sources.zip"><u><font color=#0000ff>源码下载地址</font></u></a>，目前最新版本是0.48a，我看得主要是0.47c的源码，然后前一阵 <a href="http://www.verycd.com/"><u><font color=#0000ff>verycd</font></u></a>除了一个<a href="http://download.verycd.com/easyMule/easyMule-071109Beta-Setup.exe"><u><font color=#0000ff>easyemule</font></u></a>，<a href="http://download.verycd.com/easyMule/EasyMule-VeryCD-src-071109.rar"><u><font color=#0000ff>easyemule源码</font></u></a>，算是emule的mod吧，还比较适合国内用，加入了反吸血，不过我还没有怎么仔细看源码，对于emule这个东东，我也发布过几篇学习的文章：<a href="http://dolf.cn/2007/09/05/emule%e4%b8%ad%e8%8a%82%e7%82%b9%e5%8a%a0%e5%85%a5kad%e7%bd%91%e7%bb%9c%e8%bf%87%e7%a8%8b%ef%bc%88%e6%ba%90%e4%bb%a3%e7%a0%81%e8%af%a6%e8%a7%a3%ef%bc%89/" rel=bookmark><span><u><font color=#0000ff>emule中节点加入Kad网络过程（源代码详解）</font></u></span></a> <a href="http://dolf.cn/2007/08/24/emule%e4%b8%adkad%e7%bd%91%e7%bb%9c%e6%b7%b1%e5%85%a5%e8%a7%a3%e6%9e%90/" rel=bookmark><span><u><font color=#0000ff>emule中kad网络深入解析</font></u></span></a>。与emule（windows平台）类似的有linux下的<a href="http://www.amule.org/"><u><font color=#0000ff>amule</font></u></a>。<a href="http://shareaza.sourceforge.net/"><u><font color=#0000ff>sharaza</font></u></a>实现了多种<span class="yo2keyword yo2keyword_tag"><a title="Tag 了 12 篇文章" href="http://dolf.cn/articles/tag/p2p-2"><u><font color=#0000ff>P2P</font></u></a></span>协议，其中包括kademlia协议。</p>
<p><a href="http://www.revconnect.com/"><u><font color=#0000ff>revconnect</font></u></a>：这个东东也很厉害，也是p2p的文件共享基于<a href="http://dcplusplus.sourceforge.net/"><u><font color=#0000ff>DC++</font></u></a>，但是结果呈现的方式和emule的完全不同，第一次看到它的呈现方式时觉得确实不错（相比较于emule），这个东东相当于是internet上的网络邻居，呵呵，不过在国内不太流行，用户不太多，国外用户还是很多的，源码写的也很不错，底层同样是基于kademlia协议。</p>
<p>我主要就是看了这三个项目，其中前两个的源码仔细研究过，不过emule的代码确实有点复杂，到现在我只能说能够将kademlia模块弄出来做些应用。</p>
<p>另外，2007年夏天我自己做了一个基于kademlia的p2p voip系统——<a href="https://sourceforge.net/projects/ppphone/" target=_blank><u><font color=#0000ff>ppPhone</font></u></a>，详细介绍可以在<a href="https://sourceforge.net/projects/ppphone/" target=_blank><u><font color=#0000ff>这里</font></u></a>看到。</p>
<p>下面共享一些我机器里的资源：</p>
<p>Kademlia: A Peer-to-peer Information System Based on the XOR Metric，这是kadmelia协议提出者发表的论文，呵呵</p>
<p>&#160;</p>
<p>kademlia协议原理简介：这是国内一个高人写的，不错</p>
<p>&#160;</p>
<p>emule 0.47a source：0.47a vc8的源码，已经包含相关的库了。</p>
<p>&#160;</p>
<p>revconnect中kademlia库的源码</p>
<p>&#160;</p>
<p>revconnect 0.674p的源码</p>
<p>&#160;</p>
<p>A performance evaluation of the kad-protocol.pdf：这是法国的一篇硕士论文，对emule中的kademlia协议做了比较详细的分析。</p>
<p>&#160;</p>
<p>An Analysis of BitTorrent's Two Kademlia-Based DHTs.pdf：分析bt中使用的kademlia协议的缺点，并提出了一些改进方法。</p>
<p>&#160;</p>
<p>Analytical Study on Improving Lookup Performance of Distributed Hash Table Systems under Churn.pdf：分析了在churn（即网络拓扑动态变化较大）情况下DHT的查询性能，以kademlia协议为代表。</p>
<p>&#160;</p>
<p>Improving lookup performance over a widely-deployed dht .pdf：对kademlia协议进行优化的文章</p>
<p>&#160;</p>
<p>Improving the Performance and Robustness of Kademlia-based Overlay Networks.pdf：kademlia协议优化</p>
<img src ="http://www.cppblog.com/tommyyan/aggbug/82049.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 15:45 <a href="http://www.cppblog.com/tommyyan/articles/82049.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>emule中节点加入Kad网络过程（源代码详解）</title><link>http://www.cppblog.com/tommyyan/articles/82047.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 07:43:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82047.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82047.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82047.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82047.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82047.html</trackback:ping><description><![CDATA[<table style="TABLE-LAYOUT: fixed">
    <tbody>
        <tr>
            <td>
            <div class=cnt id=blog_text>
            <p>程序启动：</p>
            <p><strong>EmuleDlg.cpp</strong><strong>中函数</strong>BOOL CemuleDlg::OnInitDialog()<strong>，此函数用于对话框的初始化，在这个函数里添加了定时器</strong>：VERIFY( (m_hTimer = ::SetTimer(NULL, NULL, 300, StartupTimer)) != NULL );</p>
            <p><strong>在这里添加了函数</strong>void CALLBACK CemuleDlg::StartupTimer(HWND /*hwnd*/, UINT /*uiMsg*/, UINT /*idEvent*/, DWORD /*dwTime*/)，</p>
            <p>case 2:</p>
            <p>theApp.Kad_Dlg-&gt;status++;</p>
            <p>if(!theApp.listensocket-&gt;StartListening())</p>
            <p>ASSERT(0);</p>
            <p>if(!theApp.clientudp-&gt;Create())</p>
            <p>ASSERT(0);</p>
            <p>theApp.Kad_Dlg-&gt;status++;</p>
            <p>break;</p>
            <p><strong>在StartupTimer这个函数里，添加了一个ListenSocket的侦听端，并且在本地节点创建了一个CClientUDPSocket* clientudp;</strong></p>
            <p>然后程序启动。</p>
            <p>顺便说一句，在CEmule类中定义了许多的类的实例，这都在今后使用到：</p>
            <p>UploadBandwidthThrottler* uploadBandwidthThrottler;</p>
            <p>CClientList* clientlist;</p>
            <p>CClientUDPSocket* clientudp;</p>
            <p>CListenSocket* listensocket;</p>
            <p>CSharedFileList* sharedfiles;</p>
            <p>CDownloadQueue* downloadqueue;</p>
            <p>CUploadQueue* uploadqueue;</p>
            <p>CServerList* serverlist;</p>
            <p>LastCommonRouteFinder* lastCommonRouteFinder;</p>
            <p>CServerConnect* serverconnect;</p>
            <p>CIPFilter* ipfilter;</p>
            <p>CClientCreditsList* clientcredits;</p>
            <p>CSearchList* searchlist;</p>
            <p>CKnownFileList* knownfiles;</p>
            <p>CMMServer* mmserver;</p>
            <p>AppState m_app_state; // defines application state for shutdown</p>
            <p>CMutex hashing_mut;</p>
            <p>CString m_strCurVersionLong;</p>
            <p>CPeerCacheFinder* m_pPeerCache;</p>
            <p>CFriendList* friendlist;</p>
            <p>CFirewallOpener* m_pFirewallOpener;//hyper added</p>
            <p>节点加入网络：</p>
            <p><strong>Emule</strong><strong>连接Kad网络时</strong>，调用函数：Kademlia::CKademlia::Start(); Start()这个函数没有做什么实际意义上的事情，主要是new了几个类：</p>
            <p>m_pInstance = new CKademlia();</p>
            <p>m_pInstance-&gt;m_pPrefs = pPrefs;</p>
            <p>m_pInstance-&gt;m_pUDPListener = NULL;</p>
            <p>m_pInstance-&gt;m_pRoutingZone = NULL;</p>
            <p>m_pInstance-&gt;m_pIndexed = new CIndexed();</p>
            <p>m_pInstance-&gt;m_pRoutingZone = new CRoutingZone();</p>
            <p>m_pInstance-&gt;m_pUDPListener = new CKademliaUDPListener();</p>
            <p>并且更改了几个定时器的时间。</p>
            <p><strong>接着程序转入到routingzone.cpp中执行</strong>。</p>
            <p>在上面那部分的Start ()函数体内部初始化了CRoutingZone这个类，这个类的构造函数CRoutingZone::CRoutingZone()体中调用函数 Init(NULL, 0, CUInt128((ULONG)0));来初始化根节点（应该就是本地节点）。</p>
            <p>在void CRoutingZone::Init(CRoutingZone *pSuper_zone, int iLevel, const CUInt128 &amp;uZone_index)函数体内部创建了一个新的m_pBin = new CRoutingBin();<strong>接着调用函数StartTime（）</strong>，用来开始这个区域。在StartTime（）函数内部添加事件CKademlia::AddEvent(this);</p>
            <p>在调用完函数StartTime（）函数后，从文件中读取以前保存的联系人。</p>
            <p>在调用完函数Kademlia::CKademlia::Start();之后，Kademlia开始处理，转入函数Kademlia:: CKademlia::Process()开始执行，在函数void CKademlia::Process()中调用函数pZone-&gt;OnSmallTimer();即CRoutingZone中 OnSmallTimer().。</p>
            <p><strong>CRoutingZone</strong><strong>中OnSmallTimer()</strong>，在此函数体内，当判断联系人为非空时，调用函数 CKademlia::GetUDPListener()-&gt;SendMyDetails_KADEMLIA2(KADEMLIA2_HELLO_REQ, pContact-&gt;GetIPAddress(), pContact-&gt;GetUDPPort());来发送本地节点的一些信息，其中函数的第一个参数是消息的类型， KADEMLIA2_HELLO_REQ表明是Kademlia 2.0网络的加入请求，相当于TCP/IP中的ACK，即表明这个消息是用来加入网络的。第二个参数是本地节点的IP，第三个节点是本地节点的端口。</p>
            <p><strong>接着转入</strong>KademliaUDPListener.cpp中函数void CKademliaUDPListener::SendMyDetails_KADEMLIA2(byte byOpcode, uint32 uIP, uint16 uUDPPort)运行，主要是调用函数SendPacket(byPacket, uLen, uIP, uUDPPort);，SendPacket(byPacket, uLen, uIP, uUDPPort);函数在KademliaUDPListener.cpp内部，此函数体内部调用函数theApp.clientudp-&gt; SendPacket(pPacket, ntohl(uDestinationHost), uDestinationPort);来发送包。</p>
            <p><strong>ClientUDPSocket.cpp</strong><strong>中函数</strong>theApp.clientudp-&gt;SendPacket(pPacket, ntohl(uDestinationHost), uDestinationPort);体内部将刚才的消息包（或者叫数据包）加入到controlpacket_queue的队尾， controlpacket_queue.AddTail(newpending); controlpacket_queue是一个链表，类型是CTypedPtrList&lt;CPtrList, UDPPack*&gt; controlpacket_queue;，是通过模板来实现的。接着继续调用函数theApp.uploadBandwidthThrottler- &gt;QueueForSendingControlPacket(this);此时数据包在链表UploadBandwidthThrottler* uploadBandwidthThrottler;中排队。</p>
            <p>类UploadBandwidthThrottler继承自CWinThread类，主要是作为线程来运行的。类在初始化，在构造函数中调用函数 UINT AFX_CDECL UploadBandwidthThrottler::RunProc(LPVOID pParam)，这个函数调用uploadBandwidthThrottler-&gt;RunInternal();，RunInternal()函数主要用来发送来自socket的数据包，函数体内调用两个函数：</p>
            <p>SocketSentBytes socketSentBytes = socket-&gt;SendControlData(allowedDataRate &gt; 0?(UINT)(bytesToSpend - spentBytes):1, minFragSize);</p>
            <p>以及</p>
            <p>SocketSentBytes socketSentBytes = socket-&gt;SendFileAndControlData(neededBytes, minFragSize);</p>
            <p>其中的socket类型是ThrottledFileSocket*，在类ThrottledFileSocket中这两个函数被定义为虚函数，而且在这个类内部没有具体实现，它们的实现在类CClientUDPSocket中，类CClientUDPSocket继承自CAsyncSocket以及ThrottledControlSocket，如下代码：</p>
            <p>class CClientUDPSocket : public CAsyncSocket, public ThrottledControlSocket // ZZ:UploadBandWithThrottler (UDP)。</p>
            <p>socket-&gt;SendControlData(allowedDataRate &gt; 0?(UINT)(bytesToSpend - spentBytes):1, minFragSize);</p>
            <p>以及</p>
            <p>SocketSentBytes socketSentBytes = socket-&gt;SendFileAndControlData(neededBytes, minFragSize);的实现体在ClientUDPSocket.cpp中424行：</p>
            <p>SocketSentBytes CClientUDPSocket::SendControlData(uint32 maxNumberOfBytesToSend, uint32 /*minFragSize*/){ // ZZ:UploadBandWithThrottler (UDP)</p>
            <p><strong>在它们内部调用了函数SendTo</strong>，if (!SendTo(sendbuffer, cur_packet-&gt;packet-&gt;size+2, cur_packet-&gt;dwIP, cur_packet-&gt;nPort))（在ClientUDPSocket.cpp中440行）。这个函数是类CClientUDPSocket 的成员函数。int CClientUDPSocket::SendTo(char* lpBuf,int nBufLen,uint32 dwIP, uint16 nPort)，在这个函数体内调用类CAsyncSocket的成员函数uint32 result = CAsyncSocket::SendTo(lpBuf,nBufLen,nPort,ipstr(dwIP));，类CAsyncSocket是MFC 的类库中的一个类。</p>
            <p>至此，本地节点加入网络的请求就发送完毕。</p>
            <p>下面讲述本地节点在接收到来自其他节点的回应后在本地采取的一些措施从而把自己加入到网络内。</p>
            <p><strong>当网络事件发生时（即本地网卡接收到数据包），</strong><strong>&#8220;</strong><strong>socket</strong><strong>窗口</strong><strong>&#8221;</strong><strong>接收WM_SOCKET_NOTIFY消息，消息处理函数OnSocketNotify被调用，。</strong><strong>&#8220;</strong><strong>socket</strong><strong>窗口</strong><strong>&#8221;</strong><strong>的定义和消息处理是MFC实现的，其中OnSocketNotify函数定义如下：</strong></p>
            <p>LRESULT CSocketWnd::OnSocketNotify(WPARAM wParam, LPARAM lParam)</p>
            <p>{</p>
            <p>CSocket::AuxQueueAdd(WM_SOCKET_NOTIFY, wParam, lParam);</p>
            <p>CSocket::ProcessAuxQueue();</p>
            <p>return 0L;</p>
            <p>}</p>
            <p><strong>在</strong><strong>CSocket::ProcessAuxQueue();</strong><strong>函数中</strong><strong>回调CAsyncSocket的成员函数DoCallBack，DoCallBack调用事件处理函数OnReceive。</strong></p>
            <p>int PASCAL CSocket::ProcessAuxQueue()</p>
            <p>{</p>
            <p>&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;//省略部分</p>
            <p>if (pMsg-&gt;message == WM_SOCKET_NOTIFY)</p>
            <p>{</p>
            <p>CAsyncSocket::DoCallBack(pMsg-&gt;wParam, pMsg-&gt;lParam);</p>
            <p>}</p>
            <p>&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;//省略部分</p>
            <p>return nCount;</p>
            <p>}</p>
            <p>void PASCAL CAsyncSocket::DoCallBack(WPARAM wParam, LPARAM lParam)</p>
            <p>{</p>
            <p>&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;//省略部分</p>
            <p>pSocket-&gt;OnReceive(nErrorCode);</p>
            <p><strong>/*pSocket</strong><strong>类型是：CClientUDPSocket，因为类 CClientUDPSocket继承了类 CAsyncSocket，而OnReceive在CAsyncSocket定义的虚函数，OnReceive在CClientUDPSocket中重新做了实现，因此调用的时候会转到CClientUDPSocket中OnReceive执行。*/</strong></p>
            <p>}</p>
            <p>void CClientUDPSocket::OnReceive(int nErrorCode)</p>
            <p>{</p>
            <p>&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;</p>
            <p>case OP_KADEMLIAHEADER:</p>
            <p>{</p>
            <p>// theStats.AddDownDataOverheadKad(length);</p>
            <p>if (length &gt;= 2)</p>
            <p>Kademlia::CKademlia::ProcessPacket(buffer, length, ntohl(sockAddr.sin_addr.S_un.S_addr), ntohs(sockAddr.sin_port));</p>
            <p>else</p>
            <p>throw CString(_T("Kad packet too short"));</p>
            <p>break;</p>
            <p>}</p>
            <p>&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;</p>
            <p>}</p>
            <p><strong>接着调用在kademlia.cpp中定义的函数ProcessPacket。</strong></p>
            <p>void CKademlia::ProcessPacket(const byte *pbyData, uint32 uLenData, uint32 uIP, uint16 uPort)</p>
            <p>{</p>
            <p>if( m_pInstance &amp;&amp; m_pInstance-&gt;m_pUDPListener )</p>
            <p>m_pInstance-&gt;m_pUDPListener-&gt;ProcessPacket( pbyData, uLenData, uIP, uPort);</p>
            <p>}</p>
            <p><strong>转入KademliaUDPListener类中ProcessPacket函数运行。</strong></p>
            <p>void CKademliaUDPListener::ProcessPacket(const byte* pbyData, uint32 uLenData, uint32 uIP, uint16 uUDPPort)</p>
            <p>{</p>
            <p>//&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;省略部分</p>
            <p>switch (byOpcode)</p>
            <p>{</p>
            <p>&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;//省略部分</p>
            <p>case KADEMLIA_RES:</p>
            <p>if (thePrefs.GetDebugClientKadUDPLevel() &gt; 0)</p>
            <p>DebugRecv("KADEMLIA_RES", uIP, uUDPPort);</p>
            <p>Process_KADEMLIA_RES(pbyPacketData, uLenPacket, uIP, uUDPPort);</p>
            <p>break;</p>
            <p>&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;//省略部分</p>
            <p>}</p>
            <p>}</p>
            <p><strong>转入函数Process_KADEMLIA_RES(pbyPacketData, uLenPacket, uIP, uUDPPort);执行：</strong></p>
            <p>void CKademliaUDPListener::Process_KADEMLIA_RES (const byte *pbyPacketData, uint32 uLenPacket, uint32 uIP, uint16 uUDPPort)</p>
            <p>{</p>
            <p>//&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;</p>
            <p>if(CKademlia::GetPrefs()-&gt;GetRecheckIP())</p>
            <p>{</p>
            <p>FirewalledCheck(uIP, uUDPPort);</p>
            <p>if (thePrefs.GetDebugClientKadUDPLevel() &gt; 0)</p>
            <p>DebugSend("KADEMLIA_HELLO_REQ", uIP, uUDPPort);</p>
            <p>SendMyDetails(KADEMLIA_HELLO_REQ, uIP, uUDPPort);</p>
            <p>}</p>
            <p>if(::IsGoodIPPort(ntohl(uIPResult),uUDPPortResult))</p>
            <p>{</p>
            <p>pRoutingZone-&gt;Add(uIDResult, uIPResult, uUDPPortResult, uTCPPortResult, 0);</p>
            <p>pResults-&gt;push_back(new CContact(uIDResult, uIPResult, uUDPPortResult, uTCPPortResult, uTarget, 0));</p>
            <p>}</p>
            <p>}</p>
            <p>}</p>
            <p>CSearchManager::ProcessResponse(uTarget, uIP, uUDPPort, pResults);</p>
            <p>}</p>
            <p>在这个函数体内部主要包括对4个函数的调用，分别是：</p>
            <p>SendMyDetails(KADEMLIA_HELLO_REQ, uIP, uUDPPort);</p>
            <p>pRoutingZone-&gt;Add(uIDResult, uIPResult, uUDPPortResult, uTCPPortResult, 0);</p>
            <p>pResults-&gt;push_back(new CContact(uIDResult, uIPResult, uUDPPortResult, uTCPPortResult, uTarget, 0));</p>
            <p>CSearchManager::ProcessResponse(uTarget, uIP, uUDPPort, pResults);</p>
            <p>其中第一个函数是在判断自己在防火墙或者NAT之后重新发送本地节点信息的函数，包括重新得到的IP地址以及端口。</p>
            <p>第二和第三个函数用来添加此节点作为联系人之一。</p>
            <p><strong>第三个函数是将此消息转入到CSearchManager中相应处理响应的函数进行处理。</strong></p>
            <p>void CSearchManager::ProcessResponse(const CUInt128 &amp;uTarget, uint32 uFromIP, uint16 uFromPort, ContactList *plistResults)</p>
            <p>{</p>
            <p>pSearch-&gt;ProcessResponse(uFromIP, uFromPort, plistResults);// pSearch是 CSearch类的指针</p>
            <p>}</p>
            <p><strong>进一步转入到pSearch-&gt;ProcessResponse(uFromIP, uFromPort, plistResults)中执行。</strong></p>
            <p>void CSearch::ProcessResponse(uint32 uFromIP, uint16 uFromPort, ContactList *plistResults)</p>
            <p>{</p>
            <p>// Not interested in responses for FIND_NODE.</p>
            <p>// Once we get a results we stop the search.</p>
            <p>// These contacts are added to contacts by UDPListener.</p>
            <p>if (m_uType == NODE)</p>
            <p>{</p>
            <p>// Note we got an answer</p>
            <p>m_uAnswers++;</p>
            <p>// We clear the possible list to force the search to stop.</p>
            <p>// We do this so the user has time to visually see the results.</p>
            <p>m_mapPossible.clear();</p>
            <p>delete plistResults;</p>
            <p>// Update search on the GUI.</p>
            <p>//IMPREVIEW theApp.emuledlg-&gt;kademliawnd-&gt;searchList-&gt;SearchRef(this);</p>
            <p>return;</p>
            <p>}</p>
            <p>}</p>
            <p>在这个函数内部我们将响应的节点数目增加一。</p>
            后面陆续接收到的消息处理流程与上述情形相似，只是对于不同的消息采取的响应以及动作并不相同。</div>
            </td>
        </tr>
    </tbody>
</table>
<img src ="http://www.cppblog.com/tommyyan/aggbug/82047.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 15:43 <a href="http://www.cppblog.com/tommyyan/articles/82047.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Kademlia Emule协议分析及和Bt协议的比较</title><link>http://www.cppblog.com/tommyyan/articles/82046.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 07:42:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82046.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82046.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82046.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82046.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82046.html</trackback:ping><description><![CDATA[所设计的点对点 (<a title=P2P href="http://wikipedia.cnblog.org/wiki/P2P"><font color=#9e7812><u>P2P</u></font></a>) <a class=new title=重疊網路傳輸協議 href="http://wikipedia.cnblog.org/w/index.php?title=%E9%87%8D%E7%96%8A%E7%B6%B2%E8%B7%AF%E5%82%B3%E8%BC%B8%E5%8D%94%E8%AD%B0&amp;action=edit"><font color=#9e7812><u>重叠网络传输协议</u></font></a>，以达成非集中式的点对点 (<a title=P2P href="http://wikipedia.cnblog.org/wiki/P2P"><font color=#9e7812><u>P2P</u></font></a>) <a title=电脑网络 href="http://wikipedia.cnblog.org/wiki/%C3%A7%E2%80%9D%C2%B5%C3%A8%E2%80%9E%E2%80%98%C3%A7%C2%BD%E2%80%98%C3%A7%C2%BB%C5%93"><font color=#9e7812><u>电脑网络</u></font></a>。它规制了网络的结构及规范了<a class=new title=節點 href="http://wikipedia.cnblog.org/w/index.php?title=%E7%AF%80%E9%BB%9E&amp;action=edit"><font color=#9e7812><u>节点</u></font></a>间的通讯和交换资讯的方式。Kademlia 节点间使用<a title=传输层 href="http://wikipedia.cnblog.org/wiki/%C3%A4%C2%BC%C2%A0%C3%A8%C2%BE%E2%80%9C%C3%A5%C2%B1%E2%80%9A"><font color=#9e7812><u>传输通讯协定</u></font></a> <a title=UDP href="http://wikipedia.cnblog.org/wiki/UDP"><font color=#9e7812><u>UDP</u></font></a> （请见<a title=OSI模型 href="http://wikipedia.cnblog.org/wiki/OSI%C3%A6%C2%A8%C2%A1%C3%A5%C5%BE%E2%80%B9"><font color=#9e7812><u>OSI模型</u></font></a>） 沟通。Kademlia 节点借以实作<a class=new title=分散式雜湊表 href="http://wikipedia.cnblog.org/w/index.php?title=%E5%88%86%E6%95%A3%E5%BC%8F%E9%9B%9C%E6%B9%8A%E8%A1%A8&amp;action=edit"><font color=#9e7812><u>分布式杂凑表</u></font></a> (DHT,distributed hash table) 以储存资料。透过既有的局域网/广域网( <a class=new title="Local area network 區域網" href="http://wikipedia.cnblog.org/w/index.php?title=Local_area_network_%E5%8D%80%E5%9F%9F%E7%B6%B2&amp;action=edit"><font color=#9e7812><u>LAN</u></font></a>/<a class=new title="Wide area network 廣域網" href="http://wikipedia.cnblog.org/w/index.php?title=Wide_area_network_%E5%BB%A3%E5%9F%9F%E7%B6%B2&amp;action=edit"><font color=#9e7812><u>WAN</u></font></a>) （如同<a title=網際網路 href="http://wikipedia.cnblog.org/wiki/%C3%A7%C2%B6%C2%B2%C3%A9%C5%A1%E2%80%BA%C3%A7%C2%B6%C2%B2%C3%A8%C2%B7%C2%AF"><font color=#9e7812><u>因特网</u></font></a>），一个新的<a class=new title=虛擬網路 href="http://wikipedia.cnblog.org/w/index.php?title=%E8%99%9B%E6%93%AC%E7%B6%B2%E8%B7%AF&amp;action=edit"><font color=#9e7812><u>虚拟网络</u></font></a>或是<a class=new title=重疊網路 href="http://wikipedia.cnblog.org/w/index.php?title=%E9%87%8D%E7%96%8A%E7%B6%B2%E8%B7%AF&amp;action=edit"><font color=#9e7812><u>重叠网络</u></font></a>被建立起来。每个<a class=new title=網路節點 href="http://wikipedia.cnblog.org/w/index.php?title=%E7%B6%B2%E8%B7%AF%E7%AF%80%E9%BB%9E&amp;action=edit"><font color=#9e7812><u>网络节点</u></font></a>都是以一组数字（&#8220;节点 ID&#8221;）来识别。这组数字不但做为识别之用，Kademlia 算法还会用来做其他用途。
<p>一个想要加入网络的节点需要先通过<a class=new title=啟動 href="http://wikipedia.cnblog.org/w/index.php?title=%E5%95%9F%E5%8B%95&amp;action=edit"><font color=#9e7812><u>启动</u></font></a>。在这个阶段，这个节点需要知道另一个已经在 Kademlia 网络内的节点之 <a title=IP地址 href="http://wikipedia.cnblog.org/wiki/IP%C3%A5%C5%93%C2%B0%C3%A5%C2%9D%E2%82%AC"><font color=#9e7812><u>IP 位址</u></font></a> （透过另一个使用者或储存的清单取得）。如果启动中的节点还不是网络的一部分，它便会计算一个尚未指定给其他节点的<a class=new title=隨機 href="http://wikipedia.cnblog.org/w/index.php?title=%E9%9A%A8%E6%A9%9F&amp;action=edit"><font color=#9e7812><u>随机</u></font></a> ID 编号。这个 ID 会一直使用到离开网络为止。</p>
<p>Kademlia 算法是基于两节点间的&#8220;距离&#8221;来计算。这个距离是以两节点的 ID 进行<a class=new title=互斥 href="http://wikipedia.cnblog.org/w/index.php?title=%E4%BA%92%E6%96%A5&amp;action=edit"><font color=#9e7812><u>互斥</u></font></a>运算，并将结果四舍五入至<a title=整數 href="http://wikipedia.cnblog.org/wiki/%C3%A6%E2%80%A2%C2%B4%C3%A6%E2%80%A2%C2%B8"><font color=#9e7812><u>整数</u></font></a>。</p>
<p>这个&#8220;距离&#8221;跟实际的地理环境无关，而是标明 ID 范围内的距离。因此一个德国的节点和一个澳洲的节点就有可能被称为&#8220;邻居&#8221;或&#8220;芳邻&#8221;。</p>
<p>Kademlia 内的资讯都储存在称为&#8220;数值&#8221;的东西内，每个数值都连接著一个&#8220;金钥&#8221;。</p>
<p>当搜寻某个金钥时，算法会透果几个步骤探整个网络一圈，每个步骤都会更接近要搜寻的金钥，直到被连线的节点传回数值，或找不到更近的节点。网络的大小仅会稍微影响到进行搜寻时接触到的节点数目：假如目前网络的使用者突然增为两倍，那使用者节点大概只需要在搜寻时多查询一个节点，而不是两倍的节点量。</p>
<p>非集中式的结构提供了更大的优势，并很明显地增加了对<a class=new title=拒絕服務阻斷攻擊 href="http://wikipedia.cnblog.org/w/index.php?title=%E6%8B%92%E7%B5%95%E6%9C%8D%E5%8B%99%E9%98%BB%E6%96%B7%E6%94%BB%E6%93%8A&amp;action=edit"><font color=#9e7812><u>拒绝服务阻断攻击</u></font></a>的抵抗。即使一整系列的节点被壅塞，也不会对网络可用度造成太多影响，最后网络会透过绕过这些&#8220;洞&#8221;而自我修复。</p>
<p><a name=.E5.9C.A8.E6.AA.94.E6.A1.88.E5.88.86.E4.BA.AB.E7.B6.B2.E8.B7.AF.E4.B8.AD.E7.9A.84.E6.87.89.E7.94.A8></a></p>
<p>Kademlia 被用来进行<a title=檔案分享 href="http://wikipedia.cnblog.org/wiki/%C3%A6%C2%AA%E2%80%9D%C3%A6%C2%A1%CB%86%C3%A5%CB%86%E2%80%A0%C3%A4%C2%BA%C2%AB"><font color=#9e7812><u>档案分享</u></font></a>。透过进行 Kademlia 关键字搜寻，任何人可以在档案分享网络中寻找资料以下载东西。由于没有任何中央服务器储存盘案列表的索引，因此这项工作是平均的由所有的客户端担当：拥有要分享的档案枝节点，会先处理档案的内容，并从内容计算出一组数字（<a class=new title=雜湊 href="http://wikipedia.cnblog.org/w/index.php?title=%E9%9B%9C%E6%B9%8A&amp;action=edit"><font color=#9e7812><u>杂凑</u></font></a>），这组数字将会在档案分享网络中辨识这个档案。杂凑与节点 ID 的长度必须相同。接著会搜寻几个 ID 与杂凑相近、且节点内有储存著自己 IP 位址的节点。搜寻的客户端会使用 Kademlia 来搜寻网络上节点ID离自己最近距离的节点来取得档案杂凑，然后会取得在该节点上的联络清单。当节点联入和联出时，这份存储在网络上的联络清单也将保持不变。因为内嵌的冗余存储算法，联系清单将复制在多个点上。</p>
<p>档案杂凑通常都是由其它地方的特制因特网键结来取得，或者被包含在来自其它来源中的索引档中。</p>
<p>对档案名称的搜索是基于关键字来实现的。档案名称被分成几个组成档案名称的单字。 每个关键字都会被<a class=new title=雜湊 href="http://wikipedia.cnblog.org/w/index.php?title=%E9%9B%9C%E6%B9%8A&amp;action=edit"><font color=#9e7812><u>杂凑</u></font></a>，并和相对的档案名称与档案杂凑利用和档案杂凑一样的方式储存到网络上。一个搜索者会选择其中的一个关键字，联系上和关键字杂凑最相近的节点ID，然后取得含有关键字的档案名称列。既然在档案名称列中的每个档案名称都附有自己的凑杂，那麽被选的档案就可以由一般的方式取得。</p>
<p><font color=#333333>Kad是Kademlia的简称，eMule的官方网站在2004年2月27日正式发布的 eMule v0.42b中，Kad开始正式内嵌成为eMule的一个功<br>能模块，可以说从这个版本开始eMule便开始支持Kad网络了。<br><br></font><font color=#0000ff><font color=#333333>Kad的出现，结束了之前edonkey时代，在ed圈里只存在着ED2K一种网络的模式，它通过新的协议开创并形成了自己的kad网络，使之</font><br><font color=#333333>和ED2K网络并驾齐驱，而且它还完全支持两种网络，可以在两种网络之间通用。Kad同样也属于开源的自由软件。它的程序和源代码<br>可以在官方网站</font><a href="http://www.emule-project.net/"><font color=#0000ff><u>http://www.emule-project.net</u></font></a><font color=#333333>上下载。 <br><br>Kad网络拓扑的最大特点在于它完全不需要服务器，我们都知道传统的ed2k网络需要服务器支持作为中转和存储hash列表信息，kad<br>可以不通过服务器同样完成ed2k网络的一切功能，你唯一要做的就是连线上网，然后打开kad。Kad需要UDP端口的支持，之后Emule<br>会自动按照客户端的要求，来判断它能否自由连线，然后同样也会分配给你一个id，这个过程和我们ed2k的高id和低id检查很像，不<br>过这个id所代表的意义不同于ed2k网络，它代表一个是否&#8220;freely&#8221;的状态。<br><br>Kad和ed2k网络有着完全不同的观念但是相同的目的: 都是搜索和寻找文件的源。 Kad网络的主要的目标是做到不需要服务器和改善<br>可量测性。相对于传统的ed2k服务器只能处理一定数量的使用者(我们在服务器列表也都看到了,每个服务器都有最大人数限制)，而<br>且如果服务器比较大连接人数过多,还会严重的的拖垮网络。而Kad能够自我组织,并且自我调节最佳的使用者数量以及他们的连接<br>效果。因此, 它更能使网络的损失达到最小。由于具备了以上所叙述的功能，Kad也被称之为Serverless network（无服务器<br>网络）。虽然目前一直处于开发阶段(alpha stage) 。但毫无疑问，它无可比拟的优势,将会使它成为p2p的明天。<br><br>可能很多朋友会关注， kad网络没有高低id的计算原则，是否对于低id来言就畅通无阻了呢？<br><br></font><font color=#333333>我们大家知道在ed2k网络里面，我们的id是通过ip进行如下的算法计算得出的<br>设我们的IP = A.B.C.D<br>那么我们的ID number= A + 256*B + 256*256*C + 256*256*256*D<br>low ID的产生是由于我们的ID计算结果小于16777216.<br>即 ID number= A + 256*B + 256*256*C + 256*256*256*D &lt; 16777216 <br><br>Kad的 id计算原则并不是象上面那样，他更关注我们是否open和freely。</font><font color=#333333><br>但是kad里面是如何计算我们的id呢？<br>事实上它的计算方法是这样<br>ID number=256*256*256*A+256*256*B+256*C+D <br>所以kad其实也有高低id的分别。所以内网用户在使用的时候依旧无法达到内网用户完全穿透网络的效果，而且目前来看，还存在着kad<br>模块引入,导致占用系统资源会变大以及会突然产生Memory Leak的问题，对于内存的控制，目前emule做的效果还是不好。<br><br>其实kad本身有一个nodes.dat文件，也叫做节点文件，这里面存放了我们在Kad网络中的邻居节点，我们都是通过这些节点来进入Kad<br>网络的。其实kad的网络倒更像是overnet和Kazaa网络，有兴趣的朋友大家可以对比看看。Kad网络提供了帮助寻找节点以及记录节点的机制。 <br><br>下面我们来说说这个机制的原理：<br>Kad拥有一个160bit的ID，每一个节点送出的讯息都必须包含此ID。每一个节点都必须记录一个资料来保存已经存在的节点，资料的格式是 <br>(IP address, UDP port, Node ID)，节点所必须负责的范围是2的i次方及2的i+1次方，i的范围是0 &lt; i &lt;160，这个结构叫做<br>k-bucket，该结构会形成一个tree的形状，每一次接收到新的信息时，各个节点都必须更新k-bucket內的资料，透过k-bucket结构我<br>们可以保证所有的节点状态都是新的，而且一定会知道这个节点在哪里。<br><br>Kademlia网络提供四种Potocol(RPC) <br>(1)PING 测试是否节点存在<br>(2)STORE存储通知的资料<br>(3)FIND_NODE 通知其他节点帮助寻找node<br>(4)FIND_VALUE 通知其他节点帮助寻找Value</font><br></font>而当每一个指令被接受到后，每一个节点都会到k-bucket上搜寻，通过这样的结构，kad提供一个方便快速且可以被保证在logN次数下找到所需的节点。<br><br>通俗的来讲就是在kad网络中，我们每个emule用户端只负责处理一小部分搜索和查找源的工作。分配这些工作的时候，通过我们每个用户端的唯一<br>的ID和搜索文件的hash值之间的匹配来决定。比如像我猜我猜我猜猜.rm这个文件由用户小王来负责（通过该文件的hash值来决定），那么任何其他用<br>户在下载这个文件的時候都会告诉其他用户,小王有这个文件，其他用户去下载这个文件的時候也会询问小王，小王也会告诉他们谁正在共享这个文件，这<br>样kad找源的工作就完成了。搜索时候的方法也差不多，只不过是每个人负责一个关键字。<br><br>整个过程有点像在照线索循序问路而找到正确方向，而不是路上随便到处抓人在问路。而每个地方里的网络相关信息，则会随着电脑及文件的加入而持续<br>更新。好处在于让你可以搜索整个网络，而不只是在某一地区。目前来讲，这个机制和算法是绝对领先而且非常优秀的。<br><br>如何找到用户小王则是通过将用户id异或的方式，两个id的二进位异或值决定他们之间的逻辑距离，如1100距离1101要比距离1001近。那么当一个用户<br>加入kad后，首先通过一个已知的用户找到一批用户的id和ip地址和端口。当该用户要寻找一个特定用户A的时候，该用户先询问几个已知的逻辑距离较A较<br>近的用户，如B用户,C用户,D用户，B，C，D会告诉该用户他们知道的更加近的用户的id和ip地址和端口，同理类推，这个用户最终就能找到A。所以寻<br>找的次数会在logN数量级，这里N代表询问的人数。<br><br>其实也就是一种分散式杂凑的方法，基本上是对网络上某一特定时刻的文件进行快照(snapshot)，然后将这些信息分散到整个网络里。 为了找到特定<br>的文件，搜索的要求先到达网络上的任何一台电脑上，然后这台电脑就会再将它转到另一台有更多文件信息的电脑。第三台电脑可能就拥有文件本<br>身──或者也可能再继续转到其他有正确信息的电脑。采用这种方法，通常只需要跳转两到三次，便可以轻松查找到所需文件。<br><br>以上几个部分，便是对于kad作用原理以及算法的分析，可能好多人看了之后头大，那么我们普通用户到底该注意些什么呢？<br><br>很简单，你要作的就是再使用emule的时候打开kad，你会发现有两个明显的特点<br>(1)你的下载速度会加快<br>(2)你的下载文件的源会增加<br>以上两条对于lowid和经常下载源在国外的文件用户，效果就更为突出，特别对于在ed2k网络中只有几个源或者没有源的文件，在kad网络中，一般都<br>能找到源，所以说你使用了emule下载文件，基本上不会出现没有源的请况，无论多长时间，差别只是源的多少个数问题，由于kad网络都是自动配置的<br>，所以你丝毫不用分心，那么索性我们就打开它，何乐而不为呢？<br><br>另外对于我们搜索的时候，如果采用kad网络搜索,多数情况下找到的文件源会远远多于ed2k的全局搜索，对于大家都是一个明智的选择。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>1. ID and Key</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>Node ID：160 bit （每一个Node拥有一个ID，随机产生）<br>Key：160 bit （Key也许是某个很大的数据的SHA-1 hash值）</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>(Key,Value)这一对数据保存在ID最&#8220;接近&#8221;Key的Node上。<br>&#8220;接近&#8221;的意思是Key和ID之间的&#8220;距离&#8221;很短。<br>Kad网络中&#8220;距离&#8221;的定义是：d(x,y) = x XOR y，也就是x和y的异或值。</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>* 选择XOR作为衡量距离的尺度，是因为XOR有xxx,yyy,zzz,...等特性，具体请看Kademlia的paper [1] section 2.1。</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>2. k-bucket</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>每一个Node保持160个 list。对于第 i (0 &lt;= i &lt; 160) 个 list，此 list 中保存着和该 Node 距离为 2*i ~ 2*(i+1) <br>【2的 i 次方到 2的 i+1 次方】的<br>一些 Node 的信息（Node ID，IP地址，UDP端口）。这些 list 叫做 k-bucket 。k是每一个 listk 中保存的 Node 的最大<br>个数（比如，20）。</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif><font face="courier new,courier,monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br>list 0: |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; 距离为[1,2)的node <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br></font>（ID前159 bit相同，第160 bit一定不同的node: 1个）</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif><font face="courier new,courier,monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br>list 1: |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; 距离为[2,4)的node <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br></font>（ID前158 bit相同，第159 bit一定不同的node: 2个）</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif><font face="courier new,courier,monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br>list 2: |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; 距离为[4,8)的node <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br></font>（ID前157 bit相同，第158 bit一定不同的node: 4个）</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif><font face="courier new,courier,monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br>list 3: |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; 距离为[8,16)的node <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br></font>（ID前156 bit相同，第157 bit一定不同的node: 8个）</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif><font face="courier new,courier,monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br>list 4: |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; 距离为[16,32)的node <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br></font>（ID前155 bit相同，第156 bit一定不同的node: 16个）</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>。<br>。<br>。<br><font face="courier new,courier,monospace">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br>list 159: |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; 距离为[2*159,2*160)的node <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +------------------------+<br></font>（ID第1 bit不同的node: 2*159个。此list中最多保存最近访问的k个）</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>每一个list （k-bucket）中的node按照访问的时间顺序排列，最先访问的在list头部，最近访问的在list的尾部：</font></p>
<p>&nbsp;</p>
<p><font face="courier new,courier,monospace">Head&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Tail<br>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>V&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; V<br>+--+&nbsp;&nbsp; +--+&nbsp;&nbsp; +--+&nbsp;&nbsp; +--+&nbsp;&nbsp; +--+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +--+<br>| |--&gt;| |--&gt;| |--&gt;| |--&gt;| |--&gt; ... --&gt; | |<br>+--+&nbsp;&nbsp; +--+&nbsp;&nbsp; +--+&nbsp;&nbsp; +--+&nbsp;&nbsp; +--+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +--+<br>Node0 Node1 ...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Node[N]<br>A&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A<br>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>least-recently&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; most-recently</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>3.k-bucket的刷新</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>当一个Node收到另一个Node发来的消息的时候，它根据以下规则（least-recently seen eviction policy）来刷新相应的k-bucket:<br>1) 如果发送者已经在某一个k-bucket中，将它在该 k-bucket 中移动到尾部<br>2) 如果发送者不在其对应的k-bucket中，并且该 k-bucket 还不满 k 个Node，将这个发送者加入到 k-bucket 的尾部<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果该 k-bucket 已经满了（有k个Node），那么接受消息的 Node 向该 k-bucket 中最老的（也就是头部的）<br>Node发送一个 ping 消息<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果这个最老的 Node 没有响应，则将它从 k-bucket 中删除，将新的发送消息的 Node 加入到 k-bucket 的尾部<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果这个最老的 Node 响应了，则将它移动到 k-bucket 的尾部，并抛弃新的 Node 的信息</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>* 为什么要采取这样的规则，请看Kademlia的paper [1] section 2.2</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>4. 基本协议(protocol)</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>四种基本的RPC：PING, STORE, FIND_NODE, FIND_VALUE</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>1) PING<br>PING 探测一个Node是否在线</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>2) STORE<br>STORE 以(Key,Value)为参数。指示另一个Node（消息接收者）保存一个(Key, Value)对，以供以后取用。</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>3) FIND_NODE<br>FIND_NODE 以一个160 bit的ID作为参数。接收者返回它所知道的最接近该ID的 k 个 Node 的 Contact 信息<br>（ID,UPD Port,IP）。这些 Node 不一定要从同一个 k-bucket 中产生。除非它所有的 k-bucket 中所有的 <br>Node 加在一起都不到 k 个，否则它必需凑足 k 个。</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>4) FIND_VALUE<br>FIND_VALUE 以一个160 bit的ID/Key作为参数。如果接收者先前已经收到一个 STORE RPC，而且 STORE 的<br>参数之一 Key 就是当前 FIND_VALUE 的参数，那么，接收者将 STORE 的另一个参数 Value 返回给发送者。<br>否则，FIND_VALUE 和 FIND_NODE 一样，返回它所知道的最接近该ID的 k 个 Node 的 Contact 信息<br>（ID,UPD Port,IP）。<br>FIND_VALUE 的意思就是：如果接收者有发送着所要的 Value，就将该 Value 返回。否则，接收者就帮忙找<br>更接近该 ID/Key 的 Node。</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>所有的 RPC 消息都带有一个 160 bit的随机 RPC ID，由发送者产生，接收者在响应每一个消息的时候，返回<br>的消息里面都必需拷贝此 RPC ID。目的是防止地址伪造。PING 消息可以在 RPC 响应里使用捎带确认机制来获<br>得发送者网络地址（原文：pings can also be piggy-backed on RPC replies for the RPC recipient to <br>obtain additional assurance of the sender's network address.）。</font></p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>* piggyback<br>捎带确认(法)<br>A technique used to return acknowledgement information across a full-duplex (two-way <br>simultaneous) data link without the use of special (acknowledgement) message. The <br>acknowledgement information relating to the flow of message in one direction is embedded<br>(piggybacked) into normal data-carrying message flowing in the reverse direction.<br>经全双工(双向同时)数据链路,不用专门(确认)报文返回确认信息所用的技术。与一个方向的报文流有关的确<br>认信息钳在反方向正常携带数据的报文流中。</font></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><font face=verdana,arial,helvetica,sans-serif>[1] Kadmelia的paper：</font><a href="http://www.cs.rice.edu/Conferences/IPTPS02/109.pdf"><font face=verdana,arial,helvetica,sans-serif color=#9e7812><u>http://www.cs.rice.edu/Conferences/IPTPS02/109.pdf</u></font></a></p>
<p><u></u></p>
<p><u></u></p>
<p><u></u></p>
<p>5. 节点查找(node lookup)<br>node lookup:找到距离给定的 ID 最近的 k 个 node<br>定义 a:系统范围内的并发参数，比如3。<br>步骤：<br>1) 从最近的 k-bucket 里面取出 a 个最近的 node，然后向这 a 个 node 发送并行的、异步的 FIND_NODE RPC。<br>2) 再次发送 FIND_NODE RPC 给从前一步返回的 node（这一步可以不必等到前一步中所有 a 个 PRC 都返回之后才开始）：<br>&nbsp;&nbsp;&nbsp; 从发送者已知的 k 个最接近目标的 node 当中，取出 a 个还没有查询的 node，向这 a 个 node 发送 FIND_NODE RPC。<br>&nbsp;&nbsp;&nbsp; 没有迅速响应的 node 将被排除出考虑之列，直到其响应。<br>&nbsp;&nbsp;&nbsp; 如果一轮 FIND_NODE RPC 没有返回一个比已知的所有 node 更接近目标的 node，发送者将再向 k 个最接近目<br>标的、还没有查询的 node 发送 FIND_NODE RPC。<br>3) 查找结束的条件：发送者已经向 k 个最近的 node 发送了查询，并且也得到了响应。</p>
<p>&nbsp;</p>
<p><br>6. 存储&lt;Key, Value&gt;(store a &lt;Key,Value&gt; pair)<br>步骤：<br>1) 使用node lookup算法，找到距离 Key 最近的 k 个 node<br>2) 向这 k 个 node 发送 STORE RPC<br>3) 每一个 node 必要的时候重新发布(re-publish)所有的&lt;Key,Value&gt;<br>&nbsp;&nbsp;&nbsp; （对于当前的 Kademlia 应用(文件共享)，每一个&lt;Key,Value&gt;的原始发布者被要求每隔24小时重新发布一次，<br>否则&lt;Key,Value&gt;将在发布之后的24小时之后过期。对于其他一些应用，比如digital certificates, <br>cryptographic hash to value mapping，过期时间可以更长一些）</p>
<p>&nbsp;</p>
<p>7. 搜索&lt;Key,Value&gt; （find a &lt;Key,Value&gt; pair)<br>步骤：<br>1) 使用 FIND_VALUE 代替 FIND_NODE 进行"node lookup"过程。一旦任何其他 node 返回了所要的 Value，搜索的过程就结束。<br>2) cache: 如果搜索成功，搜索的发起者将这个&lt;Key,Value&gt;对存储到已知的、最近的、但是在第一步中没有返回该 Value 的 node 上。<br>&nbsp;&nbsp;&nbsp; 显然，有了cache之后，以后对于该 &lt;Key,Value&gt; 的搜索很可能首先找到 cache，而不是直到找到最接近 Key 的那个 node。<br>&nbsp;&nbsp;&nbsp; 如果一个 &lt;Key,Value&gt; 被频繁的搜索，那么它很可能被缓存到很多 ID 不太接近 Key 的 node 中。<br>&nbsp;&nbsp;&nbsp; 为了避免过度缓存(over-caching)，每一个 &lt;Key,Value&gt; 都有一个过期时间，这个过期时间和 当前node 和&#8220;最接近Key<br>之node&#8221;之间的 node 的个数（这个数目可以从当前node的bucket接口推断出）的指数倒数成正比。（To avoid "over-caching", we make <br>the expiration time of a &lt;key,value&gt; pair in any node's database exponentially inversely proportional <br>to the number of nodes between the current node and the node whose ID is closest to the key ID. This number can be inferred from the bucket structure of the current node）<br><br>8. 刷新bucket (refresh bucket)<br>所有的 bucket 通过node之间的请求来刷新。<br>如果某一个bucket在过去一个小时之内没有任何的node lookup操作，那么这个node就随机搜索一个在这个bucket覆盖范围内的ID。<br><br>9. node加入网络<br>步骤：<br>1) 一个新加入的node（假设为u）必需首先获得一个已经在网络中的node（比如为w）的contact信息。<br>2) u 首先将 w 加入到其对应的 k-bucket 中<br>3) u 执行一个对自己ID的 node lookup 操作<br>4) u 刷新所有的 k-bucket</p>
<p><strong><font size=6>与BT的比较</font><br></strong>简单的说,emule与bt 协议两者各有千秋</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.传统连接方式</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bt使用统一的torrent文件先作一个原下载文件的信息记录,然后客户下载后通过torrent的信息与服务器连接并下载,</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; emule仅有一个文件ID,客户自行与服务器连接再下载</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.底层传输协议比较</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bt只使用TCP协议进行下载,协议简单有效,但是功能比较单一,有的功能不完整</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; emule使用TCP和UDP两种协议进行通信,更加有效的利用了网络资源,功能完整强大,但这也同时使主机的负荷加大,程序编写难度提高</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3.文件组织方式和数据验证方式</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bt会在开始前对文件进行一次完全的HASH,就是将文件首尾相联然后按固定块取SHA值,这些值最终被放入torrent文件编码中,客户从网上一次下载完全,高效简单,一般情况下bt软件会在每小块下载完成后就对其进行HASH测试,检查其正确性</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; emule 在连接字符串中只存放了整体文件的HASH值,通过将这个HASH到服务器上取出文件的相关信息,在实际的操作中,会将文件分解成9.28M大小的块并进行HASH用于对块的完整性测试.新版的emul会用一种叫AICH的技术,就是说将文件分成120K大小的块然后HASH再将HASH值进行二进迭代式（具体的看emule协议)的HASH最终组成一个HASH二叉树这种方式的好处是可以在连接时只加入根结节的HASH值而不用加入叶子节点,减小了连接时的字符串大小,如果在最终文件下载完毕后,测试出的根节点HASH与得到的根节点的HASH值不同,则可以通过协议与网络上的其它主机的树进行比较快速得出错误的块.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4.流量控制方式</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bt采用有名的针锋相对的方式处理上传下载平衡的控制,这种方式会记录短期内与客户连接的所有节点的上传下载流量,通过在固定时间内对下载流量的比较,得出允许上传的客户;为防止新客户长时间得不到其它客户的认同,bt会在一断时间停止他的上传作为对他的警告；对于已经下载完毕的客户,bt会简单的使上传流量最的客户得到更多的时间完成上传;为了防止在文件的最后阶段下载速度下降,bt会在最后时向所有连接的客户发送请求迅速完成下载;在整个下载过程中, bt会对文件块在整个网络中的存在复本的多少进行跟踪,那些存在的比较少的复本总是会得到优先的下载权,以使整个网络的文件冗余度提高.简单的说bt使用的是针对文件的流量控制方式.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Emule采用的是客户积分的方式,就是对所有用户的上传和下载量进行一个运算,从而得出一个客户的积分值,那些积分比较高的用户总是可以得到优先的下载权,甚至可以不进行排队直接下载,这样就在一个比较长的时间内对用户对其它用户的整体贡献有了一个评估.简单的说emule采用的是针对用户的流量控制方式.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 5.kad与dht</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kad和dht两者都是基于kademlia技术的分布式HASH表查找技术,可惜的是由于协议上的区别,两者不能互通.简单介绍下kad,它首先给每个客户分配一个唯一的ID值,然后对不同的ID值进行异或来得到两个客户之间的"距离",kad会维护一个桶,"距离"越近的用户桶里的数量会越多,kad 定其的对桶里的用户进行清理,以保持其有效性.对于文件和用户emule会有两个这个东西,所以我们可以通过kad来查找文件和文件相关的用户信息;同样为了考虑冗余的问题,kad会将其自身的信息复制一份给"距离"它最近的一定数量的用户,这样就算在它下线后,这些信息也不会丢失.bt的 dht不太了解,呵呵,不过估计差不多.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6.功能比较</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; emule具有查找功能,而这在bt 只能通过网站来实现</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 新版的emule在对防火墙的支持上采用的代理的方式,就是如果一个用户处在内网,那么它会找到一个在公网的用户作为它的朋友.bt在这方面没有明显的变化,但是不同的bt客户端实现方式有些不同的支持.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 7.总体性能比较</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 个人感觉bt的方式更注重于简单高效的快速传输,而emule更注重于整个网络状态的变化及用户体验.单从下载效率上说bt占优,而从网络状态及完整强大的协议支持上说,emule作了更多的事情.从性能上考虑,在相同网络状态下,bt下载单文件的能力比较强, emule比较适合于长时间的多文件下载,这源于两者对网络均衡及p2p模式的不同理解.</p>
<img src ="http://www.cppblog.com/tommyyan/aggbug/82046.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 15:42 <a href="http://www.cppblog.com/tommyyan/articles/82046.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>eMule源代码解析</title><link>http://www.cppblog.com/tommyyan/articles/82045.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 07:41:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82045.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82045.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82045.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82045.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82045.html</trackback:ping><description><![CDATA[<p><font size=3>eMule的官方首页上写着：2002年05月13日 一个叫做 Merkur 的人，他不满意原始eDonkey2000客户端并且坚信他能够做的更好，所以他开始制作。他聚集了其它开发人员在他的周围，并且eMule工程就此诞生。<br><br>eMule是一个典型的MFC程序，它的图形界面等，已经和MFC紧紧融合到了一起。因此通常情况下它只能在windows平台下运行。有一些其它的工程，如aMule等，把它进行了移植，因此跨平台的功能要强些。<br><br>其实还有另外一个叫做xMule的工程，不过现在已经人气快不行了。在aMule的主页上可以看到eMule移植到linux平台下的一些历史，最早是有个叫做lMule的工程，他使用wxwidgets来进行eMule的跨平台的移植，这个工程2003年就不再更新了，后来转变成为xMule工程，它一度是linux平台下eMule的事实上的替代品。但是他们的程序员之间由于理念不同，发生了内讧，导致aMule分裂出来，他们后来矛盾严重的时候曾经一度从理念问题上升到互相对对方进行人身攻击，并且曾经对对方的网站发动过DDos。后来aMule和xMule就是两个完全不同的工程，xMule现在只有HopeSeekr一个人在维护，基本上也没有什么更新了。这一点不仅让人感慨。今年寒假的时候我曾经和HopeSeekr进行过一些交流，感觉他非常自信，经常拿着aMule的一部分代码来给我看，说你看看他们的代码这么这么写，这简直就是一陀xx嘛，这种代码在某些情况下肯定会Crash掉嘛，相反，你看看我们xMule的代码，这里是这样这样，肯定就不会有这种问题了。<br><br>eMule从0.42版开始支持Kad技术，这是一个非常重要的里程碑。Kad是一种DHT的协议，它可以使节点之间互相保留一些其它节点的联系信息，并且利用这样一个&#8220;关系网&#8221;寻找到整个网络中的任何一个节点以及上面的资源，整个过程不需要任何中心服务器。因此向当年搞napster那样直接端掉中心服务器就搞跨napster网络一样来对付eMule的Kad网就毫无作用了。0.42版是2004年2月27日放出的，比eDonkey2000的 OverNet晚了将近一年，但是它的Kad网络的规模却在迅速扩大。Overnet和eMule中的Kad使用的都是Kademlia结构，但是具体的消息报文的格式有区别，因此两个DHT网络并不能互相兼容。OverNet直到现在，用户也仍然维持在十万左右，这是比较近的一篇文章写的。但是 eMule的Kad网的规模有多大，目前却没有一个很准确的说法。由此也可以看出开源软件的力量。目前aMule的Kad网和eMule的Kad网是兼容的，xMule中还没有Kad的支持。Kad协议的原始paper可以在我们实验室的机器上下到：<br><br></font><a href="http://bigpc.net.pku.edu.cn:8080/paper/new/by%20conference/IPTPS/IPTPS02/Kademlia%20A%20Peer-to-Peer%20Information%20System%20Based%20on%20the%20XOR%20Metric%28IPTPS02%29.pdf"><u><font color=#0000ff size=3>http://bigpc.net.pku.edu.cn:8080/paper/new/by%20conference/IPTPS/IPTPS02/Kademlia%20A%20Peer-to-Peer%20Information%20System%20Based%20on%20the%20XOR%20Metric%28IPTPS02%29.pdf</font></u></a></p>
<p><font color=#0000ff><u></u><font size=3></font></font></p>
<p><font size=3>eMule的代码结构非常合理。虽然代码量比较大，但是各个功能模块之间的划分都很合理。从它的工程文件里面就可以看出这一点。eMule把表示功能的代码文件和表示界面的代码文件分开了，Source Files和Header Files是实现功能的代码的源文件和头文件，而Interface Source和Interface Header是实现图形界面的源文件和头文件。由于eMule的代码量太大，本系列将跳过图形界面的实现，着重分析eMule的功能实现部分的代码，而且功能实现方面也主要挑主要的部分分析。本节将从emule.cpp开始分析，引出eMule中使用到的几个主要的功能实现的类，并大体描述它们的作用。最后介绍一下如何在VS2003下编译eMule。emule中还有不少模块实现的功能挺有用，我说的是在其它的程序里很有用，可以考虑在其它程序中进行复用。<br><br>emule.cpp为类CemuleApp的实现。因此在运行时，首先会运行InitInstance进行一些初始化的工作。从这个函数里面我们也可以第一次看出那些即将在整个程序中发挥作用的类了。<br><br>最开始的时候是计算出程序常用的一些目录，如配置文件，日志文件等。接下来是ProcessCommandline，它的作用有两方面，第一是确认该 eMule的运行方式，即命令行后面有没有参数，第二是确认目前eMule是不是只有一个实例在运行。在一般的情况下，双击eMule可执行文件是不会带参数的。但是通过点击链接或者打开关联文件的方式打开eMule则相当于带参数运行eMule。通过在注册表里添加一些项目可以让一个程序和某种链接或者某个后缀的文件产生关联。具体办法可以参见OtherFunctions.cpp中的Ask4RegFix，BackupReg，RevertReg三个函数的功能。ProcessCommandline中通过创建带有名称的互斥信号量来确认是否有其它的eMule实例在运行。对于一个确定的名称，CreateMutex只能创建一个互斥信号量。因此通过该信号量是否创建成功就可以知道是否有其它eMule实例运行。如果有的话，而且又是带参数的那种模式，那么直接把这个参数使用Windows的消息机制发给那个窗口即可，接下来的代码无非就是如何找到另外一个叫"eMule"的家伙以及给它发个什么消息。pstrPendingLink是一个全局变量，表示将要被处理的命令行参数。它将会在初始化完成后一段时间后被处理。<br><br>下面两个比较重要的类是CPreferences和CStatistics。前者掌握着程序的大部分配置数据，后者则进行各种统计。它们的特点都是有很多的成员变量，而且还是静态的，这种方式可以保证它们的唯一性，而且把这些变量统一到一个类管理。但是实际上并不需要了解每个变量的含义。thePrefs 和theStats是这两个类的唯一的实例。<br><br>在处理完其它一些事情，包括创建图形界面对象CemuleDlg后，接下来可以看到一排一排的创建新对象的语句。这些类将会实现eMule程序运行的主要功能，后面的系列将详细分析。<br><br>最后描述一下如何在VS2003里编译eMule，由于eMule中用到了一些其它的库，因此官方在提供eMule的源代码下载的同时如果也提供这些库的下载会使源码包变得很大。因此eMule选择了让开发者去那些库的官方网站下载它们的方式。一般来说，编译这种工程文件，很重要的地方是要保持各个库和主程序编译参数的一致性。这些编译参数中，最主要的参数有三个，字符集(多字节/Unicode)，调试/发行，单线程/多线程，这样排列组合一下就有八个版本了。因此编译的时候如果不注意，就会出现和程序设计无关的错误。<br><br>eMule0.47a解压后自带id3lib库，还需要下载以下的库：<br><br>zlib:<br>http://www.gzip.org/zlib/<br><br>ResizableLib:<br>http://sourceforge.net/projects/resizablelib/<br><br>Crypto++:<br><br>http://www.eskimo.com/~weidai/cryptlib.html<br><br>pnglib:<br><br>http://www.libpng.org/pub/png/libpng.html<br><br>下载它们，解压它们，编译它们。编译的时候注意要和eMule的工程的参数一致：字符集为Unicode，支持多线程安全，调试版或者是发行版可以根据需要选择，但是也要和eMule的工程参数一致。这些库的解压包里通常都能找到VC的工程文件，但是版本低一些，直接转化就可以了。另外建议编译这些库的时候都选择生成静态库，不要生成动态的库，这样最后生成的可执行文件就可以自己运行了。编译完这些库，包括从其它地方下载的和eMule自带的id3lib 库和CxImage库后，就可以开始编译emule了。而编译emule也无非就是注意让它能够在编译的时候找到所有的头文件，以及在链接的时候能够找到所有的库。在链接的时候能够找到所有的库可以通过修改工程文件里面的属性 -&gt;配置属性 -&gt;链接器 -&gt;输入-&gt;附加依赖项来完成。但是能够找到所有的头文件反而需要一些技巧了。由于emule的代码中对于这些库的头文件的包含，在一定程度上限定了那些库的路径和emule的工程的路径的相对位置，因此需要把那些解压过的库的目录移到一些合适的地方，有时还需要给这些目录改个名称。<br></font></p>
<p><font size=3></font></p>
<p><font size=3>eMule中要读取的配置文件数量较多，每种配置文件都是自己定义的格式，为了方便读取和存储这些文件，eMule 中有一个很重要的基础设施类来复制这些文件操作，它能够很方便得处理一些常用数据类型的读写，并且带有一定的安全保护机制。这项基础设施在 SafeFile.cpp和SafeFile.h中实现。在kademlia\io目录下有这项功能的另外一项实现。它们实现的功能基本上相似，但是 kademlia\io目录下的版本实现的时候多了一个以Tag作为单位进行读写的功能。这些实现中和另外一项基础设施，那就是字符串转化密切相关。 StringConversion.cpp和StringConversion.h是eMule中专门复制各类字符串转化的基础设施，什么Unicode 啊，多字节流啊，或者是UTF-8之类的，在这里转化全部都不是问题。关于字符串转化，个人推荐尽量使用Unicode的宽字符，这样可以最大程度得避免乱码。<br><br>SafeFile.cpp或者kademlia\io目录下的实现都有这样的特点，那就是把数据操作的行为和数据操作的对象分割开来。它们都定义了一个抽象的数据操作的基类(在SafeFile.cpp中是CFileDataIO，在kademlia目录下是DataIO.cpp实现的 Kademlia::CDataIO)，这个类中只负责实现在逻辑上操作一项数据的行为，例如，要读取出一个32位的整型，那么就是读出四个字节到一个整型数值的地址中，要读取或者写入其它类型的数据用的是类似的方法。但是这个类把物理上进行数据操作的方法全部都声明为纯虚函数，即读出多少个字节，写入多少个字节这样的。有了这样一个基类，就可以非常方便得在它上面进行重载，把这些纯虚函数定义为向某块内存中进行读写的操作，就能很方便得将较为复杂的数据序列化到一块连续的内存，而如果这些纯虚函数是向文件读写的操作，那么自然就可以很方便得用来读写各种格式比较奇怪的自己定义的配置文件了。<br><br>这些类要读取的数据对象通常有这些，各种整型，字符串，以及Tag类型。整型读写起来比较简单，从1个字节的，2个字节的到4个，8个或者16个字节类型的数据读写方法都比较类似。这里要稍微提一下16个字节的那种，16个字节是128位，是eMule中的Kad网的随机生成的ID的长度，也是eMule 中常用的MD4的hash算法生成的结果的长度。通常用来直接存取整个的这样的一个ID。在kademlia\utils目录下的UInt128.cpp 实现了一个表示128位的整数的类，功能十分完善，可以进行一些算术操作，并且可以进行比较，这样为它以后作为key出现在hash表中打下了基础。仔细学习UInt128.cpp中的代码实现可以学到很多在编写这种自定义的数据对象类型时应该注意的问题。<br><br>数据操作中另外一项很重要的操作是字符串，总的原则是先写一个长度，再写内容。但是到具体的操作的时候就需要注意这些细节了，如长度是写4个字节还是两个字节，字符串的内容要不要用UTF-8进行编码。这些操作就需要和StringConversion.cpp紧密合作了。其实后者的字符串转化函数很多也是调用ATL的相关函数，只是在外面再包上一层MFC的CString。<br><br>在kademlia\io\DataIO.cpp中实现的CDataIO中，还另外实现了按照Tag进行读写的功能。这在网络上交换共享文件的元信息非常重要，通常一个文件的元信息就可以分解成很多的Tag，如"文件名=xxx"，"文件长度=xxx"等等。也就是说，一个Tag就是表示某项属性等于某个值这样一个事实。在Opcodes.h这个文件中定义了很多的代码，其中就有很多常见的Tag的属性名称。CDataIO类中存储Tag的属性名都是先存一个字节的类型，再存名称，最后按照类型存值。<br><br>eMule中的这几项基础设施都是编写得比较好的，可以很方便得拿出来复用。像字符串编码的处理和具有一定数据结构的文件IO操作在很多地方都会很有用。 eMule中这些类的实现基本上复制到其它的工程文件中只要稍微修改一下很快就能使用。以后我们还将看到eMule中很多其它很有用的基础设施。&nbsp;&nbsp;</font></p>
<p><font size=3></font></p>
<p><font size=3></font></p>
<p><font size=3>emule作为一个文件共享方面的程序，首先要对自己共享的所有的文件的信息都十分清楚，类CKnownFileList的作用就是这样的，它在emule.cpp中随着cmuleapp类创建的时候被创建。<br><br>CKnownFileList类使用了MFC的CMap类来维护内部的hash表，这也可以看出emule和MFC的关系确实非常紧密。这里如果用STL 的map其实也是可以的。它内部维护了一个已知的文件的列表和取消了的文件列表。这些hash表的关键字都是文件的hash值。这样能够判断出文件名不同而内容相同的文件，而一般要让不同内容的文件有相同的hash值是非常困难的，这也是hash函数它设计的初衷。因此除非是碰上王小云教授这样的牛人，我们基本上可以认为，两个文件hash值相同就代表了它们内容相同。再来看CKnownFileList.cpp，这个文件其实并不长，因为管理一个列表确实不需要太多种类的操作，如果对于每个具体的文件有一个很强大的类来处理它的话。而这里确实有，它就是CKnownFile。有了这么一个类，我们就可以看到，CKnownFileList类所需要做的工作就是能够根据一些信息查找到对应的CKnownFile类，能够复制其它的列表中的信息，能够把所有的这些信息存成文件，然后下次emule运行的时候能够把这些信息快速恢复出来，最重要的是能够在完成以上工作的情况下不造成内存泄漏。<br><br>CKnownFile类就是一个专门关注某个特定文件的信息的类，它仍然有其基类CAbstractFile。但是它和CAbstractFile类的主要区别就是CAbstractFile类只有基本的信息存取的功能，而CKnownFile能够主动的生成这些信息，例如，给一个文件的路径给 CKnownFile，它能够主动地去获取和这个文件有关的一切信息，并且把它保存在自己的成员变量里(CreateFromFile)。 CKnownFile.cpp文件看上去比较长，是因为它做的工作比较多，现在版本的emule中，除了对某个文件进行全文hash以外，还采用了BT的方式，进行分块hash，这样在传输文件的时候，即使发生出错的情况，也可以不必重传整个文件，而只是重传有错误的那块，这种机制叫做高级智能损坏处理 (AICH,Advanced Intelligent Corruption Handling)，这个机制以后再继续分析。<br><br>CKnownFile把读到的文件信息都保存成一个一个的Tag。它在运行中会尽量得获取更多的文件信息，例如，对于媒体类型的文件，它能够调用 id3lib库来获取诸如作者，唱片发行年代，风格等tag信息。如果是视频媒体文件，它还会去抓图(功能实现：CFrameGrabThread)。<br><br>CKnownFile还能够随时掌握目前该文件的下载情况(内部有个CUpDownClient的列表)，当然，还会根据要求序列化和反序列化自己，LoadFromFile和WriteToFile都以CFileDataIO为参数，这样方便CKnownFileList保存和读取它的列表中的所有文件的信息。 <br></p>
<p><font size=3>1分块机制--正确传输资源的保证 为了加快内容分发的速度，分块处理是一种简单有效的方法。emule中对每个文件都进行了分块处理。另外分块还有一个好处就是如果保留了每一分块的hash值，就能在只下载到文件的一部分时判断出下载内容的有效性。</font></p>
<p><font size=3>emule在获取每个共享文件的信息时，就对它进行了分块处理，因此如果要知道emule中的分块处理和恢复机制，看CKnownFile::CreateFromFile函数的实现就行了。 </font></p>
<p><font size=3>这个函数中牵涉到的和分块处理以及hash计算相关的类都在SHAHashSet.cpp和SHAHashSet.h中。</font></p>
<p><font size=3>下面介绍其中几个主要的类：</font></p>
<p><font size=3>CAICHHash类只负责一块hash值，提供两个CAICHHash类之间的直接赋值，比较等基本操作。 CAICHHashAlgo是一个hash算法的通用的接口，其它hash算法只要实现这种接口都能使用，这样，可以很方便得使用不同的hash算法来计算hash值。CAICHHashTree则是一个树状的hash值组织方式，它有一个左子树和右子树成员变量，类型是指向CAICHHashTree的指针，这是一个典型的实现树状结构的方法。CAICHHashSet中包含了一个CAICHHashTree类型的变量，它直接向CKnownFile负责，代表的是一个文件的分块信息。 SHAHashSet.h文件的开始的注释部分向我们解释了它的分块的方式。这里要用到两个常量9728000和184320，它们分别是9500k和 180k。这是emule中两种不同粒度的分块方式，即首先把一个很大的文件分割成若干个9500k的块，把这些块组织成一颗树状的结构，然后每一个这样的块又分解成若干个180k的块(52块，再加一个140k的块)，仍然按照树状的结构组织起来。最后总的结构还是一颗树。 CKnownFile::CreateFromFile方法是在读取目标文件的内容时，逐步建立起这样一颗树的。 CAICHHashTree::FindHash能够根据读取到的目标文件的偏移量和下一块的大小，来找出对应的树枝节点(就是一个指向 CAICHHashTree的指针)。如果有必要的话，还会自动创建这些树枝节点。因此在进行分块操作的时候，把文件从头到尾读一边，整个 CAICHHashTree就建立起来了，对应的分块hash值也赋值好了。</font></p>
<p><font size=3>最后我们还需要注意的就是CKnownFile类中的hashlist变量。就是说它还单独保留直接以 9728000字节为单位的所有分块的MD4算法的hash值。这样对于一个文件就有了两套分块验证的机制，能够适应不同场合网络基础设施--网络基础设施的基础设施 MFC中已经有一些网络基础设施类，如CAsyncSocket等。但是emule在设计中，为了能够更加高效得开发网络相关的代码，构建了另外的一些类作为基础设施，这些基础设施类的代码也有很高的复用价值。</font></p>
<p><font size=3>首先是CAsyncSocketEx类。AsyncSocketEx.h中对这个类的特点已经给出了一定的说明。它完全兼容CAsyncSocket类，即把应用程序中所以的CAsyncSocket换成CAsyncSocketEx，程序仍然能够和原来的功能相同，因此在使用上更加方便。但是在这个基础上，它的效率更高，主要是在消息分发机制上，即它处理和SOCKET相关的消息的效率要比原始的MFC的 CAsyncSocket类更高。</font></p>
<p><font size=3>另外，CAsyncSocketEx类支持通过实现CAsyncSocketExLayer类的方式，将一个SOCKET分成若干个层，从而可以很方便得实现许多网络功能，如设置代理，或者是使用SSL进行加密等。 </font></p>
<p><font size=3>另外还有ThrottledSocket.h中定义的ThrottledControlSocket类和 ThrottledFileSocket类，这两个类只定义了两个接口。任何其它的网络套接字类如果想实现限速的功能，只需要在其默认的发送函数(如 Send或Sendto)中不发送数据而是把数据缓存起来，然后在实现ThrottledControlSocket或者 ThrottledFileSocket接口中的SendFileAndControlData或SendControlData方法时才真正把数据发送出去，这样就能实现上传限速，而这也是需要UploadBandwidthThrottler类进行配合，UploadBandwidthThrottler是一个WinThread的子类，平时单独运行一个线程。下一次会详细描述它是如何控制全局的上传速度的。 网络基础设施--全局限速器UploadBandwidthThrottler UploadBandwidthThrottler是emule中使用的全局的上传限速器。它继承了CWinThread类，且在该类被创建的时候，就新创建一个线程开始单独运行。在该类被析构时也会自动停止相应的线程。这个线程的目标函数就是RunProc，然后为了避免在RunProc函数不能使用 this指针的情况，它使用了RunInternal来实际完成工作线程的工作。在emule中，还有另外一个类 LastCommonRouteFinder有类似的结构。 UploadBandwidthThrottler中保存了若干的套接字(Socket)队列，这些队列的处理方式略有不同。在标准队列 (m_StandardOrder_list)里面排队的都是实现了ThrottledFileSocket接口的类，通常这些类能够传输文件内容也可以传输控制信息。</font></p>
<p><font size=3>而其它四个队列都是实现ThrottledControlSocket接口的类的队列，在这些队列中的类主要以传输控制信息为主。</font></p>
<p><font size=3>这四个队列为临时高优先级，临时普通优先级，正式高优先级，正式普通优先级。和把套件字直接添加到普通队列(AddToStandardList)不同，</font></p>
<p><font size=3>QueueForSendingControlPacket把要添加到队列的套接字全部添加到两个临时队列。根据它们的优先级添加到普通的临时队列。在RunInternal的大循环中，临时队列中的项目先被移到普通队列中，然后再进行处理。 </font></p>
<p><font size=3>UploadBandwidthThrottler使用了两个临界区，两个事件。pauseEvent是用来暂停整个大循环的动作的。而threadEndedEvent是标志整个线程停止的事件。sendLocker是大循环中使用的主要的临界区，而 tempQueueLocker是为两个临时队列额外添加的锁，这样可以一边发送已有队列中的套界字要发送的数据，一边把新的套接字加到队列中。 </font></p>
<p><font size=3>UploadBandwidthThrottler的RunInternal中的大循环是该工作线程的日常操作。这个大循环中做了以下事情，计算本次配额，即本次循环中能够发送多少字节，好安排调度，计算本次循环应该睡眠多少时间，然后进行相应的睡眠，从而进行限速。操作控制信息队列，发送该队列中的数据，注意，控制队列中的套接字(m_ControlQueueFirst_list和 m_ControlQueue_list)只使用一次就离开队列。而标准队列中的套接字不会这样。在一轮循环结束后，如果还有没有用完的发送数据的配额，则会有部分配额保存到下一轮。 网络基础设施--emule套接字CEMSocket CEMSocket是CAsyncSocketEx和ThrottledFileSocket的子类，它把若干功能整合到了一起，因此可以作为emule 使用起来比较方便的套接字。例如它可以很方便得指定代理，把CAsyncSocketEx中的创建一个新的代理层并且添加到列表中的功能对外屏蔽了。另外它可以分出状态，如当前是否在发送控制信息等。</font></p>
<p><font size=3>CEMSocket中我们需要仔细考察的是它的SendControlData和 SendFileAndControlData方法。如前所述，这些方法是用来和UploadBandwidthThrottler进行配合，以便完成全局的限速功能的。它的功能应该是按照UploadBandwidthThrottler的要求，在本次轮到它发送数据时发送指定数量的字节数。因此，应用程序的其它部分在使用CEMSocket时，如果要达到上传数据限速的目的，不应该直接调用标准的Send或者SendTo方法，而是调用 SendPacket。这里就有了另外一个结构Packet，它通常包含一个emule协议中完整的包，例如有协议的头部数据等，还内置了 PackPacket和UnPackPacket方法，可以自行进行压缩和解压的功能。SendPacket把要发送的Packet放到自己的队列中，这个队列也有两个，控制信息包队列，和标准信息包队列。如果有必要，把自己加入到UploadBandwidthThrottler的队列中。我们注意到CEMSocket的SendControlData和SendFileAndControlData方法其实都是调用自己的另一个重载的 Send方法。而且我们也已经知道这个方法是在UploadBandwidthThrottler的工作线程中的大循环中被调用的，而这个Send方法的内容本身也是一个大循环，但是意义很明了，就是在不超过自己本次发送的配额的情况下，把自己的包队列中的包取出来，并且发出去。同样，这里也用到了一个临界区，它是为了保证从包队列中取出包来发送和把包往队列中放的操作是互斥的。因此，如果把它和UploadBandwidthThrottler结合起来，我们就看到了一个两层的队列，即所有的套接字组成了一个发送队列，在UploadBandwidthThrottler的控制下保证了对速度的限制，而每个套接字即将发送的数据包又组成了一个队列，保证了每次进行数据发送的时候都会满足UploadBandwidthThrottler的要求。</font></p>
<div class=cnt>
<p><font size=3>搜索信息集－CSearchList</font></p>
<p><font size=3>CSearchList 是emule中的搜索列表，掌管emule中所有的搜索请求。CSearchFile是这个列表中的元素，代表了一次搜索的相关信息。它们的关系和之前描述的已知文件和已知文件列表有一些类似的地方。CSearchList的主要任务就是对其一个叫做list的类型为CSearchFile列表的内部变量进行维护，提供很方便得往这个列表中添加，删除，查询，变更等操作的接口。另外，每一个搜索都有一个ID，是一个32位的整数。CSearchList中记录了每个搜索目前搜到的文件个数和源的个数(m_foundFilesCount和m_foundSourcesCount)。<br><br>CSearchFile是CAbstractFile的另一个子类(CKnownFile也是)，它保存了某个文件和搜索相关的信息，而不是这个文件本身的信息(这些信息在CAbstractFile中已经包括了)，这些和搜索有关的信息就是都在哪些机器上有这个文件，以及哪个服务器上搜到的这个文件。甚至还可以向搜索文件添加预览。在这个类的定义中嵌套定义了两个简单的结构SServer和SClient，表示了该搜索文件的可能来源，服务器或者其它客户端。m_aClients和m_aServers是这两个简单结构的一个数组，CSearchFile自然也提供了对这个数组的操作的接口，方便 CSearchList使用。<br><br>CSearchList对外提供了搜索表达的接口，即每当有一个新的搜索提交时CSearchList::NewSearch会建立一个新的搜索项，但是此时还没有任何对应的搜索文件，因此只是在文件个数和搜索ID的对应表(m_foundFilesCount和m_foundSourcesCount) 中建立新的项目。另外当有搜索结果返回时ProcessSearchAnswer或ProcessUDPSearchAnswer能够对返回的包直接做处理，创建相应的搜索文件信息CSearchFile对象，并加入到自己的列表中。当然，要把重复的搜索结果去除，发现同一个hash的文件的多个源时也会给它们建立一个二级列表(CSearchFile::m_list_parent)。现在我们可以看出，CSearchList只负责和搜索有关的信息的储存和读取，本身并不进行搜索。&nbsp;&nbsp;</font></p>
<p><font size=3>服务器信息集－CServerL</font></p>
<p><font size=3>尽管目前有了Kad网络，但是使用服务器来获取各个emule用户的共享文件列表仍然是emule中主要的资源获取方式。CServerList就是 emule中负责管理服务器列表的类。和前面若干列表类结构类似，CServerList需要对外提供列表的增加，删除，查找，修改等接口。在 CServerList中，每个服务器的信息是一个CServer类。和搜索信息不一样，但是和已知文件列表一样，服务器的信息列表是需要长期保留的，因此CServerList和CKnownFileList类一样提供了把它所包含的所有信息保存到一个文件中，以及从这个文件中读回其信息的功能。<br><br>CServer中的结构比较简单，只需要保留服务器的各种信息即可。它可以通过IP地址和端口来创建，也可以通过一个简单的结构 ServerMet_Struct来创建，其中后者是用来直接从文件中读取的。该结构仅仅包含IP地址和端口以及属性的个数，CServer中其它的属性在保存到文件中时，均采用Tag方式保存。<br><br>CServerList除了提供通常的CServer信息外，还提供一些统计信息诸如所有的服务器的用户数，共享的文件数等。这些统计信息也是基于每个单独的CServer的相关信息计算出来的。<br></font></p>
<p><font size=3>emule的通信协议－一些基本的约定</font></p>
<p><font size=3>接下来将不可避免得要碰到emule的协议。emule的通信协议格式设计成一种便于扩充的格式。对于TCP连接来说，连接中的数据流都能够划分成为一个一个的Packet，CEMSocket类中就完成了把接收到的数据划分成Packet这一工作。但是具体的对于每个Packet进行处理的工作被转移到它的子类中进行。CEMSocket类的两个子类CServerSocket和CClientReqSocket所代表的TCP连接就分别是客户端和服务器之间的TCP连接以及客户端之间的TCP连接。在数据流中的第一个字节代表的是通信的协议簇代码，如0xE3为标准的edonkey协议，0xE4为 kademlia协议等等。接下来的四个字节代表包内容的长度，所有的包都用这种方式发送到TCP流中，就可以区分出来了。另外每个包内容中的第一个字节为opcode，即在确定了某个具体协议后，这个opcode确定了这个包的具体含义。<br><br><br>对于走UDP协议的包，处理起来更加得简单，因为UDP本来就是以一个包一个包作为单位在网络上流传的，因此不需要在包的内容中再包含表示长度的字段。每个UDP包的第一个字节是协议簇代码，其它内容就是包的内容。CClientUDPSocket类负责处理客户端和客户端之间的UDP包，而 CUDPSocket类负责处理客户端和服务器之间的UDP包。另外还有个Kademlia::CKademliaUDPListener类，专门处理和 Kademlia协议相关的UDP包。<br><br>最后说一下Packet类，这个类以前只是提到过。它是emule的通信协议的最小单位。我们可以看出，它的构造函数有多个版本，这也是为了可以用不同的方式来创建Packet。例如只包含一个头部信息的缓冲区，或者只是指定协议簇代码等。而且它内部实现了压缩和解压的方法，该方法直接调用zlib库中的压缩方法，可以减少数据的传输量。这里要注意一点的就是压缩的时候协议簇代码是不参与压缩的，压缩完毕后会更换协议簇代码，例如代码为标准edonkey 协议0xE3的包在压缩后，协议代码就变成0xD4了，这里进行协议代码变化是为了使接受方能够正确识别并且进行相应的解压操作。<br></font></p>
<p><font size=3>emule的通信协议－客户端和服务器之间的通信概述</font></p>
<p><font size=3>客户端和服务器之间的所有通信由类CServerConnect掌握。CServerConnect本身不是套接字的子类，但是它的成员变量 CServerSocket类型的connectedsocket是。CServerConnect内部有一列表，可以保存若干 CServerSocket类型的指针。但是这并不说明它平时连接到很多服务器上。它只是可以同时试图连接到若干个服务器上，这只是因为连接到服务器上的行为不一定能成功。<br><br>CServerSocket类是CEMSocket的子类，它比CEMSocket要多保存一些状态，比如当前的服务器连接状态。它同时还保留它当前所连接的服务器的信息。通过分析CServerSocket::ProcessPacket就可以直接把emule客户端和服务器之间的通信协议理解清楚，这里是服务器发回的包。TCP连接建立后的第一个包是在CServerConnect::ConnectionEstablished中发出的，即向服务器发出登陆信息。如果登陆成功，则能够从服务器处获取自己的ID，这是一个32位的长整数。如果这个数小于16777216，那么我们称它为LowID。具有LowID的客户端通常情况下其它客户端将不能直接连接它。得到LowID的原因比较多，例如当自己处于NAT的后端的时候。获取自己的ID后将会向服务器发送自己的共享文件列表，这一动作由共享文件列表类CSharedFileList来完成。<br><br>其它类型包没有必要全部都列出来，以后可以通过在分析其它部分时，因为牵涉到往服务器发送或者接受数据的时候再进行相应的分析。</font></p>
<p><font size=3>emule的通信协议－客户端和客户端之间的通信概述</font></p>
<p><font size=3>客户端和客户端之间的TCP通信由CListenSocket和CClientReqSocket完成。这也是提供网络服务的应用程序的典型写法。其中 CListenSocket只是CAsyncSocketEx的子类，只负责监听某个TCP端口。它只是内部有一个CClientReqSocket类的列表。而CClientReqSocket是CEMSocket的子类，因此它能够自动完成emule的packet识别工作。它有 ProcessPacket和ProcessExtPacket来处理客户端和客户端之间的包，其中前者是经典的eDonkey协议的包，后者是 emule扩展协议的包。<br><br>CListenSocket和CClientReqSocket类之间的关系和前面分析的列表类和它对应的成员类的关系是相似的，CListenSocket提供对自身的CClientReqSocket列表中的元素的增加，查询，删除等操作。同时也维护关于这些成员的一些统计信息。我们注意到CListenSocket在其构造函数中就把自己添加到CListenSocket类(theApp.listensocket，该类的唯一实际示例)的列表中。<br><br>CClientReqSocket类和CUpDownClient类之间存在着对应关系。它们都表示了另外一个客户端的一些信息，但是 CClientReqSocket类主要侧重在网络数据方面，即负责两边的互相通信，而CUpDownClient类负责的是从逻辑上对网络另一边的一个客户端进行表达。CUpDownClient类代码很长，以后再说。 </font></p>
<div class=cnt>
<p><font size=3>emule中的信誉机制</font></p>
<p><font size=3>信誉机制在P2P系统中有非常重要的作用。为了使用户更加愿意共享自己的资源，需要有一些机制能够让对整个P2P系统贡献更大的用户有更多的激励。在emule 中，激励机制的设计方案是tit-for-tat这种最直观的方案。这种方案的意义就是最简单的如果别人对你好，那么你也对别人好。<br><br>下面看实际的实现。CClientCreditsList和CClientCredits类负责emule中的信誉机制。我们再次见到这种列表和元素之间的关系，不必再重复那些语言。和信誉相关的信息是需要永久保存的，这样才有意义，因此CClientCreditsList提供了LoadList和 SaveList方法。我们另外注意到，CClientCredits类可以使用CreditStruct结构来创建，而CreditStruct结构只包含静态信息。主要是上传量和下载量等。<br><br>信誉机制的信息需要有一定的可靠性，在emule中采用了数字签名的方式来做到这一点。Crypto++库为emule全程提供和数字签名验证相关的功能。CClientCreditsList在创建时，会装载自己的公钥私钥，如果没有的话，会创建一对。CClientCreditsList中包含的有效的信息都是经过其它人数字签名的，所以更加有信服力。在实际使用中，这些信息和自己的私钥要注意保存。重装emule后应该把配置文件目录先备份，这样能够保留自己辛辛苦苦积攒的信誉。&nbsp;&nbsp;</font></p>
<p><font size=3>下载任务即部分文件的表示</font></p>
<p><font size=3>CPartFile 类是emule中用来表示一个下载任务的类。从它的名字也可以看出来，这就是一个还没有完成的文件。当一个下载任务被创建时，emule会在下载目录中创建两个文件，以三位数字加后缀part的文件，例如001.part，002.part等。还有一个以同样的数字加上.part.met的文件，表示的是对应文件的元信息。part文件会创建得和原始文件大小一样，当下载完成后，文件名会修改成它本来的名称。而事实上，诸如这个文件原来叫什么名称，修改日期等等信息都在对应的.part.met元文件中。.part.met中还包含了该文件中那些部分已经下载完成的信息。<br><br>CPartFile类中Gap_Struct来表示文件的下载情况，一个Gap_Struct就是一个坑，它表示该文件从多少字节的偏移到多少字节偏移是一个坑。下载的过程就是一个不断填坑的过程。CPartFile类中有个成员变量gaplist就是该文件目前的坑的状况列表。需要主要的是有时填了坑的中间部分后，会把一个坑变成两个坑。坑的列表也会被存进.part.met中。<br><br>CPartFile类的代码很庞大，但是这是必须的。首先，它的创建就有几种可能，从搜索文件CSearchFile中创建，这种情况发生在用户搜索到他想要的文件后点击下载时发生。从一个包含了ed2k链接的字符串中创建，它会提取出该ed2k链接中的信息，并用来创建CPartFile。剩下的一种，就是当emule程序重启后，恢复以前的下载任务。这时就是去下载目录中寻找那些.part和.met文件了。另外它还需要不断得处理下载到的数据，为了减少磁盘开销，使用了Requested_Block_Struct结构来暂存写入的数据。它内部维护一个CUpDownClient的列表，如果知道了该文件的一个新的来源信息，就会创建一个对应的CUpDownClient。后者是emule中代码量最大的类。它还要把它的状态用彩色的条装物显示出来提供给GUI。<br><br>前面提到的AICH机制对于最大程度得保证下载文件的正确性以及尽量减少重复传输都有很大的帮助，在下载的过程中，该机制会经常对下载到的数据进行校验。<br><br>最后提一下它的Process方法。该方法是emule中为了尽量减少线程的使用而采取的一种有一些类似于轮询的机制。其它很多类中也有Process方法，这个方法要做的事情就是在一些和日常运行有关的事情，例如检查为了下载该文件而链接到自己的各个客户端的状态，向它们发送下载请求等。</font></p>
<p><font size=3>下载任务队列</font></p>
<p><font size=3>CDownloadQueue 是下载队列类。这个队列中的项目是CPartFile指针。因此和emule中出现的很多其它的列表类一样，它需要能够提供对这个列表中的元素进行增加，查询，删除的功能。例如查询的时候能够根据该文件的hashID或者索引来进行查询。CDownloadQueue同时还要完成一些统计工作。<br><br>和其它的列表类不一样的是，它的所有元素的信息并不是集中存放于一个文件，而是对应于每一个下载任务，单独得存放在一个元信息文件(.part.met) 中，因此当该类进行初始化的时候，它需要寻找所有可能的下载路径，从那些路径中找到所有的.part.met文件，并且试图用这些文件来生成 CPartFile类，并且将这些通过.part.met文件正确生成的CPartFile类添加到自己的列表中，同样，在退出时，所有的下载任务的元信息也是自行保存，不会合成为一个文件。<br><br>CDownloadQueue中的Process方法的主要任务就是把它的列表中的CPartFile类中的Process方法都调一遍，另外主要的一些关于下载情况的统计信息也是在每一轮的Process后进行更新的。从这里我们也可以看出Process方法在emule中的意义，就是一个需要经常执行的方法，通过经常执行它们来完成日常工作，而且所有的这些Process方法肯定是顺序执行，因此可以减少很多多线程的同步之类的问题。emule中已经尽量减少了多线程的使用，但是在很多地方如果多线程是不可避免的话，也不会排斥。&nbsp;&nbsp;</font></p>
<p><font size=3>上传任务队列</font></p>
<p><font size=3>CUploadQueue 是上传队列类。这个列表类中只有以CUpDownClient为元素的列表，它和其它列表类还有一个很大的不同就是它所保存的信息都不需要持久化，即不需要在当前的emule退出后还记住自己正在给谁上传文件，然后下次上线的时候再继续给他们传，这在大部分情况下是没有意义的。<br><br>上传队列类列表中有两个列表，上传列表和排队列表。当一个收到一个新的下载请求后，它会把对应的客户端先添加到排队列表中，以后再根据情况，把它们不断添加到上传列表中。在这里，信誉机制将会对此产生影响。<br><br>CUploadQueue的Process方法就相对简单了，那就是向上传队列中的所有客户端依次发送数据，而排队的客户端是不会得到这个机会的。另外它还需要完成关于上传方面的一些统计信息。<br><br>另外我们还需要注意在CUploadQueue的构造函数里面，创建了一个以100毫秒为间隔的定时器，这个定时器成为以上所有的Process所需要的基础。我们看它的UploadTimer就可以看出这一点。这里面充斥了各个类的Process方法的执行，其中包括以前我们提到的一些类，但是没有提到它们的Process方法，因为其过于简单，基本上就只是更新了一下要保存的信息。 </font></p>
<p><font size=3>emule中代码量最大的类CUpDownClient</font></p>
<p><font size=3>CUpDownClient 类的作用是从逻辑上表示一个其它的客户端的各种信息，它是emule中代码量最大的类。我们注意到，定义它的头文件是UpDownClient.h，但是却没有对应的CUpDownClient.cpp，而它的实现，都分散到 BaseClient.cpp，DownloadClient.cpp，PeerCacheClient.cpp，UploadClient.cpp和 URLClient.cpp中。<br><br>BaseClient.cpp中实现的是该类的一些基本的功能，包括基本的各种状态信息的获取和设置，以及按照要求处理和发送各种请求。在这里，逻辑实现和网络进行了区分，CUpDownClient类本身不从网络接受或者发送消息，它只是提供各种请求的处理接口，以及在发送请求时，构造好相应的 Packet，并交给自己对应的网络套接字发出去。<br><br>DownloadClient.cpp中实现的是和下载相关的功能，它包括了各种下载请求的发送以及相应的数据的接收。另外还有一个A4AF的机制，它是 emule中的一个机制，因为一个客户端在同一个时间内只能向另外一个客户端请求同一个文件。这样，对于很多个下载任务(CPartFile)，有可能出现它们的源(即有该文件的客户端)有部分重叠的现象，而这时，如果其它下载任务正在从这个源下载，那么当前的下载任务就不能从这个源下载了。但是 emule允许用户对其手动进行控制，如对下载任务的优先级进行区分，这样他就可以将一个源从另外一个下载任务那里切换过来。A4AF其实就是ask for another file的简称。<br><br>UploadClient.cpp中实现的是上传相关功能，即接受进来的下载请求，并且生成相应的文件块发送出去。<br><br>PeerCacheClient.cpp实现的是和PeerCache相关的功能，PeerCache是一个由Joltid公司开发的技术，它可以允许你从ISP提供的一些快照服务器上快速得上传或者下载一些文件(或者是一部分)，这个技术的好处是可以减少骨干网络的带宽消耗，将部分本来需要在骨干网上走的流量转移到ISP的内部。当然这个功能需要ISP的配合。如果发现ISP提供了这项服务的话，emule会利用它来减少骨干网的带宽消耗。<br><br>URLClient.cpp实现的功能是利用http协议对原有的emule协议进行包装，以便使它能够尽可能地穿越更多的网络的防火墙。&nbsp;&nbsp;</font></p>
<p><font size=3>emule常规部分小结</font></p>
<p><font size=3>emule 中还有其它的很多类，它们使得emule的功能更加的强大和完善。有很多类在前面没有提到，但是不代表它没有作用。而且即时是前面提到的类也只是大体的介绍，它们之间互相配合的一些细节没有体现。但是这些细节应该已经可以通过对它们的大体的功能的了解而更加容易被把握。至于GUI的设计，它也最终是要对应到某个功能实现类的数据的。<br><br>对于emule中的通信协议只是大体得描述了一下它的数据包的格式，但是并没有详细得描述它的每一个Opcode对应的包的意义，因为我认为这是没有必要的，在知道通信协议的格式以及处理它们的代码所在的位置后，可以很简单的通过追踪某条消息的前因后果把整个通信协议都分析出来。<br><br>这里再稍微提一下在emule中使用到的其它类及其功能。我们可以看到，如果单纯只是为了能够搜到以及下载到文件的话，有不少类是可以精简的，但是，正是由于它们的存在，使得emule的功能更加的完善。CIPFilter，IP地址过滤器，通过识别各种类型的IP地址过滤信息，它能够把不希望连接的网络地址过滤掉，emule中所有需要连接网络的地方使用的都是统一的过滤数据。CWebServer能够在本地打开一个Web服务器，然后你可以通过浏览器来控制你的emule。CScheduler能够实现下载任务的定时下载。CPeerCacheFinder为前面提到的PeerCache技术的主控制类。另外，emule还内置了一个IRC客户端，一个主要成员函数都为静态的CPartFileConvert类，能够对其它版本的驴的下载文件进行转换。它甚至还提供了一个自动处理zip和rar的类CArchiveRecovery。<br><br>Kademlia网络是emule中相当重要的一部分，因此特意把这一部分单独拿出来，把它放在这个小结之后进行描述。<br></p>
<div class=cnt>
<p><font size=3>emule中的Kademlia代码总体描述</font></p>
<p><font size=3>当 emule中开始使用Kademlia网络后，便不再会有中心服务器失效这样的问题了，因为在这个网络中，没有中心服务器，或者说，所有的用户都是服务器，所有的用户也是客户端，从而完完全全得实现了P2P。接下来讲针对emule中的Kademlia网络进行分析，会有一节进行原理方面的分析。另外的几节将会根据emule中实现Kademlia所使用的不同的类分别进行讲述。其中：<br><br>CKademlia是整个Kademlia网络的主控类，可以直接开始或者停止Kademlia网，并且含有Process方法来处理日常事务。<br><br>CPrefs负责处理自身的Kademlia相关信息，如自身的ID等。<br><br>CRoutingZone，CRoutingBin和CContact三个类组成了每个节点所了解的联系信息以及由这些联系信息所组成的数据结构。<br><br>CKademliaUDPListener负责处理网络信息。<br><br>CIndexed负责处理本地存储的索引信息。<br><br>CSearch，CSearchManager负责处理和搜索有关的操作，其中前者表示的是一个单一的搜索任务，后者负责对所有搜索任务进行处理。<br><br>CUInt128负责处理一个128位的长整数，并且内置其各种运算。前面已经提到过。<br></font></p>
<p><font size=3>emule中的Kademlia的基本原理</font></p>
<p><font size=3>Kademlia 是一种结构化的覆盖网络(Structured Overlay Network)，所谓的覆盖网络，就是一种在物理的Internet上面再次构建的虚拟网络，所有参与的节点都知道一部分其它节点的IP地址，这些节点称为它的邻居，如果需要查找什么东西，它先在本地寻找，如果找不到，就把这个查询转发到它的邻居处，希望能够有可能查找到相应的结果。覆盖网络里面分成了结构化和非结构化的两种情况，它们的区别在于每个节点知道哪些其它节点的信息是否有特定的规律。在非结构化的覆盖网中，每个节点的邻居状况没有特定的规律。因此在非结构化网络中，如果要进行查询，会采取一种叫做泛洪(flooding)的方法，每个节点如果在本地没有查找到想要的结果，会把查找请求转发到它的邻居中，然后再通过邻居的邻居这种方式来进行一步步的查找。但是这种方法如果处理不好，会造成整个网络的消息负载过大。已经有不少文章对于优化非结构化覆盖网络中的查询进行了很深入的探讨。<br><br>对于结构化的覆盖网络，它的特点是每个节点它会选择和哪些节点做邻居是有一定的规律的，从而在进行搜索的时候，节点把搜索请求进行转发的时候它能够通过一定的规律进行选择把请求转发到哪些邻居节点上。这样同时也能减少搜索代价。结构化的覆盖网络通常要求每一个节点随机生成一个ID，用以判断各个节点之间的关系。这个ID和它所在的物理网络必须是没有关系的。<br><br>对于Kademlia网络来说，这个ID是一个128位的数值，所有的节点都用这个ID来衡量自己与其它节点的逻辑距离。而逻辑距离的计算方法就是将两个节点进行异或(XOR)操作。在Kademlia网络的形成过程中，每个节点选择邻居的原则是离自己逻辑距离越近的节点越有可能被加入到自己的邻居节点列表中，具体来说就是在每次新得到一个节点的信息的时候，是否把它加入到自己的邻居节点列表是根据距离的远近来处理的。后面分析具体程序的代码时会有说明。<br><br>结构化的网络的好处就是如果我们要寻找一个距离某个ID逻辑距离足够近的节点，我们可以保证在O(logn)级别的跳数找到。只要先寻找自己已知的离目标 ID逻辑距离足够断的节点，然后再问它知不知道更近的，然后就这样下去。因此在搜索的时候也是这样，当需要发布资源的时候，把文件进行hash，这样就能够计算出一个128位的ID，或者把关键字进行hash。然后寻找到离这个结果逻辑距离最近的节点，把文件或者关键字的信息发送给它，让它存起来。当有人要搜索同样的东西的时候，由于它用的是同一个hash算法，因此能够计算出对应的ID，并且去搜索那些和这个ID逻辑距离相近的节点，因为它知道，如果网络中真有这些资源的话，这些节点是最有可能知道这些信息的。由此我们可以看出，结构化的网络的资源查找效率是很高的，但是它和非结构化的覆盖网络比起来，缺点是不能进行复杂查询，即只能通过简单的关键字或者文件的hash值进行查找。非结构化的网络的查找本身就是随意转发的，每个收到的查询请求的节点都对本地的资源掌握的很清楚，因此自然可以支持复杂查询，但是显然非结构化的网络支持的复杂查询不太可能动员所有的节点都来做这一动作。目前还没有方法能够把两种覆盖网络的优点结合起来，我也非常想知道这样的一种方法。<br></font></p>
<p><font size=3>emule中的Kademlia的基础设施类</font></p>
<p><font size=3>Kademlia 的主控类是CKademlia，它负责启动和关闭整个Kademlia网的相关代码。在它的Process函数中，会处理和Kademlia网相关的事务，例如隔一段时间检查某个区间的节点数是否过少，如果是则寻找一些新的节点。另外经常对自己的邻居进行检查等，这些都是属于需要进行日常安排的工作。所有搜索任务的日常处理也需要它来调度。它还作为Kademlia网的代表，向emule其它部分的代码返回Kademlia网的一些统计信息。<br><br>另一个基础设施类是CPrefs，它和emule普通代码中的CPreferences作用类似，但是CPrefs只保留和Kademlia网相关的，需要长期保存的本地信息。具体到这个版本来说，主要就是本地的ID。<br><br>还有一个很重要的基础设施就是CUInt128，实现对128位的ID的各种处理，前面的部分已经提到。&nbsp;&nbsp;</font></p>
<p><font size=3>emule中的Kademlia的联系人列表管理</font></p>
<p><font size=3>CRoutingZone，CRoutingBin和CContact三个类组成了联系人列表数据结构。它要达到我们搜索的要求，即搜索到目标的时间要能够接受，而且所占用的空间也要能够接受。<br><br>首先CContact类包含的是一个联系人的信息，主要包括对方的IP地址，ID，TCP端口，UDP端口，kad版本号和其健康程度 (m_byType)。其中健康程度有0-4五个等级。刚刚加入的联系人，也就是健康状况未知的，这个数值设置为3。系统会经常通过与各个联系人进行联系的方式对其进行健康状况检查，经常能够联系上的联系人，这个数值会慢慢减少到0。而很就没有联系的，这个数值会慢慢增加，如果增加到4后再过一段时间未能成功联系上的，则将会被从联系人列表中删除。<br><br>CRoutingBin类包含一个CContact的列表。这里要注意的是要访问联系人的信息必须通过某个 CRoutingBin，CRoutingZone内部是不直接包含联系人信息的。可以把新的联系人信息往一个特定的CRoutingBin中加，当然也可以进行联系人查找。它也提供方法能够寻找出离某个ID距离最近的联系人，并给出这样的一个列表。这是相当重要的。最后，一个CRoutingBin类中能够包含的CContact的数量也是有限制的。<br><br>CRoutingZone类处于联系人数据结构的最上层，直接为Kademlia网提供操作接口。该类的结构为一个二叉树，内含两个 CRoutingZone指向它的左子树和右子树，另外也包含一个CRoutingBin类型的指针。但是只有在当前的CRoutingZone类为整个二叉树的叶节点时，这个指向CRoutingBin类型的指针才有意义。这个二叉树的特点是，每个节点以下的所有联系人的ID都包含一个共同前缀，节点的层数越深，这个共同前缀越长。例如，根节点的左子树的所有的节点的ID一定有一个前缀"0"，而右子树的所有节点一定有前缀"1"。同样，根节点的左子树的右子树下的所有节点的ID一定有前缀"01"，等等，依此类推。我们设想一下节点不断得往这个二叉树添加的过程。刚开始只有一个根节点，它也就是叶节点，这时它内部的CRoutingBin是有意义的，当联系人信息不断得被添加进去以后，这个CRoutingBin的容量满了，这时要进行的就是一个分裂的操作。这时，会添加两个左子节点和右子节点，然后把自身的CRoutingBin中的联系人信息按照它们的前缀特点分别复制往左节点和右节点，最后把自身的CRoutingBin废除掉，这样这个分裂过程就完了。当分裂完成后，就会再次试图添加该联系人信息，此时会试图按照它的ID，把它添加到对应的子树中。但是并不是所有的这种情况节点都会发生分裂，因为如果允许任意分裂的话，本地所需存储的节点信息数量就会急剧上升。这里，自身ID的作用就体现了。只有当自身ID和当前准备分裂的节点有共同前缀时，这个节点才会分裂，而如果判断到一个节点不能分裂，而它的CRoutingBin又满掉了，那么就会拒绝添加联系人信息。<br><br>我们可以看出，在以上政策的进行下，离自身ID逻辑距离越近(也就是共同前缀越长)的联系人信息越有可能被加入，因为它所对应的节点越有可能因为分裂而获得更多的子节点，也就对应了更多的容量。这样，在Kademlia网中，每一个参与者知道的其它参与者信息中，离自己逻辑距离越近的参与者比例越高。由于在搜索的时候也只需要不断得寻找更近的ID，而且每一步都一定会有进展，所以寻找到目标ID所需要的时间上的代价是O(logn)，从这个二叉树的结构来看，我们也可以看到，由于只有部分节点会分裂，所以实质上存储所需要的空间代价也是O(logn)。<br><br>实际上CRoutingZone在实现时和理论上的Kademlia有一些区别，如从根节点开始，有一个最低分裂层数，也就是说，如果层数过低的话，是永远允许分裂的，这样它知道的其它地区的联系人信息就能够稍微多一些。&nbsp;&nbsp;</font></p>
<p><font size=3>emule中的Kademlia网络消息处理</font></p>
<p><font size=3>CKademliaUDPListener 负责处理所有和Kademlia网相关的消息。前面已经对emule的通信协议的基本情况做了一个大概的描述，我们就可以知道，CKademliaUDPListener处理的消息一定是只和Kademlia网相关的，分拣工作已经在emule的普通UDP客户端处理代码那里处理好了。具体的消息格式前面也有一些介绍，下面会就一些具体的消息分类做说明。<br><br>首先是健康检查方面的消息，这样的消息就是一般的ping-pong机制。对应的消息有KADEMLIA_HELLO_REQ和 KADEMLIA_HELLO_RES。当对本地联系人信息列表进行检查时，会对它们发出KADEMLIA_HELLO_REQ消息，然后处理收到的 KADEMLIA_HELLO_RES消息。<br><br>最常用的消息是节点搜索消息，在Kademlia网络中，进行节点搜索是日常应用所需要传输的主要消息，它的实现方式是迭代式的搜索。这种方式就是说当开始搜索某个ID时，在本地联系人信息列表中查找到距离最近的联系人，然后向它们发出搜索请求，这样通常都能够得到一些距离更近的联系人信息，然后再向它们发送搜索请求，通过不断得进行这样的搜索查询，就能够得到距离目标ID最近的那些联系人信息。这里对应的消息代码是KADEMLIA_REQ和 KADEMLIA_RES。<br><br>接下来就是对内容进行发布或者搜索。这一点结合后面的CIndexed类的分析可以知道得更加清楚。emule中存储在Kademlia网中的信息主要有三类：文件源，关键字信息和文件的评论。文件源对应的是每一个具体的文件，每个文件都用它的内容的hash值作为该文件的唯一标示，一条文件源信息就是一条关于某人拥有某个特定的文件的这样一个事实。一条关键字信息则是该关键字对应了某个文件这样一个事实。很显然，一个关键字可能会对应多个文件，而一个特定的文件的文件源也很有可能不止一个。但是它们的索引都以固定的hash算法作为依据，这样使得搜索和发布都变得很简单。<br><br>我们来看发布过程。每个emule客户端把自己的共享文件的底细已经摸清楚了，在传统的有中心索引服务器的场景里，它把自己的所有文件的信息都上传到中心索引服务器里。但是在Kademlia网里，它就需要分散传播了，它首先做的事情是把文件名进行切词，即从文件名中分解出一个一个的关键词出来，它切词的方法非常简单，就是在文件名中寻找那些有分割符含义的字符，如下划线等，然后把文件名切开。计算出这些关键字的hash值后，它把这些关键字信息发布到对应的联系人那里。并且把文件信息也发布到和文件内容hash值接近的联系人那里。对应的消息是KADEMLIA_PUBLISH_REQ和 KADEMLIA_PUBLISH_RES。另外emule允许用户对某个文件发表评论，评论的信息单独保存，但是原理也是一样的。<br><br>当用户使用Kademlia网络来进行搜索并且下载文件的时候，首先是对一个关键词进行搜索，由于使用的是同样的hash算法，这样它只要找到ID值和计算出来的hash值结果相近的联系人信息后，它就可以直接向它们发送搜索特定关键词的请求了。如果得到了返回信息，那么搜索者就知道了这个关键词对应了多少文件，然后把这些文件的信息都列出来。当用户决定下载某个文件的时候，针对这一特定文件的搜索过程就开始了，这一次如果搜索成功，那么返回的就是这个文件的文件源信息。这样emule接下来就只需要按照这些信息去连接相应的地址，并且使用传统的emule协议去和它们协商下载文件了。这里对应的消息是 KADEMLIA_SEARCH_REQ和KADEMLIA_SEARCH_RES。<br><br>实际的实现中有Kademlia2这种协议，它的原理是一样的，只有协议代码和具体的消息格式不一样，例如KADEMLIA_REQ和 KADEMLIA_RES对应了KADEMLIA2_HELLO_REQ和KADEMLIA2_HELLO_RES，但是后者在具体的消息中包含了比前者丰富一些的信息。在实现的时候0.47c更加倾向于使用Kademlia2，而0.47a更加倾向于使用Kademlia。当然，它们两种协议都能够处理。另外，0.47c增加了一个对于已发出的请求的追踪的特性，就是一个包含TrackPackets_Struct类型的列表，这里面详细纪录了什么时间曾经对哪个IP发出过那种opcode对应的请求。为什么要这样呢？这是为了防止针对DHT的一种路由污染攻击，因为在搜索联系人的时候，如果搜索到了一些联系人信息，也会试图把它先加入到本地的联系人信息列表中。这样如果有人想恶意攻击的话，它只要不断得往它想攻击的emule客户端发送 KADEMLIA_RES，并且在消息的内容中包含大量的虚假联系人信息，就可以使对方的联系人信息列表中充满垃圾。这样，由于缺少正确有效的联系人信息，它的Kademlia网功能基本上就废了。而在0.47c里面增加的这个特性，就会对那种还没有发出请求就收到回应的情况直接无视，从而避免被愚弄。&nbsp;&nbsp;</font></p>
<p><font size=3>emule中的Kademlia的分布式索引管理</font></p>
<p><font size=3>Kademlia 网络的最大的好处是把原来需要存储到中心索引服务器中的信息分散存储到各个客户端当中，如果要说得更加准确一点，那我们就可以说它把这些信息分散得存储到各个emule客户端的CIndexed类当中。我们可以具体开始看CIndexed的设计，看它是如何完成这一工作的。在这之前我们要稍微详细得说一下 emule发布到Kademlia网络中的信息的各种类型。<br><br>一个文件源信息是一个文件内容的hash值和拥有这个文件的客户端的IP地址，各种端口号以及其它信息之间的对应关系。而一个关键词信息则是该关键词和它对应的文件之间的关系。在关键词信息中，它对应的文件信息要更加详细，通常包括这个文件的文件名，文件大小，文件内容的hash值，如果是MP3或者其它媒体文件，还会包含包括作者，生产时间，文件长度(这个长度是用时间来衡量的媒体文件的播放长度)，流派等等tag信息。其中文件内容的hash值用来区分该关键词对应的不同文件。<br><br>CIndexed中利用了一系列的Map来存储这些对应信息，CMap是MFC中实现标准STL中的map的模板类，CIndexed中包含了四个这样的类，分别用来存储文件源信息，关键词信息，文件评论信息以及负载信息。其中文件评论信息是不长久保存的，而其它的信息都会在退出的时候写到文件中，下次重新启动emule时再重新调入。另外负载信息不是等其它联系人来发布的，而是根据文件源信息和关键词信息的发布情况自行进行动态调整的。每一次收到发布信息时，对应的ID的负载会增大，这一事实会在回应消息(KADEMLIA_PUBLISH_RES)中体现。<br><br>CIndexed中的信息会经常进行检查，每隔三十分钟它会把自己存储的所有信息中太老的信息清除掉。其中文件源信息的保存时间为五小时，关键词信息为二十四小时，文件评论的信息保存时间也为二十四小时。因此文件的发布和关键词也要周期性得反复进行。其实这对于整个Kademlia网络的稳定性也是有好处的，因为每一次联系都会试图把对方添加到自己的联系人列表中，或者在联系人列表中标注上一次见到对方的时间。<br><br>CIndexed为其它部分的代码提供了它们所需要的增加信息和搜索信息的接口，这样在从网络中获取到相关的搜索或者发布请求，并且CKademliaUDPListener完成消息的解释后，就可以交给CIndexed来进行处理了。&nbsp;&nbsp;</font></p>
<p><font size=3>emule中的Kademlia搜索任务管理</font></p>
<p><font size=3>CSearch 和CSearchManager是完成具体搜索任务的。CSearch对应的是一个具体的搜索任务，它包括了一个搜索任务从发起到结束的全部过程，要注意的是搜索任务并不只是指搜索文件源或者关键词的任务，一次发布任务它也需要创建一个CSearch对象，并且让它开始执行。CSearchManager 则掌握所有的搜索任务，它包含了一个包含所有CSearch指针对象的CMap，使用CMap的原因是因为所有的CSearch都一定对应一个ID，那个 ID就是该CSearch所对应的目标，不管是要查找节点，还是要搜索或者发布信息，一定都要找到和目标ID相近的联系人。因此 CSearchManager可以使用CMap来表示所有的搜索任务。<br><br>我们注意到CSearch在创建的时候就把自己加入到CSearchManager当中。另外CSearch在创建的时候需要说明它的类型，例如是只是为了搜索节点还是要搜索关键词信息或者文件源信息，当然也有可能是发布文件源信息或者关键词信息。我们介绍一下CSearch的几个方法的作用就可以大概了解CSearch的工作过程。Go是它的启动过程，它会开始第一次从本地的联系人列表中寻找候选的联系人，然后开始发动搜索。SendFindValue 的功能就是向某个联系人发送一个搜索某ID的联系人信息这样一个请求。JumpStart则是在搜索进行到一定地步的时候，如得到了一些中间结果，开始进行下一步的行动，下一步的行动仍然可能是SendFindValue，也有可能认为搜索到的联系人离目标已经足够近了，于是就可以开始实质性的请求。 StorePacket就是这样一个实质性的请求，例如在一个以发布文件源为任务的CSearch中，StorePacket会向目标联系人发送 KADEMLIA2_PUBLISH_SOURCE_REQ(如果不支持Kademlia2，那么是KADEMLIA_PUBLISH_REQ)。最后，CSearch能够处理各种搜索结果，然后向调用它的代码返回处理好的结果。<br><br>CSearchManager直接和Kademlia网的其它部分代码接触，例如，如果CKademliaUDPListener搜索到了一些结果，它会把这些结果交给CSearchManager，然后CSearchManager再去寻找这个结果是属于那个搜索任务的，并且进行转交。另外 CSearchManager对外提供创建各种新的搜索任务的接口，作用类似于设计模式中的Factory，其它部分的代码只需要说明需要开始一个什么样的搜索任务即可，CSearchManager来完成相应的创建CSearch的任务。 </font></p>
</div>
</font></div>
</div>
<br></font>
<img src ="http://www.cppblog.com/tommyyan/aggbug/82045.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 15:41 <a href="http://www.cppblog.com/tommyyan/articles/82045.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>eMule源代码学习心得</title><link>http://www.cppblog.com/tommyyan/articles/82042.html</link><dc:creator>星仁</dc:creator><author>星仁</author><pubDate>Wed, 06 May 2009 07:35:00 GMT</pubDate><guid>http://www.cppblog.com/tommyyan/articles/82042.html</guid><wfw:comment>http://www.cppblog.com/tommyyan/comments/82042.html</wfw:comment><comments>http://www.cppblog.com/tommyyan/articles/82042.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tommyyan/comments/commentRss/82042.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tommyyan/services/trackbacks/82042.html</trackback:ping><description><![CDATA[<div class=cnt id=blog_text>
<p><font size=3>1, eMule源代码学习心得(1)：eMule代码的总体风格和其它相关工程</font></p>
<p><font size=3>eMule的官方首页上写着：2002年05月13日 一个叫做 Merkur 的人，他不满意原始eDonkey2000客户端并且坚信他能够做的更好，所以他开始制作。他聚集了其它开发人员在他的周围，并且eMule工程就此诞生。</font></p>
<p><font size=3>eMule是一个典型的MFC程序，它的图形界面等，已经和MFC紧紧融合到了一起。因此通常情况下它只能在windows平台下运行。有一些其它的工程，如aMule等，把它进行了移植，因此跨平台的功能要强些。</font></p>
<p><font size=3>其实还有另外一个叫做xMule的工程，不过现在已经人气快不行了。在aMule的主页上可以看到eMule移植到 linux平台下的一些历史，最早是有个叫做lMule的工程，他使用wxwidgets来进行eMule的跨平台的移植，这个工程2003年就不再更新了，后来转变成为xMule工程，它一度是linux平台下eMule的事实上的替代品。但是他们的程序员之间由于理念不同，发生了内讧，导致aMule 分裂出来，他们后来矛盾严重的时候曾经一度从理念问题上升到互相对对方进行人身攻击，并且曾经对对方的网站发动过DDos。后来aMule和xMule就是两个完全不同的工程，xMule现在只有HopeSeekr一个人在维护，基本上也没有什么更新了。这一点不仅让人感慨。今年寒假的时候我曾经和 HopeSeekr进行过一些交流，感觉他非常自信，经常拿着aMule的一部分代码来给我看，说你看看他们的代码这么这么写，这简直就是一陀xx嘛，这种代码在某些情况下肯定会Crash掉嘛，相反，你看看我们xMule的代码，这里是这样这样，肯定就不会有这种问题了。</font></p>
<p><font size=3>eMule从0.42版开始支持Kad技术，这是一个非常重要的里程碑。Kad是一种DHT的协议，它可以使节点之间互相保留一些其它节点的联系信息，并且利用这样一个&#8220;关系网&#8221;寻找到整个网络中的任何一个节点以及上面的资源，整个过程不需要任何中心服务器。因此向当年搞napster那样直接端掉中心服务器就搞跨napster网络一样来对付eMule的Kad网就毫无作用了。0.42版是2004年2月27日放出的，比eDonkey2000的OverNet晚了将近一年，但是它的Kad网络的规模却在迅速扩大。Overnet和eMule中的Kad使用的都是 Kademlia结构，但是具体的消息报文的格式有区别，因此两个DHT网络并不能互相兼容。OverNet直到现在，用户也仍然维持在十万左右，这是比较近的一篇文章写的。但是eMule的Kad网的规模有多大，目前却没有一个很准确的说法。由此也可以看出开源软件的力量。目前aMule的Kad网和 eMule的Kad网是兼容的，xMule中还没有Kad的支持。Kad协议的原始paper可以在我们实验室的机器上下到：</font></p>
<p><a href="http://bigpc.net.pku.edu.cn:8080/paper/new/by%20conference/IPTPS/IPTPS02/Kademlia%20A%20Peer-to-Peer%20Information%20System%20Based%20on%20the%20XOR%20Metric%28IPTPS02%29.pdf"><font color=#000080 size=3><u>http://bigpc.net.pku.edu.cn:8080/paper/new/by%20conference/IPTPS/IPTPS02/Kademlia%20A%20Peer-to-Peer%20Information%20System%20Based%20on%20the%20XOR%20Metric%28IPTPS02%29.pdf</u></font></a></p>
<p><font size=3>2. eMule源代码学习心得(2)：从emule.cpp开始，顺便谈如何编译emule</font></p>
<p><font size=3>先说一声抱歉，因为前两天刚回到家中，休息了一下，所以这两天没有更新。今天继续昨天的话题。</font></p>
<p><font size=3>eMule的代码结构非常合理。虽然代码量比较大，但是各个功能模块之间的划分都很合理。从它的工程文件里面就可以看出这一点。eMule把表示功能的代码文件和表示界面的代码文件分开了，Source Files和Header Files是实现功能的代码的源文件和头文件，而Interface Source和Interface Header是实现图形界面的源文件和头文件。由于eMule的代码量太大，本系列将跳过图形界面的实现，着重分析eMule的功能实现部分的代码，而且功能实现方面也主要挑主要的部分分析。本节将从emule.cpp开始分析，引出eMule中使用到的几个主要的功能实现的类，并大体描述它们的作用。最后介绍一下如何在VS2003下编译eMule。emule中还有不少模块实现的功能挺有用，我说的是在其它的程序里很有用，可以考虑在其它程序中进行复用。</font></p>
<p><font size=3>emule.cpp为类CemuleApp的实现。因此在运行时，首先会运行InitInstance进行一些初始化的工作。从这个函数里面我们也可以第一次看出那些即将在整个程序中发挥作用的类了。</font></p>
<p><font size=3>最开始的时候是计算出程序常用的一些目录，如配置文件，日志文件等。接下来是 ProcessCommandline，它的作用有两方面，第一是确认该eMule的运行方式，即命令行后面有没有参数，第二是确认目前eMule是不是只有一个实例在运行。在一般的情况下，双击eMule可执行文件是不会带参数的。但是通过点击链接或者打开关联文件的方式打开eMule则相当于带参数运行eMule。通过在注册表里添加一些项目可以让一个程序和某种链接或者某个后缀的文件产生关联。具体办法可以参见OtherFunctions.cpp 中的Ask4RegFix，BackupReg，RevertReg三个函数的功能。ProcessCommandline中通过创建带有名称的互斥信号量来确认是否有其它的eMule实例在运行。对于一个确定的名称，CreateMutex只能创建一个互斥信号量。因此通过该信号量是否创建成功就可以知道是否有其它eMule实例运行。如果有的话，而且又是带参数的那种模式，那么直接把这个参数使用Windows的消息机制发给那个窗口即可，接下来的代码无非就是如何找到另外一个叫"eMule"的家伙以及给它发个什么消息。pstrPendingLink是一个全局变量，表示将要被处理的命令行参数。它将会在初始化完成后一段时间后被处理。</font></p>
<p><font size=3>下面两个比较重要的类是CPreferences和CStatistics。前者掌握着程序的大部分配置数据，后者则进行各种统计。它们的特点都是有很多的成员变量，而且还是静态的，这种方式可以保证它们的唯一性，而且把这些变量统一到一个类管理。但是实际上并不需要了解每个变量的含义。thePrefs和theStats是这两个类的唯一的实例。</font></p>
<p><font size=3>在处理完其它一些事情，包括创建图形界面对象CemuleDlg后，接下来可以看到一排一排的创建新对象的语句。这些类将会实现eMule程序运行的主要功能，后面的系列将详细分析。</font></p>
<p><font size=3>最后描述一下如何在VS2003里编译eMule，由于eMule中用到了一些其它的库，因此官方在提供eMule 的源代码下载的同时如果也提供这些库的下载会使源码包变得很大。因此eMule选择了让开发者去那些库的官方网站下载它们的方式。一般来说，编译这种工程文件，很重要的地方是要保持各个库和主程序编译参数的一致性。这些编译参数中，最主要的参数有三个，字符集(多字节/Unicode)，调试/发行，单线程/多线程，这样排列组合一下就有八个版本了。因此编译的时候如果不注意，就会出现和程序设计无关的错误。</font></p>
<p><font size=3>eMule0.47a解压后自带id3lib库，还需要下载以下的库：</font></p>
<p><font size=3>zlib:<br></font><a href="http://www.gzip.org/zlib/"><font color=#000080 size=3><u>http://www.gzip.org/zlib/</u></font></a></p>
<p><font size=3>ResizableLib:<br></font><a href="http://sourceforge.net/projects/resizablelib/"><font color=#000080 size=3><u>http://sourceforge.net/projects/resizablelib/</u></font></a></p>
<p><font size=3>Crypto++:</font></p>
<p><a href="http://www.eskimo.com/%7Eweidai/cryptlib.html"><font color=#000080 size=3><u>http://www.eskimo.com/~weidai/cryptlib.html</u></font></a></p>
<p><font size=3>pnglib:</font></p>
<p><a href="http://www.libpng.org/pub/png/libpng.html"><font color=#000080 size=3><u>http://www.libpng.org/pub/png/libpng.html</u></font></a></p>
<p><font size=3>下载它们，解压它们，编译它们。编译的时候注意要和eMule的工程的参数一致：字符集为Unicode，支持多线程安全，调试版或者是发行版可以根据需要选择，但是也要和eMule的工程参数一致。这些库的解压包里通常都能找到VC的工程文件，但是版本低一些，直接转化就可以了。另外建议编译这些库的时候都选择生成静态库，不要生成动态的库，这样最后生成的可执行文件就可以自己运行了。编译完这些库，包括从其它地方下载的和eMule自带的id3lib库和CxImage库后，就可以开始编译emule了。而编译emule也无非就是注意让它能够在编译的时候找到所有的头文件，以及在链接的时候能够找到所有的库。在链接的时候能够找到所有的库可以通过修改工程文件里面的属性 -&gt;配置属性 -&gt;链接器 -&gt;输入-&gt;附加依赖项来完成。但是能够找到所有的头文件反而需要一些技巧了。由于emule的代码中对于这些库的头文件的包含，在一定程度上限定了那些库的路径和emule的工程的路径的相对位置，因此需要把那些解压过的库的目录移到一些合适的地方，有时还需要给这些目录改个名称。</font></p>
<p><font size=3>3. eMule源代码学习心得(3)：emule中最重要的几个基础设施<br>eMule中要读取的配置文件数量较多，每种配置文件都是自己定义的格式，为了方便读取和存储这些文件，eMule中有一个很重要的基础设施类来复制这些文件操作，它能够很方便得处理一些常用数据类型的读写，并且带有一定的安全保护机制。这项基础设施在SafeFile.cpp和SafeFile.h中实现。在kademlia\ io目录下有这项功能的另外一项实现。它们实现的功能基本上相似，但是kademlia\io目录下的版本实现的时候多了一个以Tag作为单位进行读写的功能。这些实现中和另外一项基础设施，那就是字符串转化密切相关。StringConversion.cpp和StringConversion.h是 eMule中专门复制各类字符串转化的基础设施，什么Unicode啊，多字节流啊，或者是UTF-8之类的，在这里转化全部都不是问题。关于字符串转化，个人推荐尽量使用Unicode的宽字符，这样可以最大程度得避免乱码。</font></p>
<p><font size=3>SafeFile.cpp或者kademlia\io目录下的实现都有这样的特点，那就是把数据操作的行为和数据操作的对象分割开来。它们都定义了一个抽象的数据操作的基类(在SafeFile.cpp中是CFileDataIO，在kademlia目录下是 DataIO.cpp实现的Kademlia::CDataIO)，这个类中只负责实现在逻辑上操作一项数据的行为，例如，要读取出一个32位的整型，那么就是读出四个字节到一个整型数值的地址中，要读取或者写入其它类型的数据用的是类似的方法。但是这个类把物理上进行数据操作的方法全部都声明为纯虚函数，即读出多少个字节，写入多少个字节这样的。有了这样一个基类，就可以非常方便得在它上面进行重载，把这些纯虚函数定义为向某块内存中进行读写的操作，就能很方便得将较为复杂的数据序列化到一块连续的内存，而如果这些纯虚函数是向文件读写的操作，那么自然就可以很方便得用来读写各种格式比较奇怪的自己定义的配置文件了。</font></p>
<p><font size=3>这些类要读取的数据对象通常有这些，各种整型，字符串，以及Tag类型。整型读写起来比较简单，从1个字节的，2个字节的到4个，8个或者16个字节类型的数据读写方法都比较类似。这里要稍微提一下16个字节的那种，16个字节是128位，是eMule中的Kad网的随机生成的ID的长度，也是eMule中常用的MD4的hash算法生成的结果的长度。通常用来直接存取整个的这样的一个ID。在kademlia\ utils目录下的UInt128.cpp实现了一个表示128位的整数的类，功能十分完善，可以进行一些算术操作，并且可以进行比较，这样为它以后作为 key出现在hash表中打下了基础。仔细学习UInt128.cpp中的代码实现可以学到很多在编写这种自定义的数据对象类型时应该注意的问题。</font></p>
<p><font size=3>数据操作中另外一项很重要的操作是字符串，总的原则是先写一个长度，再写内容。但是到具体的操作的时候就需要注意这些细节了，如长度是写4个字节还是两个字节，字符串的内容要不要用UTF-8进行编码。这些操作就需要和StringConversion.cpp紧密合作了。其实后者的字符串转化函数很多也是调用ATL的相关函数，只是在外面再包上一层MFC的CString。</font></p>
<p><font size=3>在kademlia\io\DataIO.cpp中实现的CDataIO中，还另外实现了按照Tag进行读写的功能。这在网络上交换共享文件的元信息非常重要，通常一个文件的元信息就可以分解成很多的Tag，如"文件名=xxx"，"文件长度=xxx"等等。也就是说，一个Tag就是表示某项属性等于某个值这样一个事实。在Opcodes.h这个文件中定义了很多的代码，其中就有很多常见的Tag的属性名称。 CDataIO类中存储Tag的属性名都是先存一个字节的类型，再存名称，最后按照类型存值。</font></p>
<p><font size=3>eMule中的这几项基础设施都是编写得比较好的，可以很方便得拿出来复用。像字符串编码的处理和具有一定数据结构的文件IO操作在很多地方都会很有用。eMule中这些类的实现基本上复制到其它的工程文件中只要稍微修改一下很快就能使用。以后我们还将看到eMule 中很多其它很有用的基础设施。 </font></p>
<p><font size=3>4. eMule源代码学习心得(4)：对自己的资源要了如指掌，CKnownFileList类的作用<br>emule作为一个文件共享方面的程序，首先要对自己共享的所有的文件的信息都十分清楚，类CKnownFileList的作用就是这样的，它在emule.cpp中随着cmuleapp类创建的时候被创建。</font></p>
<p><font size=3>CKnownFileList类使用了MFC的CMap类来维护内部的hash表，这也可以看出emule和MFC 的关系确实非常紧密。这里如果用STL的map其实也是可以的。它内部维护了一个已知的文件的列表和取消了的文件列表。这些hash表的关键字都是文件的 hash值。这样能够判断出文件名不同而内容相同的文件，而一般要让不同内容的文件有相同的hash值是非常困难的，这也是hash函数它设计的初衷。因此除非是碰上王小云教授这样的牛人，我们基本上可以认为，两个文件hash值相同就代表了它们内容相同。再来看CKnownFileList.cpp，这个文件其实并不长，因为管理一个列表确实不需要太多种类的操作，如果对于每个具体的文件有一个很强大的类来处理它的话。而这里确实有，它就是 CKnownFile。有了这么一个类，我们就可以看到，CKnownFileList类所需要做的工作就是能够根据一些信息查找到对应的 CKnownFile类，能够复制其它的列表中的信息，能够把所有的这些信息存成文件，然后下次emule运行的时候能够把这些信息快速恢复出来，最重要的是能够在完成以上工作的情况下不造成内存泄漏。</font></p>
<p><font size=3>CKnownFile类就是一个专门关注某个特定文件的信息的类，它仍然有其基类CAbstractFile。但是它和CAbstractFile类的主要区别就是CAbstractFile类只有基本的信息存取的功能，而CKnownFile能够主动的生成这些信息，例如，给一个文件的路径给CKnownFile，它能够主动地去获取和这个文件有关的一切信息，并且把它保存在自己的成员变量里 (CreateFromFile)。CKnownFile.cpp文件看上去比较长，是因为它做的工作比较多，现在版本的emule中，除了对某个文件进行全文hash以外，还采用了BT的方式，进行分块hash，这样在传输文件的时候，即使发生出错的情况，也可以不必重传整个文件，而只是重传有错误的那块，这种机制叫做高级智能损坏处理(AICH,Advanced Intelligent Corruption Handling)，这个机制以后再继续分析。</font></p>
<p><font size=3>CKnownFile把读到的文件信息都保存成一个一个的Tag。它在运行中会尽量得获取更多的文件信息，例如，对于媒体类型的文件，它能够调用id3lib库来获取诸如作者，唱片发行年代，风格等tag信息。如果是视频媒体文件，它还会去抓图(功能实现： CFrameGrabThread)。</font></p>
<p><font size=3>CKnownFile还能够随时掌握目前该文件的下载情况(内部有个CUpDownClient的列表)，当然，还会根据要求序列化和反序列化自己，LoadFromFile和WriteToFile都以CFileDataIO为参数，这样方便 CKnownFileList保存和读取它的列表中的所有文件的信息。 </font></p>
<p><font size=3>5. eMule源代码学习心得(5)：分块机制--正确传输资源的保证<br>为了加快内容分发的速度，分块处理是一种简单有效的方法。emule中对每个文件都进行了分块处理。另外分块还有一个好处就是如果保留了每一分块的hash值，就能在只下载到文件的一部分时判断出下载内容的有效性。emule在获取每个共享文件的信息时，就对它进行了分块处理，因此如果要知道emule中的分块处理和恢复机制，看 CKnownFile::CreateFromFile函数的实现就行了。</font></p>
<p><font size=3>这个函数中牵涉到的和分块处理以及hash计算相关的类都在SHAHashSet.cpp和SHAHashSet.h中。下面介绍其中几个主要的类：</font></p>
<p><font size=3>CAICHHash类只负责一块hash值，提供两个CAICHHash类之间的直接赋值，比较等基本操作。 CAICHHashAlgo是一个hash算法的通用的接口，其它hash算法只要实现这种接口都能使用，这样，可以很方便得使用不同的hash算法来计算hash值。CAICHHashTree则是一个树状的hash值组织方式，它有一个左子树和右子树成员变量，类型是指向CAICHHashTree的指针，这是一个典型的实现树状结构的方法。CAICHHashSet中包含了一个CAICHHashTree类型的变量，它直接向CKnownFile负责，代表的是一个文件的分块信息。</font></p>
<p><font size=3>SHAHashSet.h文件的开始的注释部分向我们解释了它的分块的方式。这里要用到两个常量9728000和 184320，它们分别是9500k和180k。这是emule中两种不同粒度的分块方式，即首先把一个很大的文件分割成若干个9500k的块，把这些块组织成一颗树状的结构，然后每一个这样的块又分解成若干个180k的块(52块，再加一个140k的块)，仍然按照树状的结构组织起来。最后总的结构还是一颗树。</font></p>
<p><font size=3>CKnownFile::CreateFromFile方法是在读取目标文件的内容时，逐步建立起这样一颗树的。 CAICHHashTree::FindHash能够根据读取到的目标文件的偏移量和下一块的大小，来找出对应的树枝节点(就是一个指向 CAICHHashTree的指针)。如果有必要的话，还会自动创建这些树枝节点。因此在进行分块操作的时候，把文件从头到尾读一边，整个 CAICHHashTree就建立起来了，对应的分块hash值也赋值好了。最后我们还需要注意的就是CKnownFile类中的hashlist变量。就是说它还单独保留直接以9728000字节为单位的所有分块的MD4算法的hash值。这样对于一个文件就有了两套分块验证的机制，能够适应不同场合。</font></p>
<p><font size=3>6. eMule源代码学习心得(6)：网络基础设施--网络基础设施的基础设施<br>MFC中已经有一些网络基础设施类，如CAsyncSocket等。但是emule在设计中，为了能够更加高效得开发网络相关的代码，构建了另外的一些类作为基础设施，这些基础设施类的代码也有很高的复用价值。</font></p>
<p><font size=3>首先是CAsyncSocketEx类。AsyncSocketEx.h中对这个类的特点已经给出了一定的说明。它完全兼容CAsyncSocket类，即把应用程序中所以的CAsyncSocket换成CAsyncSocketEx，程序仍然能够和原来的功能相同，因此在使用上更加方便。但是在这个基础上，它的效率更高，主要是在消息分发机制上，即它处理和SOCKET相关的消息的效率要比原始的MFC的 CAsyncSocket类更高。另外，CAsyncSocketEx类支持通过实现CAsyncSocketExLayer类的方式，将一个 SOCKET分成若干个层，从而可以很方便得实现许多网络功能，如设置代理，或者是使用SSL进行加密等。</font></p>
<p><font size=3>另外还有ThrottledSocket.h中定义的ThrottledControlSocket类和 ThrottledFileSocket类，这两个类只定义了两个接口。任何其它的网络套接字类如果想实现限速的功能，只需要在其默认的发送函数(如 Send或Sendto)中不发送数据而是把数据缓存起来，然后在实现ThrottledControlSocket或者 ThrottledFileSocket接口中的SendFileAndControlData或SendControlData方法时才真正把数据发送出去，这样就能实现上传限速，而这也是需要UploadBandwidthThrottler类进行配合， UploadBandwidthThrottler是一个WinThread的子类，平时单独运行一个线程。下一次会详细描述它是如何控制全局的上传速度的。 </font></p>
<p><font size=3>7. eMule源代码学习心得(7)：网络基础设施--全局限速器UploadBandwidthThrottler<br>UploadBandwidthThrottler 是emule中使用的全局的上传限速器。它继承了CWinThread类，且在该类被创建的时候，就新创建一个线程开始单独运行。在该类被析构时也会自动停止相应的线程。这个线程的目标函数就是RunProc，然后为了避免在RunProc函数不能使用this指针的情况，它使用了RunInternal 来实际完成工作线程的工作。在emule中，还有另外一个类LastCommonRouteFinder有类似的结构。</font></p>
<p><font size=3>UploadBandwidthThrottler中保存了若干的套接字(Socket)队列，这些队列的处理方式略有不同。在标准队列(m_StandardOrder_list)里面排队的都是实现了ThrottledFileSocket接口的类，通常这些类能够传输文件内容也可以传输控制信息。而其它四个队列都是实现ThrottledControlSocket接口的类的队列，在这些队列中的类主要以传输控制信息为主。这四个队列为临时高优先级，临时普通优先级，正式高优先级，正式普通优先级。和把套件字直接添加到普通队列 (AddToStandardList)不同，QueueForSendingControlPacket把要添加到队列的套接字全部添加到两个临时队列。根据它们的优先级添加到普通的临时队列。在RunInternal的大循环中，临时队列中的项目先被移到普通队列中，然后再进行处理。</font></p>
<p><font size=3>UploadBandwidthThrottler使用了两个临界区，两个事件。pauseEvent是用来暂停整个大循环的动作的。而threadEndedEvent是标志整个线程停止的事件。sendLocker是大循环中使用的主要的临界区，而 tempQueueLocker是为两个临时队列额外添加的锁，这样可以一边发送已有队列中的套界字要发送的数据，一边把新的套接字加到队列中。</font></p>
<p><font size=3>UploadBandwidthThrottler的RunInternal中的大循环是该工作线程的日常操作。这个大循环中做了以下事情，计算本次配额，即本次循环中能够发送多少字节，好安排调度，计算本次循环应该睡眠多少时间，然后进行相应的睡眠，从而进行限速。操作控制信息队列，发送该队列中的数据，注意，控制队列中的套接字(m_ControlQueueFirst_list和 m_ControlQueue_list)只使用一次就离开队列。而标准队列中的套接字不会这样。在一轮循环结束后，如果还有没有用完的发送数据的配额，则会有部分配额保存到下一轮。</font></p>
<p><font size=3>8. eMule源代码学习心得(8)：网络基础设施--emule套接字CEMSocket<br>CEMSocket 是CAsyncSocketEx和ThrottledFileSocket的子类，它把若干功能整合到了一起，因此可以作为emule使用起来比较方便的套接字。例如它可以很方便得指定代理，把CAsyncSocketEx中的创建一个新的代理层并且添加到列表中的功能对外屏蔽了。另外它可以分出状态，如当前是否在发送控制信息等。</font></p>
<p><font size=3>CEMSocket中我们需要仔细考察的是它的SendControlData和 SendFileAndControlData方法。如前所述，这些方法是用来和UploadBandwidthThrottler进行配合，以便完成全局的限速功能的。它的功能应该是按照UploadBandwidthThrottler的要求，在本次轮到它发送数据时发送指定数量的字节数。因此，应用程序的其它部分在使用CEMSocket时，如果要达到上传数据限速的目的，不应该直接调用标准的Send或者SendTo方法，而是调用 SendPacket。这里就有了另外一个结构Packet，它通常包含一个emule协议中完整的包，例如有协议的头部数据等，还内置了 PackPacket和UnPackPacket方法，可以自行进行压缩和解压的功能。SendPacket把要发送的Packet放到自己的队列中，这个队列也有两个，控制信息包队列，和标准信息包队列。如果有必要，把自己加入到UploadBandwidthThrottler的队列中。</font></p>
<p><font size=3>我们注意到CEMSocket的SendControlData和SendFileAndControlData方法其实都是调用自己的另一个重载的Send方法。而且我们也已经知道这个方法是在UploadBandwidthThrottler的工作线程中的大循环中被调用的，而这个Send方法的内容本身也是一个大循环，但是意义很明了，就是在不超过自己本次发送的配额的情况下，把自己的包队列中的包取出来，并且发出去。同样，这里也用到了一个临界区，它是为了保证从包队列中取出包来发送和把包往队列中放的操作是互斥的。因此，如果把它和 UploadBandwidthThrottler结合起来，我们就看到了一个两层的队列，即所有的套接字组成了一个发送队列，在 UploadBandwidthThrottler的控制下保证了对速度的限制，而每个套接字即将发送的数据包又组成了一个队列，保证了每次进行数据发送的时候都会满足UploadBandwidthThrottler的要求。 </font></p>
<p><font size=3>9. eMule源代码学习心得(9)：搜索信息集－CSearchList<br>CSearchList是 emule中的搜索列表，掌管emule中所有的搜索请求。CSearchFile是这个列表中的元素，代表了一次搜索的相关信息。它们的关系和之前描述的已知文件和已知文件列表有一些类似的地方。CSearchList的主要任务就是对其一个叫做list的类型为CSearchFile列表的内部变量进行维护，提供很方便得往这个列表中添加，删除，查询，变更等操作的接口。另外，每一个搜索都有一个ID，是一个32位的整数。CSearchList中记录了每个搜索目前搜到的文件个数和源的个数(m_foundFilesCount和m_foundSourcesCount)。</font></p>
<p><font size=3>CSearchFile是CAbstractFile的另一个子类(CKnownFile也是)，它保存了某个文件和搜索相关的信息，而不是这个文件本身的信息(这些信息在CAbstractFile中已经包括了)，这些和搜索有关的信息就是都在哪些机器上有这个文件，以及哪个服务器上搜到的这个文件。甚至还可以向搜索文件添加预览。在这个类的定义中嵌套定义了两个简单的结构SServer和SClient，表示了该搜索文件的可能来源，服务器或者其它客户端。m_aClients和m_aServers是这两个简单结构的一个数组，CSearchFile自然也提供了对这个数组的操作的接口，方便CSearchList使用。</font></p>
<p><font size=3>CSearchList对外提供了搜索表达的接口，即每当有一个新的搜索提交时CSearchList:: NewSearch会建立一个新的搜索项，但是此时还没有任何对应的搜索文件，因此只是在文件个数和搜索ID的对应表 (m_foundFilesCount和m_foundSourcesCount)中建立新的项目。另外当有搜索结果返回时 ProcessSearchAnswer或ProcessUDPSearchAnswer能够对返回的包直接做处理，创建相应的搜索文件信息 CSearchFile对象，并加入到自己的列表中。当然，要把重复的搜索结果去除，发现同一个hash的文件的多个源时也会给它们建立一个二级列表 (CSearchFile::m_list_parent)。现在我们可以看出，CSearchList只负责和搜索有关的信息的储存和读取，本身并不进行搜索。 </font></p>
<p><font size=3>10. eMule源代码学习心得(10)：服务器信息集－CServerList<br>嗯，明天要回北京了，今天打算好好休息一下，故挑了个内容少的来说，呵呵。</font></p>
<p><font size=3>尽管目前有了Kad网络，但是使用服务器来获取各个emule用户的共享文件列表仍然是emule中主要的资源获取方式。CServerList就是emule中负责管理服务器列表的类。和前面若干列表类结构类似，CServerList需要对外提供列表的增加，删除，查找，修改等接口。在CServerList中，每个服务器的信息是一个CServer类。和搜索信息不一样，但是和已知文件列表一样，服务器的信息列表是需要长期保留的，因此CServerList和CKnownFileList类一样提供了把它所包含的所有信息保存到一个文件中，以及从这个文件中读回其信息的功能。</font></p>
<p><font size=3>CServer中的结构比较简单，只需要保留服务器的各种信息即可。它可以通过IP地址和端口来创建，也可以通过一个简单的结构ServerMet_Struct来创建，其中后者是用来直接从文件中读取的。该结构仅仅包含IP地址和端口以及属性的个数，CServer 中其它的属性在保存到文件中时，均采用Tag方式保存。</font></p>
<p><font size=3>CServerList除了提供通常的CServer信息外，还提供一些统计信息诸如所有的服务器的用户数，共享的文件数等。这些统计信息也是基于每个单独的CServer的相关信息计算出来的。</font></p>
<p><br><font size=3>11. eMule源代码学习心得(11)：emule的通信协议－一些基本的约定<br>接下来将不可避免得要碰到emule的协议。emule的通信协议格式设计成一种便于扩充的格式。对于TCP连接来说，连接中的数据流都能够划分成为一个一个的 Packet，CEMSocket类中就完成了把接收到的数据划分成Packet这一工作。但是具体的对于每个Packet进行处理的工作被转移到它的子类中进行。CEMSocket类的两个子类CServerSocket和CClientReqSocket所代表的TCP连接就分别是客户端和服务器之间的TCP连接以及客户端之间的TCP连接。在数据流中的第一个字节代表的是通信的协议簇代码，如0xE3为标准的edonkey协议，0xE4为 kademlia协议等等。接下来的四个字节代表包内容的长度，所有的包都用这种方式发送到TCP流中，就可以区分出来了。另外每个包内容中的第一个字节为opcode，即在确定了某个具体协议后，这个opcode确定了这个包的具体含义。</font></p>
<p><br><font size=3>对于走UDP协议的包，处理起来更加得简单，因为UDP本来就是以一个包一个包作为单位在网络上流传的，因此不需要在包的内容中再包含表示长度的字段。每个UDP包的第一个字节是协议簇代码，其它内容就是包的内容。CClientUDPSocket类负责处理客户端和客户端之间的UDP包，而CUDPSocket类负责处理客户端和服务器之间的UDP包。另外还有个Kademlia:: CKademliaUDPListener类，专门处理和Kademlia协议相关的UDP包。</font></p>
<p><font size=3>最后说一下Packet类，这个类以前只是提到过。它是emule的通信协议的最小单位。我们可以看出，它的构造函数有多个版本，这也是为了可以用不同的方式来创建Packet。例如只包含一个头部信息的缓冲区，或者只是指定协议簇代码等。而且它内部实现了压缩和解压的方法，该方法直接调用zlib库中的压缩方法，可以减少数据的传输量。这里要注意一点的就是压缩的时候协议簇代码是不参与压缩的，压缩完毕后会更换协议簇代码，例如代码为标准edonkey协议0xE3的包在压缩后，协议代码就变成0xD4了，这里进行协议代码变化是为了使接受方能够正确识别并且进行相应的解压操作。</font></p>
<p><font size=3>12. eMule源代码学习心得(12)：emule的通信协议－客户端和服务器之间的通信概述<br>客户 端和服务器之间的所有通信由类CServerConnect掌握。CServerConnect本身不是套接字的子类，但是它的成员变量 CServerSocket类型的connectedsocket是。CServerConnect内部有一列表，可以保存若干 CServerSocket类型的指针。但是这并不说明它平时连接到很多服务器上。它只是可以同时试图连接到若干个服务器上，这只是因为连接到服务器上的行为不一定能成功。</font></p>
<p><font size=3>CServerSocket类是CEMSocket的子类，它比CEMSocket要多保存一些状态，比如当前的服务器连接状态。它同时还保留它当前所连接的服务器的信息。通过分析CServerSocket::ProcessPacket就可以直接把emule客户端和服务器之间的通信协议理解清楚，这里是服务器发回的包。TCP连接建立后的第一个包是在CServerConnect:: ConnectionEstablished中发出的，即向服务器发出登陆信息。如果登陆成功，则能够从服务器处获取自己的ID，这是一个32位的长整数。如果这个数小于16777216，那么我们称它为LowID。具有LowID的客户端通常情况下其它客户端将不能直接连接它。得到LowID的原因比较多，例如当自己处于NAT的后端的时候。获取自己的ID后将会向服务器发送自己的共享文件列表，这一动作由共享文件列表类 CSharedFileList来完成。</font></p>
<p><font size=3>其它类型包没有必要全部都列出来，以后可以通过在分析其它部分时，因为牵涉到往服务器发送或者接受数据的时候再进行相应的分析。 </font></p>
<p><font size=3>13. eMule源代码学习心得(13)：emule的通信协议－客户端和客户端之间的通信概述<br>客户端和客户端之间的TCP通信由CListenSocket和CClientReqSocket完成。这也是提供网络服务的应用程序的典型写法。其中 CListenSocket只是CAsyncSocketEx的子类，只负责监听某个TCP端口。它只是内部有一个CClientReqSocket类的列表。而CClientReqSocket是CEMSocket的子类，因此它能够自动完成emule的packet识别工作。它有 ProcessPacket和ProcessExtPacket来处理客户端和客户端之间的包，其中前者是经典的eDonkey协议的包，后者是 emule扩展协议的包。</font></p>
<p><font size=3>CListenSocket和CClientReqSocket类之间的关系和前面分析的列表类和它对应的成员类的关系是相似的，CListenSocket提供对自身的CClientReqSocket列表中的元素的增加，查询，删除等操作。同时也维护关于这些成员的一些统计信息。我们注意到CListenSocket在其构造函数中就把自己添加到CListenSocket类 (theApp.listensocket，该类的唯一实际示例)的列表中。</font></p>
<p><font size=3>CClientReqSocket类和CUpDownClient类之间存在着对应关系。它们都表示了另外一个客户端的一些信息，但是CClientReqSocket类主要侧重在网络数据方面，即负责两边的互相通信，而CUpDownClient类负责的是从逻辑上对网络另一边的一个客户端进行表达。CUpDownClient类代码很长，以后再说。 </font></p>
<p><font size=3>14. eMule源代码学习心得(14)：emule中的信誉机制<br>信誉机制在P2P系统中有非常重要的作用。为了使用户更加愿意共享自己的资源，需要有一些机制能够让对整个P2P系统贡献更大的用户有更多的激励。在emule中，激励机制的设计方案是 tit-for-tat这种最直观的方案。这种方案的意义就是最简单的如果别人对你好，那么你也对别人好。</font></p>
<p><font size=3>下面看实际的实现。CClientCreditsList和CClientCredits类负责emule中的信誉机制。我们再次见到这种列表和元素之间的关系，不必再重复那些语言。和信誉相关的信息是需要永久保存的，这样才有意义，因此 CClientCreditsList提供了LoadList和SaveList方法。我们另外注意到，CClientCredits类可以使用 CreditStruct结构来创建，而CreditStruct结构只包含静态信息。主要是上传量和下载量等。</font></p>
<p><font size=3>信誉机制的信息需要有一定的可靠性，在emule中采用了数字签名的方式来做到这一点。Crypto++库为 emule全程提供和数字签名验证相关的功能。CClientCreditsList在创建时，会装载自己的公钥私钥，如果没有的话，会创建一对。 CClientCreditsList中包含的有效的信息都是经过其它人数字签名的，所以更加有信服力。在实际使用中，这些信息和自己的私钥要注意保存。重装emule后应该把配置文件目录先备份，这样能够保留自己辛辛苦苦积攒的信誉。 </font></p>
<p><font size=3>15. eMule源代码学习心得(15)：下载任务即部分文件的表示<br>CPartFile类是 emule中用来表示一个下载任务的类。从它的名字也可以看出来，这就是一个还没有完成的文件。当一个下载任务被创建时，emule会在下载目录中创建两个文件，以三位数字加后缀part的文件，例如001.part，002.part等。还有一个以同样的数字加上.part.met的文件，表示的是对应文件的元信息。part文件会创建得和原始文件大小一样，当下载完成后，文件名会修改成它本来的名称。而事实上，诸如这个文件原来叫什么名称，修改日期等等信息都在对应的.part.met元文件中。.part.met中还包含了该文件中那些部分已经下载完成的信息。</font></p>
<p><font size=3>CPartFile类中Gap_Struct来表示文件的下载情况，一个Gap_Struct就是一个坑，它表示该文件从多少字节的偏移到多少字节偏移是一个坑。下载的过程就是一个不断填坑的过程。CPartFile类中有个成员变量gaplist就是该文件目前的坑的状况列表。需要主要的是有时填了坑的中间部分后，会把一个坑变成两个坑。坑的列表也会被存进.part.met中。</font></p>
<p><font size=3>CPartFile类的代码很庞大，但是这是必须的。首先，它的创建就有几种可能，从搜索文件 CSearchFile中创建，这种情况发生在用户搜索到他想要的文件后点击下载时发生。从一个包含了ed2k链接的字符串中创建，它会提取出该ed2k 链接中的信息，并用来创建CPartFile。剩下的一种，就是当emule程序重启后，恢复以前的下载任务。这时就是去下载目录中寻找那些.part 和.met文件了。另外它还需要不断得处理下载到的数据，为了减少磁盘开销，使用了Requested_Block_Struct结构来暂存写入的数据。它内部维护一个CUpDownClient的列表，如果知道了该文件的一个新的来源信息，就会创建一个对应的CUpDownClient。后者是 emule中代码量最大的类。它还要把它的状态用彩色的条装物显示出来提供给GUI。</font></p>
<p><font size=3>前面提到的AICH机制对于最大程度得保证下载文件的正确性以及尽量减少重复传输都有很大的帮助，在下载的过程中，该机制会经常对下载到的数据进行校验。</font></p>
<p><font size=3>最后提一下它的Process方法。该方法是emule中为了尽量减少线程的使用而采取的一种有一些类似于轮询的机制。其它很多类中也有Process方法，这个方法要做的事情就是在一些和日常运行有关的事情，例如检查为了下载该文件而链接到自己的各个客户端的状态，向它们发送下载请求等。</font></p>
<p><font size=3>16. eMule源代码学习心得(16)：下载任务队列<br>CDownloadQueue是下载队列类。这个队列中的项目是CPartFile指针。因此和emule中出现的很多其它的列表类一样，它需要能够提供对这个列表中的元素进行增加，查询，删除的功能。例如查询的时候能够根据该文件的hashID或者索引来进行查询。CDownloadQueue同时还要完成一些统计工作。</font></p>
<p><font size=3>和其它的列表类不一样的是，它的所有元素的信息并不是集中存放于一个文件，而是对应于每一个下载任务，单独得存放在一个元信息文件(.part.met)中，因此当该类进行初始化的时候，它需要寻找所有可能的下载路径，从那些路径中找到所有的.part.met文件，并且试图用这些文件来生成CPartFile类，并且将这些通过.part.met文件正确生成的CPartFile类添加到自己的列表中，同样，在退出时，所有的下载任务的元信息也是自行保存，不会合成为一个文件。</font></p>
<p><font size=3>CDownloadQueue中的Process方法的主要任务就是把它的列表中的CPartFile类中的 Process方法都调一遍，另外主要的一些关于下载情况的统计信息也是在每一轮的Process后进行更新的。从这里我们也可以看出Process方法在emule中的意义，就是一个需要经常执行的方法，通过经常执行它们来完成日常工作，而且所有的这些Process方法肯定是顺序执行，因此可以减少很多多线程的同步之类的问题。emule中已经尽量减少了多线程的使用，但是在很多地方如果多线程是不可避免的话，也不会排斥。 </font></p>
<p><font size=3>17. eMule源代码学习心得(17)：上传任务队列<br>CUploadQueue是上传队列类。这个列表类中只有以CUpDownClient为元素的列表，它和其它列表类还有一个很大的不同就是它所保存的信息都不需要持久化，即不需要在当前的 emule退出后还记住自己正在给谁上传文件，然后下次上线的时候再继续给他们传，这在大部分情况下是没有意义的。</font></p>
<p><font size=3>上传队列类列表中有两个列表，上传列表和排队列表。当一个收到一个新的下载请求后，它会把对应的客户端先添加到排队列表中，以后再根据情况，把它们不断添加到上传列表中。在这里，信誉机制将会对此产生影响。</font></p>
<p><font size=3>CUploadQueue的Process方法就相对简单了，那就是向上传队列中的所有客户端依次发送数据，而排队的客户端是不会得到这个机会的。另外它还需要完成关于上传方面的一些统计信息。</font></p>
<p><font size=3>另外我们还需要注意在CUploadQueue的构造函数里面，创建了一个以100毫秒为间隔的定时器，这个定时器成为以上所有的Process所需要的基础。我们看它的UploadTimer就可以看出这一点。这里面充斥了各个类的Process方法的执行，其中包括以前我们提到的一些类，但是没有提到它们的Process方法，因为其过于简单，基本上就只是更新了一下要保存的信息。 </font></p>
<p><font size=3>18. eMule源代码学习心得(18)：emule中代码量最大的类CUpDownClient<br>CUpDownClient 类的作用是从逻辑上表示一个其它的客户端的各种信息，它是emule中代码量最大的类。我们注意到，定义它的头文件是UpDownClient.h，但是却没有对应的CUpDownClient.cpp，而它的实现，都分散到BaseClient.cpp，DownloadClient.cpp， PeerCacheClient.cpp，UploadClient.cpp和URLClient.cpp中。</font></p>
<p><font size=3>BaseClient.cpp中实现的是该类的一些基本的功能，包括基本的各种状态信息的获取和设置，以及按照要求处理和发送各种请求。在这里，逻辑实现和网络进行了区分，CUpDownClient类本身不从网络接受或者发送消息，它只是提供各种请求的处理接口，以及在发送请求时，构造好相应的Packet，并交给自己对应的网络套接字发出去。</font></p>
<p><font size=3>DownloadClient.cpp中实现的是和下载相关的功能，它包括了各种下载请求的发送以及相应的数据的接收。另外还有一个A4AF的机制，它是emule中的一个机制，因为一个客户端在同一个时间内只能向另外一个客户端请求同一个文件。这样，对于很多个下载任务(CPartFile)，有可能出现它们的源(即有该文件的客户端)有部分重叠的现象，而这时，如果其它下载任务正在从这个源下载，那么当前的下载任务就不能从这个源下载了。但是emule允许用户对其手动进行控制，如对下载任务的优先级进行区分，这样他就可以将一个源从另外一个下载任务那里切换过来。A4AF其实就是ask for another file的简称。</font></p>
<p><font size=3>UploadClient.cpp中实现的是上传相关功能，即接受进来的下载请求，并且生成相应的文件块发送出去。</font></p>
<p><font size=3>PeerCacheClient.cpp实现的是和PeerCache相关的功能，PeerCache是一个由 Joltid公司开发的技术，它可以允许你从ISP提供的一些快照服务器上快速得上传或者下载一些文件(或者是一部分)，这个技术的好处是可以减少骨干网络的带宽消耗，将部分本来需要在骨干网上走的流量转移到ISP的内部。当然这个功能需要ISP的配合。如果发现ISP提供了这项服务的话，emule会利用它来减少骨干网的带宽消耗。</font></p>
<p><font size=3>URLClient.cpp实现的功能是利用http协议对原有的emule协议进行包装，以便使它能够尽可能地穿越更多的网络的防火墙。 </font></p>
<p><font size=3>19. eMule源代码学习心得(19)：emule常规部分小结<br>emule中还有其它的很多类，它们使得emule的功能更加的强大和完善。有很多类在前面没有提到，但是不代表它没有作用。而且即时是前面提到的类也只是大体的介绍，它们之间互相配合的一些细节没有体现。但是这些细节应该已经可以通过对它们的大体的功能的了解而更加容易被把握。至于GUI的设计，它也最终是要对应到某个功能实现类的数据的。</font></p>
<p><font size=3>对于emule中的通信协议只是大体得描述了一下它的数据包的格式，但是并没有详细得描述它的每一个Opcode对应的包的意义，因为我认为这是没有必要的，在知道通信协议的格式以及处理它们的代码所在的位置后，可以很简单的通过追踪某条消息的前因后果把整个通信协议都分析出来。</font></p>
<p><font size=3>这里再稍微提一下在emule中使用到的其它类及其功能。我们可以看到，如果单纯只是为了能够搜到以及下载到文件的话，有不少类是可以精简的，但是，正是由于它们的存在，使得emule的功能更加的完善。CIPFilter，IP地址过滤器，通过识别各种类型的IP地址过滤信息，它能够把不希望连接的网络地址过滤掉，emule中所有需要连接网络的地方使用的都是统一的过滤数据。CWebServer能够在本地打开一个Web服务器，然后你可以通过浏览器来控制你的emule。CScheduler能够实现下载任务的定时下载。CPeerCacheFinder为前面提到的PeerCache技术的主控制类。另外，emule还内置了一个IRC客户端，一个主要成员函数都为静态的CPartFileConvert类，能够对其它版本的驴的下载文件进行转换。它甚至还提供了一个自动处理zip和rar的类CArchiveRecovery。</font></p>
<p><font size=3>Kademlia网络是emule中相当重要的一部分，因此特意把这一部分单独拿出来，把它放在这个小结之后进行描述。</font></p>
<p><font size=3>20. eMule源代码学习心得(20)：emule中的Kademlia代码总体描述<br>首先要说一下抱歉，这两个星期的事情比较多，所以现在才来写。不过总算是把这些东西写完了。而且最后用的代码是0.47c。</font></p>
<p><font size=3>当emule中开始使用Kademlia网络后，便不再会有中心服务器失效这样的问题了，因为在这个网络中，没有中心服务器，或者说，所有的用户都是服务器，所有的用户也是客户端，从而完完全全得实现了P2P。接下来讲针对emule中的Kademlia网络进行分析，会有一节进行原理方面的分析。另外的几节将会根据emule中实现Kademlia所使用的不同的类分别进行讲述。其中：</font></p>
<p><font size=3>CKademlia是整个Kademlia网络的主控类，可以直接开始或者停止Kademlia网，并且含有Process方法来处理日常事务。</font></p>
<p><font size=3>CPrefs负责处理自身的Kademlia相关信息，如自身的ID等。</font></p>
<p><font size=3>CRoutingZone，CRoutingBin和CContact三个类组成了每个节点所了解的联系信息以及由这些联系信息所组成的数据结构。</font></p>
<p><font size=3>CKademliaUDPListener负责处理网络信息。</font></p>
<p><font size=3>CIndexed负责处理本地存储的索引信息。</font></p>
<p><font size=3>CSearch，CSearchManager负责处理和搜索有关的操作，其中前者表示的是一个单一的搜索任务，后者负责对所有搜索任务进行处理。</font></p>
<p><font size=3>CUInt128负责处理一个128位的长整数，并且内置其各种运算。前面已经提到过</font></p>
<p><font size=3>21. eMule源代码学习心得(21)：emule中的Kademlia的基本原理<br>Kademlia 是一种结构化的覆盖网络(Structured Overlay Network)，所谓的覆盖网络，就是一种在物理的Internet上面再次构建的虚拟网络，所有参与的节点都知道一部分其它节点的IP地址，这些节点称为它的邻居，如果需要查找什么东西，它先在本地寻找，如果找不到，就把这个查询转发到它的邻居处，希望能够有可能查找到相应的结果。覆盖网络里面分成了结构化和非结构化的两种情况，它们的区别在于每个节点知道哪些其它节点的信息是否有特定的规律。在非结构化的覆盖网中，每个节点的邻居状况没有特定的规律。因此在非结构化网络中，如果要进行查询，会采取一种叫做泛洪(flooding)的方法，每个节点如果在本地没有查找到想要的结果，会把查找请求转发到它的邻居中，然后再通过邻居的邻居这种方式来进行一步步的查找。但是这种方法如果处理不好，会造成整个网络的消息负载过大。已经有不少文章对于优化非结构化覆盖网络中的查询进行了很深入的探讨。</font></p>
<p><font size=3>对于结构化的覆盖网络，它的特点是每个节点它会选择和哪些节点做邻居是有一定的规律的，从而在进行搜索的时候，节点把搜索请求进行转发的时候它能够通过一定的规律进行选择把请求转发到哪些邻居节点上。这样同时也能减少搜索代价。结构化的覆盖网络通常要求每一个节点随机生成一个ID，用以判断各个节点之间的关系。这个ID和它所在的物理网络必须是没有关系的。</font></p>
<p><font size=3>对于Kademlia网络来说，这个ID是一个128位的数值，所有的节点都用这个ID来衡量自己与其它节点的逻辑距离。而逻辑距离的计算方法就是将两个节点进行异或(XOR)操作。在Kademlia网络的形成过程中，每个节点选择邻居的原则是离自己逻辑距离越近的节点越有可能被加入到自己的邻居节点列表中，具体来说就是在每次新得到一个节点的信息的时候，是否把它加入到自己的邻居节点列表是根据距离的远近来处理的。后面分析具体程序的代码时会有说明。</font></p>
<p><font size=3>结构化的网络的好处就是如果我们要寻找一个距离某个ID逻辑距离足够近的节点，我们可以保证在O(logn)级别的跳数找到。只要先寻找自己已知的离目标ID逻辑距离足够断的节点，然后再问它知不知道更近的，然后就这样下去。因此在搜索的时候也是这样，当需要发布资源的时候，把文件进行hash，这样就能够计算出一个128位的ID，或者把关键字进行hash。然后寻找到离这个结果逻辑距离最近的节点，把文件或者关键字的信息发送给它，让它存起来。当有人要搜索同样的东西的时候，由于它用的是同一个hash算法，因此能够计算出对应的ID，并且去搜索那些和这个ID逻辑距离相近的节点，因为它知道，如果网络中真有这些资源的话，这些节点是最有可能知道这些信息的。由此我们可以看出，结构化的网络的资源查找效率是很高的，但是它和非结构化的覆盖网络比起来，缺点是不能进行复杂查询，即只能通过简单的关键字或者文件的hash值进行查找。非结构化的网络的查找本身就是随意转发的，每个收到的查询请求的节点都对本地的资源掌握的很清楚，因此自然可以支持复杂查询，但是显然非结构化的网络支持的复杂查询不太可能动员所有的节点都来做这一动作。目前还没有方法能够把两种覆盖网络的优点结合起来，我也非常想知道这样的一种方法。</font></p>
<p><font size=3>22. eMule源代码学习心得(22)：emule中的Kademlia的基础设施类<br>Kademlia 的主控类是CKademlia，它负责启动和关闭整个Kademlia网的相关代码。在它的Process函数中，会处理和Kademlia网相关的事务，例如隔一段时间检查某个区间的节点数是否过少，如果是则寻找一些新的节点。另外经常对自己的邻居进行检查等，这些都是属于需要进行日常安排的工作。所有搜索任务的日常处理也需要它来调度。它还作为Kademlia网的代表，向emule其它部分的代码返回Kademlia网的一些统计信息。</font></p>
<p><font size=3>另一个基础设施类是CPrefs，它和emule普通代码中的CPreferences作用类似，但是CPrefs只保留和Kademlia网相关的，需要长期保存的本地信息。具体到这个版本来说，主要就是本地的ID。</font></p>
<p><font size=3>还有一个很重要的基础设施就是CUInt128，实现对128位的ID的各种处理，前面的部分已经提到。</font></p>
<p><font size=3>23. eMule源代码学习心得(23)：emule中的Kademlia的联系人列表管理<br>CRoutingZone，CRoutingBin和CContact三个类组成了联系人列表数据结构。它要达到我们搜索的要求，即搜索到目标的时间要能够接受，而且所占用的空间也要能够接受。</font></p>
<p><font size=3>首先CContact类包含的是一个联系人的信息，主要包括对方的IP地址，ID，TCP端口，UDP端口，kad 版本号和其健康程度(m_byType)。其中健康程度有0-4五个等级。刚刚加入的联系人，也就是健康状况未知的，这个数值设置为3。系统会经常通过与各个联系人进行联系的方式对其进行健康状况检查，经常能够联系上的联系人，这个数值会慢慢减少到0。而很就没有联系的，这个数值会慢慢增加，如果增加到4 后再过一段时间未能成功联系上的，则将会被从联系人列表中删除。</font></p>
<p><font size=3>CRoutingBin类包含一个CContact的列表。这里要注意的是要访问联系人的信息必须通过某个 CRoutingBin，CRoutingZone内部是不直接包含联系人信息的。可以把新的联系人信息往一个特定的CRoutingBin中加，当然也可以进行联系人查找。它也提供方法能够寻找出离某个ID距离最近的联系人，并给出这样的一个列表。这是相当重要的。最后，一个CRoutingBin类中能够包含的CContact的数量也是有限制的。</font></p>
<p><font size=3>CRoutingZone类处于联系人数据结构的最上层，直接为Kademlia网提供操作接口。该类的结构为一个二叉树，内含两个CRoutingZone指向它的左子树和右子树，另外也包含一个CRoutingBin类型的指针。但是只有在当前的 CRoutingZone类为整个二叉树的叶节点时，这个指向CRoutingBin类型的指针才有意义。这个二叉树的特点是，每个节点以下的所有联系人的ID都包含一个共同前缀，节点的层数越深，这个共同前缀越长。例如，根节点的左子树的所有的节点的ID一定有一个前缀"0"，而右子树的所有节点一定有前缀"1"。同样，根节点的左子树的右子树下的所有节点的ID一定有前缀"01"，等等，依此类推。我们设想一下节点不断得往这个二叉树添加的过程。刚开始只有一个根节点，它也就是叶节点，这时它内部的CRoutingBin是有意义的，当联系人信息不断得被添加进去以后，这个CRoutingBin的容量满了，这时要进行的就是一个分裂的操作。这时，会添加两个左子节点和右子节点，然后把自身的CRoutingBin中的联系人信息按照它们的前缀特点分别复制往左节点和右节点，最后把自身的CRoutingBin废除掉，这样这个分裂过程就完了。当分裂完成后，就会再次试图添加该联系人信息，此时会试图按照它的ID，把它添加到对应的子树中。但是并不是所有的这种情况节点都会发生分裂，因为如果允许任意分裂的话，本地所需存储的节点信息数量就会急剧上升。这里，自身ID的作用就体现了。只有当自身ID和当前准备分裂的节点有共同前缀时，这个节点才会分裂，而如果判断到一个节点不能分裂，而它的 CRoutingBin又满掉了，那么就会拒绝添加联系人信息。</font></p>
<p><font size=3>我们可以看出，在以上政策的进行下，离自身ID逻辑距离越近(也就是共同前缀越长)的联系人信息越有可能被加入，因为它所对应的节点越有可能因为分裂而获得更多的子节点，也就对应了更多的容量。这样，在Kademlia网中，每一个参与者知道的其它参与者信息中，离自己逻辑距离越近的参与者比例越高。由于在搜索的时候也只需要不断得寻找更近的ID，而且每一步都一定会有进展，所以寻找到目标ID所需要的时间上的代价是 O(logn)，从这个二叉树的结构来看，我们也可以看到，由于只有部分节点会分裂，所以实质上存储所需要的空间代价也是O(logn)。</font></p>
<p><font size=3>实际上CRoutingZone在实现时和理论上的Kademlia有一些区别，如从根节点开始，有一个最低分裂层数，也就是说，如果层数过低的话，是永远允许分裂的，这样它知道的其它地区的联系人信息就能够稍微多一些。 </font></p>
<p><font size=3>24. eMule源代码学习心得(24)：emule中的Kademlia网络消息处理<br>CKademliaUDPListener 负责处理所有和Kademlia网相关的消息。前面已经对emule的通信协议的基本情况做了一个大概的描述，我们就可以知道， CKademliaUDPListener处理的消息一定是只和Kademlia网相关的，分拣工作已经在emule的普通UDP客户端处理代码那里处理好了。具体的消息格式前面也有一些介绍，下面会就一些具体的消息分类做说明。</font></p>
<p><font size=3>首先是健康检查方面的消息，这样的消息就是一般的ping-pong机制。对应的消息有 KADEMLIA_HELLO_REQ和KADEMLIA_HELLO_RES。当对本地联系人信息列表进行检查时，会对它们发出 KADEMLIA_HELLO_REQ消息，然后处理收到的KADEMLIA_HELLO_RES消息。</font></p>
<p><font size=3>最常用的消息是节点搜索消息，在Kademlia网络中，进行节点搜索是日常应用所需要传输的主要消息，它的实现方式是迭代式的搜索。这种方式就是说当开始搜索某个ID时，在本地联系人信息列表中查找到距离最近的联系人，然后向它们发出搜索请求，这样通常都能够得到一些距离更近的联系人信息，然后再向它们发送搜索请求，通过不断得进行这样的搜索查询，就能够得到距离目标ID最近的那些联系人信息。这里对应的消息代码是 KADEMLIA_REQ和KADEMLIA_RES。</font></p>
<p><font size=3>接下来就是对内容进行发布或者搜索。这一点结合后面的CIndexed类的分析可以知道得更加清楚。emule中存储在Kademlia网中的信息主要有三类：文件源，关键字信息和文件的评论。文件源对应的是每一个具体的文件，每个文件都用它的内容的hash值作为该文件的唯一标示，一条文件源信息就是一条关于某人拥有某个特定的文件的这样一个事实。一条关键字信息则是该关键字对应了某个文件这样一个事实。很显然，一个关键字可能会对应多个文件，而一个特定的文件的文件源也很有可能不止一个。但是它们的索引都以固定的hash算法作为依据，这样使得搜索和发布都变得很简单。</font></p>
<p><font size=3>我们来看发布过程。每个emule客户端把自己的共享文件的底细已经摸清楚了，在传统的有中心索引服务器的场景里，它把自己的所有文件的信息都上传到中心索引服务器里。但是在Kademlia网里，它就需要分散传播了，它首先做的事情是把文件名进行切词，即从文件名中分解出一个一个的关键词出来，它切词的方法非常简单，就是在文件名中寻找那些有分割符含义的字符，如下划线等，然后把文件名切开。计算出这些关键字的 hash值后，它把这些关键字信息发布到对应的联系人那里。并且把文件信息也发布到和文件内容hash值接近的联系人那里。对应的消息是 KADEMLIA_PUBLISH_REQ和KADEMLIA_PUBLISH_RES。另外emule允许用户对某个文件发表评论，评论的信息单独保存，但是原理也是一样的。</font></p>
<p><font size=3>当用户使用Kademlia网络来进行搜索并且下载文件的时候，首先是对一个关键词进行搜索，由于使用的是同样的 hash算法，这样它只要找到ID值和计算出来的hash值结果相近的联系人信息后，它就可以直接向它们发送搜索特定关键词的请求了。如果得到了返回信息，那么搜索者就知道了这个关键词对应了多少文件，然后把这些文件的信息都列出来。当用户决定下载某个文件的时候，针对这一特定文件的搜索过程就开始了，这一次如果搜索成功，那么返回的就是这个文件的文件源信息。这样emule接下来就只需要按照这些信息去连接相应的地址，并且使用传统的emule协议去和它们协商下载文件了。这里对应的消息是KADEMLIA_SEARCH_REQ和KADEMLIA_SEARCH_RES。</font></p>
<p><font size=3>实际的实现中有Kademlia2这种协议，它的原理是一样的，只有协议代码和具体的消息格式不一样，例如 KADEMLIA_REQ和KADEMLIA_RES对应了KADEMLIA2_HELLO_REQ和KADEMLIA2_HELLO_RES，但是后者在具体的消息中包含了比前者丰富一些的信息。在实现的时候0.47c更加倾向于使用Kademlia2，而0.47a更加倾向于使用Kademlia。当然，它们两种协议都能够处理。另外，0.47c增加了一个对于已发出的请求的追踪的特性，就是一个包含TrackPackets_Struct类型的列表，这里面详细纪录了什么时间曾经对哪个IP发出过那种opcode对应的请求。为什么要这样呢？这是为了防止针对DHT的一种路由污染攻击，因为在搜索联系人的时候，如果搜索到了一些联系人信息，也会试图把它先加入到本地的联系人信息列表中。这样如果有人想恶意攻击的话，它只要不断得往它想攻击的 emule客户端发送KADEMLIA_RES，并且在消息的内容中包含大量的虚假联系人信息，就可以使对方的联系人信息列表中充满垃圾。这样，由于缺少正确有效的联系人信息，它的Kademlia网功能基本上就废了。而在0.47c里面增加的这个特性，就会对那种还没有发出请求就收到回应的情况直接无视，从而避免被愚弄。</font></p>
<p><font size=3>25. eMule源代码学习心得(25)：emule中的Kademlia的分布式索引管理<br>Kademlia 网络的最大的好处是把原来需要存储到中心索引服务器中的信息分散存储到各个客户端当中，如果要说得更加准确一点，那我们就可以说它把这些信息分散得存储到各个emule客户端的CIndexed类当中。我们可以具体开始看CIndexed的设计，看它是如何完成这一工作的。在这之前我们要稍微详细得说一下 emule发布到Kademlia网络中的信息的各种类型。</font></p>
<p><font size=3>一个文件源信息是一个文件内容的hash值和拥有这个文件的客户端的IP地址，各种端口号以及其它信息之间的对应关系。而一个关键词信息则是该关键词和它对应的文件之间的关系。在关键词信息中，它对应的文件信息要更加详细，通常包括这个文件的文件名，文件大小，文件内容的hash值，如果是MP3或者其它媒体文件，还会包含包括作者，生产时间，文件长度(这个长度是用时间来衡量的媒体文件的播放长度)，流派等等tag 信息。其中文件内容的hash值用来区分该关键词对应的不同文件。</font></p>
<p><font size=3>CIndexed中利用了一系列的Map来存储这些对应信息，CMap是MFC中实现标准STL中的map的模板类，CIndexed中包含了四个这样的类，分别用来存储文件源信息，关键词信息，文件评论信息以及负载信息。其中文件评论信息是不长久保存的，而其它的信息都会在退出的时候写到文件中，下次重新启动emule时再重新调入。另外负载信息不是等其它联系人来发布的，而是根据文件源信息和关键词信息的发布情况自行进行动态调整的。每一次收到发布信息时，对应的ID的负载会增大，这一事实会在回应消息(KADEMLIA_PUBLISH_RES)中体现。</font></p>
<p><font size=3>CIndexed中的信息会经常进行检查，每隔三十分钟它会把自己存储的所有信息中太老的信息清除掉。其中文件源信息的保存时间为五小时，关键词信息为二十四小时，文件评论的信息保存时间也为二十四小时。因此文件的发布和关键词也要周期性得反复进行。其实这对于整个 Kademlia网络的稳定性也是有好处的，因为每一次联系都会试图把对方添加到自己的联系人列表中，或者在联系人列表中标注上一次见到对方的时间。</font></p>
<p><font size=3>CIndexed为其它部分的代码提供了它们所需要的增加信息和搜索信息的接口，这样在从网络中获取到相关的搜索或者发布请求，并且CKademliaUDPListener完成消息的解释后，就可以交给CIndexed来进行处理了。 </font></p>
<p><font size=3>26. eMule源代码学习心得(26)：emule中的Kademlia搜索任务管理<br>CSearch 和CSearchManager是完成具体搜索任务的。CSearch对应的是一个具体的搜索任务，它包括了一个搜索任务从发起到结束的全部过程，要注意的是搜索任务并不只是指搜索文件源或者关键词的任务，一次发布任务它也需要创建一个CSearch对象，并且让它开始执行。CSearchManager 则掌握所有的搜索任务，它包含了一个包含所有CSearch指针对象的CMap，使用CMap的原因是因为所有的CSearch都一定对应一个ID，那个 ID就是该CSearch所对应的目标，不管是要查找节点，还是要搜索或者发布信息，一定都要找到和目标ID相近的联系人。因此 CSearchManager可以使用CMap来表示所有的搜索任务。</font></p>
<p><font size=3>我们注意到CSearch在创建的时候就把自己加入到CSearchManager当中。另外CSearch在创建的时候需要说明它的类型，例如是只是为了搜索节点还是要搜索关键词信息或者文件源信息，当然也有可能是发布文件源信息或者关键词信息。我们介绍一下 CSearch的几个方法的作用就可以大概了解CSearch的工作过程。Go是它的启动过程，它会开始第一次从本地的联系人列表中寻找候选的联系人，然后开始发动搜索。SendFindValue的功能就是向某个联系人发送一个搜索某ID的联系人信息这样一个请求。JumpStart则是在搜索进行到一定地步的时候，如得到了一些中间结果，开始进行下一步的行动，下一步的行动仍然可能是SendFindValue，也有可能认为搜索到的联系人离目标已经足够近了，于是就可以开始实质性的请求。StorePacket就是这样一个实质性的请求，例如在一个以发布文件源为任务的CSearch中， StorePacket会向目标联系人发送KADEMLIA2_PUBLISH_SOURCE_REQ(如果不支持Kademlia2，那么是 KADEMLIA_PUBLISH_REQ)。最后，CSearch能够处理各种搜索结果，然后向调用它的代码返回处理好的结果。</font></p>
<p><font size=3>CSearchManager直接和Kademlia网的其它部分代码接触，例如，如果 CKademliaUDPListener搜索到了一些结果，它会把这些结果交给CSearchManager，然后CSearchManager再去寻找这个结果是属于那个搜索任务的，并且进行转交。另外CSearchManager对外提供创建各种新的搜索任务的接口，作用类似于设计模式中的 Factory，其它部分的代码只需要说明需要开始一个什么样的搜索任务即可，CSearchManager来完成相应的创建CSearch的任务。</font></p>
<p><font size=3>27. eMule源代码学习心得(27)：后记<br>终于把自己想干的另一件事情干完了。eMule的代码写得很好，里面有很多都是值得我们学习的地方。由于时间关系，我也只看了那些和实现最基本的功能相关的代码，而实际上eMule里面还有很多代码实现了很多很有意思的功能。eMule的GUI设计的代码也是很不错的，但是我也没有来得及看。最后，欢迎大家来和我讨论关于P2P技术方面的问题。&nbsp;&nbsp; </font></p>
<br><br></div>
<img src ="http://www.cppblog.com/tommyyan/aggbug/82042.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tommyyan/" target="_blank">星仁</a> 2009-05-06 15:35 <a href="http://www.cppblog.com/tommyyan/articles/82042.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>