Pull to refresh

Пишем простейший торрент трекер на php

Reading time 5 min
Views 28K
Обмен файлами всегда привлекал людей, для этого собственно и был изобретен протокол BitTorrent.

Большинство торрент трекеров написано на PHP, хотя встречаются и такие, которые написаны на C# языке, но для ознакомления мы будем использовать именно PHP.

Давайте рассмотрим что из себя представляет трекер.


Программы называемые бит-торрент клиентами, такие как utorrent, BitTorrent и т.д.

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

Данные передаются GET запросом, для просмотра этих данных вы можете установить http-сниффер например HttpAnalyzerStd.

Анализ показывает что, куда, каким образом передаётся. Примерный вид запроса будет такой:

  1.  
  2. сайт/announce.php?info_hash=%c4N%8f%cc%f4%e5lz%af%7b%b6M%c0%cez%da%25%ac%e7%8f&peer_id=-UT2000-xGv%adq%1b%b7%e4%29%a1%f1%ad&port=57911&uploaded=0&downloaded=0&left=2335117312&corrupt=0&key=AEC92B93&event=started&numwant=200&compact=1&no_peer_id=1
  3.  


Из полученных от клиента данных аннонсер узнает зарегистрирован ли такой торрент на трекере, если да то отдаёт список пиров для файлообмена.

Для того чтобы трекер смог функционировать нам понадобится база данных например mysql.

Создадим таблицу peers чтобы мы могли хранить данные.

  1.  
  2. CREATE TABLE IF NOT EXISTS `peers` (
  3. `info_hash` binary(20) NOT NULL,
  4. `ip` int(11) NOT NULL,
  5. `port` smallint(5) unsigned NOT NULL,
  6. `peer_id` binary(20) NOT NULL,
  7. `uploaded` bigint(20) unsigned NOT NULL default '0',
  8. `downloaded` bigint(20) unsigned NOT NULL default '0',
  9. `left` bigint(20) unsigned NOT NULL default '0',
  10. `update_time` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
  11. `expire_time` timestamp NOT NULL default '0000-00-00 00:00:00',
  12. PRIMARY KEY  (`info_hash`,`ip`,`port`)
  13. ) ENGINE=MEMORY DEFAULT CHARSET=cp1251;
  14.  


Далее приступаем к PHP

Создаём файл например tracker.php, и работаем с ним.

Соединяемся с базой:

  1.  
  2. <?php
  3. $db_server = 'localhost';
  4. $db_user = 'пользователь базы';
  5. $db_pass = 'Пароль пользователя';
  6. $db_db = 'Имя бызы';
  7. $db_table = 'peers'; //таблица для хранения данных
  8.  
  9. $min_announce_interval = 900; // об этой переменной в следующем посте
  10.  
  11. $max_announce_rate = 500;  // об этой переменной в следующем посте
  12.  
  13. $expire_factor = 1.2; // об этой переменной в следующем посте
  14.  
  15. $scrape_factor = 0.5; // об этой переменной в следующем посте
  16.  
  17. $require_announce_protocol = 'standard'; // об этой переменной в следующем посте
  18. ?>
  19.  


Для того чтобы PHP мог «понимать» и обмениваться с клиентом информацией нам понадобится класс для работы с торрент файлами.

  1.  
  2. <?
  3. require_once 'bencoding.php';
  4. ?>
  5.  


Нам нужно создать функцию которая будет отправлять ответ клиенту в случае неверных данных:

  1.  
  2. <?php
  3. function errorexit($reason) {
  4. exit(bencode(array('failure reason' => $reason)));
  5. }
  6. ?>
  7.  


А также напишем функцию для преобразования ип-адреса 
  1.  
  2. <?php
  3. function resolve_ip($host) {
  4. $ip = ip2long($host);
  5. if (($ip === false) || ($ip == -1)) {
  6. $ip = ip2long(gethostbyname($host));
  7. if (($ip === false) || ($ip == -1)) {
  8. return false;
  9. }
  10. }
  11. return $ip;
  12. }
  13. ?>
  14.  


Поскольку данные у нас будут только текстовые установим:

  1.  
  2. <?php
  3. header('Content-Type: text/plain');
  4. ?>
  5.  


Далее нам нужно проверить все ли данные нам переданы:

  1.  
  2. <?php
  3. if (empty($_GET['info_hash']) || empty($_GET['port']) || !is_numeric($_GET['port']) || empty($_GET['peer_id']) || !isset($_GET['uploaded']) || !is_numeric($_GET['uploaded']) || !isset($_GET['downloaded']) || !is_numeric($_GET['downloaded']) || !isset($_GET['left']) || !is_numeric($_GET['left']) || (!empty($_GET['event']) && ($_GET['event'] != 'started') && ($_GET['event'] != 'completed') && ($_GET['event'] != 'stopped'))) {
  4. errorexit('invalid request (see bitconjurer.org/BitTorrent/protocol.html)');
  5. }
  6. ?>
  7.  


Также проверяем стандарты бит-торрент протокола:

  1.  
  2. <?php
  3. if ($require_announce_protocol == 'no_peer_id') {
  4. if (empty($_GET['compact']) && empty($_GET['no_peer_id'])) {
  5. errorexit('standard announces not allowed; use no_peer_id or compact option');
  6. }
  7. }
  8. else if ($require_announce_protocol == 'compact') {
  9. if (empty($_GET['compact'])) {
  10. errorexit('tracker requires use of compact option');
  11. }
  12. }
  13.  
  14. $ip = resolve_ip(empty($_GET['ip']) ? $_SERVER['REMOTE_ADDR'] : $_GET['ip']);
  15. if ($ip === false) {
  16. errorexit("unable to resolve host name $_GET[ip]");
  17. }
  18. ?>
  19.  


Соединяемся с базой, а также запрашиваем данные:

  1.  
  2. <?php
  3. @mysql_pconnect($db_server, $db_user, $db_pass) or errorexit('Ошибка соединения');
  4. @mysql_select_db($db_db) or errorexit('Ошибка базы');
  5.  
  6. $query = @mysql_query("SELECT COUNT(*) FROM `$db_table` WHERE `expire_time` > NOW();") or errorexit('database error');
  7. $num_peers = mysql_result($query, 0);
  8. $query = @mysql_query("SELECT COUNT(*) FROM `$db_table` WHERE `update_time` > NOW() - INTERVAL 1 MINUTE;") or errorexit('database error');
  9. $announce_rate = mysql_result($query, 0);
  10. $announce_interval = max($num_peers * $announce_rate / ($max_announce_rate * $max_announce_rate) * 60, $min_announce_interval);
  11. ?>
  12.  


Проверяем какое событие (остановлен, запущен):

  1.  
  2. <?php
  3. if (!empty($_GET['event']) && ($_GET['event'] == 'stopped')) {
  4. $expire_time = 0;
  5. }
  6. else {
  7. $expire_time = $announce_interval * $expire_factor;
  8. }
  9. ?>
  10.  
  11.  


Вставляем/обновляем данные в базе

  1.  
  2. <?php
  3. $columns = '`info_hash`, `ip`, `port`, `peer_id`, `uploaded`, `downloaded`, `left`, `expire_time`';
  4. $values = '\'' . mysql_escape_string($_GET['info_hash'])  . '\', ' . $ip . ', ' . $_GET['port'] . ', \'' . mysql_escape_string($_GET['peer_id']). '\', ' . $_GET['uploaded'] . ', ' . $_GET['downloaded'] . ', ' . $_GET['left'] . ", NOW() + INTERVAL $expire_time SECOND";
  5. @mysql_query("REPLACE INTO `$db_table` ($columns) VALUES ($values);") or errorexit('database error');
  6. ?>
  7.  


Получем список пиров, с таким же info_hash:

  1.  
  2. <?php
  3. $peers = array();
  4. $numwant = empty($_GET['numwant']) ? 50 : intval($_GET['numwant']);
  5. $query = @mysql_query("SELECT `ip`, `port`, `peer_id` FROM `$db_table` WHERE `info_hash` = '" . mysql_escape_string($_GET['info_hash']) . "' AND `expire_time` > NOW() ORDER BY RAND() LIMIT $numwant;") or errorexit('database error');
  6. if (!empty($_REQUEST['compact'])) {
  7. $peers = '';
  8. while ($array = mysql_fetch_assoc($query)) {
  9. $peers .= pack('Nn', $array['ip'], $array['port']);
  10. }
  11. }
  12. else if (!empty($_REQUEST['no_peer_id'])) {
  13. while ($array = mysql_fetch_assoc($query)) {
  14. $peers[] = array('ip' => long2ip($array['ip']), 'port' => intval($array['port']));
  15. }
  16. }
  17. else {
  18. while ($array = mysql_fetch_assoc($query)) {
  19. $peers[] = array('ip' => long2ip($array['ip']), 'port' => intval($array['port']), 'peer id' => $array['peer_id']);
  20. }
  21. }
  22. ?>
  23.  
  24.  


Ну и наконец отправляем данные клиенту:

<?php

exit(bencode(array('interval' => intval($announce_interval), 'peers' => $peers)));

?>

Tags:
Hubs:
+50
Comments 100
Comments Comments 100

Articles