路由表实现

回顾一下上一篇讲的内容,上一篇提到从dht网络中获取infohash,那么加入dht网络后的最重要的第一步就是怎么去建立路由表。

路由表里面保存的是dht中其他node的信息,所以node可以这么设计

public class Node implements Comparable<Node>{

    private String nodeId;//16进制字符串

    private String ip; //node的ip

    private Integer port; //node的端口

    private Date updateTime;//最后更新时间

    private byte[] nodeIdBytes;//20字节

    private Integer k=0;//k桶应该有的位置

    private Integer currentK=0;//当前的位置

    private Integer rank=0; //node的rank分值 ,路由表满的时候,优先移除分值低的
.....
}

因为路由表的每个bucket最多只有存8个,所以当路由表的bucket满的时候,需要不断的删除rank分最低的node,为了高效比较和删除bucket我们可以用PriorityQueue,每个路由表最多有160个bucket,所以可以用map来存储路由表

private Map<Integer,PriorityQueue<Node>> tableMap=new ConcurrentHashMap<>();

因为路由表一开始只有一个bucket,当节点数量超过8个就会分裂成两个bucket,为了确定新节点应该插入到哪个bucket中,所以把每个bucket设计成链表

public static class Bucket{
private int k; //当前是第几个k桶
private Bucket next;//下一个k桶
}

好了我们再来看怎么添加一个node

public void put(Node node) {
int bucketIndex = getBucketIndex(node);
if(bucketIndex==0){//是自己就不用加入了
return;
}
PriorityQueue<Node> pq = tableMap.get(bucketIndex);
if(CollectionUtils.isEmpty(pq)){
//如果是空 那么找最近的那个节点加入
boolean isAdd=false;
while(bucket.next != null){
if(bucketIndex > bucket.getK()
&& bucketIndex < bucket.next.getK()){
//先往小的里面放
node.setCurrentK(bucket.getK());
isAdd=putAccurate(tableMap.get(bucket.getK()),node,false,bucket,tableMap);
if(!isAdd){
node.setCurrentK(bucket.next.getK());
isAdd=putAccurate(tableMap.get(bucket.next.getK()),node,true,bucket,tableMap);
}
}
bucket=bucket.next; }
if(!isAdd){
//没有添加成功 那么往最后一个节点添加
node.setCurrentK(bucket.getK());
putAccurate(tableMap.get(bucket.getK()),node,true,bucket,tableMap);
} }else{//如果不空 那么直接加 简单点来吧
if(pq.size()<8){
if(!pq.contains(node)){
node.setCurrentK(node.getK());
pq.add(node);
}else{
reAdd(pq,node);
}
}else{
pq.add(node);
pq.poll();
}
}
}

其中比较重要的是方法是putAccurate

/**
* @param pq 当前bucket
* @param node 需要插入的node
* @param isSplit 是否需要分裂
* @param bucket 需要插入的bucket的位置
* @param tableMap 路由表
* @return 返回是否添加成功
*/
@SneakyThrows
public boolean putAccurate(PriorityQueue<Node> pq,Node node,boolean isSplit,Bucket bucket,Map<Integer,PriorityQueue<Node>> tableMap){
boolean isAdd=false;
if(pq.contains(node)){
return reAdd(pq,node);
}
if(pq.size()<8){
pq.add(node);
isAdd=true;
}
if(isSplit && !isAdd){
PriorityQueue<Node> priorityQueue=new PriorityQueue<Node>((x,y)->x.getRank()-y.getRank());
priorityQueue.add(node);
tableMap.putIfAbsent(node.getK(),priorityQueue);
//创建新的k桶后需要把两边的bucket距离比较近的都放到自己的k桶里面 如果超过8个就丢了 最好是可以ping一下
//先从小的开始放
PriorityQueue<Node> collect1 = new PriorityQueue<>();
collect1.addAll(tableMap.get(bucket.getK()).stream().filter(n -> {
if (priorityQueue.size() < 8 &&
Math.abs(n.getK() - n.getCurrentK()) > Math.abs(n.getK() - node.getK())) {
n.setCurrentK(node.getK());
priorityQueue.add(n);
return false;
}
return true;
}).collect(Collectors.toSet()));
tableMap.put(bucket.getK(),CollectionUtils.isNotEmpty(collect1)?collect1:new PriorityQueue<Node>());
if(bucket.next!=null && CollectionUtils.isNotEmpty(tableMap.get(bucket.next.getK()))){
PriorityQueue<Node> collect = new PriorityQueue<>();
collect.addAll(tableMap.get(bucket.next.getK()).stream().filter(n -> {
if (priorityQueue.size() < 8 &&
Math.abs(n.getK() - n.getCurrentK()) > Math.abs(n.getK() - node.getK())) {
n.setCurrentK(node.getK());
priorityQueue.add(n);
return false;
}
return true;
}).collect(Collectors.toSet()));
tableMap.put(bucket.next.getK(),CollectionUtils.isNotEmpty(collect)?collect:new PriorityQueue<Node>());
}
Bucket b=new Bucket(node.getK(),bucket.next);
bucket.next=b;
isAdd=true;
node.setCurrentK(node.getK());
}
return isAdd;
}

上一篇我们知道路由表主要通过find_node来建立,那我们自己也会收到别人发起的find_node请求,所以我们还要实现根据nodeid来查找最近的8个node

/**
* 根据nodeid 查找最近的8个node
* @param trargetBytes 需要查找目标id
* @return
*/
public List<Node> getForTop8(byte[] trargetBytes){
int bucketIndex = getBucketIndex(trargetBytes);
List<Node> l=new ArrayList<>();
PriorityQueue<Node> pq = tableMap.get(bucketIndex);
if(CollectionUtils.isEmpty(pq)){
while(bucket.next != null){
if(bucketIndex > bucket.getK()
&& bucketIndex < bucket.next.getK()){ tableMap.get(bucket.next.getK()).stream().forEach(x->{
if(l.size()<8){
l.add(x);
}
});
}
bucket=bucket.next;
}
if(CollectionUtils.isEmpty(l)){
tableMap.get(bucket.getK()).stream().forEach(x->{
if(l.size()<8){
l.add(x);
}
});
} }else{//如果不空 那么直接加 简单点来吧
l.addAll(pq.stream().collect(Collectors.toList()));
}
return l;
}

好了,到了这里路由表大致就实现啦。已经成功完成了第一步,现在呢路由表还没有初始化刚开始什么数据都没有,而且我们还是不能从dht中获取infohash,下一篇再来讲dht 协议,里面还会讲怎么初始化路由表,实现了dht协议也就完成了一大半了。

本章路由表部分还可以参考源码里面的RoutingTable,应该都能看得懂,地址:https://github.com/mistletoe9527/dht-spider

如何用java实现一个p2p种子搜索(2)-路由表实现的更多相关文章

  1. 如何用java实现一个p2p种子搜索(1)-概念

    前言 说句大实话,网上介绍怎么用java实现p2p种子的搜索这种资料不是特别多,大部分都是python的,用python的话就会简单很多,它里面有很多简单方便的包,libtorrent等等,当然你用这 ...

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

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

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

    dht协议实现 上一篇完成了路由表的实现,建立了路由表后,我们还要对路由表进行初始化,因为一开始路由表为空,所以我们需要借助一些知名的dht网络中的节点,对这些节点进行find_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. Linux下Nginx配置阿里云 SSL证书实现HTTPS访问

    这篇文章主要介绍了nginx配置ssl证书实现https访问的示例 1.服务器系统:Centos 2. 阿里云申请SSL证书 选择“免费版DV SSL”,点击立即购买: 下载证书 列表中找到已签发的证 ...

  2. springboot实现简单的文件上传

    承接上一篇,这里记录一下简单的springboot文件上传的方式 首先,springboot简单文件上传不需要添加额外的jar包和配置 这里贴一下后端controller层的实现代码 补一份前台的HT ...

  3. 继收购Magento十个月,Adobe推出Commerce Cloud商务云服务

    去年 5 月,软件巨头 Adobe 以 16.8 亿美元的价格,受过了 Magento .在经历了十个月的业务整合后,该公司终于推出了全新的 Commerce Cloud 商务云服务.据悉,其本质上是 ...

  4. vivado2016.1下载程序出错:End of startup status: LOW

    现象 使用JTAG下载程序,发现刚开始下载就出现了End of startup status: LOW错误.但能检测到芯片,证明JTAG没烧毁. 流程 前几次下载都没有问题,然后就有问题了. (1)怀 ...

  5. 介绍一款自动给添加不同浏览器CSS3前缀的插件~Autoprefixer(附其他前端开发插件)

    正文 自动给CSS文件添加不同浏览器的CSS3前缀:Autoprefixer 安装 只需兼容主流浏览器 正常情况使用:(在书写完的CSS样式文件中,按F1,选择Autoprefixer CSS) 这时 ...

  6. Hibernate对应关系(了解)

    布置的任务要用就写一下总结一下 hibernate有以下几种关系 一对一 一对多 多对一 多对多 首先这些对应关系是分单向和双向的 单向和双向有什么区别呢? 这个双向单向是面向对象的说法 意思就是你更 ...

  7. MongoDB常用配置项目

    systemLog:  destination: file  logAppend: true  path: /data/mongod/log/mongod-rs1.log processManagem ...

  8. 在graphviz中创建可点击的图形

    1.创建一个dot文件,在节点属性中使用URL关键字: target关键字指定链接打开的方式 //test.dot digraph Arch { A; B [URL="http://docs ...

  9. wqs二分

    今天模拟赛有一道林克卡特树,完全没有思路 赛后想了一想,不就是求\(k+1\)条不相交的链,使其权值之和最大嘛,傻了. 有一个最裸的\(DP\),设\(f[i][j][k]\)表示在以\(i\)为根的 ...

  10. 安卓上用Termux终端模拟器安装MC Forge 1.12.2服务器!

    本教程重度新手向,大神勿喷(因为楼主本人就是Linux小白)! 为了让教程合乎简约至上的原则,将不在基本操作上赘述 测试环境:酷派大神Note3 Android5.0 2GBRAM 未root 截图是 ...