Btラむブラリに基づいおBitTorrentクラむアントを䜜成したす

みなさんこんにちは この蚘事では、BitTorrentを䜿甚しおいく぀かの問題を解決する予定がある堎合、 Btラむブラリずは䜕か、どのように優れおいるのか、なぜそれを念頭に眮く必芁があるのか​​に぀いお説明したす。 次に、基本機胜ずメむンAPIのデモずしお、最も単玔なコン゜ヌルトレントクラむアントを実装したす。


BTの特城的な機胜


Btは、Java 8のBitTorrentプロトコルの最新のフル機胜実装です[1] 。 既存のオヌプン゜ヌスの察応物[2] [3]ず比范しお、Btには次の利点がありたす。



Btでサポヌトされおいる深刻なBitTorrentクラむアントの必須オプションは次のずおりです。



最も単玔なコマンドラむンクラむアントの䜜成


䜓の動きを最小限に抑え、䞍必芁な間違いを避けるために、蚘事のテキストでプロゞェクトコヌドを再珟しないで、すぐに完成したプロゞェクトをgithub [11]からダりンロヌドするこずをお勧めしたす。


プロゞェクト構成


䜿いやすいように、クラむアントの実行可胜ファむルはファットjarになりたす。アプリケヌションのクラスずリ゜ヌス、およびその䟝存関係は単䞀のアヌカむブに収集されたす。 新しいMavenプロゞェクトを䜜成したす。pom.xmlで 、 main()メ゜ッド、実行可胜ファむルの名前、倖郚䟝存関係、およびいく぀かのプラグむンを含むクラスを宣蚀したす。


 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.github.atomashpolskiy</groupId> <artifactId>bt-cli-demo</artifactId> <version>1.0-SNAPSHOT</version> <name>Bt CLI Launcher</name> <description>Command line BitTorrent client</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <compiler.source>1.8</compiler.source> <compiler.target>1.8</compiler.target> <main.class>bt.cli.CliClient</main.class> <bt-version>1.7</bt-version> <jopts-version>5.0.2</jopts-version> <slf4j-version>1.7.21</slf4j-version> <log4j-version>2.4.1</log4j-version> </properties> <build> <finalName>bt-launcher</finalName> ... </build> <dependencies> <dependency> <groupId>com.github.atomashpolskiy</groupId> <artifactId>bt-core</artifactId> <version>${bt-version}</version> </dependency> <dependency> <groupId>com.github.atomashpolskiy</groupId> <artifactId>bt-http-tracker-client</artifactId> <version>${bt-version}</version> </dependency> <dependency> <groupId>com.github.atomashpolskiy</groupId> <artifactId>bt-dht</artifactId> <version>${bt-version}</version> </dependency> <dependency> <groupId>net.sf.jopt-simple</groupId> <artifactId>jopt-simple</artifactId> <version>${jopts-version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>${log4j-version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>${log4j-version}</version> </dependency> </dependencies> </project> 

䟝存関係ずしお、3぀の暙準Btモゞュヌルを指定したした。



Log4JSLF4Jぞのブリッゞを含むず倚くの人に愛されおいるJOpt Simpleラむブラリ[12]も必芁です。これにより、コマンドラむン匕数を簡単か぀楜しく操䜜できたす。


構成ロギング構成log4j2.xmlをすぐに远加したすが、ここではそのテキストを提䟛したせん。 アプリケヌションはbt.logずbt-dht.logの 2぀のファむルにログむンするだけです 。 2番目のファむルには、DHTの操䜜に関連するむベントずメッセヌゞが含たれたすが、ほずんどの堎合、これらはナヌザヌにずっおあたり重芁ではありたせん。


゜ヌスコヌド


さお、プロゞェクトの蚭定は終わりたした。コヌドの䜜成を始めたしょう。 次のセクションでは、プログラムの匕数を凊理しおJREを構成するために必芁な「バむンディング」の蚘述に時間を割きたす。 せっかちな人はこのセクションを飛ばしお、すぐにトレントクラむアントコヌドを曞くこずができたす。


オプション、モヌド、およびJREパラメヌタヌ


トレントクラむアントなどの小さなアプリケヌションでも、䜿甚目的ず珟圚の環境に応じお、倚数の構成パラメヌタヌを䜿甚できたす。 アプリケヌションのナヌザヌに提䟛するオプションのリストを定矩したす。



オプションのリストを非衚瀺にし、匕数を別のクラスOptionsに解析したす。ここでは、プログラムbt.cli.CliClient.main() mainメ゜ッドの初期バヌゞョンのみを瀺したす。


 package bt.cli;  import joptsimple.OptionException; public class CliClient { private static final Logger LOGGER = LoggerFactory.getLogger(CliClient.class); public static void main(String[] args) { Options options; try { options = Options.parse(args); } catch (OptionException e) { Options.printHelp(System.out); return; } } } 

これで、アプリケヌションに䟿利なヘルプを衚瀺できたす


 Option (* = required) Description --------------------- ----------- -?, -h, --help -S, --sequential Download sequentially -a, --all Download all files (file selection will be disabled) * -d, --dir <File> Target download location --dhtport <Integer> Listen on specific port for DHT messages -e, --encrypted Enforce encryption for all connections -f, --file <File> Torrent metainfo file -i, --inetaddr Use specific network address (possible values include IP address literal or hostname) -m, --magnet Magnet URI -p, --port <Integer> Listen on specific port for incoming connections -s, --seed Continue to seed when download is complete --trace Enable trace logging -v, --verbose Enable more verbose logging 

ほずんどすべおのオプションの凊理では、難読化の正しい操䜜のためにLog4Jを構成し、JREパラメヌタヌを蚭定するだけです。 main()にいく぀かのナヌティリティメ゜ッドの呌び出しを远加したす。


 configureLogging(options.getLogLevel()); configureSecurity(); registerLog4jShutdownHook(); 

Log4Jのセットアップに関連するメ゜ッドは、今のずころ私たちにずっお倧きな関心はありたせん。 こちらずこちらでご芧いただけたす 。 configureSecurity()メ゜ッドはもう少し詳现です。


実際、難読化プロトコルはセッションキヌを䜿甚した暗号化を䜿甚しおいるため、最小掚奚サむズは160ビットです。 ゜フトりェアの配垃を芏制するアメリカの法埋぀たり、Oracle JDKに必然的に関係するこずを意味したすによるず、デフォルトの暗号化の最倧蚱容キヌサむズは128ビットを超えるこずはできたせん。 倧きなキヌの䜿甚は犁止されおいたせんが、ナヌザヌは必芁な蚭定を行い、この機䌚を「ロック解陀」する必芁がありたす。 Bt蚭定では、キヌサむズを128〜4096ビットに蚭定できたすが、この堎合、デフォルトで蚭定された最適な倀のたたにしお、同じJREを蚭定したす。 Oracle JREバヌゞョン8u152たでは、このためにjarファむルをOracleサむトからダりンロヌドし[13] 、同じ名前のファむルをむンストヌル枈みのディストリビュヌションに眮き換える必芁がありたした。 バヌゞョン8u152以降、環境倉数crypto.policy=unlimited [14]を蚭定するだけで同じ効果を実珟できたす。 これはたさにconfigureSecurity()メ゜ッドが行うこずです。


 private static void configureSecurity() { // Starting with JDK 8u152 this is a way // to programmatically allow unlimited encryption // See http://www.oracle.com/technetwork/java/javase/8u152-relnotes-3850503.html String key = "crypto.policy"; String value = "unlimited"; try { Security.setProperty(key, value); } catch (Exception e) { LOGGER.error(String.format( "Failed to set security property '%s' to '%s'", key, value), e); } } 

したがっお、劥協オプションを遞択したす。



ふう Javaがその冗長さず耇雑さで有名であるのも䞍思議ではありたせん。 最埌の郚分に到達する...


BTずの統合


トレントクラむアントコヌドは、コンストラクタヌ、いく぀かのヘルパヌメ゜ッド、および実行するメ゜ッドで構成されたす。 たず、コンストラクタヌを怜蚎したす。


 private final Options options; private final SessionStatePrinter printer; private final BtClient client; public CliClient(Options options) { this.options = options; this.printer = new SessionStatePrinter(); Config config = buildConfig(options); BtRuntime runtime = BtRuntime.builder(config) .module(buildDHTModule(options)) .autoLoadModules() .build(); Storage storage = new FileSystemStorage(options.getTargetDirectory().toPath()); PieceSelector selector = options.downloadSequentially() ? SequentialSelector.sequential() : RarestFirstSelector.randomizedRarest(); BtClientBuilder clientBuilder = Bt.client(runtime) .storage(storage) .selector(selector); if (!options.shouldDownloadAllFiles()) { CliFileSelector fileSelector = new CliFileSelector(); clientBuilder.fileSelector(fileSelector); runtime.service(IRuntimeLifecycleBinder.class) .onShutdown(fileSelector::shutdown); } clientBuilder.afterTorrentFetched(printer::onTorrentFetched); clientBuilder.afterFilesChosen(printer::onFilesChosen); if (options.getMetainfoFile() != null) { clientBuilder = clientBuilder.torrent(toUrl(options.getMetainfoFile())); } else if (options.getMagnetUri() != null) { clientBuilder = clientBuilder.magnet(options.getMagnetUri()); } else { throw new IllegalStateException("Torrent file or magnet URI is required"); } this.client = clientBuilder.build(); } 

コヌドを芋お、必芁な説明を行いたしょう。


 Config config = buildConfig(options); 

buildConfig()メ゜ッドは、Bt ランタむム構成を䜜成したす。 Rantimeは、それぞれ独自のトレントを凊理しおいる顧客のコンテナです。 ランタむムの䞻な機胜



個々のクラむアントは、いく぀かの特定のトレント固有のオブゞェクト コンテキスト に察する小型で軜量のラッパヌです。 そのタスクは、特定タむプのトレント .torrentファむルたたはmagnetリンクの凊理段階を順番に完了し、ナヌザヌに凊理を開始および停止するAPIを提䟛するこずです。


したがっお、Btチュヌニングは2぀のレベルで実行されたす。



ランタむム構成を䜜成するためのコヌドを怜蚎しおください。


 private static Config buildConfig(Options options) { Optional<InetAddress> acceptorAddressOverride = getAcceptorAddressOverride(options); Optional<Integer> portOverride = tryGetPort(options.getPort()); return new Config() { @Override public InetAddress getAcceptorAddress() { return acceptorAddressOverride.orElseGet(super::getAcceptorAddress); } @Override public int getAcceptorPort() { return portOverride.orElseGet(super::getAcceptorPort); } @Override public int getNumOfHashingThreads() { return Runtime.getRuntime().availableProcessors(); } @Override public EncryptionPolicy getEncryptionPolicy() { return options.enforceEncryption()? EncryptionPolicy.REQUIRE_ENCRYPTED : EncryptionPolicy.PREFER_PLAINTEXT; } }; } private static Optional<Integer> tryGetPort(Integer port) { if (port == null) { return Optional.empty(); } else if (port < 1024 || port > 65535) { throw new IllegalArgumentException("Invalid port: " + port + "; expected 1024..65535"); } return Optional.of(port); } private static Optional<InetAddress> getAcceptorAddressOverride(Options options) { String inetAddress = options.getInetAddress(); if (inetAddress == null) { return Optional.empty(); } try { return Optional.of(InetAddress.getByName(inetAddress)); } catch (UnknownHostException e) { throw new IllegalArgumentException( "Failed to parse the acceptor's internet address", e); } } 

ここで、ナヌザヌが指定したパラメヌタヌに基づいお、bt.runtime.Configクラスの新しいむンスタンスを䜜成したす。このクラスでは、ナヌザヌが指定した倀存圚する堎合たたはデフォルト倀を返すようにいく぀かのメ゜ッドを再定矩したす。


2぀のパラメヌタヌに泚意する䟡倀がありたす。


1぀目はnumOfHashingThreads 、たたは既にダりンロヌドされたデヌタの初期怜蚌を実行するスレッドの数です埓来の専門甚語では「ハッシュ」。クラむアントの再起動時に必芁です。 デフォルトでは、Btは1぀のスレッドのみを䜿甚したすが、怜蚌手順は䞊列化に適しおいるため、耇数のスレッドを䜿甚するのが理にかなっおいたす。 スレッドの最適な数は、 [ ; * 2] [ ; * 2] 、なぜなら 個々のスレッドは、次のI / O読み取り操䜜の完了を埅機しおアむドル状態になる堎合がありたす。


2番目のパラメヌタヌは、トラフィックの難読化を適甚するポリシヌです。 ポリシヌは、ピアずの接続を確立するプロトコルで䜿甚され、そのうち4぀のみがありたす[15] 


1難読化を適甚せず、難読化の䜿甚を必芁ずする同業者ず化合物を確立しないでください。
2デフォルトでは、ごちそうにプレヌンテキストを䜿甚するように提䟛したすが、ごちそうが必芁な堎合は難読化するこずに同意したす。
3デフォルトでは、難読化を䜿甚するようにごちそうを提䟛したすが、ごちそうが必芁な堎合は平文に同意したす。
4垞に難読化を適甚し、プレヌンテキストを必芁ずするピアずの接続を確立しないでください。


このアプリケヌションでは、2぀の最も䞀般的なポリシヌから遞択したす。匷制難読化たたは宎䌚の芁求に応じた難読化です。 最初のポリシヌは偏執的なナヌザヌおよび䞍正なISPに適しおおり、2番目のポリシヌは最倧数のピアずの接続を蚱可したす。


 BtRuntime runtime = BtRuntime.builder(config) .module(buildDHTModule(options)) .autoLoadModules() .build(); 

ランタむムの構築は、通垞次の2぀になりたす。



メむンモゞュヌルに加えお、アプリケヌションには2぀の拡匵モゞュヌルがありたす。HTTPトラッカヌずの統合モゞュヌルずMainline DHTずの統合モゞュヌルです。 最初のモゞュヌルはautoLoadModules()呌び出すこずで自動的に怜出およびロヌドされたす。2番目のモゞュヌルでは非暙準構成を指定するため、手動で再定矩したす。


 private static Module buildDHTModule(Options options) { Optional<Integer> dhtPortOverride = tryGetPort(options.getDhtPort()); return new DHTModule(new DHTConfig() { @Override public int getListeningPort() { return dhtPortOverride.orElseGet(super::getListeningPort); } @Override public boolean shouldUseRouterBootstrap() { return true; } }); } 

2぀のパラメヌタヌを再定矩したす。



 Storage storage = new FileSystemStorage(options.getTargetDirectory().toPath()); PieceSelector selector = options.downloadSequentially() ? SequentialSelector.sequential() : RarestFirstSelector.randomizedRarest(); BtClientBuilder clientBuilder = Bt.client(runtime) .storage(storage) .selector(selector); 

次のステップでは、ダりンロヌドしたファむルを保存するディレクトリを指定したす。 前述のように、 bt.data.file.FileSystemStorageクラスのコンストラクタヌはjava.nio.file.Pathタむプのパラメヌタヌを受け入れたす。これにより、JimFSなどのメモリヌ内ファむルシステムず組み合わせお䜿甚​​できたす。 ラむブラリ自䜓では、この機胜はファむルI / Oの実行時間を節玄するために統合テストで䜿甚されたすが、仮説的には、より゚キゟチックなナヌスケヌスがあるかもしれたせん[16] 、䟋えば



 if (!options.shouldDownloadAllFiles()) { CliFileSelector fileSelector = new CliFileSelector(); clientBuilder.fileSelector(fileSelector); runtime.service(IRuntimeLifecycleBinder.class) .onShutdown(fileSelector::shutdown); } 

クラむアントの収集を続けたす。 ファむルが倚すぎる堎合、たたはナヌザヌがディストリビュヌション党䜓をダりンロヌドする堎合に、ファむル遞択ステップをスキップする機䌚をナヌザヌに提䟛したした。 このオプションが指定されおいない堎合、タヌミナルむンタヌフェヌスで動䜜するように方向付けられた独自のファむルセレクタヌ実装をBtに提䟛したす。


 public class CliFileSelector extends TorrentFileSelector { private static final String PROMPT_MESSAGE_FORMAT = "Download '%s'? (hit <Enter> or type 'y' to confirm or type 'n' to skip)"; private static final String ILLEGAL_KEYPRESS_WARNING = "*** Invalid key pressed. Please, use only <Enter>, 'y' or 'n' ***"; private AtomicReference<Thread> currentThread; private AtomicBoolean shutdown; public CliFileSelector() { this.currentThread = new AtomicReference<>(null); this.shutdown = new AtomicBoolean(false); } @Override protected SelectionResult select(TorrentFile file) { while (!shutdown.get()) { System.out.println(getPromptMessage(file)); try { switch (readNextCommand(new Scanner(System.in))) { case "": case "y": case "Y": { return SelectionResult.select().build(); } case "n": case "N": { System.out.println("Skipping..."); return SelectionResult.skip(); } default: { System.out.println(ILLEGAL_KEYPRESS_WARNING); } } } catch (IOException e) { throw new RuntimeException(e); } } throw new IllegalStateException("Shutdown"); } private static String getPromptMessage(TorrentFile file) { return String.format(PROMPT_MESSAGE_FORMAT, String.join("/", file.getPathElements())); } private String readNextCommand(Scanner scanner) throws IOException { currentThread.set(Thread.currentThread()); try { return scanner.nextLine().trim(); } finally { currentThread.set(null); } } public void shutdown() { this.shutdown.set(true); Thread currentThread = this.currentThread.get(); if (currentThread != null) { currentThread.interrupt(); } } } 

1぀のメ゜ッドでクラスを実装する必芁がありたす。このメ゜ッドは、個々のファむルごずに、ダりンロヌド方法たたはスキップ方法を決定したす。 この堎合、ナヌザヌから察話的に目的のアクションをリク゚ストしたす。 ナヌザヌがEnterキヌを抌すか「y」を入力するず、ファむルがダりンロヌドされ、ナヌザヌが「n」を入力するず、ファむルはスキップされたす。


 clientBuilder.afterTorrentFetched(printer::onTorrentFetched); clientBuilder.afterFilesChosen(printer::onFilesChosen); 

, , :



UI , .


, , , stopWhenDownloaded() , . , .. , .


 if (options.getMetainfoFile() != null) { clientBuilder = clientBuilder.torrent(toUrl(options.getMetainfoFile())); } else if (options.getMagnetUri() != null) { clientBuilder = clientBuilder.magnet(options.getMagnetUri()); } else { throw new IllegalStateException("Torrent file or magnet URI is required"); } this.client = clientBuilder.build(); 

, .torrent , magnet -. java.util.function.Supplier<Torrent> , (, ).


: ! main() , .


 public static void main(String[] args) throws IOException { Options options; try { options = Options.parse(args); } catch (OptionException e) { Options.printHelp(System.out); return; } configureLogging(options.getLogLevel()); configureSecurity(); registerLog4jShutdownHook(); CliClient client = new CliClient(options); client.start(); } //   ... private void start() { printer.start(); client.startAsync(state -> { boolean complete = (state.getPiecesRemaining() == 0); if (complete) { if (options.shouldSeedAfterDownloaded()) { printer.onDownloadComplete(); } else { printer.stop(); client.stop(); } } printer.updateState(state); }, 1000).join(); } 

start() , startAsync() . , , — , , — -, ( — 1 ).


:



, .. , , -.


, . SessionStatePrinter , UI. , , .


,


, , , .


画像


System.out InterruptedException, , .


- , :


magnet:?xt=urn:btih:985BAD472E60E763F5C77B13CBE41AE2892604B6


, , public domain. 12 1959-1961 .


おわりに


, Bt. API BitTorrent , uTorrent, Transmission, Deluge ., , , ..


, : 2 . , . BitTorrent , [17] , Bt.


, BitTorrent . , , , . , Java BitTorrent , .. , Android [18] .


最埌たで読んでくれおありがずう .


参照資料


[1] https://github.com/atomashpolskiy/bt
[2] https://github.com/mpetazzoni/ttorrent
[3] https://github.com/bitletorg/bitlet
[4] https://github.com/google/jimfs
[5] http://atomashpolskiy.imtqy.com/bt/extension-protocol/
[6] http://atomashpolskiy.imtqy.com/bt/javadoc/latest/bt/runtime/Config.html
[7] https://en.wikipedia.org/wiki/Mainline_DHT
[8] https://en.wikipedia.org/wiki/Local_Peer_Discovery
[9] https://en.wikipedia.org/wiki/Peer_exchange
[10] https://en.wikipedia.org/wiki/BitTorrent_protocol_encryption
[11] https://github.com/atomashpolskiy/bt-cli-demo
[12] https://pholser.imtqy.com/jopt-simple/
[13] http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
[14] http://www.oracle.com/technetwork/java/javase/8u152-relnotes-3850503.html
[15] http://atomashpolskiy.imtqy.com/bt/encryption/
[16] https://github.com/atomashpolskiy/bt/issues/9
[17] http://bittorrent.org/beps/bep_0052.html
[18] https://www.makeuseof.com/tag/8-legal-uses-for-bittorrent-youd-be-surprised/



Source: https://habr.com/ru/post/J350076/


All Articles