在实质性的进入Tracker内部逻辑之前,让我先水一篇分析性的文章~
框架结构
- NexusPHP
像NP这种较老的框架并没有一个很明显的架构,各项信息杂糅在一起。但从导航栏相关中,大体可以分为以下几个子模块。
以下分类方式仅代表本人意见。

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

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

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

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

Tracker分析
在这一手记前,首先先列一下几个相关文件,以方便快速查阅:
- BEP 0003 :The BitTorrent Protocol Specification
- BEP 0007 :IPv6 Tracker Extension
- BEP 0023 :Tracker Returns Compact Peer Lists
- BEP 0048 :Tracker Protocol Extension: Scrape
- Private Tracker实现
- ZJUT/NexusPHP/announce.php ,同时也包括其他开源的NP魔改架构,如nwafu-mta/mtpt、zcqian/tjupt
- taobataoma/meanTorren/modules/announce/server/controllers/announces.server.controller.js
- WhatCD/Ocelot/worker.cpp (Gazelle框架背后的主程序)
- mojie126/YoRHa/application/announce/controller/TrackerController.php
首先从最简单的信息查询来看,BT客户端会在还未正式向 /announce 发送请求之前,会向 /scrape 先发送请求(一般在种子 添加或者校验的时候),请求头仅添加种子的info_hash信息,示例如下:
1 | 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 |
关于scrape的相关定义及返回信息在之前列出来的BEP 0048中,根据定义,这次抓取(scrape) 会向用户返回一个字典描述对应种子的完成、未完成以及下载中的情况。本处直接引述定义:
The response to a successful request is a bencoded dictionary containing one key-value pair: the key
fileswith 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,则会直接返回站内所有种子的做种信息。(如下图)

以上问题修复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 | { |
此外,一个正常的客户端(例如Transmission 2.94)会定期向Tracker发送(announce)以下信息,并期望从tracker中获取回复以获取其他peer信息(这里仅指Private Tracker,其他的还有DHT网络、用户交换等方式)。
1 | 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 |
而tracker则会根据GET字段中的compact以及no_peer_id两个字段分别返回不同构造的结果,并设置相应头为
1 | 200 OK |
当compact=0&no_peer_id=0时,其返回的json格式如下:
1 | { |
其中, peer_id 视用户Requests字段这一项可以不要,而如果用户申请使用compact的形式返回,那么peer字段则会返回一个string而不是dict。
如果发生错误,则会返回一个错误字段(string),JSON格式内容如下:
1 | { |
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 |
这些甚至不需要你自己一个一个拼合,现在有超多的库帮你进行拼合,你只需要构造好相应的字典,直接调用即可,例如:
