前言

PT(Private Tracker)是一种改良自 BitTorrent 协定的 P2P 下载方式,“Private Tracker”指私有种子服务器。与 BT 最大的不同点分别为可进行私密范围下载,及可统计每个用户的上载及下载量。

PT 站的架构

NexusPHP

简介

根据其代码中的介绍(aboutnexus.php 页面)来看:

NexusPHP 由来自浙江大学的 Nexus 团队发起并开发完成。主要是提供一个完整的、有序的、重视用户信誉和知识的资源分享社区的解决方案,是一个 PT(Private Tracker,即私有的 Tracker 服务器,是 BT 下载的一种)开源软件。

aboutnexus.jpg

(当然也有人和我说,这是某浙大生毕业设计作品 Orz)

虽然一遍一遍的在群里鄙视着 NexusPHP 的垃圾代码,但不得不承认就目前来看国内 PT 圈依然在大量使用 NP(这里应该不用做详细数据统计了吧 2333)。

而其他一些尝试,诸如:

  • Gazelle:国外著名 PT 架构,然而部署安装略麻烦,且纯英文界面。国内曾有人尝试翻译并建站,然而该主比我还不靠谱,以致于在 17 年度被人提及是开站狂魔 2333。此外就目前看 TVPlay 这个基于 Gazelle 的国内剧集站点现状比一滩死水还不如。
  • YoRHa:根本没有人知道的国内构架,主要原因是因为基于 ThinkPHP 的构架并没有完成 23333,作者曾在 PT 吧发过主贴 【20171009】开源一个国产 PT 项目…【pt 吧】_百度贴吧 ,而且目前 demo 还存活(并开放 debug 模式),但已经有一段时间没有进行更新了 Orz。
  • meanTorrent:同样是一个国内开源框架,基于 Node.js,同样部署安装麻烦(后面会具体讲~)。目前主站被人戏称(实际是官方简称)是地雷站 ,但就我目前感觉,迈的步子有些大——一方面,过于绚丽的前端对于 PT 是否需要值得思榷;另一方面,主站目前只接受 1080P 的一些规定是否在站点初期有利于发展。此外,该站积分策略、上传方式中的一些不同与 NP 的新形势新方法,对于已经在 NP 惯性中的使用者来说是不是能很快适应。这些都要经过时间的考验,毕竟今年 6 月(2018 年 6 月)也才正式上线。
  • TBsource:基于 PHP,是 NP 的前身,应该也就只有 CFFBits 以及 TTG 这种_远古_站点在使用了吧~
  • XBNBT: 一个基于 C++ 的构架,国内就只有紫荆 PT 在使用。
  • 其他一些在 Discuz 基础上修改的 tracker:这块多为自己写的,而且未能找到公开 repo,在此仅作站点列举:六维空间、北交知行、天雪 PT。

所以,面对众多的 NP 改 站点来说,其实这个古老的(甚至连官网都挂了的)架构依然在支撑着国内 PT 网站的运行。

缺陷

但是从现代的角度来看 NP,我们其实能发现很多问题。

有些属于已经被发现漏洞,多为跨站请求类以及 SQL 注入类。截止目前(2018/7/19),在 国家信息安全漏洞库 中以 NexusPHP 为关键词搜索可以找出近 26 条记录 。

CNNVD.jpg

而更多被诟病的其实是其代码架构过于老旧也不宜于维护。下面列几点我印象比较深刻的:

  • 使用这个 mysql 已经被抛弃的库(PHP 5.5.0 起废弃,PHP 7.0.0 起移除),而不是 mysqli 库或 PDO 库。而 PHP 7 至少从评测上看,执行效率比 PHP 5 优秀很多~
  • 代码使用 print 等方式输出网页。项目受限于时代背景,没有使用 MVC 等先进思想。
  • 数据库设置不合理,大量查询以及写入语句落在 users 以及 torrents 等表上,造成数据表过热,故当站点人数过多或者种子数过多时,对站点所在服务器配置要求极其高(譬如 NPU 就因搜索问题换用外挂搜索引擎);而另一方面,诸如 faq、rules 这些应该可以直接静态输出的却使用数据库进行存储(估计是为了多语言支持)
  • torrentsrss.php 页面对 passkey 是否存在没有进行检查,导致非站内用户能通过构造相关链接直接读取站内 ** 所有 ** 种子信息。(部分站点已经修复这个漏洞)

目前分支情况

鉴于 NP 在很早之前就已经停止维护(Github 上停留在 7 年前,即 2011 年),目前很多使用 NP 作为基础架构的站点都对其进行了一定的改良。我仅从用户体验(比如前端页面的更改)将站点分成以下几类:

  • ** 基本没怎么动 **: 不得不说,很多站点对原始 NP 模板并没有进行修改(这是句瞎话,比如 BYR 看起来和原版似乎一模一样,但是后端的修改还是挺多的;又如二娘,修改了种子的 BUFF 系统。)。但其实不管该站 Sysop 到底出力没出力,都可以将对应站点视为一个模板。
  • ** 葡萄 **:该前端模板将个人信息做成置顶栏,而不是和基本模板一样放在导航栏之下。种子搜索增加“随意看看”。

putao_group.jpg

  • ** 蝴蝶 **:以蝴蝶和珞樱为代表,对搜索词的词长存在限制,拥有用户自定义 Javascript 以及自定义 css。
  • ** 蚂蚁 **:虽然蚂蚁因为一些原因已经停止运营,但是模板并没有消失 2333。目前仍有很多新站使用蚂蚁的模板进行建站,如_高清街_以及_美盒子_(都是小站)。我个人不是很喜欢蚂蚁的模板,它将一个种子的所有信息使用两个 tr 加一堆 rowspan 进行合并展示,在我看来其实增加后期维护的困难程度,尤其是在没有模板引擎的 NP 构架之下。

mayi_tamp.jpg

截图为他人提供的美盒子截图。

  • ** 蒲公英 **:蒲公英是魔改 NP 最为成功的一个站点。在它之前或者之后都有人尝试将 Bootstrap 与 NexusPHP 进行结合,虽不能说他们都失败了,但至少 NPU 成为了一个典型喵喵喵。。其他一些更改,比如 rp 系统、自动转种系统等都是该站点特点。
  • HDA:良心站点 HDArea 添加了种子预告系统,后被集市使用。(毕竟这两个站点早期开发是同一个人)

嘛,我影响稍微深刻的就这些站点,以及他们使用的模板。其他还有一些站点,此处就不一一列举了。

而从公开仓库来看,目前站点公开并有在更新代码的有以下:

也确实是寥寥无几~


为了去了解站点构架,一个简单的搭建过程也是需要了解的。

NP 搭建的最重要注意点是,** 使用的 PHP 版本最高不应该超过 5.6,并安装 memcache 软件及 PHP 扩展 **。对数据库版本要求不是很严格,我使用 MySQL 5.7 测试可行。

本人写的十分简略(因为真心没有什么好讲的),如果有必要,还请参照他人的详细搭建过程。

Linux 下搭建

LNMP 环境及 Memcache

网上的教程真心啰嗦,lnmp 一个一个的写过去,我个人还是喜欢用一键包来配置。lnmp1.5 的自动值守命令为

1
2
screen -S lnmp
wget http://soft.vpser.net/lnmp/lnmp1.5.tar.gz -cO lnmp1.5.tar.gz && tar zxf lnmp1.5.tar.gz && cd lnmp1.5 && LNMP_Auto="y" DBSelect="2" DB_Root_Password="lnmp.org" InstallInnodb="y" PHPSelect="4" SelectMalloc="1" ./install.sh lnmp

复制粘贴,然后接杯奶茶等编译完成,我们基础的 lnmp 环境就搭建好了。(注意:这样安装后主 PHP 版本为 5.5,如果希望主版本用 7.x 的请自己使用 LNMP 一键安装包无人值守命令生成器 生成值守命令或者交互式安装,然后 ./install.sh mphp 添加多 PHP 支持)

然后安装 memcache,在 lnmp1.5 文件夹中进入 lnmp 解压后的目录,执行:

1
./addons.sh install memcached

选择 php-memcache 即会安装软件及 PHP 扩展。

NP 源码及数据库

NP 源码个人建议从 SourceForge 中获取,而不是从 Github 仓库。(之前 Blog 也说过,Github 上的建表语句有问题)故,依次进行:

  1. NexusPHP - Browse Files at SourceForge.net 下载最新的 zip 包并解压到对应网站根目录即可。
  2. 使用 phpmyadmin 或者其他 CLI 软件恢复 /_db/dbstructure.sql 文件记录。
  3. 修改 config/allconfig.php 文件的以下内容使其对应:
1
2
3
4
5
6
7
‘SITENAME’  =>  ‘站点名称’
‘baseURL’ => ‘网站 URL’
‘announce_url’ => ‘localhost/announce.php’(announce 的 url 地址)
‘mysql_host’ => ‘MySQL 主机’
‘mysql_user’ => ‘数据库用户名’
‘mysql_pass’ => ‘数据库密码’
‘mysql_db’ => ‘数据库名’
  1. 设置目录权限 777,因为 NP 的站点配置是通过操作 config 目录下文件的修改完成的。
1
2
sudo chmod 777  /dir/to/your/nexusphp
sudo chmod 777 /dir/to/your/nexusphp/config
  1. 自己访问网站然后注册一个用户名,接着进入数据库管理(phpMyAdmin),在 users 表里面找到你注册的用户,编辑它的 class 属性为 16

Windows 下搭建

Windows 下搭建我个人推荐使用 Laragon 作为基础环境,因为相比其他 WNMP、WAMP、XAMPP,环境管理更加方便,内置软件更为齐全。例如我选择的就包含了几乎全套我想要使用的工具 2333

官网下载地址:https://laragon.org/download/

laragon.jpg

然而需要注意的是,默认 Laragon 提供的是 PHP 7.x,我们需要额外到 PHP 官网上下载 PHP 5.5 版本的 Portable 以及 Memcache 扩展。下载位置分别如下:

完成基础环境的搭建后,Win 下关于 NP 自身的文件以及数据库均与 Linux 下类似,在此不累述。

搭建中一些可能的问题

  • ** 直接提示 HTTP ERROR 500** :多数情况下是使用了 PHP 7.x 或者其他高于 5.6 的版本,建议使用 PHP 5.3-5.5 之间的版本进行搭建。
  • Warning: Memcache::connect() [memcache.connect]: Can’t connect to localhost: 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。 : 修改 classes\class_cache.php 中的 localhost 为 127.0.0.1
  • ** 点击登陆后提示 Error: Errno:0 SQL:;**: 使用 Github 上源码(这个源码真的是只能远观不能亵玩 233)搭建,换用 SourceForge 的源。
  • 其他待补充~

meanTorrent

简介

在简单的介绍完 NP 的历史以及搭建,让我们先跳出 NP 框架,了解下其他的一些前面讲过的框架及其搭建过程。

meanTorrent 是目前来看,国人新框架中功能最为齐全、开发程度较高的一个。(其他我了解的都基本在开发过程中滴说)

从官方的介绍来看:

meanTorrent - MEAN.JS BitTorrent Private Tracker - Full-Stack JavaScript Using MongoDB, Express, AngularJS, and Node.js, A BitTorrent Private Tracker CMS with Multilingual, and IRC announce support, CloudFlare support.

相比已经停止开发维护的 NP 框架,一个有正经团队维护的项目从好的看,出现问题能统一修复解决,但从另一方面,有点剥离站点个性化的需求——从目前项目列出来的几个站点来看,站点雷同程度 ** 有过之而无不及 ** 。反正国人擅长建站,小站死了又是一批,能活成大站就是可以收割的时候 2333)

同样考虑到更新频繁这一点,本处的搭建手记也仅做示例,具体还是请参见项目主页 README 的介绍。

2018.08.27 请注意 meanTorrent 自 v1.8 停止开源维护。且该版本存在_严重_的信息暴露漏洞,** 不建议使用该框架建站 **。
2019.07.24 MINE 站已经长时间 522,而本月的 18 号另外一个用 meanTorrent 建站的中型站点 PTDream + 也宣布重新回到 NPHP 框架,实际也意味着 meanTorrent 框架的失败了。

依赖分析

meanTorrent 其所用的依赖十分基础:Git(版本管理)、Node.js(运行程序)、MongoDB(数据库) 、Bower (浏览器包管理器)。

2018-07-25_152451.jpg

开机及环境准备

按照本人惯例,开一台 Vultr (←这里有个很生硬的 afflink) 的 $5 虚拟机进行搭建测试,系统为 Ubuntu 16.04,地点随意。

同样,对基础依赖不大,所以全部程序直接包管理安装即可,喜欢折腾的可以通过源代码编译安装,但这里只是测试(踩坑),所以一切就简。

1
2
3
4
5
6
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80  --recv 9DA31620334BD75D9DCB49F368818C72E52529D4
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get update && sudo apt-get upgrade
sudo apt-get install -y git nodejs mongodb-org
sudo service mongod start

然后安装 npm 的包管理器 bower (话说现在不应该都用 Webpack 或者 Yarn 了嘛?)

1
npm install -g bower

安装 meanTorrent

** 建议使用非 root 账户进行安装!请使用 adduser 以及 visudo 创建新账户并赋予新账号超级用户权限。**

首先从 Github 上 clone 一份源代码来

1
git clone https://github.com/taobataoma/meanTorrent.git

然后使用 npm 安装依赖(过程中一堆 WARNING 都可以不用管 233)

1
2
cd meanTorrent
npm install

但似乎还是有问题,报错如下。在安装 node-gyp、iconv 以及 sharp 的时候报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
rhilip@vultr:~/meanTorrent$ npm install

> iconv@2.2.3 install /root/meanTorrent/node_modules/iconv
> node-gyp rebuild

Traceback (most recent call last):
File "/usr/lib/node_modules/npm/node_modules/node-gyp/gyp/gyp_main.py", line 13, in <module>
import gyp
File "/usr/lib/node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/__init__.py", line 8, in <module>
import gyp.input
File "/usr/lib/node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/input.py", line 5, in <module>
from compiler.ast import Const
ImportError: No module named compiler.ast
gyp ERR! configure error
`gyp ERR! stack Error: `gyp` failed with exit code: 1`
gyp ERR! stack at ChildProcess.onCpExit (/usr/lib/node_modules/npm/node_modules/node-gyp/lib/configure.js:336:16)
gyp ERR! stack at emitTwo (events.js:126:13)
gyp ERR! stack at ChildProcess.emit (events.js:214:7)
gyp ERR! stack at Process.ChildProcess._handle.onexit (internal/child_process.js:198:12)
gyp ERR! System Linux 4.4.0-127-generic
gyp ERR! command "/usr/bin/node" "/usr/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /root/meanTorrent/node_modules/iconv
gyp ERR! node -v v8.11.3
gyp ERR! node-gyp -v v3.6.2
gyp ERR! not ok

很明显的,还是出现了依赖缺失的情况,根据项目自身的介绍、 node-gyp 以及 lovell/sharp#1087 的相关提醒,安装 libicu-devGCC 编译库(主要是 c++ 以及 make 这两个会被 node-gyp 调用)以及 python-dev。然后重新安装依赖。

1
2
sudo apt-get install libicu-dev build-essential python-dev python-pip
npm install

使用 npm start 运行。过程中如果出现以下报错,说明 bower 以及相关浏览器端依赖没有正确安装(特别是在 root 账户下,这里直接使用新用户解决)。按照作者的建议是使用 bower install --allow-root && bower prune --allow-root 手动装一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rhilip@vultr:~/meanTorrent$ npm start

> meanTorrent@0.1.0 start /root/meanTorrent
> gulp

[08:54:34] Using gulpfile ~/meanTorrent/gulpfile.js
[08:54:34] Starting 'default'...
[08:54:34] Starting 'env:dev'...
[08:54:34] Starting 'copyLocalEnvConfig'...
[08:54:34] Starting 'makeUploadsDir'...
[08:54:34] Finished 'makeUploadsDir' after 835 μs
[08:54:34] Finished 'copyLocalEnvConfig' after 14 ms
[08:54:34] Starting 'lint'...
[08:54:34] Starting 'less'...
Potentially unhandled rejection [2] '../../../../public/lib/bootstrap/less/mixins/text-emphasis.less' wasn't found. Tried - /root/meanTorrent/public/lib/bootstrap/less/mixins/text-emphasis.less,../../../../public/lib/bootstrap/less/mixins/text-emphasis.less in file /root/meanTorrent/modules/core/client/less/mt-var.less line no. 4

如果一切没有问题,那么在运行 npm start 后会提示一下信息:

TIM 截图 20180725190508.jpg

Nginx 反代以及开机自启

通过以上环节,我们已经把所有的软件都基本配置齐全了,并启用了 meanTorrent 的开发环境。但是默认监听在 localhost 而不是 0.0.0.0 上,同样这个程序会因为 ssh 的断开而终止。所以我们需要安装 Nginx 以配置反代,同时使用 systemctl 或者其他守护工具(如作者使用多个 bashscript 脚本以及 forever 进行管理,如果你要使用 forever 的话,请先全局安装 npm i forever -g)使其能开机运行并崩溃重启。

安装 Nginx 也直接用包管理器吧,lnmp1.5 也提供单 Nginx 安装方式,反正怎么方便怎么来就 ok~

1
2
sudo apt-get install -y nginx-extras
vi /etc/nginx/sites-available/default

然后将默认的信息全部替换为以下常见反代配置(无 SSL)

如果是在 CloudFlare 后,建议参照本人之前教程进行设置。(Cloudflare 下 Nginx 获取用户真实 IP 地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
listen 80 default_server;
listen [::]:80 default_server;

server_name _;

location / {
proxy_pass http://localhost:3000/;
proxy_redirect default;
proxy_set_header Accept-Encoding "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

location ~ /\.ht {
deny all;
}
}

然后重新载入 Nginx 配置信息并重启 npm,你就可以直接使用 ip 或者域名 来进行访问了。

1
2
sudo systemctl reload nginx
npm start

TIM 截图 20180725201334.jpg

先不进行注册,退出我们来先写个 service 文件来使用 Systemd 进行守护管理。sudo vi /etc/systemd/system/meantorrent.service。并填入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[Unit]
Description=meanTorrent
Documentation=https://github.com/taobataoma/meanTorrent/wiki
After=network-online.target
[Service]
Type=forking
User=rhilip
Group=rhilip
UMask=007
Environment=NODE_ENV=production
WorkingDirectory=/home/rhilip/meanTorrent
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=10
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=meanTorrent
[Install]
WantedBy=multi-user.target

** 注意修改 User、Group、WorkinDirectory 为你自己的信息。**

然后使用以下命令重载 systemd 配置以及实现开机启动等管理

1
2
3
4
5
6
sudo systemctl daemon-reload # 重载 systemd 配置
sudo systemctl start meantorrent # 启动
sudo systemctl stop meantorrent # 停止
sudo systemctl restart meantorrent # 重启
sudo systemctl enable meantorrent # 开机自动启动 (只需执行一次)
sudo systemctl disable meantorrent # 取消开机启动

系统配置

个人建议这部分参考作者介绍 Getting Started With meanTorrent,对 config/env/torrent.js 下文件进行配置修改。关于站点个性化设置,本处不再累述~

配置好邮件系统和 Tracker Announce 部分后,重启 meanTorrent,然后注册账号即可。如果你只是和我一样测试,不对邮件系统进行配置,默认情况下注册后因为无法发送邮件导致账号状态为 inactive 无法使用,需要进入 mongo 修改账户信息(其实我是默认 config 下进行账号注册的 2333)。方法如下:

1
2
3
4
5
rhilip@vultr:~/meanTorrent$ mongo
> use mean-v2
switched to db mean-v2
> db.users.update({'username':'admin1234567'},{$set:{'status':'normal'}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

那么,就完事了~

success.jpg


框架结构及相关分析

在实质性的进入 Tracker 内部逻辑之前,让我先水一篇分析性的文章~

框架结构

  • NexusPHP

像 NP 这种较老的框架并没有一个很明显的架构,各项信息杂糅在一起。但从导航栏相关中,大体可以分为以下几个子模块。

以下分类方式仅代表本人意见。

nexusphp_framework.jpg

当然,目前各站在这个基础上均增加了一些新的系统,比如(万恶的)勋章系统、发布预告系统、转种系统(含自引用与他站引用)、任务系统、考核系统;对原有系统也有些许扩展,例如 Bangumi 榜单、AniDB 榜单等的添加。

  • meanTorrent

meanTorrent 的可以在其 modules 下明确的了解其架构。撰稿时,架构如下:

meantorrent_framwork.jpg

Tracker 请求生命周期

从整个请求流程来看,用户的 Bittorrent 客户端使用 HTTP GET 的形式向 Tracker 服务器发起请求,Tracker 服务器根据内部逻辑向数据库及缓存请求请求相关信息,并最终回复给用户。具体流程可见下方简图:

lifetime.jpg

当然上面的肯定过于简单了,没有对 Tracker 的内部逻辑进行分析,而就 Tracker 的内部逻辑来看,以 NP 这个面向流程的框架为例子,简图如下:

tracker_life.jpg

而从整个 Tracker 网络(见下方图片示意)来看,用户在请求后,Tracker 并不参与文件交换,同样,也不能真实的获取用户实际交换信息情况。(于是,我又可以就这个方向水一篇 SP 了)

main-qimg-ff65100c115e14ddf6c606b3799e0ae7.png

图片来自 Why is BitTorrent not downloading? - Quora

Tracker 分析

在这一手记前,首先先列一下几个相关文件,以方便快速查阅:


首先从最简单的信息查询来看,BT 客户端会在还未正式向 /announce 发送请求之前,会向 /scrape 先发送请求(一般在种子 添加或者校验的时候),请求头仅添加种子的 info_hash 信息,示例如下:

1
2
3
4
5
GET http://nexusphp.localhost/scrape.php?passkey=110099109e337b7e335aa368200d7907c&info_hash=%e4%40%ad%c6%27%db%02%40%f7%c8%ed%9e%7b%aab%a3%e1gFW HTTP/1.1
Host: nexusphp.localhost
User-Agent: uTorrent/2210(25302)
Accept-Encoding: gzip
Connection: Close

关于 scrape 的相关定义及返回信息在之前列出来的 BEP 0048 中,根据定义,这次 抓取(scrape) 会向用户返回一个字典描述对应种子的完成、未完成以及下载中的情况。本处直接引述定义:

The response to a successful request is a bencoded dictionary containing one key-value pair: the key files with the value being a dictionary of the 20-byte string representation of an infohash paired with a dictionary of swarm metadata.

根据 NP 的实现(见 NexusPHP/scrape.php ),会从 param 字段中搜索并匹配出来 info_hash 信息,如果 param 中没有能匹配出 info_hash,则会直接返回站内 ** 所有 ** 种子的做种信息。(如下图)

TIM 截图 20180730165317.jpg

以上问题修复 patch 见 :lock: Not allow empty info_hash when request scrape page by Rhilip · Pull Request #11 · zcqian/tjupt,截止目前,多数站点未对该问题进行修复。

而如果存在 info_hash 的话,则会对每一个提供的 hash 值进行数据库查找,如果查询结果不存在,则返回 Torrent not registered with this tracker. 的错误,而如果存在,则以一个 benc 编码的字典返回结果。其 json 格式如下:

1
2
3
4
5
6
{
"files": {
"xxxxxxxxxxxxxxxxxxxx": {"complete": 11, "downloaded": 13772, "incomplete": 19},
"yyyyyyyyyyyyyyyyyyyy": {"complete": 21, "downloaded": 206, "incomplete": 20}
}
}

此外,一个正常的客户端(例如 Transmission 2.94)会定期向 Tracker 发送(announce)以下信息,并期望从 tracker 中获取回复以获取其他 peer 信息(这里仅指 Private Tracker,其他的还有 DHT 网络、用户交换等方式)。

1
2
3
4
5
GET http://nexusphp.localhost/announce.php?passkey=db534098baaaa68bd725aaaae3051518&info_hash=aaaaaaaaaaaaaaaaaaaa&peer_id=-TR2940-lhqkh1jtwjqp&port=21736&uploaded=0&downloaded=0&left=739573325&corrupt=0&key=DDDD2A2B&event=started&numwant=200&compact=1&no_peer_id=1 HTTP/1.1
Host: nexusphp.localhost
User-Agent: Transmission/2.94
Accept-Encoding: gzip
Connection: Close

而 tracker 则会根据 GET 字段中的 compact 以及 no_peer_id 两个字段分别返回不同构造的结果,并设置相应头为

1
2
3
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Pragma: no-cache

compact=0&no_peer_id=0 时,其返回的 json 格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"interval": 50,
"min interval": 20,
"complete": 6871,
"incomplete": 3,
"peer": [
{
"ip": "xxx.xxxx.xxx.xxx",
"peer_id": "-TR2940-lhqkh1j31jqp",
"port": 5698,
},
....
]
}

其中, peer_id 视用户 Requests 字段这一项可以不要,而如果用户申请使用 compact 的形式返回,那么 peer 字段则会返回一个 string 而不是 dict。

如果发生错误,则会返回一个错误字段(string),JSON 格式内容如下:

1
2
3
{
"failure reason": "Error msg",
}

Bencode 编码

在上面的例子中,我都是以 json 格式的形式来展示 tracker 返回的 Response 信息,但实际上,返回的信息是以 Bencode 的形式进行编码的。关于 Bencode 的介绍,可以访问Bencode - Wikipedia。这里我们只需要知道一下编码规则就行:

data types raw encoded
int -42 i-42e
string ‘spam’ 4:spam
list [‘XYZ’, 4321] l3:XYZi4321ee
dict {‘XYZ’: 4321} d3:XYZi4321ee

这些甚至不需要你自己一个一个拼合,现在有超多的库帮你进行拼合,你只需要构造好相应的字典,直接调用即可,例如:


构造一个 Private-Tracker 的 Demo

在这节及之后的 PT 架构书写过程中,我将使用 ThinkPHP 5 作为 MVC 框架,rchouinard/bencode 作为 Bencode 编码库,实现一个演示性质的 Private Tracker。在此,我将默认你已经对前面的内容有所理解,并对 PHP 以及 composer 有了解运用。

该项目代码见: https://github.com/Rhilip/Simple-Private-Tracker ,仅供学习无法运行~

请注意,本文所列方法,仅表示本人的一种实现。实际只要符合 BEP 0003 以及 BEP 0027 的都是可行的实现~
请注意,本处所列代码并不一定是最新的,仅代表思考逻辑过程,具体代码请看 repo

相关 commit:c2c37e668a3f63722b6d4d736e957c8cda76b2a8

基础准备

首先,我们需要准备好 PHP 环境(建议为 7.x)以及数据库,因为学习,所以缓存暂时使用文件缓存。并使用 composer 安装 ThinkPHP5 以及 bencode ,其命令分别如下:

1
2
3
composer create-project topthink/think=5.0.* tp5  --prefer-dist
cd pt
composer require rych/bencode

准备相关数据表,分别用来存储 Torrent(种子信息)、User(用户信息)、Peer(做种人信息)、Snatch(做种完成情况),此处为了方便(偷懒)直接使用 NP 的建表语句(-> 见 NexusPHP/_db/dbstructure.sql 相关)就行(实际很多字段不需要)。顺带也方面后续兼容~

而文件夹方面,依次添加以下文件:

1
2
3
4
├─application
│ ├─tracker
│ │ ├─controllers
│ │ │ └─Index.php

并在路由(route\route.php)中注册两个控制器

1
2
Route::get('tracker/scrape','tracker/Index/scrape');
Route::get('tracker/announce','tracker/Index/announce');

并在设置中开启你的 debug 模式以及应用 trace,准备 postman 或其他作为调试工具。

方法准备

我们先要为 TrackerController 准备一些公用方法,分别用于构造响应信息(包括正常的以及错误)、禁用浏览器访问。修改 app\Http\Controllers\TrackerController.php 为以下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?php
namespace app\tracker\controller;
use think\Controller;
use think\Db;
use think\facade\Cache;
use think\Request;
use think\Response;
use think\Validate;
use Rych\Bencode\Bencode;
class Index extends Controller
{
private $errormsg = [
// Error message about requests params
// Error message about Bittorrent Client
// Error message about User Account
// Error message about Torrent
// Error message about Server
];
private $announce_param = []; // Announce Param HERE~
public function announce(Request $request)
{
}
public function scrape(Request $request)
{
}
private function block_browser()
{
$judge = false;
$request = Request();
if (preg_match("/Mozilla|Opera|Links|Lynx/", $request->header("user_agent"))) {
$judge = true;
}
if (!$request->isSSl()) {
if (
(null !== $request->header("Cookie", null)) ||
(null !== $request->header("Accept-Language", null)) ||
(null !== $request->header("Accept-Charset", null))
) {
$judge = true;
}
}
return $judge;
}
private function sendErrorMsg($code = 999, $msg = null)
{
if ($code && !$msg) {
$msg = $this->makeErrorMsg($code);
}
return $this->bencResp([
"failure reason" => $msg,
]);
}
private function makeErrorMsg($code)
{
return "$code - " . $this->errormsg[$code];
}
private function bencResp($obj)
{
$rep_benc = Bencode::encode($obj);
return response($rep_benc)
->header("Content-Type", "text/plain; charset=utf-8")
->header("Pragma", "no-cache");
}
}

我们将在 $errormsg 中定义错误信息,并在 announce 以及 scrape 这两个公开方法中定义具体逻辑。** 而所有的响应应该使用 bencResp 构造。**

构建 Scrape

相比较为复杂的 Announce 逻辑,我们先来处理较为简单的 Scrape 逻辑:

  1. 从请求头中获取所有 info_hash 信息,
  2. 从数据库中匹配出来对应的做种内容,
  3. 构造返回或错误信息。

下面我们开始写 Scrape 的具体逻辑。首先我们先禁用掉 非 GET 请求 以及 浏览器及非 BT 客户端请求。代码如下,但实际上,因为已经设置的路由关系,我们其实已经禁止了非 GET 请求,这里需要不需要都无所谓了

1
2
3
4
// 1. Block NON-GET requests, (though it should be block in Router)
if (!$request->isGet()) return $this->sendErrorMsg(100);
// 2. Block Browser, Crawler or Cheater
if ($this->block_browser()) return $this->sendErrorMsg(101);

然后我们从请求头中获取 info_hash 信息,并检查其是否存在(这里附加对各个 info_hash 的字节数进行检查也行)。当其不存在时,返回错误信息。

** 注意 **,根据 BEP0048 规定 ,info_hash 在 url 中是以 info_hash=xxxxx&info_hash=yyyyy 的形式存在的,故本人之前的写法是错误的(之前写法见Archive.org 的备份,只能匹配 info_hash[]=xxxx 的情况)

应为:

1
2
preg_match_all('/info_hash=([^&]*)/i', urldecode(Request::server('query_string')), $info_hash_match);
$info_hash = $info_hash_match[1];

针对 info_hash 未找到的情况进行处理。

1
2
3
4
if (count($info_hash) < 1) {
return $this->sendErrorMsg(null,
str_replace(':attribute', 'info_hash', $this->makeErrorMsg(102)));
}

使用查询构造器生成 SQL 语句并查询,并对查询结果进行检查;当数据库中未检查到该种子时,返回错误信息。

1
2
3
4
5
6
7
$res = Db::table("torrents")
->field(['info_hash', 'times_completed', 'seeders', 'leechers'])
->where('info_hash', "IN", $info_hash)
->select();
if (count($res) < 1) {
return $this->sendErrorMsg(131);
}

如果没有任何问题,我们需要把原来数据库中查询的结果(如下)进行转换

1
2
3
4
5
6
7
8
array(1) {
[0] => array(4) {
["info_hash"] => string(20) "aaaaaaaaaaaaaaaaaaaaa"
["times_completed"] => int(0)
["seeders"] => int(0)
["leechers"] => int(0)
}
}

方法如下,并最后使用 bencResp($obj) 的方式进行编码并发送

1
2
3
4
5
6
7
8
9
10
11
12
$rep = [
'files' => array_map(function ($item) {
return array (
$item["info_hash"] => [
"complete" => $item["seeders"],
"downloaded" => $item["times_completed"],
"incomplete" => $item["leechers"],
]
);
},$res)
];
return $this->bencResp($rep);

scrape_resp.jpg


PT 站的做种和魔力值是如何增加换算的?

PT 站的做种和魔力值是如何增加换算的?
相信混了一段 PT 界的小伙伴,一定头疼一个问题,各大站点的 PT 魔力值是怎么计算的呢?
以下是整理的帮助理解 PT 网站的魔力值计算方法。

** 统计工具采用 PTTools 这款 PT 助手 的截图(隐私部分已做打码处理)**

首先要说一下的是,国内 PT 站点基本上都是同一套魔力值公式,在计算魔力值的差异上基本不大。但是相信很多小伙伴还是云里雾里。下面上一张魔力值计算的公示图 (国内 90% 的 PT 网站都采用的这套公式)

如图所示,很复杂的一套公式。文科生包括小编我在内,对这个公式真实“恨之入骨”~ 当然了,其实也不复杂。经过小编通过多种资料的查询,最终得出的结论如下(简易版):

**1. 发布时间较长 **

**2. 做种人数小于 10 个 **

**3. 体积越大越好 **

只要在保种的同时满足以上 3 点,基本上跑满进度条是很有可能的!当然啦,如果小伙伴需要了解更加详细的资料,可以继续往下看。

** 魔力值分析 **

魔力值的 A 公式如下:

式中

  • A 为中间变量
  • Ti 为第 i 个种子的生存时间, 即自种子发布起到现在所经过的时间, 单位是周
  • T0 为参数。T0 = 4
  • Si 为第 i 个种子的大小,单位是 GB
  • Ni 为第 i 个种子当前的做种者数
  • N0 为参数。N0 = 7
  • B 为 1 小时中用户获得的做种魔力值点数
  • B0 为参数,代表用户 1 小时获得魔力值的上限。B0 = 50
  • L 为参数。L = 300

**A 值 **

我们只讨论单个种子的情况,因此忽略掉加和运算,该公式包含三个变量,为了研究每个变量的影响,我们把公式拆分

(1)1-10^(-0.25*i) 种子存活周期影响

(2)1+1.414*10^(-0.167*(i-1)) 当前做种人数影响

其中 i 为变量,分别代表周期数(以周为单位)和种子数

(1)函数图形如下

从图中我们可以很明显看到:8 周后时间对函数值没有明显影响。

(2)函数图形如下

从图中我们可以很明显看到:大于 10 个做种人数后时间对函数值没有明显影响。

(3)(1)*(2)的函数图形为

我们假设做种数和周期的数值相等,可得如下图像

从中可以看到,种子数计时间为 5 时 ,有最大值。

(4)(1)*(2)*Si 的图形

我们让做种数、大小和周期的数值相等,可得如下图像

**B 值 **

B 值(也即每小时魔力值)与 A 值关系见下图:

在 0-300 时基本是直线关系。

** 特殊情况分析 **

当周期大于 8,种子数多于 10,函数(1)(2)的值不再随他们的增加而明显改变,因此以此时的值代替周期大于 8,种子数多于 10 的值是合理的。

下面分析满足以上情况时的魔力值。

首先周期等于 8,种子数等于 10 时,函数(1)(2)的值分别为 0.99 和 1.04,则 A 值为 A=1.0296*Si ,B 值为 B= 31.85*atan((1.0296*Si)/300)

其图形如下

重要结论:经拟合,以上线性方程为 y=0.09658x+0.545,x 为种子尺寸,y 为一个种子每小时魔力值。相关系数为 0.99867,标准偏差为 0.2886。该方程适合周期大于 8 ,做种数多于 10 的情况。

同时我们还可以得到一个重要结论:占用同样的做种磁盘空间,多个种子和一个种子所获得的魔力值是相等的(不考虑 0.6*12,适用于周期大于 8,做种数多于 10 的情况),因为他们的 A 值是相等的,比如 100g 种子 1 个和 1g 种子 100 个,他们的 A 值可用 A=1.0296*Si 计算,显然二者相等。

** 结论 **

1. 8 周后时间的增加不会让魔力值明显增加;

2. 做种人数超过 10 后,做种人数的增加不会明显减小魔力值;

3. 一个种子存活 8 周后,且做种人数超过 10,种子尺寸小于 200G 时,魔力值与种子大小有近似直线关系,即 B=0.09658x+0.545;

4. 占用同样的做种磁盘空间,多个种子和一个种子所获得的魔力值是相等的。

以上就是 PT 站点魔力值的计算方法,看似复杂,其实主要记住最重要的三点“** 大 ” “” “ 少 **” (即种子体积大,种子存活时间长,种子的做种人数少!)


参考来源:
漫谈 PT 构架(1):NexusPHP 简介


to be continued…