当前位置: 首页 > news >正文

一种使用 Java / Kotlin 编写检测BT种子的磁力链接是否有可用 peers 的程序

一、问题背景

当我们想下载种子的资源时候,我们无法快速的知道一个种子是否可用,只有当我们放到种子下载器进行尝试下载,才可以知道一个种子是否可用。当我们有多个种子的时候,如果一个一个的尝试,那就会非常耗费时间和精力。

因此我们需要一个程序,帮助我们先去对种子进行一次筛选,先去简单判断一下种子是否可用。本文将使用 Java / Kotlin 编写一种通过检查是否有可用的 peers 的方式去检测 BT 种子的磁力链接是否可用的程序。

磁力连接形如:

magnet:?xt=urn:btih:<info-hash>

二、概要设计

本文使用的 Java 库为 atomashpolskiy/bt:https://github.com/atomashpolskiy/bt。这是一个为 Java 实现的支持种子下载的所有特性的 BitTorrent 的库。

官方 wiki:https://atomashpolskiy.github.io/bt/

本方案将利用 atomashpolskiy/bt 种子下载的功能,先将 种子链接和 tracker 进行组装,然后调用 BtClient 进行下载种子,并利用回调的方式知道种子下载的状态,再从状态中获取 peers 的信息,一旦获取到 peers 信息,就立刻停止下载,表示此种子可用。如果在规定时间内,未获取到 peers 信息,则表示种子不可用。此方案的流程图如下:

在这里插入图片描述

三、实现细节

(一)在 gradle 中导入 atomashpolskiy/bt

首先,我们需要先导入 atomashpolskiy/bt 的相关依赖,主要有:

  • com.github.atomashpolskiy:bt-core:核心库
  • com.github.atomashpolskiy:bt-http-tracker-client:提供 tracker 能力
  • com.github.atomashpolskiy:bt-dht 提供 DHT 能力

当前最新的版本为 1.10,因此 gradle 中编写如下内容,即可导入 atomashpolskiy/bt 库。

dependencies {// BT 提供的库def bt_version = "1.10"implementation "com.github.atomashpolskiy:bt-core:$bt_version"implementation "com.github.atomashpolskiy:bt-http-tracker-client:$bt_version"implementation "com.github.atomashpolskiy:bt-dht:$bt_version"
}

(二)拼接 magnetURL 与 trackerURL

首先,我们需要先实现拼接 magnetURLtrackerURL,由于 magnetURL 里面只有种子的 hash 值,需要配置 tracker 的信息才能寻找到可供下载的用户,并帮助建立链接,可以参考:https://trackerslist.com/#/zh

同时,在磁力链接里面支持直接写 Tracker,通过参数 &tr= 进行连接,例如:

magnet:?xt=urn:btih:<info-hash>&tr=tracker1&tr=tracker2&tr=tracker3

因此,我们在检测种子的磁力链接是否可用的时候,需要提供一个 magnetURLtrackerURL 的列表,利用 StringBuilder 拼接字符串。

// 将 tracker 拼接到 磁力链之后
val torrent = StringBuilder(torrentUri)
trackers?.forEach { tracker ->torrent.append("&tr=").append(tracker)
}

(三)构建 Config

对于 atomashpolskiy/bt 库中下载 BT 的相关配置是由 bt.runtime.Config 类进行定义的,在本方案中,为了加快发现 peers 的速度,从源码中来看,maxConcurrentlyActivePeerConnectionsPerTorrent 的默认值为20,因此可以适当增加 peer 的连接数。

val config = Config()
// 增大获取peers的线程数
config.maxConcurrentlyActivePeerConnectionsPerTorrent = 50

源码如下:
在这里插入图片描述

(四)构建不下载的 Storage

由于在下载的时候,一定需要指定一个 Storage,表示下载该种子的文件时存储的目录。但是我们只是需要检测种子是否可用,不需要实际下载,因此需要自定义一个 Storage 类,使其不会进行下载,一种实现方法就是覆写所有方法,并且都是空实现,以便实现禁止下载。方法如下:

object : Storage {override fun getUnit(torrent: Torrent?, torrentFile: TorrentFile?): StorageUnit? {return object : StorageUnit {override fun readBlock(buffer: ByteBuffer?, offset: Long): Int = -1override fun writeBlock(buffer: ByteBuffer?, offset: Long): Int = -1override fun writeBlock(buffer: ByteBufferView?, offset: Long): Int = -1override fun capacity(): Long = 0override fun size(): Long = 0override fun close() {}}}override fun flush() {}
}

(五)构建 BtClient

按照官方的文档,构建下载 BT 种子磁力链的 BtClient

val client: BtClient = Bt.client().config(config)// 不下载文件.storage(object : Storage {override fun getUnit(torrent: Torrent?, torrentFile: TorrentFile?): StorageUnit? {return object : StorageUnit {override fun readBlock(buffer: ByteBuffer?, offset: Long): Int = -1override fun writeBlock(buffer: ByteBuffer?, offset: Long): Int = -1override fun writeBlock(buffer: ByteBufferView?, offset: Long): Int = -1override fun capacity(): Long = 0override fun size(): Long = 0override fun close() {}}}override fun flush() {}}).magnet(torrent.toString()).autoLoadModules().build()

(六)定时回调获取种子状态

BtClient 有一个 startAsync 方法,其可以在独立的线程中开始下载种子,并且其可以传递一个 Consumer<TorrentSessionState> 的参数和时间间隔,BtClient 将按时间间隔定时回调 Consumer<TorrentSessionState> 方法,获取种子的下载状态,从中可以获取到相关的已连接的 peers 信息。我们可以定义一个超时时间,如果轮询时间达到了,但是仍没有获取到 peers 信息,则认为种子不可用,结束 BtClient。如果在轮询时间内,获取到了 peers 信息,则认为种子可用,并结束 BtClient。相关代码如下:

// 定义种子是否可用
var available = false
// 定义已轮询的次数
var count = 0// 每隔一段时间检测一次是否有peers
val future = client.startAsync({ s: TorrentSessionState ->// 如果 connectedPeers 不为空 则表示有可以连接的 peers 则认为种子可用if (s.connectedPeers.isNotEmpty()) {available = trueclient.stop()return@startAsync}// 如果轮询次数已经达到了指定次数 即已经超时了 仍没有获取到 peers 则认为种子不可用if (++count >= checkCount) {client.stop()}// 定义每次轮询的时间间隔
}, CHECK_PEERS_INTERVAL)// 等待 client 完成 stop 或 超时
future.join()return available

四、完整实现

完整的实现如下:

import bt.Bt
import bt.data.Storage
import bt.data.StorageUnit
import bt.metainfo.Torrent
import bt.metainfo.TorrentFile
import bt.net.buffer.ByteBufferView
import bt.runtime.BtClient
import bt.runtime.Config
import bt.torrent.TorrentSessionState
import com.teleostnacl.bt.utils.BTUtil.CHECK_PEERS_COUNT
import java.nio.ByteBuffer/*** BT 种子工具类*/
object BTUtil {/*** 检查 Peers 的时间间隔*/private const val CHECK_PEERS_INTERVAL = 1000L/*** 检查 Peers 超时的时长 单位: 分钟*/const val CHECK_PEERS_TIME_MIN = 1/*** 检查 Peers 的次数*/private const val CHECK_PEERS_COUNT = CHECK_PEERS_TIME_MIN * 60/*** 检查种子是否可用的方法** @param torrentUri 种子的链接* @param trackers 自定义的tracker列表* @param checkCount 检查 Peers 的次数, 默认为 [CHECK_PEERS_COUNT]*/fun isTorrentUrlAlive(torrentUri: String,trackers: List<String>? = null,checkCount: Int = CHECK_PEERS_COUNT): Boolean {val startTime = System.currentTimeMillis()// 将 tracker 拼接到 磁力链之后val torrent = StringBuilder(torrentUri)trackers?.forEach { tracker ->torrent.append("&tr=").append(tracker)}val config = Config()// 增大获取peers的线程数config.maxConcurrentlyActivePeerConnectionsPerTorrent = 50val client: BtClient = Bt.client().config(config)// 不下载文件.storage(object : Storage {override fun getUnit(torrent: Torrent?, torrentFile: TorrentFile?): StorageUnit? {return object : StorageUnit {override fun readBlock(buffer: ByteBuffer?, offset: Long): Int = -1override fun writeBlock(buffer: ByteBuffer?, offset: Long): Int = -1override fun writeBlock(buffer: ByteBufferView?, offset: Long): Int = -1override fun capacity(): Long = 0override fun size(): Long = 0override fun close() {}}}override fun flush() {}}).magnet(torrent.toString()).autoLoadModules().build()var available = falsevar count = 0// 每隔一段时间检测一次是否有peersval future = client.startAsync({ s: TorrentSessionState ->if (s.connectedPeers.isNotEmpty()) {available = trueclient.stop()return@startAsync}if (++count >= checkCount) {client.stop()}}, CHECK_PEERS_INTERVAL)// 等待 client 完成 stop 或 超时future.join()return available}
}
http://www.xdnf.cn/news/1409131.html

相关文章:

  • 扩展:如何设计与实现一个微服务架构下的跨服务异常处理适配器?
  • linux修改权限命令chmod
  • sunset: twilight靶场
  • 利用ms-swift微调和百炼平台微调大模型
  • FTP - 学习/实践
  • 【学习笔记】LLM Interview(Agent相关)
  • (附源码)基于Vue的教师档案管理系统的设计与实现
  • 安装Android Studio
  • centos 7 安装docker、docker-compose教程
  • SketchUp Pro 2024 Mac 3D建模 草图设计大师
  • Redis八股小记
  • 【了解下TJ、TC、TB、TT、TA、qJA、qJC、qJB、YJB、YJT】
  • Asible——将文件部署到受管主机和管理复杂的Play和Playbook
  • [linux仓库]解剖Linux内核:文件描述符(fd)的‘前世今生’与内核数据结构探秘
  • 编写一个用scala写的spark程序从本地读取数据,写到本地
  • 【ArcGIS微课1000例】0150:如何根据地名获取经纬度坐标
  • openssl使用SM2进行数据加密和数据解密
  • 科普:requirements.txt 和 environment.yml
  • Labview使用modbus或S7与PLC通信
  • Machine Learning HW3 report:图像分类(Hongyi Lee)
  • 《深入剖析Kafka分布式消息队列架构奥秘》之Springboot集成Kafka
  • 中级统计师-统计实务-第四章 专业统计
  • 嵌入式ARM程序高级调试技能:20.qemu arm ARM Linux 上 addr2line 的实际应用示例
  • 【重学MySQL】九十五、Linux 下 MySQL 大小写规则设置详解
  • CF每日3题(1500-1600)
  • 阿里云创建自己的博客,部署wordpress
  • 基于Matlab元胞自动机的强场电离过程模拟与ADK模型分析
  • Scikit-learn Python机器学习 - 数据集的划分
  • 网格图--Day03--网格图DFS--2658. 网格图中鱼的最大数目,1034. 边界着色,1020. 飞地的数量
  • Cartographer中的gflag与lua文件