@(基础技术)

现在有一种方法,可以通过磁力链接,例如magnet:?xt=urn:btih:0482e0811014fd4cb5d207d08a7be616a4672daa,就可以获取BT文件。

这个是通过DHT网络来实现的。

DHT网络是一个去中心化的,分布式信息存储系统。

存储的信息就是bt文件。

一、节点

每一台电脑,就是一个节点。它既是客户端,也是服务端。

每个节点都有一个节点ID,IP地址和端口号(节点进程的端口)。

节点ID由160位的二进制字符串组成,也就是长度为32的16进制字符串,跟我们常用的md5一样。

通过异或算法,可以计算两个节点ID的距离。例如01和00的异或结果是01,也就是距离是1。

二、路由表

每个节点都会保存一个路由表,保存其他节点的信息,节点信息包括:节点ID,节点的IP地址和端口号。

路由表中,会有多个bucket,例如bucket-1,bucket-2等等。

bucket-i保存的是与自身节点ID距离为[2i-1,2i)的节点信息

每个nodeid可以理解为深度是160的二叉树,二bucket-i就是自身的叶子的第i个父节点的兄弟节点的所有叶子节点(不太严谨)

如下图:

所以i最大值是160。

而为什么要这么存了?

这样存是为了可以快速找到目标节点N2。

例如自身的节点ID是N1,需要寻找N2的IP和端口号。

  • 计算N1和N2的距离D
  • 从bucket-D,找一个节点N3,如果N3=N2,就找到了,否则就向N3发送寻找节点N2的请求
  • N3接收到请求后,计算N2和N3的距离D1,从N3的路由表里面的bucket-D1,找到一个节点N4,返回N4的信息给N1
  • N1收到返回后,如果N4=N2,就找到了,否则继续向N4发送寻找节点N2的请求。一直递归。

因为N2和N3会处于同一个bucket,所以他们的距离D1不会超过D/2,所以每一次循环,获得的节点NN与N2的距离都会比之前的请求缩小1倍。所以时间复杂度是logN。跟二分查找是一样的。

三、信息发布

当发布者,需要发布信息(例如一个bt文件)到DHT网络。

  • 发布者会计算信息的md5,M1
  • 通过发布者的路由表,查询与M1的距离小于等于K的多个节点
  • 向这些节点发送保存信息(Store)的请求,就会把信息存储在这些节点上

k一般要大于1。不然只会把信息存储在一个节点上,万一节点下线,或者退出网络,就会导致信息不能被找到。

四、数据包

节点与节点之间,通过UDP协议,传输数据包来通讯。

DHT网络的数据包都是json格式。

必须字段:

  • t:消息的id。因为是UDP传输,所以要带上消息ID,不要就不知道每个包对应是哪个包的回复。
  • y:数据包的类型,取值可以是:
    • q,请求包
    • r,回复包
    • e,错误包,其实也是回复的一种

1. 请求和回复包

请求包必须字段

  • q,请求的类型,

    • ping 嗅探Node是否可用
    • find_node。寻找Node的请求
    • get_peers。寻找有资源的Node
    • announce_peer ,请求下载资源
  • a,请求的参数,类型是json里面的字典

回复包必须字段:

*r 回复的内容,字典

1.1ping

请求包

a包含字段

  • id,请求者的nodeid

包例子

{"t":"aa", "y":"q","q":"ping", "a":{"id":"abcdefghij0123456789"}}

回复包

r包含字段

  • id 回复者的nodeid

包例子

{"t":"aa", "y":"r", "r":{"id":"mnopqrstuvwxyz123456"}}

1.2find_node

请求包

a包含字段

  • id,请求者的nodeid
  • target,需要寻找的Node的nodeid

包例子:

{"t":"aa", "y":"q","q":"find_node", "a":{"id":"abcdefghij0123456789","target":"mnopqrstuvwxyz123456"}}

回复包

r包含字段

  • id 回复者的nodeid
  • nodes 在回复者的路由表中,与请求的target 的nodeid最接近的几个节点的信息,包含节点的ip,端口,nodeid。

包例子

 {"t":"aa", "y":"r", "r":{"id":"0123456789abcdefghij", "nodes":"def456..."}}

1.3 get_peers

请求包

a包含字段

  • id,请求者的nodeid
  • info_hash 寻找的资源的hash
  • token 密钥

包例子

{"t":"aa", "y":"q","q":"get_peers", "a":{"id":"abcdefghij0123456789","info_hash":"mnopqrstuvwxyz123456"}}

回复包

如果回复者的路由表中,有存有info_hash资源的节点信息,就返回value,否则返回node,node的值和find_node一样

r包含字段

  • id 回复者的nodeid
  • value,拥有info_hash的节点信息
  • nodes 和find_node的nodes一样

包例子

{"t":"aa", "y":"r", "r":{"id":"abcdefghij0123456789", "token":"aoeusnth","values": ["axje.u", "idhtnm"]}}

1.4 announce_peer

请求包

a包含字段

  • id,请求者的nodeid
  • info_hash 寻找的资源的hash
  • token 密钥
  • port,下载资源的端口

包例子

{"t":"aa", "y":"q","q":"announce_peer", "a":{"id":"abcdefghij0123456789","info_hash":"mnopqrstuvwxyz123456", "port":6881, "token": "aoeusnth"}}

回复包

r包含字段

  • id 回复者的nodeid

包例子

{"t":"aa", "y":"r", "r":{"id":"mnopqrstuvwxyz123456"}}

2. 错误包

  • e 列表类型,第一个元素时错误id,第二个是错误的说明

    {"t":"aa", "y":"e", "e":[201,"A Generic Error Ocurred"]}

错误类型有:

  • 201 一般错误
  • 202 服务错误
  • 203 协议错误,比如不规范的包,无效的参数,或者错误的token
  • 204 未知方法

五、工作流程

1.初始化

  • 向一个固定的服务器,获取节点ID,完成冷启动
  • 不断向已知的节点发送find_node请求,让自己的路由表里面的节点更多

2. 根据磁力链接,获取信息(bt文件)

  • 获取磁力链接里面的md5,转换为二进制M1。
  • 通过路由表,获取与M1距离最近的节点,然后向它们发送announce_peer 请求。如果节点有我们想要的信息,就会把信息发过来,这样我们就获取到了bt文件了。

六、DHT网络中收集bt文件的原理

向三个固定服务器发送find_node的请求,target是随机的nodeid或者是自己的nodeid,N1

服务器返回最接近N1的的3个nodeid的信息,这些信息是一个加密了的,固定协议的字符串,里面有node的ip,port和nodeid。自身节点把所有的node存储到路由表

新开一个线程,对node,再发送find_node请求,这时自己的nodeid是随机的

这样,就会导致在很多个DHTNode中,都有我们ip和端口的信息,而且映射到很多不同的nodeid

这样别人去这些DHTNode中寻找bt资源的时候,这些Node就很可能会返回我们的IP,PORT给别人,那么别人就会向我们发送announce_peer的请求,这样我们就能拿到bt文件了

  1. 初始化,目的是让自己的nodeid加入到DHT网络中,并认识尽量多的其他node,放到我们的路由表。

    1. 生成自己的nodeid。
    2. 向固定的服务器(例如:),发送find_node请求,target是自己的nodeid,这样,自己的nodeid就会进入到固定服务器的路由表,这样其他node想固定服务器发送find_node请求的话,固定服务器就会返回我们的nodeid给他们,这样我们的nodeid就会进入很多其他Node的路由表了。
    3. 发送给固定服务器的find_node请求中,会返回我们附近的node的信息,保存到我们自己的路由表
  2. 接收其他节点的请求。当我们加入到DHT网络中,就会有其他节点发送请求给我们。下面的请求处理完后,我们都把请求者加入到我们的路由表中。

    1. 当我们收到ping请求,就返回自己的id给它,表示自己在正常运行。
    2. 当我们收到find_node请求, 就从我们的路由表查找离target最近的N个node的信息,返回给它。
    3. 当我们收到get_peers请求,就从我们的路由表中查找拥有该资源的peers信息,返回给它。
    4. 当我们收到announce_peer 请求,就从发送info_hash的资源到对应的端口

七、Bt文件下载原理

当得到BT文件后,就可以用bt文件下载器进行文件的下载

BT文件里面包含

  • tracket地址
  • 目标文件列表,和分块信息。每一块是2k的倍数。分块信息包含每一个分块的索引和MD5
  • BT文件的基本信息,如标题,每个文件的大小和文件名等

下载流程

  • 下载器请求tracket地址,获取其他也在下载该bt文件的节点信息
  • 下载器连接其他节点,告诉自身缺少的分块的索引和获取到对方缺少的分块索引
  • 如果自身有分块1,而对方没有,就向对方发送分块1
  • 如果对方有分块2,而自身没有,就接收分块2
  • 接收完一个分块后,计算md5,然后和bt文件里面的md5对比,如果正确,就下载完成,否则要重新下载。

所以bt文件的下载过程,并不是去中心化的,tracket服务器就是一个中心化的服务器。

tracket服务器只管理下载节点的信息,并不会存储文件的具体分块。所以压力也比较小。

节点越多,下载的速度越快。

参考

未经允许,请不要转载

DHT网络的更多相关文章

  1. [搜片神器]直接从DHT网络下载BT种子的方法

    DHT抓取程序开源地址:https://github.com/h31h31/H31DHTDEMO 数据处理程序开源地址:https://github.com/h31h31/H31DHTMgr DHT系 ...

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

    继续接着上一篇写:使用C#实现DHT磁力搜索的BT种子后端管理程序+数据库设计(开源)[搜片神器] 昨天由于开源的时候没有注意运行环境,直接没有考虑下载BT种子文件时生成子文件夹,可能导致有的朋友运行 ...

  3. [搜片神器]之DHT网络爬虫的代码实现方法

    继续接着第一篇写:使用C#实现DHT磁力搜索的BT种子后端管理程序+数据库设计(开源)[搜片神器] 谢谢园子朋友的支持,已经找到个VPS进行测试,国外的服务器: http://www.sosobta. ...

  4. [搜片神器]之DHT网络爬虫的C++程序初步开源

    回应大家的要求,特地整理了一开始自己整合的代码,这样最简单,最直接的可以分析流程,至于文章里面提供的程序界面更多,需要大家自己开发. 谢谢园子朋友的支持,已经找到个VPS进行测试,国外的服务器: ht ...

  5. 最近研究了一个.NET的DHT网络搜索引擎,顺便重新整理了下引擎思路,供大家分享讨论下。

    最近研究了一个.NET的DHT网络搜索引擎,顺便重新整理了下引擎思路,供大家分享讨论下.

  6. DHT网络第一部分研究结果 加入长期在线的node

    源码:http://jijiea.com/upfile/DHT_Part1_How_To_Join_In_DHT.zip

  7. BT网络中DHT和UPnp的解释(转)

    DHT 类似Tracker的根据种子特征码返回种子信息的网络.DHT全称叫分布式哈希表(Distributed Hash Table),是一种分布式存储方法.在不需要服务器的情况下,每个客户端负责一个 ...

  8. DHT协议网络爬虫磁力链接和BT种子搜索引擎

    系统功能和用到的技术. 系统包括几个独立的部分: 使用 Python 的 Scrapy 框架开发的网络爬虫,用来爬取磁力链接和种子: 使用 PHP CI 框架开发的简易网站: 搜索引擎目前直接使用的 ...

  9. bt协议详解 DHT篇(上)

    bt协议详解 DHT篇(上) 最近开发了一个免费教程的网站,突然产生了仔细了解bt协议的想法,这篇文章是bt协议详解系列的第三篇,后续还会写一些关于搜索和索引的东西,都是在开发这个网站的过程中学习到的 ...

随机推荐

  1. Unity 显示FPS(C#语言)

    直接上脚本了: using UnityEngine; using System.Collections; public class ShowFPS : MonoBehaviour { //设置帧率 A ...

  2. java第二周的学习知识4(对原码,补码,反码和java中浮点数计算不准确的总结)

    原码:一个正数,转换为二进制位就是这个正数的原码.负数的绝对值转换成二进制位然后在高位补1就是这个负数的原码. 但是原码有几个缺点,零分两种 +0 和 -0 .很奇怪是吧!还有,在进行不同符号的加法运 ...

  3. JS_高程5.引用类型(5)Array类型的操作方法

    一.操作方法 1.concat()方法 基于当前数组中的所有项创建一个新数组.具体说,是先创建当前数组的一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组.在没有给concat() ...

  4. 关于js的函数

    1.获取内容的兼容函数 /* * 一: 获取内容的兼容函数 * setText(obj, str) * 思路: * 1.首先判断浏览器: * 2.如果是IE浏览器,就用innerText: * 3.如 ...

  5. html的文字样式、下行线、删除线、上标、下标等实现方式

    先看效果如下: 代码如下: <del>del标签删除线</del><br/> <strike>strike标签删除线</strike>< ...

  6. windows NT的意义和各个版本

    javascript中navigator.userAgent里的window NT今天为了尝试查看网址的来源document.referrer,但是不知道每个浏览器的版本号,然后我就用navigato ...

  7. JS 正则表达式从地址中提取省市县

    var add1 = '四川省西昌市航天路'; var add2 = '北京市北京市东城区前门大街1号' var add3 = '新疆维吾尔自治区乌鲁木齐市天山区中山路479号'; var add4 ...

  8. Android的Databinding-数据、Map绑定

    本节主要说Collection的字符串数组.List.SparseArray.Map的绑定.先看看xml的布局. <layout xmlns:android="http://schem ...

  9. 关于cxf生成客户端代码中的JAXBElement<String>

    1.使用自动生成的java文件中的 ObjectFactory构造入参 关于cxf生成客户端代码中的JAXBElement<String>    在使用cxf或者x-fire进行webse ...

  10. MYSQL常用的性能指标总结和归纳

    (1) QPS(每秒Query量)QPS = Questions(or Queries) / uptimemysql> show global status like 'Question%';m ...