继续接着上⼀篇写:使⽤C#实现DHT磁⼒搜索的BT种⼦后端管理程序
+数据库设计(开源)[搜⽚神器]
继续接着上⼀篇写:
昨天由于开源的时候没有注意运⾏环境,直接没有考虑下载BT种⼦⽂件时⽣成⼦⽂件夹,可能导致有的朋友运⾏没有结果,在此表⽰对⽀持开源的朋友道谦.另外也对源程序增加了⼀些说明,已经提交.
程序下载:
个⼈电脑编译环境是WIN7+VS2005,如果程序运⾏出错,请⾃⾏下载代码进⾏编译.
先说下运⾏⽅法:
1)有固定IP的朋友可以试试数据抓取程序,会获取⼀些数据,如果>2⼩时还没有数据返回,直接说明不是固定IP的返回数据很少;
存储到数据库中,如果将此⽹站的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协议去下载,⽽是通过别⼈的⽹站下载.
会提⽰不到这个种⼦,那就说明这个⽹站没有收集到最新的BT种⼦.
可以从其它⽹站下载,⼤家可以去看下源程序⾥⾯的组合⽅法.
-
------------------------下⾯介绍⼀些从⽹上收集的资料信息----------------------------------------------
DHT⽹络爬⾍基于DHT⽹络构建了⼀个P2P资源搜索引擎。这个搜索引擎不但可以⽤于构建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客户端还是没有概念。这⾥主要可参考,以及BitTorrent⽹站上的
Kad的主要⽬的是⽤于查询某个资源对应的peer列表,⽽这个peer列表实际上是分散在整个⽹络中。⽹络中节点数量很⼤,如果要获得peer列表,最简单的做法⽆⾮就是依次询问⽹络中的每个节点。这当然不可⾏。所以在Kad算法中,设⽴了⼀个路由表。每⼀个节点都有⼀份路由表。这个是按照节点
之间的距离关系构建出来的。节点之间的距离当然也有特定的算法定义,在Kad中通过对两个节点的ID进⾏异或操作得到。节点的ID和infohash通过相同算法构建,都是20字节长度。节点和infohash之间也有距离关系,实际上表⽰的是节点和资源的距离关系。
有了这个路由表之后,再通过⼀个基于距离关系的查算法,就可以实现不⽤挨个遍历就到特定的节点。⽽查资源peer这个操作,正是基于节点查这个过程。
路由表的实现,按我的理解,有点类似⼀般的hash表结构。在这个表中有160个桶,称为K桶,这个桶的数量在实现上可以动态增长。每个桶保存有限个元素,例如K取值为8,指的就是这个桶最多保存8个元素。每个元素就是⼀个节点,节点包含节点ID、地址信息以及peer信息。这些桶可以通过距离值索引得到,即距离值会经过⼀个hash算法,使其值落到桶的索引范围内。
要加⼊⼀个DHT⽹络,需要⾸先知道这个⽹络中的任意⼀个节点。如何获得这个节点?在⼀些开源的P2P软件中,会提供⼀些节点地址,例如中提供的ansmissionbt: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是⼀种⽂本格式的编码,它还⽤于种⼦⽂件内的信息编码。
Kad协议具体格式可参考BitTorrent的定义:。这些协议包括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。关于这⼀点,我有⼀个疑问是,在⽂档中提到:
(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。tr
ansaction id 也就是DHT中每个消息消息头”t”的值。
但是search何时停⽌?transmission中是通过超时机制来停⽌。在search过程中,如果长时间没有收到跟该search关联的节点发来的回应消息,那么就撤销该search,表⽰搜索失败。
参考资料
---------------------------------------------------------
再告诉⼤家⼀个神奇的⽅法
有了HASH值可以去试下是否可以播放等功能,输⼊magnet:?xt=urn:btih:13ce77b3b934b12dc77fded6646426a6db5c3428就可以播放了;
另外求服务器进⾏程序测试,需要有固定IP,10G的WIN服务器空间,h31h31@163,谢谢.
ps:本⼈开源程序的⽬的只是⼤家交流,如果有什么违法的⾏为与本⼈⽆关.
希望⼤家多多推荐哦...⼤家的推荐才是下⼀篇介绍的动⼒...