目录 [隐藏] |
此规范的目的是详细介绍 BitTorrent 协议规范 v1.0 。Bram 的协议规范网站http://www.bittorrent.com/protocol.html 简要地叙述了此协议,在部分范围缺少详细行为阐述。希望此文档能成为 一个正式的规范,明确的条款,将来能作为讨论和执行的基础。
此文档规定由 BitTorrent 开发者维持和使用。欢迎大家为它做贡献,其中的内容代表当前协议,它仍由许多客户使用。
这里不是提出特性请求的地方。如果有请求,请见邮箱列表。
本文文件适用于 BitTorrent 协议规范的第一版(v1.0)。目前,这份文文件应用于 torrent 文件结构、用户线路协议和服务器(Tracker) HTTP/HTTPS 协议规范。如果某个协议的修改有了新的定义,它们会被指定在协议相应的页面,而不在这里。
在本文档中,使用了许多约定来简明和明确地表达信息。
为了帮助其它人找到本文档最近的修改,请填写改变日志(最后一段)。它应包含一个简短的项目(如:一行),用来记录你每次对此文档的主要改动。
编码是一种以简洁格式指定和组织数据的方法。支持下列类型:字节串、整数、表和字典。
字节串按如下编码:<以十进制 ASCII 编码的串长度>:<串数据>
注意没有开始和结束的分隔符。 例:「4:spam」 代表字符串「spam」
整数按如下编码:i<以十进制 ASCII 编码的整数>e
开始的「i」与结尾的「e」分别是开始和结束分隔符。可以使用如「i-3e」之类的负数。但不能把「0」放到数字的前面,如「i04e」。另外,「i0e」是有效的。
例:「i3e」代表整数「3」
表按如下编码:l<编码值>e
开始的「l」与结尾的「e」分别是开始和结束分隔符。 表可以包含任何已编码的类型,包括整数、串、字典和其它的表。
例:l4:span4:eggse 代表两个串的表「spam」、「eggs」
字典按如下编码:d<编码串><编码元素>e
开始的「d」与结尾的「e」分别是开始和结束分隔符。 注意关键词必须被编码为串。值可以是任何已编码类型,包括整数、串、表和其它字典。关键词必须是串,以分类的顺序出现(以原始串排列,而不是以字母数字)
例1:d3:cow3:moo4:spam4:eggse 代表字典 { "cow" => "moo", "spam" => "eggs" }
例2:d4:spaml1:a1:bee 代表字典 { "spam" => ["a", "b"] }
所有在元信息文件中的数据都要编码。编码规则如上所述。
元信息文件(以 .torrent 结尾的文件)的内容是一个编码的字典,包含以下列表中的各项。所有字符串值都以 UTF-8 编码。标记没有为「可选」的键值是必需的字段:
一个描述 torrent 文件的字典。有两种可能的形式:一种是没有目录结构的「单一文件」,另一种是包含子目录树的「多文件」
对于「单一文件」来说,信息字典包含以下的结构:
长度: 文件字节数长度(整数)
md5和: (可选)一个 32 位的 16 进位字符串,它对应于文件的 MD5和。不被 BitTorrent 所使用,但被一些程序包含,以提供更大的兼容性。
名称: 文件的名称。建议使用(字节串)。
片断长度: 每个片断的字节数(整数)。
片断: 包含所有 20 字节 SHA-1 散列值的字符串,每个片断都有唯一的值。(字节串)
对于「多文件」来说,信息字典包含以下的结构:
文件:字典列表,每个文件都有一个。每个在表中的字典包含以下键值:
长度:文件长度的字节数(整数)
md5和: (可选)一个 32 位的 16 进位字符串,它对应于文件的 MD5和。不被 BitTorrent 所使用,但被一些程序包含,以提供更大的兼容性。
路径:一个包含着一个或多个字符串元素的,它包含路径和文件名。每个表中元素对应于一个目录名或(在最后的元素的情况下)文件名。
例:文件名「dir1/dir2/file.ext」将包含三种串元素:「dir1」、「dir2」和「file.ext」。编码为串表的例子「l4:dir14:dir28:file.exte」
名称:结构中根目录的名称--包含上述文件列表中所有文件的目录(字符串)
片断长度: 每个片断的字节数(整数)。
片断: 包含所有 20 字节 SHA-1 散列值的字符串,每个片断都有唯一的值。(字节串)
注意:
服务器是用类响应 HTTP GET 请求的一种 HTTP/HTTPS 服务。该请求包括客户端的度量标准,这个标准可以帮助服务器全面统计 torrent 文件。基本的 URL 包括元数据文件(torrent)中定义的「发布 URL」。再将那些参数通过标准 CGI 方法添加到此 URL 中(如:「?」在发布 URL 之后,紧接着「参数=值」的序列,分隔符「&」)
注意所有在 URL 中的二进制数据(特别是 info_hash 和 peer_id)必须使用转义符。这表示所有的字节不使用 0-9,a-z,A-Z和$-_.+!*'(),而是采用「%xx」格式的编码,其中的「nn」是字节的 16 进位数值。(详细见RFC1738)
客户端向服务器的 GET 请求的参数如下:
<!--[if !supportLists]-->· <!--[endif]-->
<!--[if !supportLists]-->· <!--[endif]-->
<!--[if !supportLists]-->· <!--[endif]-->
服务器作出「text/plain」文文件的响应包括以下编码字典的关键词:
<!--[if !supportLists]-->· <!--[endif]-->
<!--[if !supportLists]-->· <!--[endif]-->
<!--[if !supportLists]-->· <!--[endif]-->
如上所述,用户列表长度默认值为 50。如果连接的用户少于该值,列表会更小。另外,服务器随机选择用户及其响应。服务器在响应请求时可能使用一个更智能的机构来选择用户。例如,应避免向其它做种者报告种子。
在事件发生(即:已停止或已完成)或客户端需要连接更多的用户时,客户端向服务器发送请求的间隔可以低于指定的时间间隔。但是,为了获得更多的用户而向服务器频繁地请求会被认为是错误的行为。如果客户端想在响应中得到许多用户,则需要在「需求数目」参数中设定。
使用者注意:30 个用户就算丰富的源了,官方客户端版本 3(v3)实际上在连接数少于 30 时会增加新的连接,当连接数大于或等于 55 时会拒绝连接多余的用户。这个值对性能很重要。当完成下载一个新的片断时,「已拥有」消息(见下面)将会发送到最活动的用户。结果广播通信量与用户数目成正比例增加。大于 25 时,新用户不太可能会增加下载速度。有人强烈建议用户界面设计者使该项模糊和很难修改,因为那样做几乎没有用。
根据惯例,多数服务器支持请求的另一种形式,这种方式询问给定的服务器正在处理的 torrent (或所有的 torrent)。通常叫做「刮页」,因为它自动处理「刮屏」(服务器统计页)冗长的部分。刮 URL 也是一种类似于上面描述的 HTTP GET 方法。但基本 URL 不同。用以下步骤来得到刮 URL:从发布 URL 开始寻找其中最后一个「/」。如果在文本之后的「/」不是「announce」,它将被作为一个符号,此符号不支持刮约定。如果是,则以「scrape」代替「announce」来找到刮页。
例:(发布 URL -> 刮 URL)
http://example.com/announce -> http://example.com/scrape http://example.com/x/announce ->http://example.com/x/scrape http://example.com/announce.php -> http://example.com/scrape.phphttp://example.com/a -> (不支持刮) http://example.com/announce?x=2%0644 ->http://example.com/scrape?x=2%0644 http://example.com/announce?x=2/4 -> (不支持刮)http://example.com/x%064announce -> (不支持刮)
特别注意:结束引语没有完成。此标准是由 Bram 在 BitTorrent 开发列表文件中说明的。
刮 URL 可以作为可选参数「info_hash」的一个补充,是一个 20 字节的值。这限制了服务器向特殊种子汇报。另外,服务器正在处理的所有种子的统计也被发回。为了降低服务器的负载和频宽,有人强烈建议软件作者尽可能使用「info_hash」参数。
HTTP GET 方法的响应是一个由编码字典组成的「text/plain」文文件,包括以下关键词:
注意此响应有三层字典嵌套。例如:
d5:filesd20:....................d8:completei5e10:downloadedi50e10:incompletei10eeee
其中「....................」是 20 字节的 info_hash,以上表明有 5 个做种者,10 个吸血者和 50 个完成下载的用户。
用户线路协议使元信息文件中片断的交换变得更容易。
注意原始规范在描述用户协议时也使用术语「片断」,但与元信息文件中的术语「片断」不同。由于该原因,术语「块」将在本规范中用来描述用户之间通过线路交换的数据。
客户端必须为每个远程用户的连接保持状态信息:
注意这也意味着客户端需要记住自己是否对远程用户感兴趣和阻塞它。因此,真正的列表看起来像这样:
客户端的连接以「被阻塞」和「不感兴趣」开始。也就是:
当客户端对远程用户感兴趣并且远程用户未阻塞该客户端时,客户端开始下载块。当客户端没有阻塞远程用户并且远程用户对该客户端感兴趣时,客户端开始上传块。
客户端保持通知远程用户自己是否对它们感兴趣,这是很重要的。与每个远程用户连接的状态信息应保持最新,直到该客户端被阻塞。这允许远程用户知道当自己未阻塞时,客户端是否会开始下载;反之亦然。
如果不特别指定,在用户线路协议中的所有整数都会编码成 4 字节 big endian 值。这包括所有消息中的长前缀,它在握手之后。
用户线路协议包括初始握手。之后,用户通过长前缀消息的交换来进行通信。长前缀是上述的一个整数。
握手是必需的消息,它一定是由客户端发送的第一条消息。
在 BitTorrent 协议 v1.0 中:pstrlen=19,pstr="BitTorrent protocol"
连接的发起者应该立即发送彼此的握手信息。即使接收者能同时提供多个 torrent(torrent 通过自己的 info_hash 来唯一标识),它也应等待发起者的握手信息。但是,接收者在看到握手的 info_hash 部分后必须迅速响应。服务器的 NAT 检查特性不会发送握手的 peer_id 字段。
如果客户端收到一个当前不能处理的握手 info_hash,该客户端就会断开那个连接。
如果连接的发起者收到一个握手信息,其中的 peer_id 与预期的不同,那么发起者就会断开该连接。注意发起者可能会收到来自服务器的远程用户信息,它包括远程用户注册的 peer_id。来自服务器的 peer_id 与握手信息中的应该相同。
主要有两种将客户端及其版本信息编码到 peer_id 的方法:Azureus 型和 Shadow 型。
Azureus 型使用如下编码:「-」,一个客户端标识使用两个字符,版本号用 4 个 ASCII 数字表示,「-」紧跟在随机数字之后。
例如:'-AZ2060-'...
已知采用这种方法编码的客户端是:
Shadow 型采用如下编码:用 1 个 ASCII 字母或数字标识客户端,3 个 ASCII 数字标识版本号,「----」紧跟在随机数字之后。
例如:'S587----'...
已知采用此种类型编码的客户端为:
Bram 的客户端现在采用的形式: 'M3-4-2--'
BitComet则不同。它的 peer_id 由四个 ASCII 字符组成「exbc」,后面是两个字节的「x」和「y」,之后才是随机字符。版本号「x」是在小数点前的十进制数值,「y」是小数点之后的两个十进制数值。BitLord 使用相同的结构,但在版本号后添加「LORD」字符。一个 BitComet 的非官方补丁将「exbc」替换成了「FUTB」。BitComet 用户标识的编码在 0.59 及其以前的版本都采用的 Azureus 型。
XBT Client也有自己的风格。其 peer_id 由三个大写字母「XBT」紧跟三个代表版本号的 ASCII 数字。如果客户端处在调试阶段,第七字节则是小写字母「d」,其它情况下是「-」。之后是「-」和随机数字,大写和小写字母。例:'XBT054d-'表示 0.5.4 版的开始调试阶段。
Opera 8 previews采用以下结构:前面两个字符「OP」紧跟四个相等的构建号。之后所有字符都是随机小写 16 进位的数字。
Bits on Wheels采用格式「-BOWAxx-yyyyyyyyyyyy」,其中「y」是随机大写字母,x 取决于版本。版本 1.0.6 中 xx=0C。
许多客户端使用随机数字或 12 个〇紧跟着随机数字(如以前旧版本的 Bram 客户端)。
协议中所有发送的消息均采用「<长前缀><消息标识符><有效负载>」的形式。长前缀是一个 4 字节 big endian 值。消息标识符是一个十进制字符。有效负载是消息依赖的。
keep-alive: <len=0000>
「保持活动」消息是由〇组成的字节串,将长前缀设为〇可指定。没有消息标识符和有效负载。如果在一段时间内,用户没有收到消息,它会断开连接,所以需要发送活动消息来维持连接。一条保持活动消息大概每两分钟发送一次。
choke: <len=0001><id=0>
阻塞消息是定长的,无有效负载。
unchoke: <len=0001><id=1>
未阻塞消息是定长的,无有效负载。
interested: <len=0001><id=2>
感兴趣消息是定长的,无有效负载。
not interested: <len=0001><id=3>
不感兴趣消息是定长的,无有效负载。
have: <len=0005><id=4><piece index>
拥有消息是定长的。有效负载是刚成功下载和通过散列值校验的〇基片段的索引。
使用者注意:这是严格的定义,实际上会用到某些游戏程序。特别地,因为用户极不可能下载已经拥有的片断,用户可能不会选择向另一个用户宣布已拥有那个片断的消息。少量「拥有抑制」会导致拥有消息减少一半,在协议前面将转化到 25-35%。
恶意用户可能会选择宣布拥有别人永远不会下载的片断。因此,向普通用户发送此信息是坏主意。
bitfield: <len=0001+X><id=5><bitfield>
位字段消息可能在握手序列发送完成后,在其它任何消息发送之前立即发送。它是可选的,如果客户端没有此片断则不必发送。
位字段消息是变长的,其中的「X」是位字段的长度。有效负载是代表成功下载片断的位字段。首字节的高位对应片断索引 0。已清除的位则指出缺少的片断,通过一个有效可用的片断来设置位。末位剩下的位设置为〇。
一个错误长度的位字段被认为是错误。客户端在收到不正确大小的位字段或已有设置为剩下位的位字段时应断开连接。
request: <len=0013><id=6><索引><开始><长度>
请求消息是定长的,用来请求块。有效负载包含以下信息:
索引:指定〇基片断索引的整数。
开始:指定片断内〇基字节的偏移量整数。
长度:指定被请求长度的整数。
根据官方规范有关主要版本3,「所有当前执行应使用 2^15(32 KB),请求数量大于 2^17 (128 KB)时应断开连接。」在主要版本4中,此反应修改到了 2^14 (16 KB),超过该值的用户会强迫拒绝。注意到块请求小于片断大小(>=2^18 字节),所以为下载一个完整片断需要多次请求。
由于新版本将限制定在 16 KB,尝试使用 32 KB 的块就好比用 4 发子弹来玩俄式轮*盘--会遇到困难。更小的请求会导致更大的系统时间和空间开销,因为要跟踪很多请求。结果应使用所有客户端都允许的 16 KB。
请求块大小的限制执行的选择没有减少一部分清楚。在主要版本 4 中,强制使用 16 KB 的请求,许多客户端会使用该值,只有一个严格客户端组不会使用。大多数旧客户端使用 32 KB 请求,不允许明显减少可能用户的批次。同时 16 KB 是现在部分官方的限制(「部分」是因为官方协议文文件没有更新),所以强制使用没有错。另外,允许更大的请求增大了可能用户的批次,除在非常低的频宽连接(小于 256 kbps)中,多个块会在一个阻塞周期内完成下载,从而强迫使用旧的限制仅会降低很少的性能。因此,推荐仅在旧的 128 KB 下才强行限制。
piece: <len=0009+X><id=7><索引><开始><块>
片断消息是变长的,其中的「X」是块长度。有效负载包含以下信息:
索引:由〇基片断索引指定的整数
开始:由片断内〇基字节偏移量指定的整数
块:数据块,是索引指定片断的子集。
cancel: <len=0013><id=8><索引><开始><长度>
取消信息是定长的,用来取消块的请求。有效负载与「请求」消息相同。典型用在「最后阶段」中。(见下面的算法段)
port: <len=0003><id=9><listen-port>
端口消息是由运行 DHT 服务器的新版本的主要部分。监听端口是用户 DHT 节点正在监听的埠。如果 DHT 服务器支持,则应把此用户加入本地的路由表中。
通常建议用户在每个连接上保持一些未完成的请求。因为从一块的下载到开始下载另一块需要一个完全的往返程(往返程在片断消息和下一个请求消息之间)。一旦与高 BDP (Bandwidth Delay Product,高延迟或频宽)相连,会降低很多性能。官方客户端未完成请求的默认值是 5。
用户注意:这是最严格的性能条款。一个 5 请求的静态队列对于具有 50 ms 延迟 5 Mbps 中的 32 KB 块是合理的。连接更大的频宽越来越常见,所以用户界面设计者被催促使其对于改变更适用。特别地,线缆调制解调器以调整通信量和增加其可能缓和部分由此导致的问题,这是众所周知的。
自动调整:调整此参数的一个合理的方法是连续测量单个连接的频宽。如果该用户增加频宽而队列不够,则尝试增加队列长度。使用相同标志如果减少队列长度没有减少频宽和延迟,可能是队列长度太大了。
(该项不是原始规范的一部分)
在 S-5.5 中的超级种子特性是一种新的做种算法,用来帮助只有有限频宽的做种者发布很大的文件,减少为了产生新的种子而上传的数据总量。
当做种者使用「超级种子模式」时,它不会作为标准种子,而是伪装成一个没有数据的普通客户端。当客户端连接时,它会通知它们自己收到一块从未发送或源很少的片断。这将促使客户端仅尝试下载那个片断。
当客户端完成下载该片断时,做种者看到它以前发送的片断在其它用户中至少有一个拥有后,才会继续发送另外的片断。在那之前,客户端下载不到做种者的其它片断,这样不会浪费做种者的频宽。
这种方法会有更高的效率,同时促使用户只下载源最少的数据,降低了多余数据的发送,限制了没有为该群传输数据的用户而发送的数据量。在这之前,做种者可能需要上传文件总大小的 1.5 到 2 倍,其它用户才可能成为种子。但是,使用超级种子模式的单个客户端发布大的文件只需上传文件大小的 1.05 倍就能成为种子。这是标准做种效率的 1.5 到 2 倍。
不推荐一般用户采用超级种子模式。虽然它有助于稀少数据的扩散,但是它限制了客户端下载片断的选择,同时限制了客户端下载自己已得到部分的片断。所以,超级种子模式只推荐原始做种者使用。
综上,「原始做种者模式」或「发布者模式」是更恰当的名称。
客户端可以随机顺序下载片断。
更好的方法是首先下载源最少的片断。客户端可以从每个其它用户保存的原始位字段来决定,通过拥有消息来更新。然后,客户端可以下载出现在其它用户位字段中频率最低的片断。
下载接近完成时,最后几块的速度有变慢的趋势。为了加速,客户端向其它所有拥有自己缺少块的用户发送请求。为防止变成无效,客户端在每个块完成后就向其它用户发送一个取消的消息。
没有已定的界限,推荐百分比或能用来作为指导的块计数。
何时进入最后阶段模式有待讨论。一些客户端以请求了所有块来进入最后阶段。其它的等到剩余块的数目少于传输中块的数目或不超过 20 来进入该阶段。保持很少的等待块(1 或 2 块)来将允许值减到最少,如果随机选择块的请求,可能会重复下载到已有的块。更多的协议说明见:http://hal.inria.fr/inria-00000156/en
阻塞有几种原因。TCP 拥塞控制在同时发出许多连接时表现很差。同时,阻塞使每个用户使用 tit-for-tat-ish 算法来确定自己得到一致的下载速度。
下面描述的阻塞算法是现在使用的。所有新算法同时在整个包括它们的网络中工作正常,这是很重要的。
一个好的阻塞算法应有几个标准。它应为更高的 TCP 性能改进并发上传数。它应避免被叫做「原纤化作用」的快速阻塞和未阻塞。它应互换到让自己下载的用户。最后,它应偶尔测试未使用连接来发现是否比当前使用的更好,这叫做最佳畅通。
当前使用的阻塞算法通过每 10 秒钟改变被阻塞用户来避免原纤化作用。
互换和上传数目的改进是由拥有最佳上传速度和感兴趣的 4 个未阻塞用户来控制的。这将使客户端的下载速度变得最大。这 4 个用户被称为下载者,因为它们对从客户端的下载感兴趣。
与下载者相比,具有较高上传速度的用户对未阻塞不感兴趣。如果它们感兴趣,具有最低上传速度的下载者将被阻塞。如果客户端拥有完成的文件,它使用上传速度而不是下载速度来决定哪一个用户未阻塞。
对于最佳畅通,在任何时候只有一个未阻塞用户,而不管它的上传速度(如果感兴趣,它会成为 4 个允许的下载者之一)。最佳畅通的用户 30 秒循环一次。最新连接的用户有 3 次可能作为循环中当前的最佳畅通。这给它们得到一个完成块就上传的机会。
(扩展不在官方协议中)
偶尔一个 BitTorrent 用户会被其它用户冷落,它先前从那些用户中下载了数据。在这种情况下,它通常得到很低的下载速度,直到最佳畅通找到更好的用户。为了缓和此问题,当过了 1 分钟而没有从某个用户得到一个片断,BitTorrent 认为它被那个用户「冷落」了,不上传给那个用户(除了最佳畅通以外)。这会频繁导致超过 1 个的用户同时变成最佳畅通(一个例外是最佳畅通,规则如上所述),它会使波动的下载速度回复得更快。