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.
- files: 一个由字典组成的列表,每个字典表示一个文件,字典的键值有如下内容:
例子
星球大战:
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%),而不能应用于普通做种.



