DHT爬虫 原理分析

来源:互联网 发布:穿越火线手游刷钻软件 编辑:程序博客网 时间:2024/05/18 00:33

一.BiTtorrent的DHT实现 — Kademlia 协议

在kad网络中的,每个节点都有自己的 id 值(从160bit空间中随机选取)。并规定节点间的距离是节点id值的异货值。每个节点都有一个路由表。这个路由变实际上是个二叉树,二叉树中的叶子节点存放的是具有相同最大前缀的节点 id。每个叶子节点我们叫做 k桶,这个 k桶里有 k个其他节点的信息(IP address,UDP port,Node ID)。在BitTorrent的实现中,取值为k=8。当y节点收到一个 RPC消息时,用发送者的 ip来更新我们的 K 桶。具体步骤如下:    1.在我们的路由表中找到具有最大前缀的 k 桶。    2.如果y已经在k桶中,我们需要把它移到尾部。    3.如果y不在k桶中,且k同没有满,直接将y放在k桶尾部。    4.如果 k桶满了,则取首部的记录项z,发送 ping请求,如果没有回应,则删除 z,并把 y放在尾部,    如果有回应则把 y删除。当K 桶满的时候我们可以进行分桶,把以前的 k桶中的信息分给子 k桶。当我们要查询距离某个id最近的id时,我们最多执行[logN](二叉树的深度减去1),[]为向下取证,N 为节点数量。通过这种方式,能够大大提高查找速度。假如节点 x 要找节点 id为 t的节点,kad 按照如下步骤进行路由查找:    1.在路由表中找到和 t最大前缀匹配的桶,并对桶中的节点发送 find_node 操作。    2.从x的第[㏒d]个K桶中取出α个节点的信息(“[”“]”是取整符号),同时进行FIND_NODE操作。    如果这个K桶中的信息少于α个,则从附近多个桶中选择距离最接近d的总共α个节点。    3.对接受到查询操作的每个节点,如果发现自己就是t,则回答自己是最接近t的;    否则测量自己和t的距离,并从自己对应的K桶中选择α个节点的信息给x。    4.X对新接受到的每个节点都再次执行FIND_NODE操作,此过程不断重复执行,    直到每一个分支都有节点响应自己是最接近t的。    5.通过上述查找操作,x得到了k个最接近t的节点信息。注意:这里用“最接近”这个说法,是因为ID值为t的节点不一定存在网络中,也就是说t没有分配给任何一台电脑。这里α也是为系统优化而设立的一个参数,就像K一样。在BitTorrent实现中,取值为α=3。    当节点x要查询<key,value>对时,和查找节点的操作类似,x选择k个ID值    最接近key值的节点,执行get_peers(FIND_VALUE)操作,并对每一个返回的    新节点重复执行FIND_VALUE操作,直到某个节点返回value值。    一 旦get_peers(FIND_VALUE)操作成功执行,    则<key,value>对数据会缓存在没有返回value值的最接近的节点上。    这样下一次查询相同的 key时就会更加快速的得到结果。通过这样的方式,    热门<key,value>对数据的缓存范围就逐步扩大,使系统具有极佳的响应速度。    存放<key,value>对数据的过程为:        1.发起者首先定位k个ID值最接近key的节点;        2.发起者对这k个节点发起STORE操作        3.执行announce_peer(STORE)操作的k个节点每小时重发布自己所有的<key,value>对数据。        4.为了限制失效信息,所有<key,value>对数据在初始发布24小时后过期。另外,为了保证数据发布、搜寻的一致性,规定在任何时候,当节点w发现新节点u比w上的某些<key,value>对数据更接近,则w把这些<key,value>对数据复制到u上,但是并不会从w上删除。Kad要求每个节点必须周期性的发布全部自己存放的<key,value>对数据,并把这些 数据缓存在自己的k个最近邻居处,这样存放在失效节点的数据会很快被更新到其他新节点上,这主要是为了防止一些节点的消失。

KRPC协议

    KRPC 是一个简单的 RPC机制,由在UDP上发送的bencode字典组成。有三种消息类型:     query, response, error。对于DHT协议,有四种query :ping,      find_node, get_peers, announce_peer    一个KRPC消息由一个独立的字典组成,其中有2个关键字是所有的消息都包含的,其余的附加关键字取决于消息类型。每一个消息都包含t关键字,它是一个代表了transactionID的字符串类型。transactionID由请求node产生,并且回复中要包含回显该字段,所以回复可能对应一个节点的多个请求。transactionID应当被编码为一个短的二进制字符串,比如2个字节,这样就可以对应2^16个请求。另一个每个KRPC消息都包含的关键字是y,它由一个字节组成,表明这个消息的类型。y对应的值有三种情况:q表示请求,r表示回复,e表示错误    请求    请求,即"y"的值是"q"的KRPC消息,包括两个附加键"q"和"a"。    "q"的值是请求的方法名,"a"的值是请求的参数。    响应    响应,即"y"的值是"r"的KRPC消息,包括一个附加键"r"。"r"的值是请求的返回值。    当成功完成一个请求时,发送响应消息。    错误    错误,即"y"的值是"e"的KRPC消息,包括一个附加键"e"。"e"的值是一个列表,    其中的第一个元素是一个整数,表示错误代码。第二个元素是一个错误消息的字符串。    当请求无法完成时,发送错误。下表是可能出现的错误:    201 一般错误    202 服务器错误    203 协议错误,比如异常消息包,无效参数,无效令牌等    204 方法未知    错误包示例    DE<generic error = {'t':0, 'y':'e', 'e':[201, "A Generic Error Ocurred"]} bencoded = d1:eli201e23:A Generic Error Ocurrede1:ti0e1:y1:eeDE<

DHT请求

所有的请求都有一个id的键,值是请求节点的ID。所有的响应都有一个id的键,值是响应节点的ID。

ping最基本的请求是ping, "q"="ping"。 ping请求只有一个参数"id",值是发送者的节点ID,20字节的,网络字节顺序。相应的响应也只有一个id键,值是响应结点的ID 。DE<arguments: {"id" : "<querying nodes id>"} response: {"id" : "<queried nodes id>"}DE<示例包DE<ping Query = {"t":"0", "y":"q", "q":"ping", "a":{"id":"abcdefghij0123456789"}} bencoded = d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t1:01:y1:qeDE<DE<Response = {"t":"0", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}} bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t1:01:y1:reDE<find_nodefind_node用来查找一个给定ID的节点联系信息,"q"=="find_node"。 find_node请求有两个参数,"id"包含请求结点的ID;"target"包含请求节点要查找的目标节点ID。当一个节点接收到一个 find_node请求后,它的响应应该包含一个"node"键,值是这个目标节点,或者它路由表中K(8)个离目标节点最近的好节点的紧密的节点信息。DE<arguments: {"id" : "<querying nodes id>", "target" : "<id of target node>"} response: {"id" : "<queried nodes id>", "nodes" : "<compact node info>"}DE<示例包DE<find_node Query = {'t':0, 'y':'q', 'q':'find_node', 'a': {'id':'abcdefghij0123456789', 'target':'mnopqrstuvwxyz123456'}} bencoded = d1:ad2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e1:q9: find_node1:ti0e1:y1:qeDE<DE<Response = {'t':0, 'y':'r', 'r': {'id':'0123456789abcdefghij', 'nodes': 'def456...'}} bencoded = d1:rd2:id20:0123456789abcdefghij5:nodes9:def456...e1:ti0e1:y1:reDE<get_peers(FIND_VALUE)get_peers与一个torrent的infohash关联,"q"="get_peers"。get_peers请求有两个参数:"id"包含请求结点的ID;"info_hash"包含torrent的infohash。如果接收请求的节点知道infohash的peer,它把这些peer 的紧密的IP地址/端口信息连接成一个串列表,以"value"作为键,回复给请求节点。如果接收请求的节点没有infohash的 peer ,它回复路由表中离infohash最近的K个节点,以"nodes"作为键。任何一种情况,"token"键都包含在返回值中。在将来发送 announce_peer请求的时候,token值也是必须的。这个请求我们必须要正确的回答,否则我们不可能收到 announce_peerDE<arguments: {"id" : "<querying nodes id>", "info_hash" : "<20-byte infohash of target torrent>"} response: {"id" : "<queried nodes id>", "values" : ["<compact peer info string>"]} or: {"id" : "<queried nodes id>", "nodes" : "<compact node info>"}DE<示例包bencoded = d1:ad2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e1:q9:get_peers1:ti0e1:y1:qeDE<DE<Response with peers = {'t':0, 'y':'r', 'r': {'id':'abcdefghij0123456789', 'token':'aoeusnth', 'values': ['axje.uidhtnmbrl']}} bencoded = d1:rd2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl15: axje.uidhtnmbrlee1:ti0e1:y1:reDE<DE<Response with closest nodes = {'t':0, 'y':'r', 'r': {'id':'abcdefghij0123456789', 'token':'aoeusnth', 'nodes': 'def456...'}} bencoded = d1:rd2:id20:abcdefghij01234567895:nodes9:def456...5:token8:aoeusnthe1:ti0e1:y1:reDE<这个 infohash 值是磁力链接的一种编码形式,通过下面的算法转换为磁力链接void tr_sha1_to_hex (char *out, const unsigned char *sha1) { int i; static const char hex[] = "0123456789abcdef";for (i=0; i<20; ++i) {const unsigned int val = *sha1++;*out++ = hex[val >> 4];*out++ = hex[val & 0xf];}*out = '\0';}announce_peer(STORE)声明请求节点的peer正在一个端口上下载一个torrent。announce_peer有四个参数:"id"是请求节点ID; "info_hash"是torrent的infohash;"port"是正在下载的端口号,整数; "token"是上次接收get_peers请求响应时获得的。接收announce请求的节点必须根据 IP地址检查令牌(token),即上次它作为请求节点时发给它的令牌与现在提供的令牌相同。 然后接收请求的节点应该存储请求节点的IP地址和提供的与infohash关联的端口 到本地peer联系信息存储池中。DE<arguments: {"id" : "<querying nodes id>", "info_hash" : "<20-byte infohash of target torrent>", "port" : <port number>, "token" : "<opaque token>"} response: {"id" : "<queried nodes id>"}DE<示例包DE<announce_peers Query = {'t':0, 'y':'q', 'q':'announce_peers', 'a': {'id':'abcdefghij0123456789', 'info_hash':'mnopqrstuvwxyz123456', 'port' : 6881, 'token' : 'aoeusnth'}} bencoded = d1:ad2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe1:q14:announce_peers1:ti0e1:y1:qeDE<bencoded =d1:rd2:id20:mnopqrstuvwxyz123456e1:t1:01:y1:reDE<

加入 kad 网络

 要想加入 kad网络,我们必须向已知的节点发送一个find_node 请求,以知节点会返回 离请求 id最近的节点,然后我们向这些节点发送 find_node 节点,同样是查找自己, 他们也会返回许多节点,然后继续按照套路,我们会认识到更多的节点, 我们获取 announce_peer(STORE)的几率会变大,我们抓取种子的速度也会越来越快
0 0
原创粉丝点击