Программируем torrent-клиент на C#
Рубрика: c# -> Программирование -> Журнал Хакер -> Статьи
Метки: framework | MonoTorrent | torrent | программирование
Просмотров: 24308
«Шесть лет прошло со времен первой войны людей и орков…» Действительно, прошло уже несколько месяцев с момента выхода статьи, в которой мы на практике разобрали процесс создания и парсинга torrent-файлов. К большому сожалению, до самого вкусного момента (взаимодействия с трекером) мы добрались только сегодня – из-за проблем с отладкой готового примера. Лишь после нескольких сеансов электростимуляции толстым зондом со стороны редактора рубрики я смог это дело осилить и облечь в суровые строки журнальной статьи.
C# вместо Delphi
Для первой части статьи я писал пример на моем любимом Delphi, но сегодня мне предстоит ему изменить и воспользоваться великим и могучим C#. Многие Delphi-ненавистники возрадуются и громко закричат: «Неужели на Delphi нельзя создать полноценный клиент?». Вовсе нет, на Delphi можно написать практически любое приложение и торрент-клиент – не исключение, но есть одно но. Как ты понимаешь, протокол BitTorrent – это не хухры-мухры и просто так реализовать его в приложении не удастся. В настоящее время для дельфина не существует ни одной нормальной библиотеки/модуля для упрощения взаимодействия с этим протоколом. Все те библиотеки, которые мне попадались на глаза, морально устарели и требовали переписывания до 60% кода. Заниматься переписыванием и изобретением очередного велосипеда – очень долго и нудно, а Dr.Klouniz все повышал вольтаж на моих электродах, двигая гигантским реостатом и раскатисто хохоча. Поэтому я забил на эту идею и занялся поисками альтернативных библиотек – и на этот раз я искал не для Delphi, а для C#. К счастью, тут поиски были недолгими. Буквально на второй странице результатов, Гугл выдал мне ссылку на продвинутую библиотеку для работы с протоколом BitTorrent. Недолго думая, я взял курс по найденному линку и оказался на сайте проекта – MonoTorrent for C#.
Разрешите представиться: MonoTorrent
Библиотека Monotorrent – одна из самых профессиональных и функциональных среди имеющихся альтернатив. Для программиста она предоставляет шикарный API, позволяющий использовать протокол BitTorrent с чрезвычайной легкостью, не задумываясь о лишних проблемах. Автор этого замечательного творения – Alan McGovern. Изначально библиотека входила в проект Summer of Code 2006. Но после того как она засветилась и стала набирать фанатов, Alan решил заняться доработкой MonoTorrent и сделать отдельный проект. Вот так и появился на свет MonoTorrent, который сегодня используют все C#'ники. Петь дифирамбы MT можно очень долго, поэтому давай отвлечемся от этого занимательного, но бесполезного дела и взглянем на четыре ключевые особенности, ради которых стоит юзать именно эту либу:
Установка MonoTorrent
Все, хватит занудной теории, давай переходить к долгожданной практике. Перед тем как использовать эту мощную либу, тебе нужно ее скачать и проинсталлить. Самую последнюю версию библиотеки ты всегда можешь найти на http://www.monotorrent.com. Распакуй найденный/скачанный архив и попробуй произвести перекомпиляцию всех файлов. Минута ожидания и... на тебя обрушивается водопад error'ов, в которых сообщается о невозможности обнаружения каких-то модулей. Не отчаивайся, сейчас мы это исправим. В качестве лекарства тебе придется добыть один очень популярный у программистов C# фреймворк и подключить его к своей Visual Studio. Беги на http://nunit.org и качай наиболее свежую версию дистрибутива фреймворка (чтобы далеко не бегать, достаточно просто заглянуть к нам на DVD). Установка фреймворка стандартная. Все, что от тебя требуется – просто закрыть Visual Studio и запустить скачанный инсталлятор. После завершения установки твоя среда разработки уже будет знать о местоположении библиотеки, а значит, тебе нужно вновь попробовать компильнуть сорцы MonoTorrent. На этот раз компиляция пройдет успешно.
Делаем проект вместе с MonoTorrent
Создавай в своей студии новый проект типа Console Application. Да-да, ты не ослышался, сегодня мы будем делать именно консольный торрент-клиент. Создал? Теперь потрудись и подключи к своему проекту новый «Reference», расположенный в файле MonoTorrent.dll. Сам файлик MonoTorrent.dll ты можешь найти в папке <директория с файлами monotorrent>/bin/debug. Если ты пришел к нам из Delphi и до этого никогда не юзал C# и Visual Studio, то знай же, что для подключения новой References (ссылки) необходимо:
После выполнения этой нехитрой процедуры тебе станут доступны все возможности MT. Теперь можно отвлечься от всяких организационных вопросов и приступить непосредственно к кодингу. Первое, с чего должен начинаться любой проект – с определения списка необходимых пространств имен. К имеющемуся списку добавь:
MonoTorrent.BEncoding; //здесь сосредоточена вся работа с BenCoding.
MonoTorrent.Common; //основные методы.
MonoTorrent.Client.Tracker; //методы для работы с трекером.
MonoTorrent.Client; //клиентские функции.
Итак, пространства имен подключены, пора переходить к основной части и заняться приготовлением фарша для автоматически созданного класса Main. Перейди в самое начало описания класса и объяви несколько полей:
//Путь к папке, из которой мы работаем
static string _programPath;
//Папка, в которую будем качать
static string _downloadPath;
//Имя и путь к файлу, который будет содержать служебную информацию, необходимую для возобновления закачки
static string _fastResumeFile;
//Путь к торрент-файлу.
static string _torrentPath;
//Движок, реализующий функции закачки
static ClientEngine _engine;
//вспомогательный класс
static Top10Listener _listener;
//Менеджер для хранения законченных настроек для очередного torrent-файла
static TorrentManager _manager;
Пока ты переписываешь, я буду комментировать происходящее в листинге. Наше приложение будет консольным, а значит, нужно организовать привычный для таких приложений интерфейс взаимодействия с пользователем. Ты уже наверняка догадался, что речь идет о параметрах, которые мы так любим передавать подобным тулзам. Из параметров наша программа должна принимать, как минимум, два: путь к торрент-файлу, который необходимо закачать, и папку на жестком диске, куда нужно все это сохранить. Поскольку мы точно знаем, что параметров будет два, то при запуске программы нам нужно убедиться, что так и есть. Именно это я и делаю в самой первой строчке. Если «длина» args меньше 2, то сообщим пользователю, что не хватает параметров, и преспокойно прервем работу. Успешно получив параметры, я записываю их в соответствующие переменные. Помимо этого мне приходится определять путь к текущей директории (папке, из которой работает софтина).
В ней мы станем сохранять файл temp.data, который будет содержать необходимые сведения для возобновления закачки. Кому захочется иметь торрент-клиент, который не имеет возможности докачивать? Закончив возню с переменными, нужно позаботиться о настройке обработчиков событий. Это необходимо сделать, чтобы в определенный момент наша программа, например, могла нормально приостановить свою работу и не наделать ошибок. В первую очередь позаботимся о корректном завершении нашего приложения и установим делагат для события CancelKeyPress. Оно возникает, если мы попытались прервать работу приложения и нажали
Установив обработчики событий, я вызываю процедуру doDownload(), в которой реализована процедура приема файла. Ее код приведен во второй врезке. Поэтому тебе ничего не нужно делать, кроме как начать его переписывать.
if (args.Length < 2)
{
Console.WriteLine("Please run this program with
parameters:");
Console.WriteLine("<torrent path> <Download
folder>");
Console.ReadKey();
return;
}
_programPath = Environment.CurrentDirectory;
_torrentPath = args[0];
_downloadPath = args[1];
_fastResumeFile = _programPath + "\temp.data";
_listener = new Top10Listener(10);
Console.CancelKeyPress +=
delegate { exit(); };
AppDomain.CurrentDomain.ProcessExit +=
delegate { exit(); };
AppDomain.CurrentDomain.UnhandledException +=
delegate(object sender, UnhandledExceptionEventArgs e) { Console.WriteLine(e.ExceptionObject); exit(); };
Thread.GetDomain().UnhandledException +=
delegate(object sender, UnhandledExceptionEventArgs e) { Console.WriteLine(e.ExceptionObject); exit(); };
doDownload();
Вторая врезка получилась достаточно объемной, но поверь мне, она была бы еще больше (раз в 20), если бы мы писали все вручную и не прибегали бы к помощи MonoTorrent (Пользуясь случаем, наподдам автору еще пару сотен джоулей в качестве компенсации за задержку статьи, – Прим. ред.). Итак, листинг начинается с определения порта для входящих подключений. Для своего примера я выбрал порт с номером 31337. Ради удобства использования и универсальности, номер порта можно передавать через параметры. Определив порт, нужно создать экземпляр класса Torrent. С ним мы будем выполнять загрузку торрент-файла и получать все необходимые сведения. Вытащить из него можно много чего – далее перечислены соответствующие свойства:
Если ты читал первую часть статьи, то уже понял, что эта вся та информация, над получением которой мы корпели в Delphi. Ну что поделать – здесь нам помогает библиотека, а в Delphi приходилось работать руками и головой. Проинициализировав объект для работы с торрент-файлом, нужно начать подготовку «движка», который будет содержать настройки очередной закачки. Но перед тем как перейти к движку, подготовим для него набор опций. Для этого я создаю новый экземпляр класса EngineSettings() и заполняю его основные свойства: SavePath (путь для сохранения файлов) и ListenPort (порт для входящих подключений). Определившись с опциями, я начинаю инициализацию самого движка (ClientEngine) и передаю ссылку на подготовленные EngineSettings. Оп-па, я немного забежал вперед и совершенно несправедливо обделил вниманием создание TorrentsSettings. В них ты можешь задать основные настройки, которые будут влиять на закачку в плане скорости. Например, в моем случае при инициализации переменной типа TorrentSettings я передаю следующие параметры:
На этом с настройками все. Двигаемся дальше. Нам нужно создать или прочитать «индексный» файл. Создаем – если он не существует и у нас новая закачка. А читают его так: BEncodedValue.Decode
Перед тем, как зарегистрировать torrent-файл для «движка», нам необходимо определиться, будем ли мы продолжать докачку или же начнем лить абсолютно новый файл. Для новой закачки мы просто создадим новый торрент-менеджер (new TorrentManager(_torrent,_downloadPath, _torrentDef);), а вот если закачка уже была запущена, то нужно передать наш _fastResume. После этого нам ничего не остается сделать, как выполнить метод Register проинициализированного «движка».
В качестве параметров этому методу передадим ссылку на созданный торрент-менеджер. После регистрации на менеджер будут действовать все параметры, которые мы ранее установили. Итак, уже почти все готово для начала закачки, за исключением одного нюанса – обработчиков событий. Их нужно объявить (вспоминаем про делегаты!), чтобы получить возможность следить за состоянием процесса закачки. Дабы не париться с расписыванием кода реакций на события, я просто взял стандартный шаблон (из дистрибутива MT) и немного подкорректировал. Его код ты найдешь в моем исходнике. Сложного в нем ничего нет, и если ты более-менее знаешь C#, то проблем не возникнет.
Создав соответствующие обработчики, можно стартовать закачку – с помощью метода Start() объекта типа TorrentManager. Закачка началась, и теперь все, что нам остается делать – выводить пользователю соответствующие информационные сообщения. В листинге, опубликованном в статье, есть соответствующий комментарий. Именно в этом месте и нужно написать код для вывода сообщений. Пример такого кода ты опять-таки найдешь в исходнике.
int _port;
_port = 31337;
Torrent _torrent;
EngineSettings _engineSettings =
new EngineSettings();
TorrentSettings _torrentDef =
new TorrentSettings(5, 100, 0, 0);
_engineSettings.SavePath = _downloadPath;
_engineSettings.ListenPort = _port;
_engine = new ClientEngine(_engineSettings);
BEncodedDictionary _fastResume;
try
{
fastResume =
BEncodedValue.Decode<BEncodedDictionary>
(File.ReadAllBytes(_fastResumeFile));
}
catch
{
_fastResume = new BEncodedDictionary();
}
try
{
_torrent = Torrent.Load(_torrentPath);
}
catch
{
Console.Write("Decoding error");
_engine.Dispose();
exit();
}
Console.WriteLine("Created by: {0}",
_torrent.CreatedBy);
Console.WriteLine("Creation date: {0}",
_torrent.CreationDate);
Console.WriteLine("Comment: {0}",
_torrent.Comment);
Console.WriteLine("Publish URL: {0}",
_torrent.PublisherUrl);
Console.WriteLine("Size: {0}",
_torrent.Size);
Console.WriteLine("Piece length: {0}",
_torrent.PieceLength);
Console.WriteLine("Piece count: {0}",
_torrent.Pieces.Count);
Console.WriteLine("Press any key
for continue...");
Console.ReadKey();
if (_fastResume.ContainsKey(_torrent.InfoHash))
_manager = new TorrentManager(
_torrent, _downloadPath, _torrentDef,
new FastResume((BEncodedDictionary)
_fastResume[_torrent.InfoHash]));
else
_manager = new TorrentManager(_torrent,
_downloadPath, _torrentDef);
_engine.Register(_manager);
_manager.TorrentStateChanged +=
delegate(object o, TorrentStateChangedEventArgs e)
{
lock (_listener)
_listener.WriteLine("Last status: " +
e.OldState.ToString() + " Current status: " +
e.NewState.ToString());
};
foreach (TrackerTier ttier in
_manager.TrackerManager.TrackerTiers)
{
foreach (MonoTorrent.Client.Tracker.Tracker
tr in ttier.Trackers)
{
tr.AnnounceComplete +=
delegate(object sender,
AnnounceResponseEventArgs e)
{
_listener.WriteLine(string.Format("{0}: {1}",
e.Successful, e.Tracker.ToString()));
};
}
}
_manager.Start();
int i = 0;
bool _running = true;
StringBuilder _stringBuilder =
new StringBuilder(1024);
while (_running)
{
if ((i++) % 10 == 0)
{
if (_manager.State == TorrentState.Stopped) {
_running = false;
exit();
//Здесь можно выводить всевозможную полезную информацию (скорость закачки, количество активных соединений и т.д.).
}
}
System.Threading.Thread.Sleep(500);
Потестим?
Настал час триумфа, – мы должны убедиться, что наши действия не пропали втуне, и наш торрент-клиент действительно сможет выполнить свою основную задачу. Попробуй скомпилировать и запустить проект. Не забудь при запуске передать соответствующие параметры! Чтобы убедиться в работоспособности нашего детища, я подготовил самый обычный торрент-файл (скачал с tfile.ru) и запустил клиент со следующими параметрами: xtorrent.exe C:\test.torrent C:\
.
Через пару секунд я увидел в своей консоли информацию о торрент-файлике (рисунок «Вся информация о закачке») и предложение начать загрузку. Согласился – и спустя еще мгновение мой торрент-клиент успешно соединился с трекером и приступил к закачке. Через минут пять в корне моего диска C: появился соответствующий файл, а Torrent-клиент завершил свою работу.
Статья опубликована в журнале "Хакер" (http://xakep.ru). Октябрь 2008 г.
Ссылка на журнал: http://goo.gl/3S5WLF