当前位置: 首页 > 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.dtcms.com/a/359558.html

相关文章:

  • CAD2024安装包下载与安装详细教程
  • 生物化学Note Track(I)——氨基酸
  • PortSwigger靶场之 DOM XSS in innerHTML sink using source location.search通关秘籍
  • 从 WPF 到 Avalonia 的迁移系列实战篇4:控件模板与 TemplatedControl
  • AIA中断控制器IPI的Linux内核实现
  • 【Flask】测试平台开发,产品管理功能UI重构-第九篇
  • 应用开发使用缓存
  • R notes[2]
  • VMware虚拟机网盘下载与安装指南(附安装包)
  • GaRe:面向非约束户外照片集的可重光照 3D 高斯溅射技术简要解析
  • Python与Rust语法对比详解:从入门到精通
  • Day19_【机器学习—线性回归 (1)】
  • Linux-搭建NFS服务器
  • Hutool DsFactory多数据源切换
  • 深度学习篇---MobileNet网络结构
  • 揭秘数据分组的智慧:Self-Constrained Clustering Ensemble 介绍
  • leetcode_240 搜索二维矩阵 II
  • Windows PostgreSQL JDBC驱动安装包位置
  • 基于开源AI大模型、AI智能名片与S2B2C商城小程序的“教育用户”模式探究
  • C数据结构:排序
  • Knit-易用的prompt管理和调试工具
  • 程序员独立开发直播卖产品 SOP 教程
  • 下载 | Win10正式版最新原版ISO系统映像 (22H2、19045.6282、多合一版本)-系统问题修复
  • Spring Boot 3.0 应用 HTTP 到 HTTPS 技术改造方案
  • AI 相关内容:Agent、MCP、Prompt 与 RAG 入门指南
  • VSCode `tasks.json` 中 `tasks` 数组的详细解析
  • AI 应用 图文 解说 (二) -- 百度智能云 ASR LIM TTS 语音AI助手源码
  • VSCode的launch.json配置文件在C++项目调试中的全面应用
  • React学习教程,从入门到精通, ReactJS - 架构(6)
  • 心路历程-基础命令3