BitTorrent协议

Cnic.org,开放的网络天书!

目录

简介

BitTorrent作为一种流行的P2P下载软件而被广泛使用.比起其他的P2P软件来,BitTorrent的优点有几点:

  • 下载速度特别快.这是因为BitTorrent的协议设计精妙.
  • 下载内容不会被篡改.BitTorrent使用了RSA SHA1算法来保证文件块的正确性.上传被篡改内容的伙伴会被踢下线.
  • 大量使用传统的网络工具,如HTTP等,能够方便的评价共享文件价值.

协议

Bencode编码

Bencode编码是一种简洁的表示数据的方法.它被BitTorrent协议广泛应用.它支持字符串,整数,列表和字典.

  • 字符串:
字符串被如此编码:<字符串长度>:字符串正文.这种表示法没有任何的分界符.
例子:如"8:announce"指"announce".
  • 整数:
整数被如此编码:<i>整数值<e>.可以为负数,如'i-3e'
例子:'i3e' 指 3.
  • 列表:
列表是如此被表示的:<l>Bencode Value<e>,
列表可以用来表示多个对象.
列表内容可以包括字符串,整数,字典,甚至列表本身.
例子:'l4:spam4:eggse' 指 [ "spam", eggs" ]
  • 字典:
字典是一个一对一的映射.它表示了一个主键(必须为字符串)和一个数据项(可以为任何Bencode值)的关系.字典可以用来表示一个对象的多种属性.
字典是如此被编码:<d><bencoded string><bencoded element><e>
注意:字典必须根据主键预排序.

Metainfo

BitTorrent下载时,用户必须下载一个.torrent文件.它就是所谓"Metainfo file",里面存储有关于下载内容的announce地址,长度,大小,SHA1杂凑项等内容.它由Bencode编码组成.而且字符串是用UTF-8编码的.不过在中国,常常使用GBK编码。它由如下几项组成:

  • Info:描述下载内容的信息,是一个字典.有两种可能,一种是"单文件"模式:当BitTorrent只下载一个文件的时候使用.另一种是"多文件"模式,是在下载多个内容的时候使用.两种情况下Info各有不同.
单文件模式:
  • length:整数,指文件的大小.
  • md5sum:(可选),字符串,含有32字节md5校验码.BitTorrent没有使用MD5而是使用了SHA1作为自已的签名算法.这是为其他P2P软件兼容而设置的可选内容.
  • name:字符串,这是下载文件的名字,纯粹是建议.
  • piece length:整数,是BitTorrent文件块的大小.
  • pieces:字符串,连续的存放着所有块的SHA1杂凑值,每一个文件块的杂凑值为20字节.
多文件模式:
  • files: 一个由字典组成的列表,每个字典表示一个文件,字典的键值有如下内容:
    • length:整数,指当前文件的大小.
    • md5sum:(可选),字符串,同单文件模式,指当前文件.
    • path:由字符串组成的列表,每个列表元素指一个路径名中的一个目录或文件名.比如说:"l3:abc3:abc:6abc.txte",指文件路径"abc/abc/abc.txt".
    • name:字符串,BitTorrent下载路径中最上层的目录名
    • piece length:整数,是BitTorrent文件块的大小.
    • pieces:字符串,连续的存放着所有块的SHA1杂凑值,每一个文件块的杂凑值为20字节.
    • announce:字符串,指向tracker的URL.
    • announce-list:(可选),字典,这是一个对官方协议的扩展,支持"多Tracker".
    • creation date:(可选),整数,创建日期(UNIX创世纪格式:1970-1-1日00:00UTC到当时的秒数)
    • comment:(可选),字符串,注释
    • created by:可选,字符串,创建此.torrent文件的BT下载端程序名和版本号
    • encoding:BitComet对Metafile的扩展,一般用来指出不使用utf-8而使用gbk.

例子

星球大战:
d8:announce34:http://tracker.ydy.com:86/announce10:created by13:BitComet/0.5813:creation datei1117953113e8:encoding3:GBK4:infod6:lengthi474499162e4:name51:05.262005.Star Wars Episode IV A New Hope-Rv9.rmvb10:name.utf-851:05.26.2005.Star Was Episode IV A New Hope-Rv9.rmvb12:piece lengthi262144e6:pieces36220:XXXXXXXXXXXXXXX(SHA1杂凑值)
表示了如下信息:

  Tracker地址 : http://tracker.ydy.com:86/announce
  被BitComet/0.58创建
  创建时间:1970-1-1 00:00秒后1117953113秒.即Sun Jun  5 14:31:53 2005.
  encoding是BitComet的扩展,实际上用了UTF-8就不需要GBK.
  • info: (这是单文件模式的代表)
  • 大小:474499162(452Mb)
  • 文件名:05.262005.Star Wars Episode IV A New Hope-Rv9.rmvb
  • name.utf-8:也是BitComet的扩展,指出文件名编码不是GBK而是UTF-8.实际上本来name就应用utf8进行编码.
  • 文件块大小:262144字节(256KB)
  • pieces:长度为366220的SHA1杂凑值内容,由于每一个文件块20字节SHA1杂凑值,可见文件块有36620 / 20 = 1831个

Tracker协议

Tracker是一种HTTP/HTTPS服务,为BitTorrent而设计,目标是帮助客户端得到有关下载文件的各种"动态"信息,比如下载者列表.Tracker使用传统的CGI方法提出申请,如"param1=value & param2 = val".
注意:所有不在0-9,a-z, A-Z, 和 $-_.+!*'()的字符集都要转义.比如空格要转义成"%20",其中20是空格的ascii符.详看RFC1738.

客户端根据Metafile指出的"announce"地址,对tracker提出申请:如 http://tk.greedland.net/announce?info_hash=XXXXXXXXXXXXXXXXXXXX&peer_id=AZ34343&port=6881...

下面是一个BitTorrent客户端向tracker申请的应有参数:

  • info_hash: Info键值的20字节SHA杂凑值.
  • peer_id: 20字节长的独一无二的伙伴标识,在client启动时生成. 可用BT下载客户端的程序名和随机数来生成.
  • port: 客户端监听端口.典型的端口是6881-6889.
  • uploaded: 上传总量 (从前是客户端对tracker发送"开始"event) 十进制ASCII码数字. 它表示客户端上传字节总量.这一参数并没被官方描述提到.
  • downloaded: 下载总量 (从前是客户端对tracker发送"开始"event) 十进制ASCII码数字. 它表示客户端下载字节总量.这一参数并没被官方描述提到.
  • left: 剩余下载字节, 十进制ASCII码数字.
  • compact: 指出客户端是否支持压缩模式以减少tracker通讯.
  • event: 事件有3种: 开始(started), 完成(completed), 停止(stopped)
    • started:对tracker的第一个请求必须包括开始事件.
    • stopped:必须在客户端关闭时发送此事件.
    • completed:必须在完成下载时发送此事件. 可是, 当在完成下载后重新开始任务就不可重发"completed"事件.
  • ipv6: 可选的IPV6地址
  • numwant: 可选的期望Tracker最大返回数.缺省为50个.
  • trackerid: 可选. 如果上次发布含有trackerid,这次就要重新扫送.

Tracker交将返回一个"text/plain"文档,含有Bencode编码的字典:

  • failure reason:如果有本项,说明发生了一个严重错误,将不会返回其他任何信息. 键值是人类可读的错误信息.
  • warning message: (新的) 键值是人类可读的的一般警告信息.
  • interval: 发送请求之间必须的间隔时间(秒)(必须执行)
  • min interval: 最小的发布间隔时间 (秒). 限制客户端重新发布.
  • complete: 整数, 拥有完全文件的伙伴数.
  • incomplete: 整数, 拥有不完全文件的伙伴数,也就是"水蛭".
  • peers: 一个含有字典的列表, 每一个字典含有如下内容:
    • peer id: 字符串, 伙伴的唯一名称.
    • ip: 字符串,伙伴的IPv4或IPv6地址,或是DNS名.
    • port: 整数,伙伴的端口

有一些Tracker能返回"Compact"模式的伙伴列表,如果是这种情况,peers列表就会被一个peers字符串所代替,每个伙伴占6个字节.其中前4个字节是主机IP(网络字序) , 后2个字节是端口(网络字序).


BitComet对Tracke请求的扩展:

  • localip: 发送本地IP
  • hide: 隐身模式,不允许tracker返回你的IP给别人.

对返回的扩展:

  • tracker_alias_url:返回别名tracker的列表.如是同一个主机,主机名可以省略如udp://:8080/

Tracker 'scrape' 协议

按照惯例许多Tracker支持另一种请求的形式,那就是一次查询一个或多个torrent.这就是所谓"Scrape页面".它避免了服务器在回应寻找一个Torrent时所花费的时间.

Scrape URL也是一种HTTP GET方法.用如下几步来实现Scrape:开始得到announce URL..寻找最后一个'/'字符.如果在'/'之后的文本不是"announce",说明Tracker不遵守Scrape惯例.如果是的,就用 "scrape"页面代替"announce".

 http://spam.com/announce          -> http://spam.com/scrape
 http://spam.com/x/announce        -> http://spam.com/x/scrape
 http://spam.com/announce.php      -> http://spam.com/scrape.php
 http://spam.com/a                 -> (scrape not supported)
 http://spam.com/announce?x=2%0644 -> http://spam.com/scrape?x=2%0644
 http://spam.com/announce?x=2/4    -> (scrape not supported)
 http://spam.com/x%064announce     -> (scrape not supported)

注意特别是后两个不符合announce规范.

scrape URL可以提供可选的info_hash, 这将限制Tracker报告特定的Torrent,不然它就将返回所有的torrents信息.强烈建议软件作者使用info_hash以减少服务器的带宽用量.

HTTP GET方法将返回一个"text/plain"文档:这是一个Bencodde字典,有如下键值:

  • files: 一个字典,包括一个键名/键值,指出每个torrent的相关信息.如果 info_hash 参数有效的话, 这个字典就只有一个键名和键值. 每个键名是20位info_hash值, 而每个键值都是另一个嵌套的字典,有如下键:
  • complete: 拥有完整文件的伙伴数,即种子数
    • downloaded: tracker收到的文件完成数(event=completed),即用户完成下载数
    • incomplete: 正在下载的伙伴数,即"水蛭"数
    • name: (可选) torrent的内部名称,也就是.torrent metafile中info域的name键

请注意嵌套的字典形式,这是一个例子:

d5:filesd20:....................d8:completei5e10:downloadedi50e10:incompletei10eeee

表示这个文件有5个种子,10个正在下载者,完成下载数为50次.

伙伴互联协议(TCP协议)

伙伴互联协议帮助伙伴之间交流文件块.
客户端必须维护与每个其他伙伴之间的连接.
连接之间有两种状态:

  • 窒息:是否远程伙伴窒息了本客户端.当伙伴窒息了客户端,它就提醒:没有请求会被应答,直到客户端被解除窒息.客户端不应试图在被窒息状态下请求文件块,并且应认为所有的没被回答的请求已被取消.
  • 感兴趣:是否远程伙伴对本客户端所能提供的文件块有兴趣.这表示远程伙伴将在被解除窒息后,开始请求文件块.

注意:这也将意味着客户端也需要跟踪它是否被远程用户所感兴趣,以及远程伙伴是否窒息它.真实的列表将是这样子的:

  • am_choking: 我在窒息伙伴
  • am_interested: 我被伙伴所感兴趣
  • peer_choking: 我被伙伴窒息
  • peer_interested: 我对伙伴感兴趣

客户端开始时,状态应是"被窒息"和"不被感兴趣", 也就是说:

  • am_choking = 1
  • am_interested = 0
  • peer_choking = 1
  • peer_interested = 0

两个伙伴连接时必须先握手(HandShake),

  • handshake:<pstrlen><pstr><reserved><info_hash><peer_id>
    • pstrlen: <pstr>的长度,以字节计算
    • pstr: 协议的字符串标识符("BitTorrent protocol")
    • reserved: 8个保留的字节. 所有的当前实现都使用0.
    • info_hash: metainfo info键值的SHA1杂凑值,与Tracker请求中的info_hash一样.
    • peer_id: 20字节唯一的ID, 表明不同伙伴的身位.

连接一开始,就必须先握手.info_hash保证了可以同时做种多个文件. 如果客户端收到了不存在的info_hash值,它必须将关闭连接.

  • peer id
    • Azureus使用下述编码: '-', 两个字符客户端ID, 四位ASCII码版本号, '-', 12位随机数,比如: '-AZ2060-'...
    • 使用这种形式的BT客户端:
      • 'AZ' - Azureus
      • 'BB' - BitBuddy
      • 'CT' - CTorrent
      • 'MT' - MoonlightTorrent
      • 'LT' - libtorrent
      • 'BX' - Bittorrent X
      • 'TS' - Torrentstorm
      • 'TN' - TorrentDotNET
      • 'SS' - SwarmScope
      • 'XT' - XanTorrent
      • 'BS' - BTSlave
      • 'ZT' - ZipTorrent
      • 'AR' - Arctic
      • 'SB' - Swiftbit
      • 'MP' - MooPolice
    • BitComet PeerID 定义:
 if ( memcmp(peerid, "exbc", 4) == 0 )
 {
    version = "BitComet ";
    version += (unsigned char)buf[4]+(unsigned char)'0';
    version += '.';
    version += ((unsigned char)buf[5])/10+(unsigned char)'0'; 
    version += ((unsigned char)buf[5])%10+(unsigned char)'0'; 
 }

消息

消息处理是BitTorrent的核心内容.所有消息都遵守如下格式:<消息长度><消息ID><负载>.消息长度是一个4位网络字序值.消息ID是一个10进制数字 ,负载根据消息决定.

  • keep-alive: <len=0000>
保活消息是消息长度为0的消息, 它没有消息ID,也没有负载. 如果一方发出保活消息,超出一定时间后对方不回应,则可以关闭此连接. 所以保活消息是用来存活连接有没有断开的. 一个保活消息每2分钟发送一次.
  • 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>
发送者声称自己拥有某个文件块, 负载为文件块的索引号.
  • bitfield: <len=0001+X><id=5><bitfield>
位字段消息用来表示发送者拥有的文件块.每一个被置位的位表示对应索引上一个完整的文件块.第一个字节 的最高位指向第0个文件块.

位字段消息只能在握手完成后马上发出, 不能居于其他消息之后.如果一个客户没有文件块,就不需要发送.

  • request: <len=0013><id=6><index><begin><length>
请求消息有三个参数:
  • index: 整数, 指出文件块索引值
  • begin: 整数, 指出文件块内部的偏移值
  • length: 整数, 指出请求项的长度, 通常为 2^14 (16384) 字节.
请求项的长度一般小于文件块的大小(2^18左右).
  • piece: <len=0009+X><id=7><index><begin><block>
文件块消息有三个参数:
  • index: 整数, 指出文件块索引值
  • begin: 整数, 指出文件块内部的偏移值
  • block: 数据块, 是文件块的一个子集
  • cancel: <len=0013><id=8><index><begin><length>
取消消息,用来阻塞请求.参数同文件块消息.经常被"将军"算法使用.

算法

一个好的BitTorrent客户端,必然有一个好的算法.而一个好的算法可以从如下方面考虑:

  • 多种Tracker支持, 支持多种Tracker的客户端 能较更多的得到伙伴列表
    • 传统的HTTP tracker具有连接开销大,容易 阻塞等特点. 而新型的UDP tracker节省了开销
    • Metafile支持多tracker支持, 提供了备用tracker服务器地址
    • Tracker Scrape模式
    • 优化的窒息算法. 窒息算法是BitTorrent客户端决定向谁发送数据而得到最大回报的算法.一个好的窒息算法能达到事半功倍.
    • 高效的磁盘缓存算法:由于BitTorrent协议建议客户端随机下载文件块,所以在某些系统上对硬盘造成一些伤害.有一个高效的磁盘缓存算法管理读写,会有效提高性能,降低损耗.
    • "将军"算法:当下载将要完成时,BitTorrent有变慢的趋势.为了避免这种情况,客户端发送请求,要求它没得到的文件块,并对每个已得到的文件块发送取消.
    • 超级做种:超级做种模式下,种子把自已伪装成一个没有任何数据的下载者.它会向每个连接上的伙伴声称自已得到了一个文件块:一个从没发送过的文件块,或一个几乎没发送过的文件块.这样,它控制了客户端下载某个文件块.
      超级做种只能对做首种才有效(快500%),而不能应用于普通做种.

参见

个人工具
天书
中文维客年会
网络天书
pagerank 5/10