前言

说句大实话,网上介绍怎么用java实现p2p种子的搜索这种资料不是特别多,大部分都是python的,用python的话就会简单很多,它里面有很多简单方便的包,libtorrent等等,当然你用这些包可以实现功能,但是它封装了太好,以致于你很难知道里面的细节。为了深入了解,然后我就用java实现了一把,当然中间遇到了很多的问题,也参考了github的项目。

说到p2p,我想大家可能都用种子下载过文件,比较常见的就是.torrent结尾的文件,通过种子可以下载到种子对应的文件。

基本概念

那么什么是种子呢?种子其实就是一个文件里面保存的是一个字典,其中最重要的一个字段就是info字段,里面保存文件名和文件长度。还有一个比较重要的字段是announce,这个是tracker地址通过这个地址可以查找到这个文件在哪些peer上面。还有一些种子没有announce字段,这样的种子被称为trackerless torrent,会有nodes字段来代替。

除了种子可以下载文件,还有一种磁力链接也可以下载文件。那么什么是磁力链接呢?just like magnet:?xt=urn:btih:19838A8C4DE7DC2E34382249C9A52CFD9E3BB41A

复制这个链接,打开迅雷会让你下载权利的游戏,磁力链接的前面部分是不变的,也就是说最后那一长串字符串才是对应了你下载的资源。最后一长串的字符串叫做infohash,每个种子都会对应一个infohash,所以磁力链接就是根据infohash来下载种子对应的文件。所以我们只要收集到足够多的infohash,然后根据infohash再找到那些文件,建立起infohash和文件的对应关系,那么我们的搜索也就完成了,这里其实做的是磁力的搜索。

好了现在我们需要解决两个问题,第一个是怎么爬取infohash,第二个是怎么通过infohash来找到种子包含的文件名。

那么我们怎么能够获取到infohash呢?这里以bittorrnt dht protocol为例

要想获取infohash就必须成为dht网络中的一员,dht网络中由很多node组成,每个node由160个bit组成。每个node呢,都有存储其中一部分node的信息,这个信息呢包括node的id,ip,port等等,在存储node id的时候也不是随便存储,会按照距离的远近来存储,这里的距离不是物理的距离 ,而是逻辑距离。通过两个id的异或来计算。举个小例子 node a 的id是1110, node b的id等于0110,那么node a和node b之间的距离就是2的3次方。node的信息都存在路由表里面,每个路由表分为160个K桶,上面的那个例子中,因为node a和node b距离是2^3,所以node b会存放在node a的第三个bucket。这里稍微解释下,这是因为node的id是160位,所有的id范围在0~2^160,一个路由160个bucket刚好覆盖id的所有的范围。那么这样是不是意味着数字越大的桶里面的节点数也就越多呢?为了防止一个bucket里面节点太多,所以规定了每个bucket最多有8个节点,那么当有别的节点来了,又超过了8个节点的时候应该怎么办呢?这个问题放到后面再来讲。

那怎么来建立路由表呢?首先dht网络中有4个方法,通过这四个方法可以来建立路由表

  • ping 检查一个节点是否在线

  • find_node 查找一个节点

  • get_peers 查找指定的某个infohash

  • announce_peer 发起一个通知,用来告诉节点下载完了

建立路由表,最重要的就是find_node,每发起一次find_node请求,对方就会返回距离被查询节点最近的前8个节点,通过不断的find_node,我们的路由表就建立了。
好了,到了这里其实还会有很多疑问?比方说路由表有什么用,建立了路由表后又怎么获取infohash呢?还是没讲明白,带着这几个问题继续下面的分析。

假设有一个infohash xxx,那么怎么知道xxx在哪个节点上面呢?这个地方很关键,因为infohash也是160bit,所以可以用同样的方法进行异或计算。因为在dht中规定离infohash距离最近的N个节点有责任知道这个infohash在哪,但是不一定保存这个infohash。这里用到的是get_peers,通过get_peers可以查找一个infohash,具体流程如下

(1) 从路由表中查到最近的8个node,依次发起get_peers请求
(2)如果没有查到的情况下,那么会返回离对方最近的8个node,继续对返回的node进行get_peers请求
(3)如果查到了,那么就返回values参数,里面包含了 拥有该infohash的ip和port

从上面可以知道,如果没有查到infohash对应的node,那么会不断从路由表最近的节点里面去查找,当然最后可能会找不到。
还有个announce_peer没说,上面提到dht中规定离infohash距离最近的N个节点有责任知道这个infohash在哪,当你从某个节点获取到infohash的时候,就要告诉那些节点你也获取到了infohash,这样那些节点就会保存你也有infohash这个信息。
好了只剩下ping这个没说了,ping主要用来检测一个节点是否存在存活。上面有说到路由表的每个bucket只能存放最多8个节点,当有新的节点来的时候,又超过了8个节点。这个时候就会分成两个情况。

  • 该节点本来所在的bucket不是该bucket,那么bucket就会一分为二,因为一开始bucket只有一个,而不是一开始就有160个(后面讲实现还会再来讲)
  • 该节点所在的bucket已经存在了8个节点,那么就会对节点发起ping,会替换没有响应的请求,如果所有节点都是好的,那么就丢弃该节点。

上面说的那四个ping,find_node,get_peers,announce_peer中的任意一个 当客户端收到请求的时候就会把他们加入自己的路由表。

好了讲了这么多,现在来总结一下ping和find_node都是和路由表最相关的ping用来检查bucket中的节点状态,find_node用来构建路由表。get_peers和announce_peer都是和infohash最相关get_peers可以通过主动查找的方式获取infohash,announce_peer通过被动接受的方式获取到infohash。所以get_peers和announce_peer都是我们从dht网络中获取infohash的最重要的方式。后面具体实现部分还会详细介绍。

本人能力有限,如果有出入的地方请不啬指出,也希望能留言和我讨论。

如何用java实现一个p2p种子搜索(1)-概念的更多相关文章

  1. 如何用java实现一个p2p种子搜索(4)-种子获取

    种子获取 在上一篇中我们已经可以获取到dht网络中的infohash了,所以我们只需要通过infohash来获取到种子,最后获取种子里面的文件名,然后和获取到的infohash建立对应关系,那么我们的 ...

  2. 如何用java实现一个p2p种子搜索(3)-dht协议实现

    dht协议实现 上一篇完成了路由表的实现,建立了路由表后,我们还要对路由表进行初始化,因为一开始路由表为空,所以我们需要借助一些知名的dht网络中的节点,对这些节点进行find_node,然后一步步初 ...

  3. 如何用java实现一个p2p种子搜索(2)-路由表实现

    路由表实现 回顾一下上一篇讲的内容,上一篇提到从dht网络中获取infohash,那么加入dht网络后的最重要的第一步就是怎么去建立路由表. 路由表里面保存的是dht中其他node的信息,所以node ...

  4. 如何用java创建一个jdbc程序

    第一个jdbc程序 JDBC简介 Java数据库连接(Java Database Connectivity,JDBC),是一种用于执行SQL语句的Java API,它由一组用Java编程语言编写的类和 ...

  5. 如何用java完成一个中文词频统计程序

    要想完成一个中文词频统计功能,首先必须使用一个中文分词器,这里使用的是中科院的.下载地址是http://ictclas.nlpir.org/downloads,由于本人电脑系统是win32位的,因此下 ...

  6. 一个支持种子、磁力、迅雷下载和磁力搜索的APP源代码

    磁力搜索网站2020/01/12更新 https://www.cnblogs.com/cilisousuo/p/12099547.html 一个支持种子.磁力.迅雷下载和磁力搜索的APP源代码 Lic ...

  7. 如何用Java编写一段代码引发内存泄露

    本文来自StackOverflow问答网站的一个热门讨论:如何用Java编写一段会发生内存泄露的代码. Q:刚才我参加了面试,面试官问我如何写出会发生内存泄露的Java代码.这个问题我一点思路都没有, ...

  8. 五:用JAVA写一个阿里云VPC Open API调用程序

    用JAVA写一个阿里云VPC Open API调用程序 摘要:用JAVA拼出来Open API的URL 引言 VPC提供了丰富的API接口,让网络工程是可以通过API调用的方式管理网络资源.用程序和软 ...

  9. 基于python的种子搜索网站-开发过程

    本讲会对种子搜索网站的开发过程进行详细的讲解. 源码地址:https://github.com/geeeeeeeek/bt 项目开发过程 项目简介 该项目是基于python的web类库django开发 ...

随机推荐

  1. (二)jdk8学习心得之Lambda表达式

    二.Lambda表达式 1. 格式 (参数1,参数2,…,参数n)->{方法体} 注意: (参数1,参数2,...,参数n)要与方法接口中的参数一致,但是名字可以不一样. 此外,方法类型接口,有 ...

  2. git配置ssh秘钥(公钥以及私钥)

    桌面版git,  本文以github为例,gitlab等其它托管平台一样操作 当我们将代码托管到远程平台(GitHub.gitlab等)时, 我们需要在本地使用git进行push/pull代码时,需要 ...

  3. 一次隐蔽的while死循环

    private int isStocksEnough(int goodsNum,int goodsID) { while(true) { sql = "select * from tb_go ...

  4. springboot项目中配置swagger-ui

    Git官方地址:https://github.com/SpringForAll/spring-boot-starter-swagger Demo:https://github.com/dyc87112 ...

  5. odoo Model字段的参数

    odoo Model字段的参数 class Field(object): """ The field descriptor contains the field defi ...

  6. [SCOI2009]生日礼物题解

    题目 一道模拟和队列题,但模拟比队列的成分多一些.队列也就是用两个指针模拟的. 可以用枚举的思想.首先我们知道r(即区间的右端点是肯定不会左移的),而l右移的同时,r可能不变,也可能右移,所以这样就可 ...

  7. tcpdump常用参数说明及常见操作

    tcpdump常用参数说明及常见操作 -a 将网络地址和广播地址转变成名字 -c 指定抓包的数量 -d 将匹配信息包的代码以人们能够理解的汇编格式给出 -dd 将匹配信息包的代码以c语言程序段的格式给 ...

  8. CF 441E Valera and Number

    CF 441E Description 一共执行\(k\)次,每次有\(p\%\)把\(x * 2\),有\((100 - p)\%\)把\(x + 1\).问二进制下\(x\)末尾期望\(0\)的个 ...

  9. php5.4、5.5、5.6高版本中htmlspecialchars兼容性处理

    在使用php5.4以上版本以上时会有一个函数可能会报错 如下 Warning: htmlspecialchars(): charset `gbk' not supported, assuming ut ...

  10. (N叉树 递归) leetcode589. N-ary Tree Preorder Traversal

    Given an n-ary tree, return the preorder traversal of its nodes' values. For example, given a 3-ary  ...