Pull to refresh

Использование BitTorrent в Java: jBitTorrent API

Reading time6 min
Views15K

Введение



Доброго дня всем читателям!

Недавно в одном проекте мне потребовалось программно скачивать и раздавать торренты, а также впоследствие создавать новые торрент-файлы к уже скачанным данным. Естественно, изобретать велосипед очень не хотелось, и я решил посмотреть в поисковике, что же есть в нише BitTorrent-библиотек для Java.
Хочу сказать, что выдача меня очень расстроила. Всего упоминалось лишь несколько реализаций, да и те были уже давно не обновляемые. Stackoverflow подсказал следующие:



Теперь пройдемся по каждому из них.

  1. Azureus, ныне Vuze — это торрент-клиент, написанный на Java. Меня он не устраивает, ибо реализация протокола идет через плагины, а это сложнее было интегрировать и не очень компактно.
  2. Snark — сайт умер, да и последнее обновление библиотеки было в 2003, за это время было множество изменений в BT, одно из которых — DHT.
  3. Напоследок — виновник торжества, объект описания в этой статье, знакомьтесь — jBitTorrent API, о нем чуть ниже.


Достоинства и недостатки jBitTorrent API



Несмотря на то, что это практически единственная stand-alone библиотека реализации BitTorrent протокола на Java, которая как-то вообще работает, она находится до сих пор в статусе беты. Мертвый форум на SourceForge проекта, мертвая система багтрекинга тамже, — все это не остановило меня написать автору проекта, который любезно согласился в консультации и исправлении найденных багов.

Достоинства


  • Stand-alone реализация
  • Малый вес (можно спокойно удалить ненужные вам классы)
  • Неплохой JavaDoc
  • Практически единственная нормальная реализация BitTorrent для Java
  • В комплекте идет пример реализации простейшего торрент-трекера и некоторых других мелочей


Недостатки


Все нижеописанные недостатки относятся на текущий момент, есть большая надежда, что автор проекта исправит их, и проект снова оживет. По крайней мере, я от него не отстану.

  • Мертвое сообщество (отвечает лишь автор, и то по Email)
  • Некоторые баги (ниже опишу, которые я обнаружил)


Шаблоны использования jBitTorrent в качестве клиента



К сожалению, серверной стороной jBitTorrent я не занимался (сам трекер), поэтому ничего о ней сказать не могу. Разве что надеюсь ей вскоре заняться. Поэтому, приступим к просмотру шаблонов создания торрент-файла и к шаблону загрузки файлов через торрент. Кстати говоря, в src/jBittorrentAPI есть реализации: ExampleCreateTorrent.java и ExampleDownloadFiles.java. Еще есть ExamplePublish.java, класс, показывающий API публикации торрент-файла на трекере, но я его не использовал. Да слишком нагруженные эти примеры, без пол-литра не разберешь.

Создание торрент-файла


        // Класс, реализующий обработку торрентов
        TorrentProcessor tp = new TorrentProcessor();
        // Время создания торрента
        tp.setCreationDate(System.currentTimeMillis());
        // Через какую программу был создан торрент
        tp.setCreator("jBittorrentAPI 1.0");
        // Комментарий к торренту
        tp.setComment("test");
        // Анонсирующий URL трекера, через который торрент-клиент получает список пиров и сидов. Зачастую можно оставлять пустым, ибо трекер сам переписывает этот заголовок.
        tp.setAnnounceURL(null);
        // Какой размер на каждую часть данных выделить
        tp.setPieceLength(100);
        // Ну тут все понятно
        tp.setEncoding("UTF-8");
        // В какой папке будут находиться данные, скачанные через наш торрент-файл
        tp.setName(torrentSavedTo);

        // Просмотр скачанных данных, является ли это просто файлом или директорией
        File checkData = new File(torrentSavedTo);

        if(checkData.isDirectory())
        {
            // Добавление массива File в список файлов на скачку торрент-файла
            tp.addFiles(checkData.listFiles());
        }
        else
        {
            // Тоже самое, но добавляется один файл
            tp.addFile(checkData);
        }

        // Генерация кусков данных, заданным размеров в tp.setPieceLength()
        tp.generatePieceHashes();

        // Здесь идет запись байт готового торрент-файла в физический файл
        byte[] generateTorrent = tp.generateTorrent();

        File generateTorrentFile = new File(torrentName);
        
        try
        {
            generateTorrentFile.createNewFile();
        }
        catch(IOException ex)
        {
            Logger.getLogger(DownloadTorrent.class.getName()).log(Level.SEVERE, null, ex);
        }

        try
        {
            DataOutputStream dos = new DataOutputStream(new FileOutputStream(generateTorrentFile));
            
            try
            {
                dos.write(generateTorrent);
                
                dos.flush();
                dos.close();
            }
            catch(IOException ex)
            {
                Logger.getLogger(DownloadTorrent.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        catch(FileNotFoundException ex)
        {
            Logger.getLogger(DownloadTorrent.class.getName()).log(Level.SEVERE, null, ex);
        }


Так выглядит создание торрент-файла в jBitTorrent API. Есть еще несколько способов, например, TorrentProcessor.setTorrentData(), но они практически аналогичны.

Загрузка файлов без сидирования


Здесь используем следующую конструкцию:
        // Взять торрент-файл
        TorrentProcessor tp = new TorrentProcessor();

        TorrentFile tf = tp.getTorrentFile(tp.parseTorrent(torrentPath));

        DownloadManager dm = new DownloadManager(tf, Utils.generateID());

        // Запуск закачки
        dm.startListening(6882, 6889);
        dm.startTrackerUpdate();

        while(true)
        {
            // Если загрузка завершена, то ожидание прерывается
            if(dm.isComplete())
            {
                break;
            }

            try
            {
                Thread.sleep(1000);
            }
            catch(InterruptedException ex)
            {
                Logger.getLogger(DownloadTorrent.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        // завершение закачки
        dm.stopTrackerUpdate();
        dm.closeTempFiles();

        // проверка, куда были сохранены скачанные данные (то поле, которое задается в TorrentProcessor.setName())
        String torrentSavedTo = tp.getTorrentFile(tp.parseTorrent(torrentPath)).saveAs;


Загрузка файлов с сидированием


Мне это не потребовалось, но код сокращается буквально вполовину, если не больше:
        // Взять торрент-файл
        TorrentProcessor tp = new TorrentProcessor();

        TorrentFile tf = tp.getTorrentFile(tp.parseTorrent(torrentPath));

        DownloadManager dm = new DownloadManager(tf, Utils.generateID());

        // Запуск закачки
        dm.startListening(6882, 6889);
        dm.startTrackerUpdate();

        dm.blockUntilCompletion();

        // завершение закачки
        dm.stopTrackerUpdate();
        dm.closeTempFiles();

        // проверка, куда были сохранены скачанные данные (то поле, которое задается в TorrentProcessor.setName())
        String torrentSavedTo = tp.getTorrentFile(tp.parseTorrent(torrentPath)).saveAs;


Как видно, здесь происходит вызов метода DownloadManager.blockUntilCompletion(), который и реализует работу с пирами.

Баги, баги, баги



Вообще, признаться честно, баги, описанные здесь — не критичны, хотя это и не значит, что исправлять их не требуется. Некоторое неудобство они все же доставляют.

Ошибка в методе TorrentFile.printData()


Она возникает только в случае вызова этого метода, вот скриншот:



Если посмотреть в исходный код метода, то видно, что при обходе this.piece_hash_values_as_hex.get(i) срабатывает это самое исключение, ибо размер ArrayList здесь нулевой.

Недочет при добавлении множественных файлов в генерируемый торрент-файл


При добавлении одного файла с помощью TorrentProcessor.addFile() все нормально. Но при добавлении с помощью TorrentProcessor.addFiles() множества файлов (массив List, File, либо множественный вызов TorrentProcessor.addFile()) происходит следующее:



Как здесь можно увидеть, происходит добавление полного пути до добавляемого файла в торрент-файл (фух, ну и речевой оборот!). Пока что выход здесь — добавлять архив папки, например.

Ошибка при закачке файлов


Сама ошибка не особо мешает, хотя исправить надо. Закачка также продолжается дальше, даже если было брошено исключение, файлы сохраняют целостность. К сожалению, скриншот сделать я не мог на тот момент, поэтому вот лог работы моего клиента:
run:
Contact Tracker. URL source = ***
{interval=3300, complete=6, peers=[B@1c184f4, min interval=60, incomplete=6}
Peer List updated from tracker with 22 peers
Piece completed by 109.62.187.159:35691 : 0 (Total dl = 100.0% )
Task completed

Sharing... Press Ctrl+C to stop client

Sharing... Press Ctrl+C to stop client
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

Sharing... Press Ctrl+C to stop client
at java.util.LinkedList.entry(LinkedList.java:365)
at java.util.LinkedList.remove(LinkedList.java:357)
at jBittorrentAPI.DownloadManager.optimisticUnchoke(DownloadManager.java:617)
at jBittorrentAPI.DownloadManager.unchokePeers(DownloadManager.java:608)
at jBittorrentAPI.DownloadManager.blockUntilCompletion(DownloadManager.java:171)
at torrentstealer.DownloadTorrent.run(DownloadTorrent.java:32)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)


Sharing... Press Ctrl+C to stop client

Sharing... Press Ctrl+C to stop client


Клиент многопоточно вызывает закачку для нескольких торрент-файлов, а этот вывод — дебаг клиента, поэтому есть немного мусора. На крайний случай можно приостановить запущенную программу и запустить снова — в jBitTorrent API поддерживается докачка, а не скачивание снова.

Заключение



Ну вот, в принципе и все. По мере исправлений багов автором проекта, я постараюсь уведомлять читателей хабраблога «Java» о новых версиях, если этот пост будет вам интересен.
Tags:
Hubs:
+31
Comments14

Articles

Change theme settings