[C#搜片神器] 之P2P中DHT网络爬虫原理

 

继续接着上一篇写:使用C#实现DHT磁力搜索的BT种子后端管理程序+数据库设计(开源)[搜片神器]

 

昨天由于开源的时候没有注意运行环境,直接没有考虑下载BT种子文件时生成子文件夹,可能导致有的朋友运行没有结果,在此表示对支持开源的朋友道谦.另外也对源程序增加了一些说明,已经提交.

开源地址:https://github.com/h31h31/H31DHTMgr

程序下载:H31DHT下载

个人电脑编译环境是WIN7+VS2005,如果程序运行出错,请自行下载代码进行编译.

先说下运行方法:

1)有固定IP的朋友可以试试H31DHT.exe数据抓取程序,会获取一些数据,如果>2小时还没有数据返回,直接说明不是固定IP的返回数据很少;

2)直接从http://torrage.com/sync下载几个文本文件回来,放到程序目录下,H31DHTMgr程序会自动遍历这个文件夹取HASH文件,

存储到数据库中,如果将此网站的200万数据(个人估计的)全部下载成功,那也可以搜索很多内容了.

3)新程序界面如下:需要的自己下载源代码进行编译(VS2005),不提供EXE下载,希望大家感兴趣的一起开源下;

[增加了复制磁链接和下载选中项目的代码,之前只能查看,不能显示.]

--------------------先来点大家感兴趣的东西-------------------------------------

 大家可能问目前的程序采用什么方法下载BT种子的比较关心,下面就自己的体会给大家说说:

DHT磁力种子其实就是20字节的HASH值,这个值可以直接从很多网站下载种子,举例子说明:

比如说上一篇文件中有那么多HASH值的字符串,怎么利用呢,比如有个HASH值13ce77b3b934b12dc77fded6646426a6db5c3428,有40位,因为在内存里面占用20位,显示为16进制所以显示为40位了;

有这个HASH值,我们可以加上磁头magnet:?xt=urn:btih:     两个合在一起就可以下载BT种子了,

当然需要使用BT工具,(magnet:?xt=urn:btih:13ce77b3b934b12dc77fded6646426a6db5c3428)复制试下.

但我们的程序没有使用BT协议去下载,而是通过别人的网站下载.

比如http://torrage.com/torrent/13ce77b3b934b12dc77fded6646426a6db5c3428.torrent 大家分析组合方式就明白了,

会提示找不到这个种子,那就说明这个网站没有收集到最新的BT种子.

可以从其它网站下载,大家可以去看下源程序里面的组合方法.

 

 

-------------------------下面介绍一些从网上收集的资料信息----------------------------------------------

DHT网络爬虫基于DHT网络构建了一个P2P资源搜索引擎。这个搜索引擎不但可以用于构建DHT网络中活跃的资源索引(活跃的资源意味着该网络中肯定有人至少持有该资源的部分数据),还可以分析出该网络中的热门分享资源。网络上其实也有其他人做了类似的应用:DHTmonitoringCrawling Bittorrent DHT

DHT/Magnet/Torrent

在P2P网络中,要通过种子文件下载一个资源,需要知道整个P2P网络中有哪些计算机正在下载/上传该资源。这里将这些提供某个资源下载的计算机定义为peer。传统的P2P网络中,存在一些tracker服务器,这些服务器的作用主要用于跟踪某个资源有哪些关联的peer。下载这个资源当然得首先取得这些peer。

DHT的出现用于解决当tracker服务器不可用时,P2P客户端依然可以取得某个资源的peer。DHT解决这个问题,是因为它将原来tracker上的资源peer信息分散到了整个网络中。这里将实现了DHT协议的计算机定义为节点(node)。通常一个P2P客户端程序既是peer也是节点。DHT网络有多种实现算法,例如Kademlia。

当某个P2P客户端通过种子文件下载资源时,如果没有tracker服务器,它就会向DHT网络查询这个资源对应的peer列表。资源的标识在DHT网络中称为infohash,是一个20字节长的字符串,一般通过sha1算法获得,也就是一个类似UUID的东西。

实际上,种子文件本身就对应着一个infohash,这个infohash是通过种子文件的文件描述信息动态计算得到。一个种子文件包含了对应资源的描述信息,例如文件名、文件大小等。Magnet,这里指的是磁力链接,它是一个类似URL的字符串地址。P2P软件通过磁力链接,会下载到一个种子文件,然后根据该种子文件继续真实资源的下载。

磁力链接中包含的最重要的信息就是infohash。这个infohash一般为40字节或32字节,它其实只是资源infohash(20字节)的一种编码形式。

 

Kademlia

各种DHT的实现算法,不论是Chord, Pastry还是Kademlia,其最直接的目标就是以最快的速度来定位到期望的节点,在P2P文件分享应用中则是以最快的速度来查找到正在分享某一文件/种子的peers列表信息。因为每个节点都是分布式存在于地球的任何角落,如果用地理距离来衡量两节点间的距离则可能给计算带来极大复杂性甚至不可能进行衡量,因此基本所有的DHT算法都是采用某种逻辑上的距离,在Kademlia则采用简单的异或计算来衡量两节点间的距离,它和地理上的距离没有任何关系,但却具备几何公式的绝大特征:

(1)节点和它本身之间的异或距离是0

(2)异或距离是对称的:即从A到B的异或距离与从B到A的异或距离是等同的

(3)异或距离符合三角形不等式:给定三个顶点A B C,假如AC之间的异或距离最大,那么AC之间的异或距离必小于或等于AB异或距离和BC异或距离之和.

(4)对于给定的一个距离,距离A只存在有唯一的一个节点B,也即单向性,在查找路径上也是单向的,这个和地理距离不同。

            Kademlia中规定所有的节点都具有一个节点ID,该节点ID的产生方法和种子文件中的info hash采用相同算法:即sha-1(安全hash算法),因此每个节点的id,以及每个共享文件/种子的info-hash都是唯一的,并且都是20个字符160bits位组成。两个节点间的距离就是两个节点id的异或结果,节点离键值(种子)的距离为该节点的id和该种子文件的info-hash的异或结果。Kademlia在异或距离度量的基础上又把整个DHT网络拓扑组织成一个二叉前缀树(XuanWu系统中arp的实现则是一个例子),所有的节点(所有的正在运行的,并且开取了DHT功能的Bt,Btspilits应用)等作为该二叉前缀树的叶子节点,可以想象这棵二叉树可以容纳多达2128个叶子(节点),这足以组织任何规模的网络了。对于每个节点来说按照离自己的远近区域又可以把这棵树划分为160棵子树,每一个子树和该节点都有一个共同的前缀,共同前缀越少离得越远。如下图所示:

(注意:上图只是一个划分子树的例子,节点都没有位于同一层的叶子上面)

以上图红色节点位例0011位例,它可以把其他的节点划分位4棵不同子树,离自己越近子树和自己有越长的公共前缀,如果节点是均匀分布则离自己越近的子树含有的叶子节点更少(兄弟只有一个即和自己有159个共同前缀的那个)。因为节点都位于该树最底层的叶子位置,水平看上去则所有的叶子都在一条线上,如果把这条线当作2128空间的每一个点,则更能体现上面的划分特性(折半拆分)。为了能快速到达这160棵子树,处于DHT网络中的每一个节点都记录了每棵子树上的k个节点的信息(ip,port,id),在BT中K固定为8,比如上图中红色节点就可能保存有最左边子树的8个叶子节点信息,当然靠近自己的子树可能没有8个叶子,则把所有当前存在的叶子记录上去,这份记录信息在Kademlia算法中叫作K桶,也叫作“路由表”,当然这个“路由表”的信息和我们IP路由的含义有点不同,它代表的是为了到达处于距离自己某范围[ 2i — 2i+1 )的节点,可以通过该范围内的选取的k个节点来进一步定位.

Kademlia是DHT网络的一种实现。网络上关于这个算法的文章,主要是围绕整个DHT网络的实现原理进行论述。个人觉得这些文章很蛋疼,基本上读了之后对于要如何去实现一个DHT客户端还是没有概念。这里主要可参考P2P中DHT网络介绍,以及BitTorrent网站上的DHT协议描述

Kad的主要目的是用于查询某个资源对应的peer列表,而这个peer列表实际上是分散在整个网络中。网络中节点数量很大,如果要获得peer列表,最简单的做法无非就是依次询问网络中的每个节点。这当然不可行。所以在Kad算法中,设立了一个路由表。每一个节点都有一份路由表。这个是按照节点之间的距离关系构建出来的。节点之间的距离当然也有特定的算法定义,在Kad中通过对两个节点的ID进行异或操作得到。节点的ID和infohash通过相同算法构建,都是20字节长度。节点和infohash之间也有距离关系,实际上表示的是节点和资源的距离关系。

有了这个路由表之后,再通过一个基于距离关系的查找算法,就可以实现不用挨个遍历就找到特定的节点。而查找资源peer这个操作,正是基于节点查找这个过程。

路由表的实现,按我的理解,有点类似一般的hash表结构。在这个表中有160个桶,称为K桶,这个桶的数量在实现上可以动态增长。每个桶保存有限个元素,例如K取值为8,指的就是这个桶最多保存8个元素。每个元素就是一个节点,节点包含节点ID、地址信息以及peer信息。这些桶可以通过距离值索引得到,即距离值会经过一个hash算法,使其值落到桶的索引范围内。

要加入一个DHT网络,需要首先知道这个网络中的任意一个节点。如何获得这个节点?在一些开源的P2P软件中,会提供一些节点地址,例如transmission中提供的dht.transmissionbt.com:6881。

kademlia的消息:

为了实现上面的“路由表”建立,刷新,获取peers-list,保存peers-list这些功能,kademlia定义四个最基本的KRPC操作:

(1)ping操作,作用是探测一个节点,用以判断该节点是否仍然在线。

(2)store操作,作用是通知一个节点存储一个<key,value>对,以便以后查询需要。

(3)find_node操作,作用是从自己的“路由表”对应的K桶中返回k个节点信息(IP address,UDP port,Node ID)给发送者

(4)find_value 操作,作用是把info-hash作为参数,如果本操作接收者正好存储了info-hash的peers则返回peers list,否则从自己的“路由表“中返回离info-hash更近的k个节点信息(同find_node过程)。

上面只是最基本的操作,一次nodes或者info-hash的查找lookup过程则需要节点进行若干次上面的find操作的,一个递归查找的过程。利用上面的操作更精确的描述一次一个节点x要查找ID值为t 的节点, 过程如下:

1、 计算到t 的距离:d(x,y) = x⊕y

2、 从x 的第[㏒ d]个K 桶中取出α 个节点的信息(各个实现α值不一样,有些是3有些则等于k值),同时进行FIND_NODE 操作。如果这个K 桶中的信息少于α 个,则从附近多个桶中选择距离最

接近d 的总共α个节点。

3、 对接受到查询操作的每个节点,如果发现自己就是t,则回答自己是最接近t 的。否则测量自己和t 的距离,并从自己对应的K 桶中选择α 个节点的信息给x。

4、 X 对新接受到的每个节点都再次执行FIND_NODE 操作,此过程不断重复执行,直到

每一个分支都有节点响应自己是最接近t 的,或者说FIND_NODE操作返回的节点值没有都已经被查找过了,即找不到更近的节点了。

5、 通过上述查找操作,x 得到了k 个最接近t 的节点信息。

注意:这里用“最接近”这个说法,是因为ID 值为t 的节点不一定存在网络中,也就是说t 没有分配给任何一台电脑。

查找peers-list的过程则换成find_value动作,但注意前文提到的区别即可以有类似的描述。

-----------------------------------------------------------------------------

协议

Kad定义了节点之间的交互协议。这些协议支撑了整个DHT网络里信息分布式存储的实现。这些协议都是使用UDP来传送。其协议格式使用一种称为bencode的编码方式来编码协议数据。bencode是一种文本格式的编码,它还用于种子文件内的信息编码。

Kad协议具体格式可参考BitTorrent的定义:DHT Protocol。这些协议包括4种请求:ping,find_node,get_peer,announce_peer。在有些文档中这些请求的名字会有不同,例如announce_peer又被称为store,get_peer被称为find_value。这4种请求中,都会有对应的回应消息。其中最重要的消息是get_peer,其目的在于在网络中查找某个资源对应的peer列表。

值得一提的是,所有这些请求,包括各种回应,都可以用于处理该消息的节点构建路由表。因为路由表本质就是存储网络中的节点信息。

ping

用于确定某个节点是否在线。这个请求主要用于辅助路由表的更新。

find_node

用于查找某个节点,以获得其地址信息。当某个节点接收到该请求后,如果目标节点不在自己的路由表里,那么就返回离目标节点较近的K个节点。这个消息可用于节点启动时构建路由表。通过find_node方式构建路由表,其实现方式为向DHT网络查询自己。那么,接收该查询的节点就会一直返回其他节点了列表,查询者递归查询,直到无法查询为止。那么,什么时候无法继续查询呢?这一点我也不太清楚。每一次查询得到的都是离目标节点更接近的节点集,那么理论上经过若干次递归查询后,就无法找到离目标节点更近的节点了,因为最近的节点是自己,但自己还未完全加入网络。这意味着最后所有节点都会返回空的节点集合,这样就算查询结束?

实际上,通过find_node来构建路由表,以及顺带加入DHT网络,这种方式什么时候停止在我看来并不重要。路由表的构建并不需要在启动时构建完成,在以后与其他节点的交互过程中,路由表本身就会慢慢地得到构建。在初始阶段尽可能地通过find_node去与其他节点交互,最大的好处无非就是尽早地让网络中的其他节点认识自己。

get_peer

通过资源的infohash获得资源对应的peer列表。当查询者获得资源的peer列表后,它就可以通过这些peer进行资源下载了。收到该请求的节点会在自己的路由表中查找该infohash,如果有收录,就返回对应的peer列表。如果没有,则返回离该infohash较近的若干个节点。查询者若收到的是节点列表,那么就会递归查找。这个过程同find_node一样。

值得注意的是,get_peer的回应消息里会携带一个token,该token会用于稍后的announce_peer请求。

announce_peer

该请求主要目的在于通知,通知其他节点自己开始下载某个资源。这个消息用于构建网络中资源的peer列表。当一个已经加入DHT网络的P2P客户端通过种子文件开始下载资源时,首先在网络中查询该资源的peer列表,这个过程通过get_peer完成。当某个节点从get_peer返回peer时,查询者开始下载,然后通过announce_peer告诉返回这个peer的节点。

announce_peer中会携带get_peer回应消息里的token。关于这一点,我有一个疑问是,在P2P中DHT网络介绍文档中提到:

(announce_peer)同时会把自己的peer信息发送给先前的告诉者和自己K桶中的k个最近的节点存储该peer-list信息

不管这里提到的K的最近的节点是离自己最近,还是离资源infohash最近的节点,因为处理announce_peer消息时,有一个token的验证过程。但是这K个节点中,并没有在之前创建对应的token。我通过transmission中的DHT实现做了个数据收集,可以证明的是,announce_peer消息是不仅仅会发给get_peer的回应者的。

------------------------------------------------------------------------------------

DHT爬虫

DHT爬虫是一个遵循Kad协议的假节点程序。

这个爬虫的实现方式,主要包含以下内容:

  • 通过其他节点的announce_peer发来的infohash确认网络中有某个资源可被下载
  • 通过从网络中获取这个资源的种子文件,来获得该资源的描述

通过累计收集得到的资源信息,就可以提供一个资源搜索引擎,或者构建资源统计信息。以下进一步描述实现细节。整个爬虫的实现依赖了一个很重要的信息,那就是资源的infohash实际上就是一个磁力链接(当然需要包装一下数据)。这意味着一旦我们获得了一个infohash,我们就等于获得了一个种子。

获得资源通知

当爬虫程序加入DHT网络后,它总会收到其他节点发来的announce_peer消息。announce_peer消息与get_peer消息里都带了资源的infohash,但是get_peer里的infohash对应的资源在该网络中不一定存在,即该资源没有任何可用peer。而announce_peer则表示已经确认了该网络中有节点正在下载该资源,也即该资源的数据确实存在该网络中。

所以,爬虫程序需要尽最大努力地获取其他节点发来的announce_peer消息。如果announce_peer消息会发送给离消息发送节点较近的节点,那么,一方面,爬虫程序应该将自己告诉网络中尽可能多的节点。这可以通过一次完整的find_node操作实现。另一方面,爬虫程序内部实现可以部署多个DHT节点,总之目的在于尽可能地让爬虫程序称为其他节点的较近者。

当收集到infohash之后,爬虫程序还需要通过该infohash获得对应资源的描述信息。

获取资源信息

获得资源描述信息,其实就是通过infohash获得对应的种子文件。这需要实现P2P协议里的文件分享协议。种子文件的获取其实就是一个文件下载过程,下载到种子文件之后,就可以获取到资源描述。这个过程一种简单的方法,就是从infohash构建出一个磁力链接,然后交给一个支持磁力下载的程序下载种子。

从infohash构建出磁力链接非常简单,只需要将infohash编码成磁力链接的xt字段即可,构建实现可以从transmission源码里找到.

现在你就可以做一个实验,在transmission的DHT实现中,在announce_peer消息的处理代码中,将收到的infohash通过上面的appendMagnet转换为磁力链接输出到日志文件里。然后,可以通过支持磁力链接的程序(例如QQ旋风)直接下载。有趣的是,当QQ旋风开始下载该磁力链接对应的种子文件时,你自己的测试程序能收到QQ旋风程序发出的announce_peer消息。当然,你得想办法让这个测试程序尽可能地让其他节点知道你,这可以通过很多方式实现。

UPDATE

通过详细阅读transmission里的DHT实现,一些之前的疑惑随之解开。

announce_peer会发给哪些节点

在一次对infohash的查询过程中,所有对本节点发出的get_peer作出回应的节点(不论这个回应节点回应的是nodes还是peers),当本节点取得peer信息时,就会对所有这些节点发出announce_peer。get_peer的回应消息里,不论是peer还是node,都会携带一个token,这样在将来收到对方的announce_peer时,就可以验证该token。

节点和bucket状态

在本地的路由表中,保存的node是有状态之分的。状态分为三种:good/dubious/bad。good节点基本可以断定该节点是一个在线的并且可以正常回应消息的节点;而bad节点则是可确定的无效节点,通常会尽快从路由表中移除;而dubious则是介于good和bad节点之间,表示可能有问题的节点,需要进一步发送例如ping消息来确认其状态。路由表中应该尽可能保证保存的是good节点,对查询消息的回应里也需携带好的节点。

bucket也是有状态的,当一个bucket中的所有节点在一定时间之内都没有任何活动的时候,该bucket则应该考虑进行状态的确认,确认方式可以随机选择该bucket中的节点进行find_node操作(这也是find_node除了用于启动之外的唯一作用,但具体实现不见得使用这种方式)。没有消息来往的bucket则应该考虑移除。DHT中几乎所有操作都会涉及到bucket的索引,如果索引到一个所有节点都有问题的bucket,那么该操作可能就无法完成。

search在何时停止

首先,某次发起的search,无论是对node还是对peer,都可能导致进一步产生若干个search。这些search都是基于transaction id来标识的。由一次search导致产生的所有子search都拥有相同的transaction id,以使得在该search成功或失败时可以通过该transaction id来删除对应的所有search。transaction id也就是DHT中每个消息消息头”t”的值。

但是search何时停止?transmission中是通过超时机制来停止。在search过程中,如果长时间没有收到跟该search关联的节点发来的回应消息,那么就撤销该search,表示搜索失败。

参考资料

---------------------------------------------------------

再告诉大家一个神奇的方法

有了HASH值可以去   试下是否可以播放等功能,输入magnet:?xt=urn:btih:13ce77b3b934b12dc77fded6646426a6db5c3428就可以播放了;

另外求服务器进行程序测试,需要有固定IP,10G的WIN服务器空间,h31h31@163.com,谢谢.

ps:本人开源程序的目的只是大家交流,如果有什么违法的行为与本人无关.

希望大家多多推荐哦...大家的推荐才是下一篇介绍的动力...

posted @ 2013-07-25 13:20  Miao31  阅读(27055)  评论(30编辑  收藏  举报