Работа с форматом файлов bencode (BitTorrent) в PHP 19.06.2010

Данную статью я решил разбить на три части:

  1. Разбор формата bencode (на примере торрент файлов)
  2. Создание файлов в формате bencode
  3. Создание torrent файлов (включая хеширование)

Разбор формата bencode

В качестве подопытного файла в формате bencode я взял этот торрент файл. Но это не существенно, на самом деле подойдет любой другой. Цель, которую я хотел достичь, это получение на выходе массива структурированных данных. Итак, перейдем к нудной теории.

Формат bencode содержит четыре структурных единицы:

  1. Строка (string).
  2. Целочисленное число (int)
  3. Словарь (dictionary), фактически ассоциативный массив
  4. Список (list)

Теперь немного о том, как они записываются:

Строка

в начале записывается длинна строки в десятичной форме, затем идет двоеточие «:», а затем сама строка. Для примера возьмем слово «Bender», в формате bencode, он записывается как
«6:Bender»

Число

в начале идет символ «i», затем число в десятичной форме (ему не должны предшествовать нули, кроме случая, когда число равно нулю), и в конец дописывается символ «e». На примере числа 42 получаем: «i42e». Запись «i042e» считается неверной, а запись «i0e» верной.

Словарь

началом описания словаря считается символ «d». Далее идет произвольное количество пар ключ-значение, и в конец ставиться символ «e». Ключом может являться только строка (в формате bencode), значением любая структурная единица. Пример словаря, на основе такого массива:

  1.  
  2.  "name" => "Bender",
  3.  "number" => 42
  4. )
  5.  

d4:name6:Bender6:numberi42ee

Выглядит немного непонятно, так что добавим пару пробелов и переносов строк для читаемости:

d
   4:name 6:Bender
   6:number i42e
e

Список

Список схож со словарем, с той лишь разницей, что в списке не используются ключи (неассоциативный массив). Начинается список с символа «i», содержит произвольное количество подряд идущих элементов (любых структурных единиц bencode), и заканчивается символом «e». Пример списка содержащего строку «Bender» и число «42»: l6:Benderi42ee, или если записать с пробелами: 

l
   6:Bender
   i42e
e

Теперь перейдем к написанию парсера, на вход которому подается строка, а на выход получаем PHP массив. Немного поразмыслив, я пришел к такой структуре класса декодера:

  1.  
  2. class Bencode_Decode
  3. {
  4. protected
  5. $_data = "",    // Строка, подвергаемая парсингу
  6. $_position = 0// Указатель на текущую позицию парсера
  7. $_length = 0;   // Длина данных
  8.  
  9. public function decodeString($data); // Декодирует строку
  10. public function decodeFile($file); //Открывает файл и отдает его содержимое на расшифроку
  11.  
  12. protected function detect(); // Определяет, тип следующей структуры
  13. protected function getDictionary(); // Возвращает данные словаря
  14. protected function getList(); // Возвращает список
  15. protected function getString(); // Возвращает строку
  16. protected function getInt(); // Возвращает число
  17.  
  18. protected function getChar(); // Возвращает символ, на который ссылается указатель
  19. protected function next();  //Перемещает указатель на следующий символ
  20. }
  21.  
  22.  

Теперь немного подробнее. Дабы не приводить полный листинг программы, предлагаю его вам скачать  здесь

decodeString()

записывает данные в _data, сбрасывает указатель в 0 и запускает детектор

detect()

Определяет тип данных и передает соответствующему обработчику. Если тип не может быть определен — выкидывает Exception

getString()

Считываем данные до символа «:», это будет длинна строки. Теперь мы знаем длину и без проблем можем получить строку. Незабываем перенести указатель на символ после конца строки.

getInt()

Считываем данные от «i» до «e», это и есть наше число. Переносим указатель на символ после «e».

getDictionary()

Здесь все несколько сложнее. Будем считывать пары ключ-значение, до того момента пока следующим извлеченным символом не окажется «e». Извлечение ключа сводится к вызову функции getString(), а получение значения к вызову detect(). Смещаем указатель на символ после «e»

getList()

эта функция работает также как и getDictionary, только без извлечения ключа.

Испытания

Для проверки работоспособности написанного класса, натравливаем его на торрент файл из начала статьи.
$bencode = new Bencode_Decode();
var_dump($bencode -> decodeFile('Big_Buck_Bunny_1080p_surround_frostclick.com_frostwire.com.torrent'));

у меня получился вот такой вот результат (для сохранения форматирования, прикладываю его в виде изображения):

В следующей статье я продемонстрирую, как с помощью php можно преобразовать массив (такой как на изображении) в строку формата bencode


, ,


Похожие статьи


Добавить комментарий