Введение
Доброго дня всем читателям!
Недавно в одном проекте мне потребовалось программно скачивать и раздавать торренты, а также впоследствие создавать новые торрент-файлы к уже скачанным данным. Естественно, изобретать велосипед очень не хотелось, и я решил посмотреть в поисковике, что же есть в нише BitTorrent-библиотек для Java.
Хочу сказать, что выдача меня очень расстроила. Всего упоминалось лишь несколько реализаций, да и те были уже давно не обновляемые. Stackoverflow подсказал следующие:
Теперь пройдемся по каждому из них.
- Azureus, ныне Vuze — это торрент-клиент, написанный на Java. Меня он не устраивает, ибо реализация протокола идет через плагины, а это сложнее было интегрировать и не очень компактно.
- Snark — сайт умер, да и последнее обновление библиотеки было в 2003, за это время было множество изменений в BT, одно из которых — DHT.
- Напоследок — виновник торжества, объект описания в этой статье, знакомьтесь — 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» о новых версиях, если этот пост будет вам интересен.