BitTorrent 协议规范(BEP-0003)

原文链接

BEP:3
题目:BitTorrent 协议规范
版本:0e08ddf84d8d3bf101cdf897fc312f2774588c9e
最后修改:Sat Feb 4 12:58:40 2017 +0100
作者:Bram Cohen <[email protected]>
状态:最终版
类型:标准
创建时间:10-Jan-2008
更新记录:24-Jun-2009 ([email protected]), clarified the encoding of strings in torrent files.
20-Oct-2012 ([email protected]), clarified that info-hash is the digest of en bencoding found in .torrent file. Introduced some references to new BEPs and cleaned up formatting.
11-Oct-2013 ([email protected]), correct the accepted and de-facto sizes for request messages
04-Feb-2017 ([email protected]), further info-hash clarifications, added resources for new implementors

BitTorrent 是一种分发文件的协议。 它通过 URL 识别内容,旨在与网络无缝集成。 与普通 HTTP 相比,它的优势在于,当同一文件的多次下载同时发生时,下载器会相互上传,这使得文件源可以支持非常多的下载器,而其负载仅略有增加。

一个BitTorrent 文件分发系统由以下实体组成:

  • 一个普通的 web server
  • 一个静态的 '元信息(metainfo)' 文件
  • 一个 BitTorrent tracker
  • 一个 '最初的' 下载器
  • 终端用户的 web 浏览器
  • 终端用户的下载器

理想情况下,有多个最终用户共同下载单个文件。

要开始服务,主机要经过以下步骤:

  1. 开始运行 tracker(或者,更有可能的是,已经有一个在运行中)。
  2. 开始运行一个普通的 web 服务器,比如 apache,或者用已有的。
  3. 将扩展名 .torrent 与其 Web 服务器上的 mimetype application/x-bittorrent 相关联(或已经这样做了)。
  4. 使用要提供服务的完整文件和 tracker 的 URL 生成元信息 (.torrent) 文件。
  5. 将元信息文件放在 Web 服务器上。
  6. 将元信息 (.torrent) 文件加入其他网页的链接。
  7. 启动一个已经包含有完整文件的('最初的')下载器。

要开始下载,用户执行以下操作:

  1. 安装 BitTorrent(或已经安装)。
  2. 浏览网页。
  3. 单击指向 .torrent 文件的链接。
  4. 选择在本地保存文件的位置,或选择部分下载以继续。
  5. 等待下载完成。
  6. 告诉下载器退出(否则它会一直上传直到退出)。

bencoding

译注:bencode是BitTorrent用于存储和传输松散结构数据的编码方式。支持4种类型的数据:字符串、整数、列表、字典。其中列表类似于数组,而字典类似于结构体。

  • 字符串以字符串长度的十进制数字开头,后面跟着一个冒号,然后是原始的字符串。 例如,4:spam 对应于 spam
  • 整数以i开头,后面跟着10进制的数字,最后以e结尾。 例如:i3e对应3i-3e对应-3。 整数没有大小限制。 i-0e 是无效的。 所有数字前带0的编码,例如i03e,都是无效的,但i0e除外,明显它对应于0
  • 列表以l开头,后面是列表中的元素(也经过bencode编码),最后以e结尾。 例如l4:spam4:eggse对应于['spam', 'eggs']
  • 字典以d开头,后面跟着交替出现的 键 和对应的 值 的列表,最后以e结尾。 例如,d3:cow3:moo4:spam4:eggse 对应于 {'cow': 'moo', 'spam': 'eggs'}d4:spaml1:a1:bee 对应于 {'spam': ['a', 'b']}。 键必须是字符串,并按顺序出现(按原始字符串排序,而不是包含数字前缀的字符串)。

元信息文件

元信息文件(也称为 .torrent 文件)是经过 bencode 方式编码的字典,包含以下几个键:

  • announce :tracker 的 URL。

  • info :这映射到一个字典,其中包含的键如下所述。

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

info 字典

name 这个键映射到一个 UTF-8 编码的字符串,这是保存文件(或目录)的建议名称。 这纯粹是建议性的。

piece length 映射到文件被分割成的每个片段中的字节数。 为了传输的目的,文件被分成固定大小的片段,除了最后一个可能被截断的片段外,这些片段的长度都相同。 piece length 几乎总是 2 的幂,最常见的是 2 18 = 256 K(BitTorrent 3.2 之前的版本默认使用 2 20 = 1 M)。

pieces 映射一个字符串,其长度为 20 的倍数。该字符串将被分为多个长度为 20 的子字符串,每个子字符串是相应索引处的片段的 SHA1 哈希值。

译注:即pieces对应一个由每个分片的 SHA1 哈希值组成的字符串数组。

还有一个键length,或另一个键files,这两个键既不可能同时出现,也不可能一个都没有。 如果存在length,则下载表示单个文件,否则用files表示以目录结构组织的一组文件。

在单个文件的情况下,length 映射到文件的长度(以字节为单位)。

对于其他键而言,多文件的情况将被视为只有一个文件,这个文件由文件列表中的所有文件按照其列表顺序连接而成。 文件列表由files映射,它是一个字典列表,字典中包含以下键:

length - 文件的长度,以字节为单位。

path - 与子目录名称对应的 UTF-8 编码字符串列表,列表中最后一个字符串是实际文件名(零长度列表是错误的情况)。

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

trackers

向 Tracker 服务器发送 GET 请求时,需要以下几个参数键值:

  • info_hash

    元信息文件中 经 bencode 方式编码后的info 值的 20 字节 sha1 哈希。 这个值几乎肯定必须被转义。请注意,这只是元信息文件的子串。 info-hash 必须是在 .torrent 文件中的编码后格式(的数据)的哈希,也就是说,当且仅当 输入的元信息文件被 bencode 解码器完全验证(例如:键的排序,没有前导零)并解码完成后,提取出 info 字典,然后对其进行编码,其结果应该跟 info-hash 完全相同。 相对的,这也意味着客户端必须拒绝无效的元信息文件,或从中直接提取子串处理。 他们不得对无效数据执行解码-编码过程。

  • peer_id

    一个长度为 20 的字符串,当前下载器将其作为其 ID。 每个下载器在新的下载开始时,会随机生成自己的 id。 这个值几乎肯定也必须被转义。

  • ip

    一个可选参数,给出此 peer 当前的 IP(或 dns 名称)。 如果它和 tracker 相同的机器上,通常表示这是初始的下载器。

  • port

    此 peer 正在侦听的端口号。 常见的行为是下载器尝试侦听端口 6881,如果该端口被占用,则尝试 6882、6883 等,然后在 6889 之后放弃。

  • uploaded

    到目前为止上传的总量,以十进制 ASCII 编码。

  • downloaded

    到目前为止下载的总量,以十进制 ASCII 编码。

  • left

    此 peer 仍需下载的字节数,以十进制 ascii 编码。 请注意,这不能从已下载的字节数和文件长度计算,因为它可能是恢复下载,并且有可能某些下载的数据没有通过完整性检查,必须重新下载。

  • event

    这是一个可选的键值,它映射到 startedcompletedstopped(或 empty,与键值不存在相同)。 如果此键值不存在,这是一个定期发布的通告。 下载第一次开始时,会发送started通告;下载完成时,会发送completed通告。 如果文件在启动时已完成,则不会发送completed。 下载器在停止下载时,发送stopped通告。

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

更常见的情况是 tracker 返回peer列表的一个紧凑表示,参见 BEP 23

如果您想对元信息文件或 tracker 查询进行任何扩展,请与 Bram Cohen 协调以确保所有扩展都兼容。

通过 UDP tracker 协议 进行通告是很常见的。

peer 协议

BitTorrent 的 peer 协议通过 TCP 或 uTP 运行。

Peer 连接是对称的。 双向发送的消息看起来是相同的,数据可以在任一方向流动。

Peer 协议按元信息文件中描述的索引引用文件的各个部分,从零开始。 当一个 peer 完成一个片段的下载,并检查到哈希匹配时,它会向所有 peer 宣布它拥有该片段。

连接的两端都包含两个状态位:是否已阻塞(choked),是否感兴趣(interested)。阻塞是一种通知,在解除阻塞之前不会发送任何数据。 阻塞背后的原理和常用技术将在本文档后面的部分进行解释。

一旦双方的其中一方感兴趣,而另一方没有阻塞时,就会进行数据传输。 感兴趣位的状态必须始终保持最新 - 在当前的下载中,如果一个下载器不再需要那些它们原本会向处于未阻塞状态的 peer 索要的东西时,他们必须表示不感兴趣,不管是不是正在被阻塞。 要正确的实现这一点有些棘手,但这样可以让下载器知道,如果没有阻塞的话,哪些 peer 将立即开始下载。

刚开始的连接会被置为已阻塞和不感兴趣。

在传输数据时,下载器应该将多个请求同时排队,以获得良好的 TCP 性能(这称为“流水线”。)另一方面,对于不能立即写出到 TCP 缓冲区的请求,应该在内存中排队,而不是保存在应用层的网络缓冲区中,这样当发生阻塞时它们都可以被抛出。

Peer 连线协议包含一个握手,后面是一个永不结束的消息流,流中发送的都是以长度为前缀的消息。 握手以字符 19(十进制)开始,后跟字符串“BitTorrent 协议”。 前面的字符是一个长度前缀,放在那里是希望其他新协议可以做同样的事情,从而可以轻松区分彼此。

后续在协议中发送的所有的整数都被编码为四字节的 big-endian。

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

接下来是 20 字节的 SHA1 哈希值,来自于元信息文件中以 bencode 方式编码的 info 的值 。 (这和给 tracker 发布的 info_hash 是相同的值,只不过在这里它是原始值而不是引用值)。 如果双方发送的值不同,他们就会切断连接。 一个可能的例外是,如果下载器想要通过单个端口进行多个下载,他们可以等待传入的连接首先给出下载哈希,如果下载哈希在他们的列表中,则使用相同的值进行响应。

在下载哈希之后,是 20 字节的 peer ID,它是 peer 在 tracker 请求中报告的,并包含在 tracker 响应中的 peer 列表中。 如果接收方的 peer id 与发起方所期望的不匹配,它将切断连接。

这就是握手的所有内容了,接下来是包含长度前缀和消息的交替流。 长度为零的消息是 keepalive 消息,用来保持连接活动,无需处理。 Keepalive 消息通常每两分钟发送一次,但请注意,当需要数据时,可以更快地完成超时。

peer 消息

All non-keepalive messages start with a single byte which gives their type.

The possible values are:

  • 0 - choke
  • 1 - unchoke
  • 2 - interested
  • 3 - not interested
  • 4 - have
  • 5 - bitfield
  • 6 - request
  • 7 - piece
  • 8 - cancel

'choke', 'unchoke', 'interested', and 'not interested' have no payload.

'bitfield' is only ever sent as the first message. Its payload is a bitfield with each index that downloader has sent set to one and the rest set to zero. Downloaders which don't have anything yet may skip the 'bitfield' message. The first byte of the bitfield corresponds to indices 0 - 7 from high bit to low bit, respectively. The next one 8-15, etc. Spare bits at the end are set to zero.

The 'have' message's payload is a single number, the index which that downloader just completed and checked the hash of.

'request' messages contain an index, begin, and length. The last two are byte offsets. Length is generally a power of two unless it gets truncated by the end of the file. All current implementations use 2^14 (16 kiB), and close connections which request an amount greater than that.

'cancel' messages have the same payload as request messages. They are generally only sent towards the end of a download, during what's called 'endgame mode'. When a download is almost complete, there's a tendency for the last few pieces to all be downloaded off a single hosed modem line, taking a very long time. To make sure the last few pieces come in quickly, once requests for all pieces a given downloader doesn't have yet are currently pending, it sends requests for everything to everyone it's downloading from. To keep this from becoming horribly inefficient, it sends cancels to everyone else every time a piece arrives.

'piece' messages contain an index, begin, and piece. Note that they are correlated with request messages implicitly. It's possible for an unexpected piece to arrive if choke and unchoke messages are sent in quick succession and/or transfer is going very slowly.

Downloaders generally download pieces in random order, which does a reasonably good job of keeping them from having a strict subset or superset of the pieces of any of their peers.

Choking is done for several reasons. TCP congestion control behaves very poorly when sending over many connections at once. Also, choking lets each peer use a tit-for-tat-ish algorithm to ensure that they get a consistent download rate.

The choking algorithm described below is the currently deployed one. It is very important that all new algorithms work well both in a network consisting entirely of themselves and in a network consisting mostly of this one.

There are several criteria a good choking algorithm should meet. It should cap the number of simultaneous uploads for good TCP performance. It should avoid choking and unchoking quickly, known as 'fibrillation'. It should reciprocate to peers who let it download. Finally, it should try out unused connections once in a while to find out if they might be better than the currently used ones, known as optimistic unchoking.

The currently deployed choking algorithm avoids fibrillation by only changing who's choked once every ten seconds. It does reciprocation and number of uploads capping by unchoking the four peers which it has the best download rates from and are interested. Peers which have a better upload rate but aren't interested get unchoked and if they become interested the worst uploader gets choked. If a downloader has a complete file, it uses its upload rate rather than its download rate to decide who to unchoke.

For optimistic unchoking, at any one time there is a single peer which is unchoked regardless of its upload rate (if interested, it counts as one of the four allowed downloaders.) Which peer is optimistically unchoked rotates every 30 seconds. To give them a decent chance of getting a complete piece to upload, new connections are three times as likely to start as the current optimistic unchoke as anywhere else in the rotation.

Resources

  • The BitTorrent Economics Paper outlines some request and choking algorithms clients should implement for optimal performance
  • When developing a new implementation the Wireshark protocol analyzer and its dissectors for bittorrent can be useful to debug and compare with existing ones.

Copyright

This document has been placed in the public domain.

Top