Разруливаем торренты-2 под покровом ночи: Программим кросс-платформенный torrent-клиентИгорь Антонов «Шесть лет прошло со времен первой войны людей и орков…» Действительно, прошло уже несколько месяцев с момента выхода статьи, в которой мы на практике разобрали процесс создания и парсинга 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 можно очень долго, поэтому давай отвлечемся от этого занимательного, но бесполезного дела и взглянем на четыре ключевые особенности, ради которых стоит юзать именно эту либу: 1. Упрощенная процедура для создания/чтения torrent-файлов. Все то, что мы научились делать в первой части статьи, в MonoTorrent делается в пару строчек кода. На скорость работы такая универсальность и упрощенность не повлияли – она находится на высоте и все действия (парсинг файлов, общение с сервером, переиндексация не закачанного файла) происходят очень быстро. 2. Функции на любой случай! Возможностей MN с лихвой хватит как для разработки клиентских приложений (например, Torrent-клиентов), так и серверных (например, Tracker-серверов). Все необходимые классы уже реализованы. 3. Простота использования. Код MonoTorrent написан очень качественно. Поэтому, если ты нормально ориентируешься в ООП, то без труда сможешь разобраться с внутренностями этой библиотеки и по необходимости дописать пару возможностей самостоятельно. 4. Кросс-платформенность. Разработчик MT нехило постарался и сделал свое детище полностью кросс-платформенным. Поэтому грани между платформами размываются, и ты можешь разрабатывать хоть под Unix-like системы (смотри документацию по проекту Mono, http://www.mono-project.com), хоть под Windows Mobile. Установка MonoTorrentВсе, хватит занудной теории, давай переходить к долгожданной практике. Перед тем как использовать эту мощную либу, тебе нужно ее скачать и проинсталлить. Самую последнюю версию библиотеки ты всегда можешь найти на http://www.monotorrent.com (на нашем DVD лежит последняя на момент сдачи статьи версия). Распакуй найденный/скачанный архив и попробуй произвести перекомпиляцию всех файлов. Минута ожидания и... на тебя обрушивается водопад error'ов, в которых сообщается о невозможности обнаружения каких-то модулей. Не отчаивайся, сейчас мы это исправим. В качестве лекарства тебе придется добыть один очень популярный у программистов C# фреймворк и подключить его к своей Visual Studio. Беги на http://nunit.org и качай наиболее свежую версию дистрибутива фреймворка (чтобы далеко не бегать, достаточно просто заглянуть к нам на DVD). Установка фреймворка стандартная. Все, что от тебя требуется – просто закрыть Visual Studio и запустить скачанный инсталлятор. После завершения установки твоя среда разработки уже будет знать о местоположении библиотеки, а значит, тебе нужно вновь попробовать компильнуть сорцы MonoTorrent. На этот раз компиляция пройдет успешно. Делаем проектСоздавай в своей студии новый проект типа Console Application. Да-да, ты не ослышался, сегодня мы будем делать именно консольный торрент-клиент. Создал? Теперь потрудись и подключи к своему проекту новый «Reference», расположенный в файле MonoTorrent.dll. Сам файлик MonoTorrent.dll ты можешь найти в папке <директория с файлами monotorrent>/bin/debug. Если ты пришел к нам из Delphi и до этого никогда не юзал C# и Visual Studio, то знай же, что для подключения новой References (ссылки) необходимо: 1. Перейти в Solution Explorer (View -> Solution Explorer). 2. Раскрыть группу Solution. 3. Щелкнуть правой кнопкой и выбрать пункт Add Reference. 4. В появившемся окне перейти на вкладку browse и выбрать файл MonoTorrent.dll. После выполнения этой нехитрой процедуры тебе станут доступны все возможности MT. Теперь можно отвлечься от всяких организационных вопросов и приступить непосредственно к кодингу. Первое, с чего должен начинаться любой проект – с определения списка необходимых пространств имен. К имеющемуся списку добавь:
Итак, пространства имен подключены, пора переходить к основной части и заняться приготовлением фарша для автоматически созданного класса Main. Перейди в самое начало описания класса и объяви несколько полей: //Путь к папке, из которой мы работаем Пока ты переписываешь, я буду комментировать происходящее в листинге. Наше приложение будет консольным, а значит, нужно организовать привычный для таких приложений интерфейс взаимодействия с пользователем. Ты уже наверняка догадался, что речь идет о параметрах, которые мы так любим передавать подобным тулзам. Из параметров наша программа должна принимать, как минимум, два: путь к торрент-файлу, который необходимо закачать, и папку на жестком диске, куда нужно все это сохранить. Поскольку мы точно знаем, что параметров будет два, то при запуске программы нам нужно убедиться, что так и есть. Именно это я и делаю в самой первой строчке. Если «длина» args меньше 2, то сообщим пользователю, что не хватает параметров, и преспокойно прервем работу. Успешно получив параметры, я записываю их в соответствующие переменные. Помимо этого мне приходится определять путь к текущей директории (папке, из которой работает софтина). В ней мы станем сохранять файл temp.data, который будет содержать необходимые сведения для возобновления закачки. Кому захочется иметь торрент-клиент, который не имеет возможности докачивать? Закончив возню с переменными, нужно позаботиться о настройке обработчиков событий. Это необходимо сделать, чтобы в определенный момент наша программа, например, могла нормально приостановить свою работу и не наделать ошибок. В первую очередь позаботимся о корректном завершении нашего приложения и установим делагат для события CancelKeyPress. Оно возникает, если мы попытались прервать работу приложения и нажали <Ctrl+C> в консоли. Поскольку приложение у нас достаточно непростое, нужно позаботиться о корректном завершении всех запущенных потоков. Весь код для правильного прерывания работы определим в функции exit() (ее код ты можешь посмотреть в моих исходниках). Эту же функцию нужно вызывать во время срабатывания:
Установив обработчики событий, я вызываю процедуру doDownload(), в которой реализована процедура приема файла. Ее код приведен во второй врезке. Поэтому тебе ничего не нужно делать, кроме как начать его переписывать. if (args.Length < 2) Console.WriteLine("<torrent path> <Download Console.ReadKey(); _listener = new Top10Listener(10); Console.CancelKeyPress += Thread.GetDomain().UnhandledException += doDownload(); Вторая врезка получилась достаточно объемной, но поверь мне, она была бы еще больше (раз в 20), если бы мы писали все вручную и не прибегали бы к помощи MonoTorrent (Пользуясь случаем, наподдам автору еще пару сотен джоулей в качестве компенсации за задержку статьи, – Прим. ред.). Итак, листинг начинается с определения порта для входящих подключений. Для своего примера я выбрал порт с номером 31337. Ради удобства использования и универсальности, номер порта можно передавать через параметры. Определив порт, нужно создать экземпляр класса Torrent. С ним мы будем выполнять загрузку торрент-файла и получать все необходимые сведения. Вытащить из него можно много чего – далее перечислены соответствующие свойства:
Если ты читал первую часть статьи, то уже понял, что эта вся та информация, над получением которой мы корпели в Delphi. Ну что поделать – здесь нам помогает библиотека, а в Delphi приходилось работать руками и головой. Проинициализировав объект для работы с торрент-файлом, нужно начать подготовку «движка», который будет содержать настройки очередной закачки. Но перед тем как перейти к движку, подготовим для него набор опций. Для этого я создаю новый экземпляр класса EngineSettings() и заполняю его основные свойства: SavePath (путь для сохранения файлов) и ListenPort (порт для входящих подключений). Определившись с опциями, я начинаю инициализацию самого движка (ClientEngine) и передаю ссылку на подготовленные EngineSettings. Оп-па, я немного забежал вперед и совершенно несправедливо обделил вниманием создание TorrentsSettings. В них ты можешь задать основные настройки, которые будут влиять на закачку в плане скорости. Например, в моем случае при инициализации переменной типа TorrentSettings я передаю следующие параметры:
На этом с настройками все. Двигаемся дальше. Нам нужно создать или прочитать «индексный» файл. Создаем – если он не существует и у нас новая закачка. А читают его так: BEncodedValue.Decode<BEncodedDictionary>(File.ReadAllBytes(_fastResumeFile). Затем я загружаю torrent-файл. Загрузка выполняется методом load(). В качестве одного-единственного параметра он принимает путь к файлу. Если во время загрузки возникли ошибки, то сообщим об этом пользователю и прервем выполнение программы, ну а если все тип-топ, то выведем информацию о загруженном торрент-файле. Вот теперь мы подошли к самому интересному – к закачке. Перед тем, как зарегистрировать torrent-файл для «движка», нам необходимо определиться, будем ли мы продолжать докачку или же начнем лить абсолютно новый файл. Для новой закачки мы просто создадим новый торрент-менеджер (new TorrentManager(_torrent,_downloadPath, _torrentDef);), а вот если закачка уже была запущена, то нужно передать наш _fastResume. После этого нам ничего не остается сделать, как выполнить метод Register проинициализированного «движка». В качестве параметров этому методу передадим ссылку на созданный торрент-менеджер. После регистрации на менеджер будут действовать все параметры, которые мы ранее установили. Итак, уже почти все готово для начала закачки, за исключением одного нюанса – обработчиков событий. Их нужно объявить (вспоминаем про делегаты!), чтобы получить возможность следить за состоянием процесса закачки. Дабы не париться с расписыванием кода реакций на события, я просто взял стандартный шаблон (из дистрибутива MT) и немного подкорректировал. Его код ты найдешь в моем исходнике. Сложного в нем ничего нет, и если ты более-менее знаешь C#, то проблем не возникнет. Создав соответствующие обработчики, можно стартовать закачку – с помощью метода Start() объекта типа TorrentManager. Закачка началась, и теперь все, что нам остается делать – выводить пользователю соответствующие информационные сообщения. В листинге, опубликованном в статье, есть соответствующий комментарий. Именно в этом месте и нужно написать код для вывода сообщений. Пример такого кода ты опять-таки найдешь в исходнике. int _port; Torrent _torrent; TorrentSettings _torrentDef = _engineSettings.SavePath = _downloadPath; _engine = new ClientEngine(_engineSettings); BEncodedDictionary _fastResume; catch try Console.WriteLine("Created by: {0}", if (_fastResume.ContainsKey(_torrent.InfoHash)) _manager.TorrentStateChanged += foreach (TrackerTier ttier in StringBuilder _stringBuilder = while (_running) System.Threading.Thread.Sleep(500); Потестим?Настал час триумфа, – мы должны убедиться, что наши действия не пропали втуне, и наш торрент-клиент действительно сможет выполнить свою основную задачу. Попробуй скомпилировать и запустить проект. Не забудь при запуске передать соответствующие параметры! Чтобы убедиться в работоспособности нашего детища, я подготовил самый обычный торрент-файл (скачал с tfile.ru) и запустил клиент со следующими параметрами: xtorrent.exe C:\test.torrent C:\ Через пару секунд я увидел в своей консоли информацию о торрент-файлике (рисунок «Вся информация о закачке») и предложение начать загрузку. Согласился – и спустя еще мгновение мой торрент-клиент успешно соединился с трекером и приступил к закачке. Через минут пять в корне моего диска C: появился соответствующий файл, а Torrent-клиент завершил свою работу. |