BitTorrent协议由布拉姆·科恩(Bram Cohen)于2001年创建
BitTorrent (简称 BT) 协议是一种点对点 (Peer-to-Peer, 或简写为 P2P) 传输协议, 它被设计用来高效地分发文件 (尤其是对于大文件、多人同时下载时效率非常高)
BitTorrent 协议是架构于 TCP/IP 协议之上的一个P2P文件传输通信协议,是一个应用层协议
传统http和ftp下载文件的问题:
- 服务器的带宽通常都是有限
- 默认没有端点续传
- 无法批量下载
BT协议的优势:
- 每个人都可以下载和上传,减轻了单个服务器的负担
- 有很强的鲁棒性,如果一个用户中断下载,他们可以在任何时候恢复
- 文件被分成了很多小块,增加的并发性,提高下载效率
BT协议主要的步骤:
-
资源描述:一个文件资源的下载是从一个BT种子(.torrent文件)或磁力链接(magnet link)开始的,在BT协议中,也被称为元数据(metainfo),元数据中包含该文件资源如何被分片,以及每个分片的大小,校验哈希等信息。
-
资源定位:用户从数据发布者获取到文件的元数据后,接下来需要通过资源定位的机制,来找到拥有该资源的peer,如果无法找到拥有该资源的peer,也就意味着下载失败。通常一个peer只包含一个文件资源的部分分片信息,所以需要找到多个peer才能完整地拼接出整个文件。
-
数据下载:在获取到拥有资源的peer列表后,需要通过peer传输协议来沟通协商各自拥有的分片资源,请求下载各自所需的分片,来完成整个资源的下载。
资源描述
BT种子(.torrent)的文件格式
BT 协议的 metainfo file 用来描述资源的元信息, 如资源的 URL, 资源的 SHA1 哈希等, 它的文件后缀为 .torrent, 也就是常所说的 BT 种子文件 (BT 种子文件中的文本内容统一使用 UTF-8 编码), BT 种子文件使用 bencoding 编码来描述信息的。
- Bencoding编码(BEP_0003)
Bencoding 共有 4 种基本数据类型, 分别是 String, Integer, List 和 Dictionary
类型 | 定义 | 示例 |
---|---|---|
String:字符串类型 | 使用形如 length-prefix : string-content 的形式来描述字符串, 其中 length-prefix 是字符串的长度, string-content 是字符串内容 | 4:spam 表示字符串 \'spam\' |
Integer:整数类型 | 使用字符 i 作为前缀, 使用字符 e 作为后缀, 在前缀 i 和后缀 e 中间的是整数本身 (整数都使用十进制表达) | i3e 表示整数 3, i-3e 代表整数 -3 |
List:列表类型 | 使用字符 l作为前缀, 使用字符 e 作为后缀, 在前缀 l和后缀 e 之间的是列表的元素 (列表元素也都使用 bencoding 编码方法) | l4:spam4:eggse 表示 [\'spam\', \'eggs\'] |
Dictionary:字典类型 | 使用字符 d 作为前缀, 使用字符 e 作为后缀, 在前缀 d 和后缀 e 之间的是 k-v 对, 其中字典的键 (key) 必须是 bencoding 编码的 String 形式, 而字典键所对应的值可以是 bencoding 编码的任何一种类型 (String, Integer, List, Dictionary) | d3:cow3:moo4:spam4:eggse 表示 {\'cow\': \'moo\', \'spam\':\'eggs\'} d4:spaml1:a1:bee 表示 |
- 文件格式
BT 种子文件实际上就是一个 bencoding 编码的 Dictionary, BEP_0003 定义了两个 Key, 分别是 announce 和 info。 BEP_0012(Metadata Extension)扩展协议增加了 announce-list 字段。另外,还有一些常用,但不在协议定义范围内的字段,如注释、创建时间等。
字段 | 定义 |
---|---|
announce | tracer地址 |
announce-list | 备用tracer地址 |
creation date | 创建时间 |
comment | |
info | name文件名称 piece length 分片长度(字节)256KB piece 每个分片的sha1值,20个一组 length 原始文件长度(字节) // length/piece length = 分片数量 |
磁力链的格式(magnet URI format)
磁力链接是一种特殊链接,通过不同文件内容的Hash结果生成一个纯文本的“数字指纹”,并用它来识别文件。例如 magnet:?xt=urn:btih:a27463ca05b1e87ecf89f95a07c13f158b5f2437
磁力链非常类似于WEB服务中用于定位资源的URL,其中使用资源的哈希码即可唯一标识资源,相比BT种子文件,非常简单。任何人都可以分发一个磁力链接来确保该链接指向的资源就是他想要的,而和得到该资源的方式无关。因为磁力链中并不需要包含tracker服务器信息,因此相比于BT种子,更难于封锁。磁力链在BEP_0009(Extension for Peers to Send Metadata Files)
- 协议中定义有2种格式:
magnet:?xt=urn:btih:<info-hash>&dn=<name>&tr=<tracker-url>&x.pe=<peer-address>
magnet:?xt=urn:btmh:<tagged-info-hash>&dn=<name>&tr=<tracker-url>&x.pe=<peer-address>
- 字段含义
字段 | 定义 |
---|---|
magnet | 必须,协议名 |
xt | 必须,exact topic的缩写,包含文件哈希值的统一资源名称。btih(BitTorrent Info Hash)表示哈希方法名,使用40个字符的16进制编码字符串,表示160位的哈希值。info-hash是对.torrent文件中的info-hash字段进行hash计算,在磁力链中的metadata也是指info-hash的部分。btmh是multihash ,使用较少。 |
dn | 可选,display name的缩写,表示向用户显示的文件名 |
tr | 可选,tracker服务器的地址 |
x.pe | 可选,用于获取metadata的peer地址,格式为hostname:port, ipv4-literal:port 或 [ipv6-literal]:port |
在磁力链协议中,metadata指.torrent文件中的info字段的字典信息。如果参数中不包含tracker信息,则需要从DHT(BEP_0005)获取peer。
资源定位机制
有了BT种子文件或磁力链接后,接下来我们就需要找到拥有资源的peer,来完成数据的下载。BT下载的定位机制相对简单,在种子文件中的announce字段,给出了tracker地址,客户端可以通过与tracker通信的方式获取拥有资源的peer,完成下载。对于无tracker的磁力链接来说,需要通过DHT,找到拥有metadata(包含文件分配及描述信息)的peer,获取到metadata后,再进行数据的下载。
BT的资源定位机制-Tracker协议
BT Tracker 是一个注册服务, 用来协调 BT 协议的文件分发, 通过 BT Tracker 可以知道当前有哪些终端在同时下载目标资源, 当终端执行下载任务时应首先请求 BT Tracker 服务获取当前正在下载同一资源的对等结点信息, 终端用户通过 Tracker GET 请求BT Tracker 服务。(可以是 HTTP 或 BEP_0015 UDP Tracker Protocol for BitTorrent 或其它, 具体使用的协议由 .torrent 文件的 announce 对应的 Value 值给出)
Tracker协议定义在 BEP_0003
请求tracer的参数列表
Get http://tracker.gbitt.info:80/announce?compact=0&downloaded=2&info_hash=uC%9D%5D%E3C%99%9A%B3w%C6%17%C2%C6G%90%29V%E2%82&left=5037662208&peer_id=%11%22%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%16&port=1111&uploaded=1
参数 | 说明 |
---|---|
info_hash | 上述info的sha1值,作为文件的标识符 |
peer_id | 自己的id,每次下载任务的peer_id都不一致 |
IP | 自己的IP,可选 |
Port | 自己的端口,默认6881 |
uploaded | 已经上传的文件的字节数 |
downloaded | 已经下载的文件的字节数 |
left | 当前仍需要下载的文件的字节数 |
numwant | 希望tracer返回peer的个数,默认50个 |
event | started, completed, stopped, empty,可选 |
返回值:
参数 | 说明 |
---|---|
warnging message | 警告信息 |
interval | 下次请求的等待时长 |
min interval | 下次请求的最小等待时长 |
complete | 已完成整个资源下载的 peer 的数量 |
incomplete | 未完成整个资源下载的 peer 的数量 |
peers | [{peer id,IP,Port}] |
效率更优的UDP Tracker协议
UDP tracker协议(BEP_0015 UDP Tracker Protocol for BitTorrent)是一种高性能、低消耗的tracker协议,这种协议的URL一般是这样:udp://tracker:port。 数据开销的优势:基于HTTP的tracker协议,通常一个请求和响应的数据包含10个包,1206个字节。如采用UDP协议,则可以将数据传输量减少到4个包,618个字节,数据传输量约减少了50%。对于一个客户端而言,这些数据量的减少影响可能不大,但对于一个同时服务数百万客户端的tracker服务器来说,节省的带宽开销非常可观。另外一个优势是,基于UDP的协议,采用了二进制传输方式,省去了tracker解析数据的工作,也使得tracker的代码变得更为简单。 相比于TCP协议,UDP属于无连接协议,无需维护连接状态。一方面减少了服务器维护连接的开销,提升了系统性能,另一方面也带来了不可靠的问题:
- UDP包的伪造问题:由于无需进行TCP的握手连接过程,服务端收到数据包可以对来源IP进行伪造。为此,需要在UDP协议的基础上,增加建立连接的机制,服务端通过给客户端分配connection_id的机制,来避免伪造数据包来源的问题(伪造方无法接收到服务端的connection_id信息)
- 超时重传问题:由于UDP协议是无连接的不可靠协议,需要由应用方来实现数据的可靠传输。如果客户端经过15∗2n秒后,还没有接收到来自服务端的响应数据,则重新发送数据请求,n取值从0到8。
- UDP Tracker协议有如下4种类型的请求
action | 请求类型 | 定义 |
---|---|---|
0 | connect | 客户端与服务器建立连接,从服务器端获取connection_id |
1 | announce | 向tracker请求peer列表 |
2 | scrape | 获取某个种子当前的统计信息 |
3 | error | 错误消息 |
磁力链的资源定位机制-DHT
磁力链协议属于无tracker协议,定义在BEP_0005 DHT Protocol。无tracker并不是没有tracker,而是没有中心式tracker。磁力链将tracker分散存储到DHT(Distributed Hash Table,分布式哈希表),解决了集中式的tracker一旦出现宕机或封禁,将使整个网络失效的问题。每个节点负责一个小范围的路由,并负责存储一小部分数据,从而实现整个DHT网络的寻址和存储,相当于所有人一起构成了一个庞大的分布式存储数据库。 一个BT的客户端拥有2个角色:
- 作为 BT 下载的节点,实现bittorrent协议来进行上传和下载资源,我们称为peer。
- 作为DHT网络中的一员,实现DHT协议,保存一部分其他 Peer 的地址信息,作为tracker,我们称为node。
当需要进行下载的时候,先根据自己本地存的路由表找其他节点,其他节点再去找他们保存的其他节点,直到找到拥有文件的人。一传十十传百、千、万,最后通过N个人的中转,找到应该连上的人。
get_peers
此消息用于查询于指定的 info_hash
相关的节点的联系方式,功能类似 Trakcer 服务器。发起者应发送 info_hash
,接收者应返回与 info_hash
有关的节点列表,如果接收者不知道哪些节点于 info_hash
有关,则从自己的路由表中选出 k 个距离 info_hash
最近的节点并返回。
announce_peer
当一个节点于某个 info_hash
有关时(下载或上传),它应当发起此消息。发起者应发送自己的节点 ID、info_hash
和监听端口。接收者应存储接收到的联系方式,将 info_hash
和联系方式关联起来,用于响应 get_peers 消息。
局域网服务发现机制-LSD(Local Service Discovery)
在局域网内BT客户端可以通过类似于SSDP(简单服务发现协议),采用多播的方式发现局域网内拥有资源的peer。这种方法简单高效,易于实现,在 BEP_0014 中定义。LSD使用的多播地址为
- IPv4:239.192.152.143:6771 (org-local)
- IPv6:[ff15::efc0:988f]:6771 (site-local)
在加入网络后,BT客户端应当每5分钟发送一次LSD多播消息,为了避免多播风暴,每个客户端每分钟不能发送超过一个LSD报文。当一个客户端有超过5个种子时,可以选择轮流多播各个种子信息,或者一次多播多个种子信息。接收到LSD消息的客户端,从UDP报文中获取来源IP,并进一步确认该IP是否为可连接的peer。
数据下载
Peer Protocol (BEP_0003 )是当前正在下载同一资源的对等结点 (peer) 之间进行数据传输使用的协议, BT 协议的 Peer Protocol 基于 TCP 或 uTP(BEP_0029), 在使用 BT 协议下载时, 所有正在下载同一资源的结点都是对等的, 结点之间相互建立的连接也是对等的, 对等结点建立的连接的数据传输方向是双向的, 数据可以由任何一端发往另一端, 对等结点每接收到一个完整的 piece 之后, 接收方便计算该 piece 的 SHA1 哈希并与 .torrent 文件中的 info 对应的字典的 piece 对应的 Value 的相应分段做对比, 若哈希值相等, 则说明该分片传输成功, 此时这两个对等结点都拥有了资源文件关于该 piece 的数据。
一次完整的数据获取流程
握手
<pstrlen><pstr><reserved><info_hash><peer_id>
- pstrlen, 该值固定为 19 (十进制格式, 使用 4 字节大端字节序)
- pstr, 该值为 "BitTorrent protocol" (BitTorrent 协议的关键字)
- reserved, BT 协议的保留字段, 用于以后扩展用, 一般将这 8 字节全部设置为 0, 某些 BT 客户端没有正确实现协议或者使用了某种扩展而发送了不全为 0 的握手信息, 此时忽略即可
- info_hash, 与请求 BT Tracker 时发送的 info_hash 参数值相同
- peer_id, 与请求 BT Tracker 时发送的 peer_id 参数值相同
Peer数据传输协议
<length prefix><type id><payload>
- length prefix = len(type id) + len(payload))
- type id = 4, 代表 have 消息, 当完成下载并校验某分片后,则广播拥有某个分片
- type id = 6, 代表 request 消息, request 消息用于一方向另一方请求文件数据, 它的 Payload 含有 3 个字段, 分别是 index, begin 和 length. 其中 index 指示文件分片的索引 (索引从 0 开始), begin 指示 index 对应的 piece 内的字节索引 (索引从 0 开始), length 指定请求的长度, length 一般都取 2 的整数次幂, 现在所有的 BitTorrent 实现中, length 的值都取 214214, 即 16 KB, BitTorrent 协议规定结点通过随机的顺序请求下载文件 piece
- type id = 7, 代表 piece 消息, piece 消息是对 request 消息的响应, 即返回对应的文件片段, 它的 Payload 含有 3 个字段, 分别是 index, begin 和 piece, 其中 index 和 begin 字段与 request 消息中的 index 与 begin 含义相同, 而 piece 是所请求的文件片段
- type id = 8, 代表 cancel 消息, cancel 消息与 request 消息的 Payload 字段完全相同, 但作用相反, 用于取消对应的下载请求
扩展协议:Peer间传输metadata协议
在磁力链协议中,磁力链本身不包含任何文件相关的信息,如文件如何进行分片,每个分片的校验哈希等,需要通过DHT找到拥有资源的peer,再通过peer获取文件的metadata数据。Peer间传输文件的metadata数据协议,属于扩展协议的一种。
数据传输的加密/混淆
由于BT的流量对运营商的带宽消耗很大,在某些国家(或地区)互联网服务供应商限制BitTorrent流量或用户,他们认为BitTorrent流量占用过多网络资源(增加运营成本)、干扰网络正常运行,或认为或限制“非法的”文件共享。为此,可以采用混淆和加密的方法,使BT流量更难以被检测和控制。消息流加密(Message Stream Encryption,简称MSE),被很多BT客户端支持,用来规避运营商的流量检测。
MSE使用密钥交换结合torrent的infohash创建一个RC4加密密钥。密钥交换有助于最小化被动监听器的风险,而infohash有助于避免中间人攻击。选择RC4是为了速度更快。该规范允许用户选择仅加密报头或者完全加密整个连接。加密整个连接提供更强的混淆能力,但也消耗更多的CPU时间。为确保与不支持此规范的其他客户端的兼容性,用户还可选择是否仍允许未加密的传入或传出连接。支持的客户端通过节点交换(PEX)和分布式散列表(DHT)通告它们已启用MSE。
MSE的数据加密传输过程主要有如下5个步骤:
DH秘钥交换协议:MSE协议中使用的RC4加密算法为对称加密算法,即加密和解密使用的是同一个秘钥,双方使用Diffie-Hellman密钥交换协议/算法(Diffie-Hellman Key Exchange/Agreement Algorithm),简称DH算法来交换秘钥。
实际上就是一个非对称加密,协商会话密钥的过程。
NAT 穿越问题
由于IPv4地址不足,大多数的家庭网络都使用网络地址转换(NAT)技术建立了一个网关,使用内网IP来上网。采用NAT上网的技术很好地解决了IPv4地址不足的问题,但对于P2P应用来说,却导致外网的用户无法连接到内网的客户端,导致数据传输链路不畅。
NAT穿越技术允许网络应用程序使用UPnP(Universal Plug and Play)协议与NAT路由器进行交互,通过配置端口映射的方式,来实现NAT外部端口的数据包转发到应用程序使用的内部端口上。所有这一切都是自动完成的,用户无需手动映射端口或者进行其它工作。