本文章是 The BitTorrent Protocol Specification 的中文翻译版本,使用 Grok,对部分内容进行了润色

BEP:3
标题:BitTorrent 协议规范
版本:0 e 08 ddf 84 d 8 d 3 bf 101 cdf 897 fc 312 f 2774588 c 9 e
最后修改:2017 年 2 月 4 日星期六 12:58:40 +0100
作者:Bram Cohen mailto:bram@bittorrent.com
状态:最终版
类型:标准
创建日期:2008 年 1 月 10 日
更新历史:

  • 2009 年 6 月 24 日 ( arvid@bittorrent.com ),明确了 torrent 文件中字符串的编码方式。
  • 2012 年 10 月 20 日 ( arvid@bittorrent.com ),进一步说明 info-hash 是 .torrent 文件中 bencoding 内容的摘要,引入了对新 BEP 的引用,并优化了格式。
  • 2013 年 10 月 11 日 ( arvid@bittorrent.com ),更正了 request 消息的公认及实际大小。
  • 2017 年 2 月 4 日 ( the8472.bep@infinite-source.de ),对 info-hash 做了进一步澄清,并为新开发者添加了资源参考。

BitTorrent 是一种用来分发文件的协议。它通过 URL 来识别内容,设计上能无缝融入网页。相比普通的 HTTP,它的优势在于,当多人同时下载同一个文件时,下载者之间会互相上传数据。这样一来,文件源只需要稍微增加一点负载,就能支持大量下载者同时下载。

BitTorrent 文件分发主要包括以下几个部分:

  • 一个普通的网页服务器
  • 一个静态的“元信息”文件
  • 一个 BitTorrent tracker
  • 一个“原始”下载者
  • 终端用户的网页浏览器
  • 终端用户的下载客户端

理想情况下,一个文件会有很多终端用户来下载。

要开始提供服务,主机需要按照以下步骤操作:

  • 启动一个 tracker(或者更常见的情况是,已经有一个在运行了)。
  • 启动一个普通的网页服务器,比如 apache,或者已经有一个在运行。
  • 在网页服务器上将 .torrent 扩展名关联到 mimetype application/x-bittorrent(或者已经设置好了)。
  • 使用要服务的完整文件和 tracker 的 URL,生成一个元信息(.torrent)文件。
  • 将元信息文件放到网页服务器上。
  • 在其他网页上添加指向这个元信息(.torrent)文件的链接。
  • 启动一个已经拥有完整文件的下载客户端(也就是「发种者」)。(原文这里叫做“origin”,我的理解就是刚开始第一个发种的用户)

要开始下载,用户需要按照以下步骤操作:

  • 安装 BitTorrent(或者已经装好了)。
  • 浏览网页。
  • 点击一个指向 .torrent 文件的链接。
  • 选择本地保存文件的位置,或者选择一个部分下载的文件继续下载。
  • 等待下载完成。
  • 告诉下载客户端退出(在此之前它会一直上传)。

bencoding 编码规则

  • 字符串:以长度为前缀(十进制表示),后面跟着一个冒号和具体的字符串内容。比如,4:spam 就代表字符串 'spam'。
  • 整数:以字母 'i' 开头,后面是十进制表示的数字,再以 'e' 结尾。比如,i3e 代表数字 3,i-3e 代表 -3。整数没有大小限制,但 i-0e 是无效的。所有前面带零的编码,比如 i03e,也是无效的,唯一的例外是 i0e,它代表 0。
  • 列表:以字母 'l' 开头,后面是列表中的元素(也是 bencoded 编码的),最后以 'e' 结尾。比如,l4:spam4:eggse 代表 ['spam', 'eggs']。
  • 字典:以字母 'd' 开头,后面是一系列交替出现的键和对应的值,最后以 'e' 结尾。比如,d3:cow3:moo4:spam4:eggse 代表 {'cow': 'moo', 'spam': 'eggs'},而 d4:spaml1:a1:bee 代表 {'spam': ['a', 'b']}。键必须是字符串,并且按排序顺序排列(按原始字符串排序,不是字母数字混合排序)。

元信息文件(metainfo files)

元信息文件(也称为 .torrent 文件)是采用 bencoded 编码的字典,包含以下键:

  • announce
    跟踪器(tracker)的 URL 地址。
  • info
    这映射到一个字典,具体包含的键如下所述。

.torrent 文件中所有包含文本的字符串必须使用 UTF-8 编码。

info 字典

  • name
    映射到一个 UTF-8 编码的字符串,表示建议保存文件(或目录)时使用的名称。这个名称仅供参考,没有强制性。
  • piece length
    映射到每个分片(piece)的字节数。文件为了传输会被分成固定大小的分片,除了最后一个分片可能被截断外,其他分片的长度都相同。Piece length 通常是 2 的幂次方,最常见的是 2^18 = 256 K(BitTorrent 3.2 版本之前默认使用 2^20 = 1 M)。
  • pieces
    映射到一个字符串,其长度是 20 的倍数。这个字符串会被分成多个长度为 20 的子字符串,每个子字符串是对应索引位置的分片的 SHA 1 哈希值。
  • 另外,还有一个键是 lengthfiles,但这两个键不会同时出现,也不会都不出现。如果有 length 键,说明下载的是单个文件;如果没有,则代表下载的是一组文件,这些文件会存放在一个目录结构中。

    • 在单个文件的情况下,length 映射到文件的字节长度。
    • 在多文件的情况下,为了处理其他键,会将文件列表中的文件按顺序拼接起来,视为一个单一文件。files 键映射到的值是一个文件列表,列表中的每个元素是一个字典,包含以下键:

      • length - 文件的字节长度。
      • path - 一个 UTF-8 编码的字符串列表,表示子目录名称,最后一个字符串是实际的文件名(如果列表长度为 0,则属于错误情况)。

在单个文件的情况下,name 键是文件的名称;在多文件的情况下,它是目录的名称。

跟踪器(Trackers)

跟踪器的 GET 请求包含以下键:

  • info_hash
    这是元信息文件中 info 值的 bencoded 编码形式的 20 字节 SHA 1 哈希值。几乎可以肯定,这个值需要进行转义处理。
    注意,这实际上是元信息文件的一个子字符串。Info_hash 必须是对 .torrent 文件中编码形式的哈希值,这等同于对元信息文件进行 bdecoding,提取 info 字典并重新编码,前提是 bdecoder 完全验证了输入(例如键的顺序、没有前导零)。反过来说,客户端必须拒绝无效的元信息文件,或者直接提取子字符串,而不能对无效数据进行解码-编码的往返操作。
  • peer_id
    一个长度为 20 的字符串,作为下载者的 ID。每个下载者在开始新的下载时会随机生成自己的 ID。这个值几乎肯定也需要转义。
  • ip
    一个可选参数,表示该对等方的 IP 地址(或 DNS 名称)。通常用于表示源地址,如果它与跟踪器在同一台机器上。
  • port
    该对等方监听的端口号。常见做法是下载者尝试监听 6881 端口,如果该端口被占用,则尝试 6882、6883 等,最高到 6889,如果都不可用则放弃。
  • uploaded
    迄今为止上传的总数据量,用十进制 ASCII 编码表示。
  • downloaded
    迄今为止下载的总数据量,用十进制 ASCII 编码表示。
  • left
    该对等方还需要下载的字节数,用十进制 ASCII 编码表示。注意,这个值不能简单地通过已下载量和文件长度计算出来,因为可能是续传下载,而且部分已下载数据可能未通过完整性检查,需要重新下载。
  • event
    这是一个可选键,映射到 startedcompletedstopped(或者为空,空等同于不存在)。如果不存在此键,则这是定期进行的公告之一。使用 started 的公告在下载开始时发送,使用 completed 的公告在下载完成时发送。如果文件在开始时就已经是完整的,则不发送 completed 公告。下载者停止下载时会发送使用 stopped 的公告。

跟踪器的响应是 bencoded 编码的字典。如果跟踪器响应中包含键 failure reason,则该键映射到一个人类可读的字符串,解释查询失败的原因,此时不需要其他键。否则,响应必须包含两个键:interval,映射到下载者在定期重新请求之间应等待的秒数;以及 peers,映射到一个对等方列表,列表中的每个对等方是一个字典,包含键 peer idipport,分别对应对等方自选的 ID、IP 地址或 DNS 名称(字符串形式)以及端口号。注意,如果发生事件或需要更多对等方,下载者可能会在非预定时间重新请求。

更常见的是,跟踪器会返回对等方列表的紧凑表示形式,参见 BEP 23

如果你想对元信息文件或跟踪器查询进行任何扩展,请与 Bram Cohen 协调,确保所有扩展都具有兼容性。

另外,通过 UDP 跟踪器协议进行公告也很常见。

对等方协议(Peer Protocol)

BitTorrent 的对等方协议基于 TCP 或 uTP 运行。

对等方连接是对称的。双方发送的消息格式相同,数据也可以双向流动。

对等方协议通过元信息文件中描述的索引来引用文件的各个分片(piece),索引从零开始。当一个对等方完成某个分片的下载并验证哈希值匹配后,它会向所有连接的对等方宣布自己拥有该分片。

连接在两端各有两个状态位:是否被限制(choked)以及是否感兴趣(interested)。被限制是一种通知,表示在解除限制(unchoking)之前不会发送数据。关于限制的逻辑和常见技巧会在本文档后续部分详细说明。

数据传输发生在当一方表示感兴趣且另一方未限制的情况下。感兴趣状态必须随时保持更新——每当下载者没有某个分片且当前希望从某个未被限制的对等方请求该分片时,即使自己被限制,也必须明确表示不感兴趣。正确实现这一点有些复杂,但这能让下载者知道哪些对等方在解除限制后会立即开始下载。

连接初始状态为被限制且不感兴趣。

当数据正在传输时,为了获得良好的 TCP 性能,下载者应一次性排队多个分片请求(这被称为“流水线处理”,pipelining)。另一方面,如果请求无法立即写入 TCP 缓冲区,则应将其排队在内存中,而不是保存在应用层网络缓冲区,这样在发生限制时可以一次性丢弃所有请求。

对等方有线协议包括一个握手过程,随后是无止境的长度前缀消息流。握手以字符 19(十进制)开头,紧接着是字符串“BitTorrent protocol”。前导字符是一个长度前缀,设置它的目的是希望其他新协议也能这样做,从而可以轻松区分不同的协议。

协议中后续的所有整数都以四字节大端序(big-endian)编码。

固定头部之后是八个保留字节,在当前所有实现中均为零。如果你希望使用这些字节扩展协议,请与 Bram Cohen 协调,确保所有扩展都具有兼容性。

接下来是元信息文件中 info 值的 bencoded 编码形式的 20 字节 SHA 1 哈希值。(这与向跟踪器宣布的 info_hash 值相同,只是这里是原始数据,而不是经过转义的)。如果双方发送的值不一致,则会断开连接。唯一可能的例外是,如果一个下载者希望通过单个端口进行多次下载,他们可能会等待传入连接先提供下载哈希值,如果该哈希值在他们的列表中,则回复相同的哈希值。

在下载哈希之后是 20 字节的对等方 ID,这个 ID 在跟踪器请求中报告,并且包含在跟踪器响应的对等方列表中。如果接收方的对等方 ID 与发起方期望的不匹配,则会断开连接。

握手到此结束,接下来是长度前缀和消息的交替流。长度为零的消息是保活消息(keepalive),会被忽略。保活消息通常每两分钟发送一次,但请注意,当期待数据时,超时处理可能会更快。

对等方消息(Peer Messages)

除了保活消息(keepalive)外,所有消息都以一个单字节开头,用于表示消息类型。

可能的值如下:

  • 0 - choke(限制)
  • 1 - unchoke(解除限制)
  • 2 - interested(感兴趣)
  • 3 - not interested(不感兴趣)
  • 4 - have(拥有)
  • 5 - bitfield(位字段)
  • 6 - request(请求)
  • 7 - piece(分片)
  • 8 - cancel(取消)

“Choke”、“unchoke”、“interested”和“not interested”这几种消息没有负载数据。

“Bitfield”消息只会在首次连接时发送。它的负载是一个位字段,下载者已拥有的分片索引对应的位设为 1,其余设为 0。如果下载者还没有任何分片,可以跳过发送“bitfield”消息。位字段的第一个字节对应索引 0-7,从高位到低位排列;下一个字节对应 8-15,以此类推。末尾的多余位都设为 0。

“Have”消息的负载是一个单一的数字,表示下载者刚刚完成下载并验证了哈希值的分片索引。

“Request”消息包含索引(index)、起始位置(begin)和长度(length)。后两个是字节偏移量。长度通常是 2 的幂次方,除非受到文件末尾的截断。目前所有实现都使用 2^14(即 16 kiB),如果请求的量超过这个值,连接会被关闭。

“Cancel”消息的负载与“request”消息相同。通常只在下载接近尾声时发送,这种情况被称为“endgame mode”(终局模式)。当下载快要完成时,最后几个分片往往都从单一的慢速调制解调器连接下载,导致耗时很长。为了确保最后几个分片能快速下载,一旦某个下载者还未拥有的所有分片的请求都已发出,它会向所有正在下载的对等方请求所有分片。为了避免效率低下,每当一个分片到达时,它会向其他所有对等方发送取消请求。

“Piece”消息包含索引(index)、起始位置(begin)和分片数据(piece)。注意,它们与“request”消息是隐式相关的。如果“choke”和“unchoke”消息快速连续发送,或者传输速度非常慢,可能会收到未预期的分片。

下载者通常以随机顺序下载分片,这样可以较好地避免他们的分片集合成为任何对等方的严格子集或超集。

限制(choking)的设置有多种原因。TCP 拥塞控制在同时通过多个连接发送数据时表现很差。此外,限制机制让每个对等方可以使用类似“以牙还牙”的算法,确保他们能获得稳定的下载速率。

下面描述的限制算法是当前部署的版本。非常重要的一点是,所有新算法必须既能在完全由自身组成的网络中良好运行,也能在主要由当前算法组成的网络中正常工作。

一个好的限制算法应满足几个标准。它应限制同时上传的数量以保证 TCP 性能;应避免快速切换限制和解除限制的状态,这种现象被称为“fibrillation”(抖动);应对允许其下载的对等方进行回馈;最后,应不时尝试未使用的连接,看看是否比当前使用的连接更好,这种做法称为“乐观解除限制”(optimistic unchoking)。

当前部署的限制算法通过每十秒才改变一次限制对象来避免抖动。它通过解除对下载速率最高且感兴趣的四个对等方的限制来实现回馈和上传数量限制。如果有上传速率更高的对等方但他们不感兴趣,也会被解除限制,而一旦他们表现出兴趣,上传速率最差的对等方会被限制。如果某个下载者已完成整个文件的下载,它会根据上传速率而非下载速率来决定解除谁的限制。

对于乐观解除限制,在任意时刻,只有一个对等方会被解除限制,无论其上传速率如何(如果它感兴趣,则计入四个允许下载的对等方中)。被乐观解除限制的对等方每 30 秒轮换一次。为了给他们一个上传完整分片的合理机会,新连接作为当前乐观解除限制对象的概率是轮换中其他位置的三倍。

资源

BitTorrent 经济学论文中概述了一些请求和限制算法,客户端应实现这些算法以获得最佳性能。
在开发新实现时,Wireshark 协议分析器及其针对 BitTorrent 的解析器非常有用,可以帮助调试并与现有实现进行比较。

版权

本文档已置于公共领域。

最后修改:2025 年 05 月 30 日
如果觉得我的文章对你有用,请随意赞赏