Ровно месяц назад была опубликована статья LiveDC — Быстрый доступ к p2p файлам. Смысл ее в том, что Erty_Hackward написал DC-клиент с возможностью просмотра файлов до их окончательной загрузки. С его помощью можно, например, смотреть фильмы спустя пару минут после начала закачки, перематывать их, смотреть с любого момента. А можно извлечь нужный файл из большого архива, не перекачивая целый архив.
Мне очень понравилась идея этой программы. Но она написана на C#. А хотелось бы использовать ее в линуксе. Поэтому с разрешения автора я с большим удовольствием взялся за создание аналогичной программы для линукса. В результате получился консольный битторрент клиент QLiveBittorrent.
Как оно работает
Расскажу о работе QLiveBittorrent на примере скачивания фильма. Ну, например, мой друг выложил в сеть видеозапись выпускного вечера, недавно прошедшего в нашей школе, а я хочу ее скачать, и еще раз насладиться этим незабываемым зрелищем.
Я начинаю скачивать фильм. Указываю папку, куда его надо подмонтировать. И тут же пытаюсь его открыть. Фильм открывается 1-2 мин — в зависимости от скорости. Начинаю смотреть — лагов, как правило, нет, если скорости хватает. Далее я перематываю фильм на середину. Наблюдаю тормоза ~30 сек, и продолжаю смотреть его с середины.
Происходит примерно следующее. Изначально, в момент открытия фильма в плеере, программа скачивает первые несколько блоков фильма и последний блок, чтоб получить информацию о его продолжительности. Далее она продолжает скачивать фильм с начала. Но в момент перемотки фильма на середину, программа начинает скачивать именно тот блок, который был запрошен плеером.
Кроме скачивания фильмов, программу можно использовать для работы с архивами, iso-образами, для просмотра папок с огромным числом картинок. Это очень удобно — начать качать архив, и с самого начала, не дожидаясь пока он скачается, открыть его, сразу указать, какой файл тебе нужен, и скачать именно его.
Как оно начиналось
После того, как я осознал, что будет проще написать свое, чем пытаться портировать LiveDC на линукс, я сразу же приступил к разработке.
Сначала я думал встроить свой функционал в мой любимый bittorrent клиент. Но после просмотра исходников это желание отпало. Миллионы файлов, везде defin-ы на разные системы, все это в виде простыни, и ничего не понятно. Однако, под впечатлением от qbittorrent, я выбрал тот же инструментарий.
За день был написан драйвер файловой системы qlive.
Еще за день — графика и скачивание торрентов.
Выглядело это примерно так:
На этом разработка впала в ступор. Программа работала, докачивала необходимые куски, но регулярно получала SIGSEGV (ошибка «программа обратилась не по адресу»). В это же время я грустно смотрел на функционал qbittorrent и utorrent, и до меня медленно доходило, что энтузиазма на месяцы разработки у меня не хватит…
Второе дыхание
Второе дыхание открылось, когда я решил забить на GUI. Кому он нужен? На его поддержку уходит слишком много нервов! Так появился консольный битторрент клиент. А затем появилась интересная идея разделить сидирующие торренты от качающихся.
В результате программа оказалась разделенной на качающую часть, которая умеет монтировать недокачанные файлы, ограничивать скорость скачивания и быть обычным торрент-клиентом и сидирующую часть (seed-manager), которая умеет раздавать то, что скачано, а также ограничивать скорость отдачи.
Интересные моменты разработки
- Иногда, тестируя закачку торрентов, я с удивлением обнаруживал, что скорость скачивания превосходит максимальную скорость моего роутера — 802.11G ~2MБ/с. Она достигала 40-50 МБ/с, что было в принципе невозможно! Я пытался валить все на кеши. Однако, выяснилось, что у меня было запущено 2 клиента на компьютере, которые и обменивались между собой информацией на скорости чтения жесткого диска. Один читает, другой пишет. Впоследствии я использовал это для тестирования.
- Из-за того, что клиент консольный, мне пришлось разбираться с сигналами завершения. Я добавил обработку сигнала SIGTERM (сигнал для запроса завершения процесса). Во время дебага, когда я посылал SIGTERM «руками», он успешно перехватывался и обрабатывался. Однако, на практике, когда я в терминале жал ctrl-c, сигнал не обрабатывался. Оказалось, что терминал посылает не SIGTERM, а SIGINT (сигнал для остановки процесса пользователем с терминала). Это меня поразило. Оказывается, при нажатии ctrl-c идет сигнал не «останови процесс», а «пользователь хочет, чтоб процесс остановился». Наверное, можно придумать глубокий смысл разницы этих процессов, но это было довольно неожиданно и странно.
- Несмотря на то, что клиент консольный, мне хотелось, чтобы какое-то подобие GUI все же оставалось. Но ведь, если приложение консольное, то хочется, чтобы оно запускалось в голой консоли (TTY), а это невозможно при наличии GUI… Или возможно? Для этого надо понимать, как работает графика в линуксе.
А работает она примерно так: есть X-server, есть клиенты. В какой-то момент клиент подключается. Вопрос — когда это происходит? Экспериментальным путем (методом пристального взгляда) я выяснил, что подключение происходит в момент создания QApplication. Соответственно, если нужна графика, то я создаю QApplication, если нет — QCoreApplication. Таким образом, программа может запускаться как в голой консоли, так и с графическим мини-интерефейсом.
- Смешивание Qt и libtorrent — это чистый ад. Мои нервы в конце концов не выдержали, и я начал пытаться переводить все в Qt. К сожалению, libtorrent использует std, поэтому весь мой код увешан QString::fromStdString(string) и QString.toStdString(), а так же конверсиями vector char, QVector char, QByteArray и т.п.
- Во время разработки я столкнулся с неприятной проблемой. В один прекрасный момент к программе подключаются медленные пиры и начинают неторопливо отдавать нужные куски. При этом в среднем скорость закачки хорошая, но задержки из-за них недопустимые. Я пытался с этим бороться. В какой-то степени задачу удалось решить за счет того, что первые 2 минуты программа ищет быстрых пиров, а затем ограничивает их максимальное число пятью пирами. Это позволяет отшивать большинство медленных пиров.
- Для того, чтобы после полуночи не было проблем с вычитанием времени, в программе реализована функция midnight(), которая каждую секунду проверяет, не полночь ли сейчас? В течение 5 секунд после полуночи программу колбасит — она при обновлении информации на экране обнуляет все временные счетчики, игнорируя запросы пользователя. Такой вот редкостный костыль. Я его за 3 минуты до полуночи написал.
Список фич
- Если торрент опубликовали только что, то отношение количества сидеров к личерам слишком маленькое. Это влечет за собой почти полную невозможность скачать такой торрент за разумное время. Поэтому в QLiveBittorrent добавлена возможность «превращения» из торрент-клиента в обычного клиента. Для этого надо нажать клавишу 'a' (английскую).
- При чтении подмонтированных файлов программы впадают в ступор, пока необходимые куски не будут скачаны. Но они обязательно из него выходят, когда это происходит. Это особенность программы.
- Авторитетные источники утверждают, что на канале 600КБ/с фильм весом 1.45G с внешней звуковой дорожкой и субтитрами проигрывается без лагов.
- Если нажать на клавиатуре ctrl-c (или послать SIGTERM), то программа начнет завершаться — сбрасывать данные на жесткий диск и отправлять информацию трекерам. После сброса данных можно нажать ctrl-c — тогда информация не будет послана трекерам. Это сделано, потому что меня очень расстраивают программы, которые невозможно завершить.
Источники:
- Erty_Hackward — оригинальная идея
- ximaera — рассказал про то, как надо парсить параметры из командной строки.
- статья про boost::program_options
- Libtorrent api — хорошая библиотека, но эмоции от одностраничной документации непередаваемы.
- github.com/qbittorrent/qBittorrent — читал исходники, но ничего не понял.
Скачивание
Исходники
ArchLinux (AUR)
linux-x86-64
Для работы требуются библиотеки boost, Qt, libtorrent-rasterbar.
Настройки хранятся по адресу ~/.qlivebittorrent
Автор: vtyulb