BitTorrent下载协议的译文

转载自:http://www.winu.cn/space-14160-do-blog-id-6532.html

 

简单地说BT协议就是多点多段多线程同时下载。

 

从速度上来说,网络速度<磁盘IO速度<内存速度<CPU速度,因此解决了网络传输的问题就等于解决了整个数据交换速度的问题。

 

以常见的宽带网络为例,无论是ADSL还是小区局域网,基本上以100k起,这里是指字节传输速度,即每秒种传输100k字节。如何发挥这100k的速度就需要一个比较好的算法。

 

下载算法分这几个阶段:

 

1.单线程单点下载。特点:使用单线程下载一个文件,下载源也是一个。大部分下载工具支持这种方式,实际上目前的即时通讯工具传文件也是这种方式,早期的P2P软件也是这样传输的。

 

2.多线程单点下载。特点:使用多线程分段下载一个文件,但下载源还是一个。以NetAnt和FlashGet为代表的下载方式。这种下载方式将一个文件分成多段,从同一个服务器上下载某一个文件。这种方式很快就会让服务器吃不消,一度被下载站视为洪水猛兽。

 

3.多线程多点下载方式。特点:使用多线程分段下载一个文件,下载源也是多个,即从不同结点上下载同一个文件。FlashGet后期的版本已经有这个特性了,即镜像功能,同一个文件从不同的ftp服务器上下载,这样可以达到最大下载速度。但这还是局限于C/S模式,即必须由服务器来承担所有的下载功能。

 

而目前最流行的P2P方式则改变这种观念,将服务器功能内置到每个客户端上去,每个客户端软件既是下载客户端又是提供下载的服务器。

 

当一个P2P软件要下载某个文件的时候,通过索引服务器或DHT(无服务器广播式连接)得到这个文件存放的结点,然后逐个进行连接,分段下载。同时这个P2P软件也变身为下载服务器,供别的结点来下载这个文件。

 

当享受别人的服务的同时,提供给别人相同的服务,这就是P2P的精神(当然,如果你更伟大一点愿意将空闲的带宽无偿提供给别人也是非常好的)。

 

再回到BT,来解剖一下这个非常出名的协议。

 

先做个简单的名词约定(使用英文,没想到如何翻译):

 

1.peer和client。peer是指一个下载中任何一个BT客户端参与者。client就是一个正在运行的BT客户端软件。实际上client很容易理解,就是一个软件。而peer是相对虚的,是指一个连接的另一头,可能一台机器就一个peer,也可能是无数个,每个peer是独立的,尽管可能两个peer是在同一台机器上的。在软件中往往一个peer被设计为一个线程。

 

2.piece和block。piece是指在元数据描述文件(就是.torrent文件)中切分好的一片,一般是通过SHA1的校验。block是指一段实时数据,client向peer发出数据请求就是以block为单位。block通常比较小,多个block将组成一个piece。block也是校验数据。

 

先介绍一下bt的元数据描述文件(.torrent文件)的结构,里面存放了所有的下载信息。

 

这个文件是标准的UTF-8编码(非常好,很受国人欢迎),以下是几种数据形式的格式:

 

字节字符串:<string长度 encoded in base ten
ASCII>
:<string数据> ,例: 4:spam =
"spam"。

 

整型:i<integer encoded in base ten
ASCII>
e,例:i-3e=-3,i3e=3,i0e=0。

 

列表:l<bencoded values>e ,例:
l4:spam4:eggse = ["spam", "eggs"]

 

字典:d<bencoded string><bencoded
element>
e,例:
d3:cow3:moo4:spam4:eggse = { "cow" => "moo", "spam"
=> "eggs" } 。d4:spaml1:a1:bee = { "spam"
=> ["a", "b"] }。

 

搞过python的应该了解。整个描述文件为字典型,以下为字段列表。

 

info:分两种模式,单文件和多文件。字典型。

 

单文件的情况下需要的字段:

 


  •  

  •  
    length:文件长度,单位字节。整型

     

  •  
    md5sum:文件内容的md5校验(可选)不过一般都有,为了安全嘛,也可防一下攻击和病毒。

     

  •  
    name:名称,实际上可以是任意的,但是建议用文件名。字符串

     

  •  
    piece length:每个piece的字节数,也就是按多少字节来分片的。整型

     

  •  
    pieces:把每一片的所有20个字节的sha1哈希值连在一起。字符串

 

多文件下需要的字段:

 


  •  

  •  
    files:字典列表。每个字典的字段。

     

    •    

    •    
      length:文件长度,单位字节。整型

         

    •    
      md5sum:文件内容的md5校验(可选)不过一般都有。

         

    •    
      path:文件路径,例:"dir1/dir2/file.ext"写成:" l 4:dir1 4 :dir2 8 :file.ext e"

     

  •  

     
    name:名称,实际上可以是任意的,但是建议用目录名。字符串

     

  •  

     

     
    piece
      length
    :每个piece的字节数,也就是按多少字节来分片的。整型

     

  •  

     

     

     
    pieces:把每一片的所有20个字节的sha1哈希值连在一起。字符串

 

announce:通知服务器的URL,即Tracker的地址,字符串。

 

announce-list:通知服务器URL的列表。(可选)一般都会有。列表型。

 

creation date:创建时间。标准Unix格式,秒数从 1970年1月1日 00:00:00
格林威治标准时间开始算,整型。

 

comment:注释。(可选)。

 

created by: 创建者,创建程序的名称和版本。

 

备注:

 


  •  
  • piece length
      一般是2的整数倍,处理起来方便。太小和太大都不好,通常是在512kb,除非是特殊情况,比如文件本身就非常小,或者非常大。piece的个数=ceil(
      total length / piece size )。除了最后一个piece外,其他都一样。
     
  • 每个piece都有SHA1哈希校验。pieces的长度必须是20的整数倍。

 

Tracker服务器协议。追踪服务器,可以称为通知服务器(因为客户端要进行通知),索引服务器(所有的动态客户端、片等信息都在存储在这里,相当于一个网格索引)。基于http/https,限于GET方法。实际上客户端需要从Tracker里得到其他结点的下载信息,如果没有Tracker无法进行任何下载活动。目前的DHT广播互联,也就是客户端自己充当Tracker服务器,并且广播出去,来解决Tracker不足或被封的问题。

 

Tracker的URL来自announceannounce
list
两个字段。提交的格式很简单,使用GET方法,拼成URL,使用"参数=值"的方式,并且用&连接。当然根据HTTP的规范,如果非法字符要编码一下。

 

以下是Tracker所允许的参数名:

 

info_hash:元数据中info这个字段所包含的值进行SHA1哈希后的值。

 

peer_id:
20个字节的长度,标识client的唯一ID,是client启动的时候生成的。可以是任何值。最好生成一个UUID。

 

port: client的端口。并不限制,只是需要整型,并且在操作系统的范围里。

 

uploaded: 上传字节数。

 

downloaded: 下载字节数。

 

left: 还剩多少字节没下完。

 

compact:
client接受一个紧凑的返回。peer列表将被换成一个peer字符串,每个peer六字节,首4个字节是主机(序号),后两个字节是端口(序号)。

 

event: 事件有三个选项 started, completed,
stopped。如果为空则表示正常间隔。

 

 


  •  
  • started:
      开始,在tracker服务器中第一个命令必须是started开始。
     
  • stopped: 如果client正常关闭必须通过一下tracker。
     
  • completed:
      如果下载完成就发送这个。但是如果开始的时候就是100%就不用发这个了。可能这个事件是让Tracker来增加完成下载的统计。

 

 

 

ip:(可选)client的ip地址。ipv6或普通ip格式。

 

numwant:(可选)需要peer的个数。如果不提供则缺省50个。允许为0.

 

key: (可选)允许提供一个key。

 

trackerid:(可选)上一个通告连接返回如果包含一个tracker id就设在这里,也可作为校验用。

 

tracker返回,格式是 "text/plain"纯文本。字典类型。以下是字段:

 


  •  
  • failure reason:
      失败原因,存在这个就说明有错误,这时候应该没有其他字段。
     
  • warning message: (new) 有点象 failure
      reason, 但是不是致命错误,还可以继续下去。
     
  • interval:
      间隔时间,以秒为单位。client每次查询的间隔必须等于这个间隔时间,这是强制性的。
     
  • min interval:
      最小间隔时间。限制client的最小间隔时间。否则很可能被拒绝。
     
  • tracker id:
      字符串,返回一个id,用于下一次通告连接,如果没有返回这个,则使用再上一次的值,以此类推。
     
  • complete: 整个文件的peer数。整型。例如:作种者的数量。
     
  • incomplete: 没种子的peer数。整型。
     

  •  

    peers: 所有的peer列表。每个peer描述的字段。


     

    •    
    • peer id: peer的ID,字符串
         
    • ip: peer's IP IPv6 or IPv4 or DNS
          name (字符串)
         
    • port: peer's 端口 (整型)

 

再小结一下Tracker。

 

Tracker服务器一般返回50个peer,当然如果没有这么多就可能会少一点。Tracker服务器可以搞得智能一点,反正怎么算是它自己的事。

 

要注意,如果强行索取更多的种子,或者是强行减少通告间隔时间,可能对服务器来说不是件好事,服务器也许会制止的。

 

P2P客户端软件要注意,如果为了追求速度而影响服务器或者违背了共享的宗旨,很可能就上了黑名单,这在emule世界是非常多的,前不久就有Vagaa事件。

 

连接数过多并不一定速度会提高,要根据实际情况来调整,这需要多次试验。

 

还有一个scrape刮擦协议,是查询种子的服务。

 

例: (announce URL -> scrape URL)

  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)

 

这个也是用HTTP GET,返回也是"text/plain",字典型,以下是字段:

 

files: 文件列表,每个torrent都包括了一对键/值,如果出现了 info_hash
并且有效,那么包括了单个键/值。每个键是 20个字节的哈希值。值包括了以下的字段:

 


  •  
  • complete: 完成的peer数量 (integer)
     
  • downloaded: 已经下载的client数。(实际上就是"event=complete", i.e. a client
      finished downloading the torrent)
     
  • incomplete: 不完全的数量, 可以叫作 "leechers"(依附者?应该是指光下不上传的人。) (integer)
     
  • name: (optional) torrent名称,可以是.torrent里"name"字段。

 

例子:

 

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

 

其中的 .................... :是20字节的哈希包括 5 个种子, 10个依附者和 50 个完成下载的.

 

Peer wire protocol (TCP) Peer连接协议

 

总览

 

peer协议就是用来交换torrent描述文件里定义的piece。

 

注:原来这里用了术语piece,但是实际上piece用在描述文件里的片,而block才是实际传输中的块。

 

一个client必须维护状态每个远程peer的每个连接的状态信息,状态如下:

 


  •  
  • choked:
      拒绝,可以理解为阻塞。远程peer是否拒绝(choked)了自己client。当一个peer拒绝(choked)了client,没有任何请求被回应,除非是解除了拒绝状态(unchoked)。client不去尝试发送块(block)请求,它将认为远程peer丢弃所有没响应的请求。

     
  • interested: 可以接受,有兴趣的(这个翻译比较奇怪)。允许client回应块(block)请求。

 

注:这也暗示保持跟踪远程peer状态。状态类似以下列表:

 


  •  
  • am_choking: client拒绝peer
     
  • am_interested: client可以接受peer
     
  • peer_choking: peer拒绝client
     
  • peer_interested: peer可以接受client

 

Client的连接开始状态是"choked" and "not interested".:

 


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

 

如果client可以接受一个peer,并且peer没有拒绝,client就下载一个块(block) 。如果client没有拒绝peer并且peer也可以接受client,client就上传一个块(block)
.

 

维护好状态是非常重要的。要注意时效性。

 

数据类型

 

除非特殊说明,整型是4字节,在握手之后,所有消息都包括了长度前缀,即首先发送消息长度。

 

消息流

 

一开始进行初始化握手,之后每个消息前有一个长度前缀,长度是一个整型。

 

握手

 

握手消息是必须的,而且必须是第一个消息。

 


  •  
  • handshake: {pstrlen}{pstr}{reserved}{info_hash}{peer_id}
     

    •    
    • pstrlen:  pstr的长度, 单字节
         
    • pstr: 协议的字符串标志
         
    • reserved: 保存8字节。一般为0. 可以扩展用。
         
    • info_hash: 描述文件中20字节的SHA1哈希。这个值跟tracker的参数是一样的。
         
    • peer_id: client的20字节唯一标志。还是跟tracker里是一样。

 

目前1.0协议中, pstrlen=19, and pstr="BitTorrent protocol".

 

连接的发起人必须立刻发起握手协议,然后等待回应。回应必须立刻返回。tracker的NAT检查功能不放在握手的peer_id中发送。

 

如果一个client收到一个握手,但是不能提供服务,必须立刻关闭这个连接。

 

如果连接的发起人收到一个握手,但是其中的peer_id跟自己的匹配不上。那么发起人就关闭这个连接。注:发起人可能从tracker收到peer信息,其中的peer_id是peer注册的,因此必须要匹配。

 

peer_id

 

包括了client信息和版本信息peer_id,有两种形式: Azureus-style and Shadow's-style.

 

Azureus-style 格式: '-', 两个字符代表client id,4个数字代表了版本, '-', 后面就是随机数字.

 

例: '-AZ2060-'...

 

已知客户端的格式:

 

 

Shadow's style 格式:一个字母作为客户端标志, 三个数字作为版本, '----',后面就是随机数字

 

例: 'S587----'...

 

已知客户端的格式:

 

 

Bram's client 现在用这种格式:'M3-4-2--'.

 

 BitComet
有一些不同. peer_id有四个字符'exbc', 接下来两个字节x和y, 最后是随机字符. 版本号 x和y.
0.59版本后改为Azureus-style. 所以x总是0.

 

大多种客户端使用随机数字。

 

附一个比较简单的中文协议:http://www.51cto.com/html/2005/1230/15923.htm

 

消息(Messages)

 

所有的消息格式都是这样的:<内容长度前缀><消息ID><有效内容>. 内容长度前缀是4字节,大尾格式.
消息ID是单个十进制字符. 有效内容根据消息种类不同而不同(费话).

 

注:内容长度是整型,原文four byte big-endian values,意思是4个字节,大尾格式,高位字节放前面,低位字节放后面。

 

例:一个数1234h(十六进制),如果是big-endian,则存放格式:1234h,从左开始是第0个字节。

 

如果是little-endian格式,则存放:4321h,从左开始是第0个字节。

 

big-endian通常用在Mac机中。little-endian通常在intel的pc中。

 

keep-alive: <len=0000>

 

keep-alive(保持连接) 消息内容=0,
所以内容长度设为0.这个消息既然没有ID,也没有内容。如果peer没有收到任何消息,peer可能关闭连接,所以这样发一个0000过去保持连接(说明你还活着)。通常这个消息间隔2分钟一次。

 

choke :<len=0001><id=0>

 

choke(拒绝或阻塞) 定长为1,没有内容。

 

unchoke :<len=0001><id=1>

 

unchoke(解除拒绝和阻塞) 消息是定长1,没有内容。

 

interested :<len=0001><id=2>

 

interested(感兴趣的) 消息为定长1,无内容。

 

not interested :<len=0001><id=3>

 

not interested(不感兴趣) 消息为定长1,无内容。

 

前面这几个消息都是设状态。 

 

have :<len=0005><id=4><piece index>

 

have(有片) 消息定长. 内容是一个piece索引序号,从0开始,表示已经正确下载并且经过哈希校验的片。

 

重要提示:这是一个严格定义。因为peer极有可能不去下载他们已经有的piece,一个peer可能选择不通告它有了哪个piece。至少可能会导致这个消息减少50%,转化为协议总消耗减少25-35%。

 

一个恶意的peer可能会选择宣传它已经有了从没下载的peer,也就是宣称自己有数据,但是实际上是假的。这是一个坏主意。 

 

bitfield :<len=0001+X><id=5><bitfield>

 

bitfield(片的状态) message
这个消息仅仅在握手完成后立刻发送,必须在其他消息发送之前。这是一个可选消息,如果client没有piece,则不用发。

 

这个消息长度可变最后bitfield的内容是一个X.
内容表示了已经被正确下载的片。第一个字节的最高位相当于piece序号0,依次下去,如果有效的piece则设1,如无效则设0. 如果结尾有多余的位则设0.

 

如果bitfield长度错误则被认为是一个错误。如果client收到一个不正确的长度或结尾多余位有任何被设为1,client可以关闭这个连接。

 

request
:<len=0013><id=6><index><begin><length>

 


  request(请求block) 消息定长,用来请求一个块block。内容包括以下信息
 

 

  •    
  • index(索引): integer整型,用来标识piece序号,从0开始。
       
  • begin(开始): integer整型,这个piece的偏移量,即从这个piece哪一个字节开始下载,偏移量从0算起。
       
  • length(长度): integer整型,请求字节数。通常设为2^14 (16384) 字节.
        可以用更小的值,但是通常不需要设更小,除非整个piece不是16384的整数倍,那么最后一block可能就是要更小。

 

读者将注意到block通常小于一个piece(piece通常>=2^18),如果请求一个大于16384字节的块,client可能关闭连接。

 

piece
:<len=0009+X><id=7><index><begin><block>

 


  piece(片) 消息是可变长的,X是block的长度。内容包括以下信息
 

 

  •    
  • index: integer整型,就是piece的序号,从0算起。
       
  • begin: integer整型,piece中的偏移量,从0算起,按字节算。
       
  • block: 块的数据,是piece中的一部分,根据请求发回的数据。

 

cancel
:<len=0013><id=8><index><begin><length>

 

cancel(取消)
定长消息。用来取消块请求。内容与request(请求)消息是一样的。如果发过去的请求不需要了,就用cancel照样取消。

 

port :<len=0003><id=9><listen-port>

 

port(监听端口) 这个消息用于一些新版本实现了DHT(Distributed Hash
Table分布式哈希表)的tracker。这个监听端口是peer的DHT结点进行监听。这个peer将支持本地路由表(如果支持DHT tracker)。

 

DHT:  Distributed Hash
Table分布式哈希表,目前bt的DHT技术都是基于Kademlia(很遗憾,发现wiki上的条目被封了有英文的http://en.wikipedia.org/wiki/Kademlia),是一种典型的无中央服务器模式,也是一种客户端充当服务器和路由的模式。这是一种将公网真正连接起来的技术,它将每个下载结点同时作为tracker服务器和路由,不再需要中央服务器进行索引和信息传递,所有安装客户端的软件平等自由地充当整个P2P网络中的结点。

 

 http://www.167bt.com/bbs/viewthread.php?tid=47120&fpage=1 这是引自bt精灵的一段话,可作参考。

 

Algorithms

 

Super Seeding(超级种子)

 

(这不是原来协议的一部分)

 

超级种子是一种新的种子规则,用于帮助torrent发起人限制带宽。减少上传数据。

 

当做种的client进入超级种子模式,它不是作为一个标准种子,但是装作为一个普通的无数据client。当client连接上来,它将告诉他们它收到一个piece,一个从未发送的piece或者如果所有piece已经被发送(这是非常罕见的),这导致client仅仅下载这个piece。

 

当client已经下载完这个piece后,种子不通过它其他的piece,直到它已经看到这个piece发给其它至少一个client。不是这样,这个client不允许存取其他的piece,因此不浪费种子的带宽。

 

这个原理是必须保证每个piece都有一个以上的client被下载,并且逐个piece处理,保证做种者不会占用太多带宽。

 

这个方法的结果是更多的种子传播,因为当做种初期,必须要求有很多client来下载,大概效率上会超过标准种子的150-200%。

 

超级种子模式不是推荐的。因为违背了自由传播的思想,但是可以提高传播效率,因此仅建议用于初始种子服务器。

 

可以换个名字,叫"初始做种模式"或"释放者模式"。

 

Piece downloading strategy(piece下载策略)

 

client是可以随机选择下载的piece,没有顺序要求。

 

一个更好的策略是"珍稀优先"下载。client可以保留每个peer的初始bitfield(piece状态),然后根据have消息来更新这个状态。这样,client可以根据最后的频率来判断和决定下载哪个piece。

 

End Game(结束)

 

当一个下载几乎要完成的时候,最后少量block下得比较慢。要提升这个速度,client要发送所有需要的块请求给所有的peer。保证这个适当可怕的低效率,当一个block到达的时候,client就发送cancel给其他所有人。

 

结束算法还可以有很多种选择,需要多实践。

 

bt的协议先粗略地介绍到这儿,接下去要介绍技术方面的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值