BitTorrent协议规范
BitTorrent是一个用于分发文件的协议。它通过URL识别内容,并设计为与网络无缝集成。
与普通HTTP相比,它的优势在于,当多个用户同时下载同一文件时,下载者之间会互相上传,这使得文件源能够以适度的负载支持大量下载者。
BitTorrent文件分发包括以下实体:
- 普通Web服务器
- 静态“元信息”文件
- BitTorrent跟踪器
- “原始”下载者
- 终端用户的Web浏览器
- 终端用户的下载器
理想情况下,一个文件应该有多个终端用户。
服务端准备步骤:
- 启动一个跟踪器(或更可能是已经在运行一个)。
- 启动一个普通的Web服务器,例如Apache,或已经有一个。
- 将.torrent扩展名与MIME类型application/x-bittorrent关联(或已经完成)。
- 使用要服务的完整文件和跟踪器的URL生成一个元信息(.torrent)文件。
- 将元信息文件放置在Web服务器上。
- 从其他网页链接到元信息(.torrent)文件。
- 启动一个已经拥有完整文件的下载器(“原点”)。
用户下载步骤:
- 安装BitTorrent(或已经完成)。
- 浏览网页。
- 点击.torrent文件的链接。
- 选择在本地保存文件的位置,或选择恢复部分下载。
- 等待下载完成。
- 告诉下载器退出(在这之前,它会继续上传)。
bencoding
- 字符串以长度前缀表示,后跟一个冒号和字符串。例如,
4:spam
对应于“spam”。 - 整数以“i”开头,后跟十进制数字,再以“e”结束。例如,
i3e
对应于3,i-3e对应于-3。整数没有大小限制。i-0e是无效的。以零开头的所有编码,例如i03e,都是无效的,除了i0e,它当然对应于0。 - 列表以“l”开头,后跟其元素(也经过bencoding编码),最后以“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文件)是一个bencoded字典,包含以下键:
- announce:跟踪器的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哈希。
还可以有一个length或files键,但不能同时存在。
如果length存在,则下载表示单个文件;否则,它表示一组在目录结构的文件。
在单文件情况下,length映射到文件的字节长度。
在其他键的目的上,多文件情况被视为只有一个文件,通过按文件列表中出现的顺序连接文件。文件列表的值是files,映射到一个字典列表,包含以下键:
- length:文件的字节长度。
- path:对应子目录名称的UTF-8编码字符串列表,最后一个是实际文件名(零长度列表是错误情况)。
在单文件情况下,name键是文件名;在多文件情况下,它是目录名。
跟踪器
跟踪器的GET请求具有以下键:
- info_hash:bencoded形式的info值的20字节SHA1哈希。该值几乎肯定需要进行转义。
请注意,这个值是元信息文件的一个子串。info-hash必须是编码形式的哈希,正如在.torrent文件中找到的那样,这与b解码元信息文件、提取info字典并编码相同,仅当bencode完全验证输入(例如,键排序、没有前导零)时才能这样做。
相反,这意味着客户端必须拒绝无效的元信息文件,或者直接提取子串。它们不得在无效数据上进行解码-编码的循环。 - peer_id:下载器使用的长度为20的字符串作为其ID。每个下载器在新的下载开始时随机生成自己的ID。该值也几乎肯定需要进行转义。
- ip:可选参数,给出该对等体的IP(或DNS名称)。通常用于原点,如果它与跟踪器在同一台机器上。
- port:该对等体正在监听的端口号。通常的行为是下载器尝试监听6881端口,如果该端口已被占用,则尝试6882、6883等,并在6889之后放弃。
- uploaded:到目前为止,总共上传的量,以十进制ASCII编码。
- downloaded:到目前为止,总共下载的量,以十进制ASCII编码。
- left:该对等体仍需下载的字节数,以十进制ASCII编码。请注意,这不能从下载量和文件长度计算出来,因为它可能是恢复下载,且有可能部分下载的数据未通过完整性检查,必须重新下载。
- event:这是一个可选键,映射到started、completed或stopped(或空,表示与不出现时相同)。如果不存在,则这是定期进行的公告之一。
当下载首次开始时,会发送使用started的公告;当下载完成时,会发送使用completed的公告。如果文件在开始时已完成,则不会发送completed。下载者在停止下载时发送一个使用stopped的公告。
跟踪器响应是bencoded字典。如果跟踪器响应有一个键failure reason,则它映射到一个可读字符串,解释查询失败的原因,且不需要其他键。
否则,它必须有两个键:interval,映射到下载器应该等待的秒数,以便进行定期重新请求,和peers。peers映射到对应对等体的字典列表,每个字典包含peer id、ip和port键,分别映射到对等体自选的ID、IP地址或DNS名称作为字符串和端口号。
请注意,下载者在发生事件或需要更多对等体时,可以在非预定时间重新请求。
更常见的是,跟踪器返回对等体列表的紧凑表示,见BEP 23。
如果您想对元信息文件或跟踪器查询进行任何扩展,请与Bram Cohen协调,以确保所有扩展都是兼容的。
通常通过UDP跟踪器协议进行公告。
对等协议
BitTorrent的对等协议通过TCP或uTP运行。
对等连接是对称的。双向发送的消息看起来相同,数据可以在任一方向流动。
对等协议通过索引引用文件的片段,如元信息文件中所描述,从零开始。当对等体完成下载一个片段并检查哈希匹配时,它会向所有对等体宣布它拥有该片段。
连接在两端包含两个状态位:阻塞或不阻塞,以及感兴趣或不感兴趣。阻塞是一个通知,在解除阻塞之前不会发送任何数据。阻塞背后的原因和常见技术将在本文档后面解释。
数据传输发生在一个对等体(peer)感兴趣而另一个对等体没有被阻塞(unchoked)时。兴趣状态必须始终保持更新——当下载者在未被阻塞的情况下,发现没有什么需要请求的内容时,他们必须表达缺乏兴趣,即使他们当前被阻塞。
这一过程的实现比较复杂,但这样做的目的是让下载者能够知道,哪些对等体在解除阻塞后会立即开始下载。
连接开始时被阻塞且不感兴趣。
当数据正在传输时,下载者应同时保持多个片段请求排队,以获得良好的TCP性能(这被称为“流水线”)。另一方面,无法立即写入TCP缓冲区的请求应在内存中排队,而不是保留在应用程序级别的网络缓冲区,以便在发生阻塞时可以全部丢弃。
对等协议的消息流由握手开始,后跟一个永无止境的长度前缀消息流。握手以字符19(十进制)开头,后跟字符串“BitTorrent protocol”。前导字符是长度前缀,旨在希望其他新协议也能这样做,从而彼此显著区分。
所有后续发送的整数都以四字节大端格式编码。
在固定头之后,有八个保留字节,当前实现中都为零。如果您希望使用这些字节扩展协议,请与Bram Cohen协调,以确保所有扩展都是兼容的。
接下来是元信息文件中info值的20字节SHA1哈希(这是跟踪器上公告为info_hash的相同值,只是在这里是原始的而不是引用的)。如果双方发送的值不相同,则断开连接。
一个可能的例外是,如果下载者希望通过单个端口进行多个下载,他们可能会等待传入连接首先提供下载哈希,并在其列表中如果存在则响应相同的值。
握手之后,接下来是一个交替的长度前缀和消息的流。长度为零的消息是保持活跃消息,忽略。保持活跃消息通常每两分钟发送一次,但在期望数据时超时可以更快完成。
对等消息
所有非保持活跃消息以一个单字节开头,表示其类型。
可能的值是:
- 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,以此类推。末尾的闲置位设置为零。
“have”消息的负载是一个单一数字,即该下载者刚刚完成并检查哈希的索引。
“request”消息包含索引、开始和长度。最后两个是字节偏移。长度通常是2的幂,除非在文件末尾被截断。所有当前实现使用2^14(16 KiB),并关闭请求大于该量的连接。
“cancel”消息的负载与请求消息相同。它们通常仅在下载接近完成时发送,在所谓的“结束模式”下。当下载几乎完成时,最后几个片段通常全部从一个拥挤的调制解调器线路下载,耗时很长。为了确保最后几个片段快速到达,一旦所有该下载者尚未拥有的片段的请求正在进行,它会向所有正在下载的对等体发送请求。为了避免效率低下,它在每次接收到片段时向其他所有对等体发送取消。
“piece”消息包含索引、开始和片段。请注意,它们与请求消息隐含相关。如果在快速连续发送的阻塞和解除阻塞消息中,可能会收到意外的片段。
下载者通常以随机顺序下载片段,这样可以合理地避免拥有任何对等体的片段的严格子集或超集。
阻塞是出于多种原因。TCP拥塞控制在同时通过多个连接发送时表现非常差。此外,阻塞让每个对等体使用一种互惠算法,确保它们获得一致的下载速度。
以下描述的阻塞算法是当前部署的算法。所有新算法在由它们自身组成的网络中和以此为主的网络中都应良好运作。
一个好的阻塞算法应满足多个标准。
它应限制同时上传的数量以确保良好的TCP性能。
它应避免快速阻塞和解除阻塞,这称为“颤动”。
它应对让其下载的对等体进行互惠。
最后,它应偶尔尝试未使用的连接,以查找它们是否可能比当前使用的连接更好,这称为乐观解除阻塞。
当前部署的阻塞算法通过每十秒钟仅更改被阻塞的对象,避免了颤动。它通过解除四个上传速率最佳且感兴趣的对等体来实现互惠和上传数量的限制。
那些上传速率更好但不感兴趣的对等体会被解除阻塞,如果它们变得感兴趣,则最差的上传者会被阻塞。如果下载者拥有完整文件,它会使用其上传速率而非下载速率来决定解除哪个对等体的阻塞。
在乐观解除阻塞方面,任何时候都有一个对等体被解除阻塞,无论其上传速率如何(如果感兴趣,它会算作四个允许的下载者之一)。被乐观解除阻塞的对等体每30秒轮换一次。为了给它们一个良好的机会上传完整的片段,新连接比当前乐观解除阻塞的连接更有三倍的可能性。