返回列表 发帖

BitTorrent协议规范

BitTorrent 是一种分发文件的协议。它通过URL来识别内容,并且可以无缝的和web进行交互。它基于HTTP协议,它的优势是:如果有多个下载者并发的下载同一个文件,那么,每个下载者也同时为其它下载者上传文件,这样,文件源可以支持大量的用户进行下载,而只带来适当的负载的增长。(译注:因为大量的负载被均衡到整个系统中,所以提供源文件的机器的负载只有少量增长) 一个BT文件分布系统由下列实体组成: 一个普通的web服务器 一个静态的“元信息”文件 一个跟踪(tracker)服务器 终端用户的web浏览器 终端下载者 理想的情况是多个终端用户在下载同一个文件。 要提供文件共享,那么一台主机需要执行以下步骤: Ø运行一个 tracker服务器(或者,已经有一个tracker服务器在运行了也可以) Ø运行一个web服务器,例如apache,或者已经有一个web服务器在运行了。 Ø在web服务器上,将文件扩展名.torrent 和MIME类型 application/x-bittorrent关联起来(或者已经关联了) Ø根据 tracker服务器的 URL 和要共享的文件来创建一个“元信息”文件(.torrent)。 Ø将“元信息”文件发布到web服务器上 Ø在某个web页面上,添加一个到“元信息”文件的链接。 Ø运行一个已经拥有完整文件的下载者(被成为’origin’,或者’seed’,种子) 要开始下载文件,那么终端用户执行以下步骤: Ø安装 BT(或者已经安装) Ø访问提供 .torrent 文件的web服务器 Ø点击到 .torrent 文件的链接(译注:这时候,bt会弹出一个对话框) Ø选择要把下载的文件保存到哪里?或者是一次断点续传 Ø等待下载的完成。 Ø结束bt程序的运行(如果不主动结束,那么bt会一直为其它人提供文件上传) 各个部分之间的连通性如下: 网站负责提供一个静态的文件,而把BT辅助程序(客户端)放在客户端机器上。 Trackers从所有下载者处接收信息,并返回给它们一个随机的peers的列表。这种交互是通过HTTP或HTTPS协议来完成的。 下载者周期性的向tracker登记,使得tracker能了解它们的进度;下载者之间通过直接连接进行数据的上传和下载。这种连接使用的是 BitTorrent 对等协议,它基于TCP。 Origin只负责上传,从不下载,因为它已经拥有了完整的文件。Origin是必须的。 元文件和tracker的响应都采用的是一种简单、有效、可扩展的格式,被称为bencoding,它可以包含字符串和整数。由于对不需要的字典关键字可以忽略,所以这种格式具有可扩展性,其它选项以后可以方便的加进来。 Bencoding格式如下: 对于字符串,首先是一个字符串的长度,然后是冒号,后面跟着实际的字符串,例如:4:spam,就是“ spam” 整数编码如下,以 ‘i’ 开始,然后10进制的整数值,最后以’e’结尾。例如,i3e表示3,I-3e表示-3。整数没有大小限制。I-0e是无效的。除了 i0e外,所以以0起始的整数都无效。I0e当然表示0。 列表编码如下,以’l’开始,接下来是列表值的编码(也采用bencoded编码),最后以’e’结束。例如:l4:spam4:eggse 表示 [‘spam’, ‘eggs’]。 字典编码如下,以’d’开始,接下来是可选的keys和它对应的值,最户以’e’结束。例如:d3:cow3:moo4:spam4:eggse,表示{‘cow’:’moo’,’spam’:’eggs’},而d4:spaml1:al:bee 表示 {‘spam’:[‘a’,’b’]}。键值必须是字符串,而且已经排序(并非是按照字母顺序排序,而是根据原始的字符串进行排序)。 元文件是采用bencoded编码的字典,包括以下关键字: announce tracker的服务器 info 它实际上是一个字典,包括以下关键字: Name: 一个字符串,在保存文件的时候,作为一个建议值。仅仅是个建议而已,你可以用别的名字保存文件。 Piece length: 为了更好的传输,文件被分隔成等长的片断,除了最后一个片断以外,这个值就是片断的大小。片断大小几乎一直都是2的幂,最常用的是 256k(BT的前一个版本3.2,用的是1M作为默认大小) Pieces: 一个长度为20的整数倍的字符串。它将再被分隔为20字节长的字符串,每个子串都是相应片断的hash值。 此外,还有一个length或files的关键字,这两个关键字只能出现一个。如果是length,那么表示要下载的仅仅是单个文件,如果是files那么要下载的是一个目录中的多个文件。 如果是单个文件,那么length是该文件的长度。 为了能支持其它关键字,对于多个文件的情况,也把它当作一个文件来看,也就是按照文件出现的顺序,把每个文件的信息连接起来,形成一个字符串。每个文件的信息实际上也是一个字典,包括以下关键字: Length:文件长度 Path:子目录名称的列表,列表最后一项是文件的实际名称。(不允许出现列表为空的情况)。 Name:在单文件情况下,name是文件的名称,而在多文件情况下,name是目录的名称。 Tracker查询。Trakcer通过HTTP的GET命令的参数来接收信息,而响应给对方(也就是下载者)的是经过bencoded编码的消息。注意,尽管当前的tracker的实现需要一个web服务器,它实际上可以运行的更轻便一些,例如,作为apache的一个模块。 Tracker GET requests have the following keys: 发送给Tracker的GET请求,包含以下关键字: Info_hash: 元文件中info部分的sha hash,20字节长。这个字符创几乎肯定需要被转义(译注:在URL中,有些字符不能出现,必须通过unicode进行编码) Peer_id: 下载者的id,一个20字节长的字符串。每个下载者在开始一次新的下载之前,需要随机创建这个id。这个字符串通常也需要被转义。 Ip: 一个可选的参数,给出了peer的ip地址(或者dns名称?)。通常用在origin身上,如果它和tracker在同一个机器上。 Port: peer所监听的端口。下载者通常在在 6881 端口上监听,如果该端口被占用,那么会一直尝试到 6889,如果都被占用,那么就放弃监听。 Uploaded: 已经上载的数据大小,十进制表示。 Downloaded: 已经下载的数据大小,十进制表示 Left: 该peer还有多少数据没有下载完,十进制表示。注意,这个值不能根据文件长度和已下载数据大小计算出来,因为很可能是断点续传,如果因为检查文件完整性失败而必须重新下载的时候,这也提供了一个机会。 Event: 一个可选的关键字,值是started、compted或者stopped之一(也可以为空,不做处理)。如果不出现该关键字,。在一次下载刚开始的时候,该值被设置为started,在下载完成之后,设置为completed。如果下载者停止了下载,那么该值设置为stopped。 Tracker的响应是用bencoded编码的字典。如果tracker的响应中有一个关键字failure reason,那么它对应的是一个字符串,用来解释查询失败的原因,其它关键字都不再需要了。否则,它必须有两个关键字:Interval:下载者在两次发送请求之间的时间间隔。Peers:一个字典的列表,每个字典包括以下关键字:Peer id,Ip,Port,分别对应peer所选择的id、ip地址或者dns名称、端口号。注意,如果某些事件发生,或者需要更多的peers,那么下载者可能不定期的发送请求, (downloader 通过 HTTP 的GET 命令来向 tracker 发送查询请求,tracker 响应一个peers 的列表) 如果你想对元信息文件或者tracker查询进行扩展,那么需要同Bram Cohen协调,以确保所有的扩展都是兼容的。 BT对等协议基于TCP,它很有效率,并不需要设置任何socket选项。(译注:BT对等协议指的是peer与peer之间交换信息的协议) 对等的两个连接是对称的,消息在两个方向上同样的传递,数据也可以在任何一个方向上流动。 一旦某个peer下载完了一个片断,并且也检查了它的完整性,那么它就向它所有的peers宣布它拥有了这个片断。 连接的任何一端都包含两比特的状态信息:是否choked,是否感兴趣。Choking是通知对方,没有数据可以发送,除非unchoking发生。Choking的原因以及技术后文解释。 一旦一端状态变为interested,而另一端变为非choking,那么数据传输就开始了。(也就是说,一个peer,如果想从它的某个peer那里得到数据,那么,它首先必须将它两之间的连接设置为 interested,其实就是发一个消息过去,而另一个peer,要检查它是否应该给这个家伙发送数据,如果它对这个家伙是 unchoke,那么就可以给它发数据,否则还是不能给它数据)Interested状态必须一直被设置――任何时候。要用点技巧才能比较好的实现这个目的,但它使得下载者能够立刻知道哪些peers将开始下载。 对等协议由一个握手开始,后面是循环的消息流,每个消息的前面,都有一个数字来表示消息的长度。握手的过程首先是先发送19,然后发送“BitTorrent protocol”。19就是“BitTorrent protocol”的长度。 后续的所有的整数,都采用big-endian 来编码为4个字节 在协议名称之后,是8个保留的字节,这些字节当前都设置为0。 接下来对元文件中的 info 信息,通过 sha1 计算后得到的 hash值,20个字节长。接收消息方,也会对 info 进行一个 hash 运算,如果这两个结果不一样,那么说明对方要的文件,并不是自己所要提供的,所以切断连接。 接下来是20个字节的 peer id。 这就是握手过程 接下来就是以消息长度开始的消息流,这是可选的。长度为0 的消息,用于保持连接的活动状态,被忽略。通常每隔2分钟发送一个这样的消息。 其它类型的消息,都有一个字节长的消息类型,可能的值如下: ‘choke’, ‘unchoe’, ‘interested’, not interested’类型的消息不再含有其它数据了。 ‘bitfield’永远也仅仅是第一个被发送的消息。它的数据实际是一个位图,如果downloader已经发送了某个片断,那么对应的位置1,否则置0。Downloaders如果一个片断也没有,可以忽略这个消息。(通过这个消息,能知道什么了?) ‘have’类型的消息,后面的数据是一个简单的数字,它是下载者刚刚下载完并检查过完整性的片断的索引。(由此,可以看到,peer通过这种消息,很快就相互了解了谁都有什么片断) ‘request’类型的消息,后面包含索引、开始位置和长度)长度是2的幂。当前的实现都用的是215 ,而关闭连接的时候,请求一个超过2 17的长度。(这种类型的消息,就是当一个peer希望另一个peer给它提供片断的时候,发出的请求) ‘cancel’类型的消息,它的数据和’request’消息一样。它们通常只在下载趋向完成的时候发送,也就是在‘结束模式“阶段发送。在一次下载接近完成的时候,最后的几个片断需要很长时间才能下载完。为了确保最后几个片断尽快下载完,它向所有的peers发送下载请求。为了保证这不带来可怕的低效,一旦某个片断下载完成,它就其它peers发送’cancel’消息。(意思就是说,我不要这个片断了,你要是准备好了,也不用给我发了,可以想象,如果对方还是把数据发送过来了,那么这边必须忽略这些重复的数据)。 ‘piece’类型的消息,后面保护索引号、开始位置和实际的数据。注意,这种类型的消息和 ‘request’消息之间有潜在的联系(译注:因为通常有了request消息之后,才会响应‘piece’消息)。如果choke和unchoke消息发送的过于迅速,或者,传输速度变的很慢,那么可能会读到一些并不是所期望的片断。( 也就是说,有时候读到了一些片断,但这些片断并不是所想要的) BitTorrent协议详解 BitTrrent(简称BT,比特洪流)是一个文件分发协议,它通过URL识别内容并且和网络无缝结合。它在HTTP平台上的优势在于,同时下在一个文件的下载者在下载的同时不断互相上传数据,使文件源可以在很有限的负载增加的情况下支持大量下载者同时下载。 一个BT式文件分发需要以下实体: ·一个普通网络服务器 ·一个静态元信息文件 ·一个BT Tracker ·一个“原始”下载者 ·网络终端浏览者 ·网络终端下载者 这里假设理想情况下一个文件有多个下载者。 架设一个BT服务器步骤如下: 1.开始运行Tracker(已运行的跳过这一步); 2.开始运行普通网络服务器端程序,如Apache,已运行的跳过这一步; 3.在网络服务器上将.torrent文件关联到Mimetype类型application/x-bittorrent(已关联的跳过这一步); 4.用要发布的完整文件和Tracker的URL创建一个元信息文件(.torrent文件); 5.将元信息文件放置在网络服务器上; 6.在网页上发布元信息文件(.torrent文件)链接; 7.原始下载者提供完整的文件(原本)。 通过BT下载步骤如下: 1.安装BT客户端程序(已安装的跳过这一步); 2.上网; 3.点击一个链到.torrent文件的链接; 4.选择本地存储路径,选定需要下载的文件(对有选择下载功能的BT客户端用户); 5.等待下载完成; 6.用户退出下载(之前下载者不停止上传)。 连接状况如下: ·网站正常提供静态文件连接,并且启动客户端上的BT程序; ·Tracker即时接收所有下载者信息,并且给每个下载者一份随机的peer列表。通过HTTP或HTTPS协议实现; ·下载者每隔一段时间连一次Tracher,告知自己的进度,并和那些已经直接连接上的peer进行数据的上传下载。这些连接遵循BitTorrent peer协议,通过TCP协议进行通信。 ·原始下载者只上传不下载,他拥有整个文件,所以很必要向网络中传输完文件的所有部分。在一些人气很旺的下载中,原始下载者经常可以在较短的时间内退出上传,由其它已经下载到整个文件的下载者继续提供上传。 元信息文件和Tracker的回应信息都以一种简单高效可扩展的格式(Bencoding,B编码)传送。B编码过的信息就是以包含字符串和整型数据的字典和列表的嵌套(像在Python中一样),可扩展性是指可以通过减少字典忽略的关键值来添加新的特性。 B编码规则如下: ·字符串表示为十进制数的既定字符串长度加冒号再跟原字符串。 如4:spam就相当于';spam';。 ·整型数据表示成前面加';i';后面加';e';中间是十进制数,如i3e就相当于3,i-3e就是-3。整型数据没有长度限制。i-0e无效,所有以';i0';开头的除了代表0的i0e,其它都无效。 ·列表编码为一个';l';开头后面跟它所包含的项目(已经编码过)最后加一个';e';,比如l4:spam4:eggse就等于[';spam';, ';eggs';]。 ·字典编码为一个';d';开头后面跟一个交替关键值(key)及其对应值的列表最后加一个';e';。 如:d3:cow3:moo4:spam4:eggse相当于{';cow';: ';moo';, ';spam';: ';eggs';} d4:spaml1:a1:bee相当于{';spam';: [';a';, ';b';]} 关键值必须是处理过的字符串(用原始字符串编码的,而且不是数字字母混合编码的)。 元信息文件就是B编码的有以下关键值的字典: announce(声明) Tracker的URL。 info(信息) 此关键值对应一个字典包含以下描述的关键值: 关键值name对应一个字符串,代表默认的下载文件或存成目录的名字。它是纯粹建议性的。 关键值piece length(块长)对应文件分割成的块的字节数。出于传输需要,文件被分割成大小相等的块,除了最后一块通常会小一些。块长一般来说是2的权值,大部分设块长为256K(2的18次幂)。 关键值pieces(块)对应一个字符串,此字符串长度是20的倍数。它可以再分成每20字节一段的多个字符串,分别对应块在索引中的SHA1校验码(hash)。 还有关键值length(长度)和files(文件),它们不能同时出现也不能都不出现。当length出现说明这个元信息文件只是单文件下载,否则说明是多文件的目录结构下载。 单文件情况下,length对应文件长度的字节数。 多文件情况被看作是把许多单文件按文件列表中的顺序连成一个大文件下载,而关键值files就对应文件列表,是一个字典的列表,其中每个字典又包含以下关键值: length(长度) 文件长度的字节数。 path(路径) 一个包含字符串的列表,字符串就是子目录名,最后一项的字符串是文件名。 (一个长度为零的length表单是错误的。) 在单文件情况下,关键值name是文件名;多文件情况下,它就成了目录名。 Tracker质询是双向的。Tracker通过HTTP GET参数获得信息,然后返回一个B编码后的信息。尽管Tracker需要在服务器端执行,但它运行流畅像Apache的一个模块。 Tracker的GET请求有如下关键值: info_hash 20字节长的SHA1验证码,来自B编码过的元信息文件中的info值下,是元信息文件的一个支链。这个值是自动转换的。 peer_id 一个20字节长的字符串,是每个用户开始下载时随机生成的ID。这个值也是是自动转换的。 ip 一个可选择的参数给出peer所在的IP(或DNS主机名),一般是和Tracker同机器的原始下载者得到后以便散发文件。 port 监听端口,官方默认的是从6881端口开始试,如果端口被占用则依次向后推一个端口找空闲端口,到6889端口为止。 uploaded 目前总上传量,编码为十进制ASCII码。 downloaded 目前总下载量,编码为十进制ASCII码。 left 未下载的字节数,编码为十进制ASCII码。这个数不是通过文件长度和已下载数算出来的,因为文件可能在被续传,还有一些已经下载的数据不能通过完整性检查必须重新下载。 event 这是个选择性的关键值,选项有started,completed或stopped(或empty,等同于没有运行)。如果没有运行,这个声明会定期间隔一定时间发出。开始下载时发出started值,完成下载时发出completed。当文件完整后再开始,没有completed发出,下载者中止下载时发出stopped。 Tracker的回应也是B编码字典。如果Tracker回应中有关键值failure reason(失败原因),就会对应一个人可以读懂的字符串信息解释质询失败的原因,不需要其它关键值。否则,回应必须有两个关键值:interval(间隔)对应下载者定期发出请求的间隔秒数;peers,peer自选ID,IP地址或DNS主机名的字符串和端口号。记住peers不会完全按照计划的间隔发送请求,假如他们发生一个事件或者想要更多的peers。 如果你想对元信息文件或者Tracker质询进行扩展,请与Bram Cohen进行协调,确保所有扩展都兼容。 BitTorrent peer协议通过TCP协议进行操作。它不用调节任何socket选项就可以流畅运行。 peer之间的连接是对称的。两个方向送出的信息要协调一致,数据可以流入任一方。 peer协议指一个peer从零开始下载,每得到元信息文件索引中所描述的一个块且验证码一致,就向所有peer声明已得到此块。 连接的两个终端有2个状态指标,被阻塞与否,被关注与否,被阻塞(choking)是表明在恢复通畅之前数据不再发出的通知。发生阻塞的原因和技术问题稍后会提到。 数据传输发生在一方关注对方且对方没有阻塞的情况下。关注状态必须一致保持-如果一个没阻塞的peer没有别人需要的数据,别人对他就会失去关注,转而关注那些正在阻塞的peer。完全执行这种条件需要非常慎重,但这样的确可以让下载者知道哪些peer在阻塞消失后可以马上开始下载。 连接会逐渐断开不感兴趣和阻塞的peer。 当数据传输时,下载者要备好多份请求排成队列,以获得较高的TCP传输效率(这叫“管运请求”)。另一方面,不能被写入TCP缓冲区的请求要被立即排入内存,而不是一个应用程序级的网络缓冲,一旦阻塞出现,这些请求全部丢弃。 peer连线协议包括一次握手跟着不断的大小一致且确定的信息流。握手的开始是字符十九(十进制),跟着是字符串';BitTorrentprotocol';。开头的字符是长度固定的,希望其它新协议也能这样以便区分。 此后所有送入协议的整数都编码为4字节大中止端。 在现有的应用中头部数据之后是8个全部预留为0的字节,若果你想通过改变这8个预留字节以扩展协议,请与Bram Cohen协调以保证所有扩展兼容。 然后是来自元信息文件中B编码的info值中长20字节的SHA1验证码(和info_hash向Tracker声明的值相同,但这里是原始值那里是引用)。如果双方的值不同,连接断开。一个例外是下载者想只用一个端口进行多个连接下载,它们会先从接入连接得到一个验证码,然后和列表里面的对照,有相同的就答复。 验证码之后是20字节的peer id,它包含在Tracker回应的peer列表中,在向Tracker的请求中被报告。如果接受方peer id不符合发送方希望,连接断开。 握手完毕。之后是长度固定的交互信息流。零长度信息用来保持连接,被忽略。这种信息一般2分钟发出一次,但是在等待数据期间很容易超时。 所有非保持连接用信息开头的字节给出类型,可能值如下: ·0-阻塞 ·1-通畅 ·2-关注 ·3-不关注 ·4-有 ·5-比特组 ·6-请求 ·7-块 ·8-取消 “阻塞”、“通畅”、“关注”和“不关注”类信息没有荷载。 “比特组”类信息仅作为首信息发出。它负载一个比特组,下载者有索引的设为1,其它为0。开始下载时没有任何数据的下载者跳过“比特组”信息。首字节高位到低位对应索引0-7,依次类推,第二字节对应8-15,等等。尾部的剩余的比特位设为0。 “已有”类信息负载一个数,即刚下载并核对完验证码的索引数。 “请求”类信息包括包含一个索引,开始和长度。后两者是字节偏移。长度一般是2的权值除非被文件尾截断。现行一般是2的15次幂,并且关闭大于2的17次幂长度的连接。 “取消”类信息负载和“请求”类信息有一样的负载。它通常在下载接近完成即“最后阶段”发出。当下载快要完成时,剩下几个块有都从同一个线程下载的趋向,这样会很慢。为了确保剩余块下载迅速,一旦还没有决定剩余块的下载请求向谁发出,先向所有他正在从对方下载数据的连接者发送要求所有剩余块的请求。为避免低效,每当一个块开始下载就向其他peer发出取消信息。 “块”类信息包含一个索引,开始和块。记住它和“请求”类信息是相关的。当传输速度很慢或“阻塞”“通畅”类信息高频率交替发出或两者同时发生,可能会载到一个不需要的块。 下载者下载块的顺序是随机的,这样适当防止下载者与其他Peers仅有相同的块子集或超集。 阻塞的发生有很多原因。TCP协议的信息拥挤控制在即时向多连接发送信息的过程中表现极差。同时,阻塞的存在使下载者们能够用以牙还牙式的算法来确保稳定的下载速率。 下面描述的阻塞算法是目前基础的配置。重要的是所有新算法不光要在包含全部扩展算法的网络中运行良好,也要在主要包含这个基础算法的网络中运行良好。 一个优秀的阻塞算法有许多标准。它必须封锁一定同时上传的数量以获得良好的TCP表现,还要避免频繁的堵塞和通畅交替,即所谓“纤维化”。它应该用数据交换报答给自己数据的peer。最后,它还应该偶尔尝试一下与未使用过的peer端连接,找出比现有连接好的连接,这叫做尝试性疏通。 现行的阻塞算法避免纤维化的手段是每10秒转换被阻塞的名单。疏通4个自己关注且能从他们身上得到最高下载速率的peer,进行上传和数据交换。有较高上传速率但是不被关注下载者的peer被疏通,一旦这些peer开始被关注,那些上传率最低的peer的就被阻塞。如果下载者有了完整的文件,他用自己的上传率而不是下载率来决定疏通谁的连接。 在尝试性疏通中,任何一次中都有一个peer被疏通不管他的上传率如何(如果被关注,他会成为4个提供下载的peer之一)。被尝试性疏通的这种peer每30秒轮换一次。为了给它们一个上传整一个块的机会,新连接会以轮换中尝试性疏通次数的3倍开始连接。 通常BT客户端每几分钟就要向tracker发送一次请求.对于一些比较大的BT站点,其tracker的压力是可想而知的.降低tracker的压力首先考虑到的当然是采用更低网络开销的udp协议.于是Bittorrent udp-tracker protocol应运而生. 这个协议很简单. 下面是实现它的封装类: // UDPTrackerClient.h: interface for the CUDPTrackerClient class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_UDPTRACKERCLIENT_H__69B6ACC8_8193_4680_81D8_925B1550E92C__INCLUDED_) #define AFX_UDPTRACKERCLIENT_H__69B6ACC8_8193_4680_81D8_925B1550E92C__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include #pragma comment(lib, "ws2_32.lib") #ifndef _DISABLEWARNING4786_4355 #define _DISABLEWARNING4786_4355 #pragma warning( disable : 4786 ) #pragma warning( disable : 4355 ) #endif #ifndef _ENABLEUSESTL #define _ENABLEUSESTL #include #include #include #include #include #include using namespace std; #endif class CPeerHostInfo { public: DWORD IP;//节点IP WORD Port;//节点端口 }; class CUDPTrackerClient { public: CUDPTrackerClient(); virtual ~CUDPTrackerClient(); void CancelSocketOperate(); BOOL Connect(const char * szServer,WORD wPort = 0); DWORD Announcing(BYTE* pInfoHash,BYTE * pPeerID, __int64 idownloaded,__int64 ileft,__int64 iuploaded, int ievent, DWORD dwIP,WORD wPort); BOOL Disconnect(); public: SOCKET m_socket; DWORD m_dwIP; WORD m_wPort; __int64 m_iConnection_id; DWORD m_dwConnectTick; string m_strError; //如果请求失败,此变量保存错误信息 DWORD m_dwDonePeers; //种子数 DWORD m_dwNumPeers; //当前下载者个数 DWORD m_dwInterval; //查询间隔时间 list m_listPeers; }; #endif // !defined(AFX_UDPTRACKERCLIENT_H__69B6ACC8_8193_4680_81D8_925B1550E92C__INCLUDED_) // UDPTrackerClient.cpp: implementation of the CUDPTrackerClient class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "UDPTrackerClient.h" #include "DataStream.h" #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// #define RECVBUFSIZE 2048 CUDPTrackerClient::CUDPTrackerClient() { m_socket = INVALID_SOCKET; m_iConnection_id = 0; m_dwConnectTick = 0; m_dwIP = 0; m_wPort = 0; m_dwDonePeers = 0; //种子数 m_dwNumPeers = 0; //当前下载者个数 m_dwInterval = 0; //查询间隔时间 } CUDPTrackerClient::~CUDPTrackerClient() { Disconnect(); } void CUDPTrackerClient::CancelSocketOperate() { if(m_socket != INVALID_SOCKET) { LINGER lingerStruct; // If we';re supposed to abort the connection, set the linger value // on the socket to 0. lingerStruct.l_onoff = 1; lingerStruct.l_linger = 0; setsockopt(m_socket, SOL_SOCKET, SO_LINGER, (char *)&lingerStruct, sizeof(lingerStruct) ); } } BOOL CUDPTrackerClient::Disconnect() { m_iConnection_id = 0; m_dwDonePeers = 0; //种子数 m_dwNumPeers = 0; //当前下载者个数 m_dwInterval = 0; //查询间隔时间 if ( m_socket != INVALID_SOCKET ) { m_dwIP = 0; m_wPort = 0; // Now close the socket handle. This will do an abortive or // graceful close, as requested. shutdown(m_socket,SD_BOTH); closesocket(m_socket); m_socket = INVALID_SOCKET; return TRUE; } return FALSE; } //szServer连接的主机,可以是下列形式的字符串: //easeso.com:1000 //easeso.com //如果wPort不为0,则szServer不应该包含端口信息 BOOL CUDPTrackerClient::Connect(const char * szServer,WORD wPort) { m_strError = ""; BOOL bRes = FALSE; if ( m_socket == INVALID_SOCKET ) { //用UDP初始化套接字 BOOL optval = TRUE; m_socket =socket(AF_INET,SOCK_DGRAM,0); if(m_socket == INVALID_SOCKET) return FALSE; //设置超时时间 int TimeOut=10000; int err = setsockopt (m_socket, SOL_SOCKET,SO_RCVTIMEO,(CHAR *) &TimeOut,sizeof (TimeOut)); } if(m_dwIP == 0) { CString strServer = szServer; CString strHost; if(wPort == 0) { int iNext = strServer.Find(';:';); if(iNext>0) { strHost = strServer.Mid(0,iNext); CString strPort = strServer.Mid(iNext+1); m_wPort = (WORD)atoi(strPort); } else strHost = strServer; } else { strHost = strServer; m_wPort = wPort; } if(m_wPort == 0) m_wPort = 80; //Check if address is an IP or a Domain Name int a = strHost[0]; if (a > 47 && a < 58) m_dwIP = inet_addr(strHost); else { struct hostent *pHost; pHost = gethostbyname(strHost); if(pHost != NULL) m_dwIP = *((ULONG*)pHost->h_addr); else m_dwIP = 0; } } if((GetTickCount()-m_dwConnectTick)>30000) { m_dwConnectTick = 0; m_iConnection_id = 0; } if(m_socket != INVALID_SOCKET && m_dwIP && m_wPort && m_iConnection_id ==0) { DWORD dwTransaction_id = GetTickCount(); SOCKADDR_IN from; int fromlength=sizeof(SOCKADDR); char buf[RECVBUFSIZE]; from.sin_family=AF_INET; from.sin_addr.s_addr=m_dwIP; from.sin_port=htons(m_wPort); CDataStream sendstream(buf,2047); sendstream.clear(); __int64 iCID = 0x41727101980; sendstream.writeint64(CNetworkByteOrder::convert(iCID)); sendstream.writedword(CNetworkByteOrder::convert((int)0)); sendstream.writedword(dwTransaction_id); int iRes = 0; int iTimes = 6; while(iTimes>0&&m_dwIP) { sendto(m_socket,sendstream.getbuffer(),sendstream.size(),0,(struct sockaddr FAR *)&from,sizeof(from)); iRes = recvfrom(m_socket,buf,RECVBUFSIZE-1,0,(struct sockaddr FAR *)&from,(int FAR *)&fromlength); if(iRes >=0) break; iTimes--; } if(iRes>=16) { CDataStream recvstream(buf,RECVBUFSIZE-1); DWORD dwAction = (DWORD)CNetworkByteOrder::convert((int)recvstream.readdword()); DWORD dwTIDResp= recvstream.readdword(); if(dwTIDResp == dwTransaction_id) { if(dwAction == 0) { m_iConnection_id = recvstream.readint64(); //BitComet将回复0x16字节数据,最后6字节是服务器查看到的本地IP和UDP端口 } else if(dwAction == 3)//得到一个错误信息包 { buf[iRes]=0; m_strError = recvstream.readstring(); } } } } if(m_iConnection_id) bRes = TRUE; return bRes; } //提交请求 //pInfoHash 20字节的数据缓冲区指针 //pPeerID 20字节的数据缓冲区指针 //ievent参数值: //none = 0 //completed = 1 //started = 2 //stopped = 3 DWORD CUDPTrackerClient::Announcing(BYTE* pInfoHash,BYTE * pPeerID, __int64 idownloaded,__int64 ileft,__int64 iuploaded, int ievent, DWORD dwIP,WORD wPort) { m_listPeers.clear(); m_dwNumPeers = 0; m_dwDonePeers = 0; m_strError = ""; DWORD dwReturnCode = 0; if(m_iConnection_id && m_socket != INVALID_SOCKET && m_dwIP & m_wPort) { DWORD dwTransaction_id = GetTickCount(); //srand(dwTransaction_id); //DWORD dwKey = rand(); DWORD dwKey = 0x3753; SOCKADDR_IN from; int fromlength=sizeof(SOCKADDR); char buf[RECVBUFSIZE]; from.sin_family=AF_INET; from.sin_addr.s_addr=m_dwIP; from.sin_port=htons(m_wPort); CDataStream sendstream(buf,RECVBUFSIZE-1); sendstream.clear(); sendstream.writeint64(m_iConnection_id); sendstream.writedword(CNetworkByteOrder::convert((int)1)); sendstream.writedword(dwTransaction_id); sendstream.writedata(pInfoHash,20); sendstream.writedata(pPeerID,20); sendstream.writeint64(CNetworkByteOrder::convert(idownloaded)); sendstream.writeint64(CNetworkByteOrder::convert(ileft)); sendstream.writeint64(CNetworkByteOrder::convert(iuploaded)); sendstream.writedword(CNetworkByteOrder::convert(ievent)); sendstream.writedword(dwIP); sendstream.writedword(CNetworkByteOrder::convert((int)dwKey)); sendstream.writedword(CNetworkByteOrder::convert((int)200)); sendstream.writedword(CNetworkByteOrder::convert(wPort)); int iRes = 0; int iTimes = 2; while(iTimes>0&&m_dwIP) { sendto(m_socket,sendstream.getbuffer(),sendstream.size(),0,(struct sockaddr FAR *)&from,sizeof(from)); iRes = recvfrom(m_socket,buf,RECVBUFSIZE-1,0,(struct sockaddr FAR *)&from,(int FAR *)&fromlength); if(iRes >=0) break; iTimes--; } if(iRes>=20) { CDataStream recvstream(buf,RECVBUFSIZE-1); DWORD dwAction = (DWORD)CNetworkByteOrder::convert((int)recvstream.readdword()); DWORD dwTIDResp= recvstream.readdword(); if(dwTIDResp == dwTransaction_id) { if(dwAction == 1) { m_dwInterval = (DWORD)CNetworkByteOrder::convert((int)recvstream.readdword()); m_dwNumPeers = (DWORD)CNetworkByteOrder::convert((int)recvstream.readdword()); m_dwDonePeers = (DWORD)CNetworkByteOrder::convert((int)recvstream.readdword()); CPeerHostInfo hi; for(int iCurPos = 20;iCurPos+6<=iRes;iCurPos+=6) { hi.IP= recvstream.readdword(); hi.Port = (WORD)CNetworkByteOrder::convert((unsigned short)recvstream.readword()); m_listPeers.push_back(hi); } if(m_dwNumPeers>m_listPeers.size()) { iRes = 0; iTimes = 6; while(iTimes>0&&m_dwIP) { iRes = recvfrom(m_socket,buf,RECVBUFSIZE-1,0,(struct sockaddr FAR *)&from,(int FAR *)&fromlength); if(iRes >=0) break; iTimes--; } if(iRes>=6) { for(iCurPos = 0;iCurPos+6<=iRes;iCurPos+=6) { hi.IP= recvstream.readdword(); hi.Port = (DWORD)CNetworkByteOrder::convert((int)recvstream.readword()); m_listPeers.push_back(hi); } } } m_dwNumPeers = m_listPeers.size(); dwReturnCode = 200; } else if(dwAction == 3)//得到一个错误信息包 { buf[iRes]=0; m_strError = recvstream.readstring(); dwReturnCode = 400; } } } } //每次都要求重新连接 m_iConnection_id = 0; return dwReturnCode; } // DataStream.h: interface for the CDataStream class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_DATASTREAM_H__D90A2534_EA73_4BEA_8B7E_87E59A3D1D26__INCLUDED_) #define AFX_DATASTREAM_H__D90A2534_EA73_4BEA_8B7E_87E59A3D1D26__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include //数据流操作函数 class CDataStream { public : CDataStream(char * szBuf,int isize) { m_isize = isize; buffer = szBuf; current = buffer; } ~CDataStream() { } void clear() { current = buffer; current[0]=0; } //此函数不动态增加内存,一次打印的数据长度不应该超过缓冲区的三分之一,否则可能导致失败 bool printf(const char * format,...) { if(current) { if(current - buffer > (m_isize*2)/3) return false; va_list argPtr ; va_start( argPtr, format ) ; int count = vsprintf( current, format, argPtr ) ; va_end( argPtr ); current += count ; return true; } return false; } //此函数拷贝字符串 bool strcpy(const char * szStr) { if(current&&szStr) { int ilen = lstrlen(szStr); if((m_isize-(current - buffer)) < (ilen +2)) return false; memcpy(current,szStr,ilen+1); current += ilen; return true; } return false; } char * getcurrentpos() { return current; } void move(int ilen)//当前指针向后移动ilen { current += ilen; } void reset() { current = buffer; } BYTE readbyte() { current ++; return *(current-1); } void writebyte(BYTE btValue) { *current = btValue; current ++; } WORD readword() { current +=2; return *((WORD*)(current-2)); } void writeword(WORD wValue) { *((WORD*)current) = wValue; current +=2; } DWORD readdword() { current +=4; return *((DWORD*)(current-4)); } void writedword(DWORD dwValue) { *((DWORD*)current) = dwValue; current +=4; } __int64 readint64() { current +=8; return *((__int64*)(current-8)); } void writeint64(__int64 iValue) { *((__int64*)current) = iValue; current +=8; } BYTE * readdata(DWORD dwLen) { current +=dwLen; return (BYTE*)(current-dwLen); } void writedata(BYTE * pData,DWORD dwLen) { memcpy(current,pData,dwLen); current +=dwLen; } char * readstring() { char * szRes = current; int ilen = lstrlen(current); current +=(ilen+1); return szRes; } int size() { return (int)(current-buffer); } const char * getbuffer(){return buffer;} private : char* buffer; char* current; int m_isize; }; class CNetworkByteOrder { public: static unsigned short int convert(unsigned short int iValue) { unsigned short int iData; ((BYTE*)&iData)[0] = ((BYTE*)&iValue)[1]; ((BYTE*)&iData)[1] = ((BYTE*)&iValue)[0]; return iData; } static int convert(int iValue) { int iData; ((BYTE*)&iData)[0] = ((BYTE*)&iValue)[3]; ((BYTE*)&iData)[1] = ((BYTE*)&iValue)[2]; ((BYTE*)&iData)[2] = ((BYTE*)&iValue)[1]; ((BYTE*)&iData)[3] = ((BYTE*)&iValue)[0]; return iData; } static __int64 convert(__int64 iValue) { __int64 iData; ((BYTE*)&iData)[0] = ((BYTE*)&iValue)[7]; ((BYTE*)&iData)[1] = ((BYTE*)&iValue)[6]; ((BYTE*)&iData)[2] = ((BYTE*)&iValue)[5]; ((BYTE*)&iData)[3] = ((BYTE*)&iValue)[4]; ((BYTE*)&iData)[4] = ((BYTE*)&iValue)[3]; ((BYTE*)&iData)[5] = ((BYTE*)&iValue)[2]; ((BYTE*)&iData)[6] = ((BYTE*)&iValue)[1]; ((BYTE*)&iData)[7] = ((BYTE*)&iValue)[0]; return iData; } }; #endif // !defined(AFX_DATASTREAM_H__D90A2534_EA73_4BEA_8B7E_87E59A3D1D26__INCLUDED_) BitTorrent 文件发布系统采用针锋相对(tit_for_tat)的方法来达到帕累托有效,与当前已知的协作技术相比,它具有更高的活力。本文将解释BitTorrent 的用途,以及是怎样用经济学的方法来达到这个目标的。 1、BitTorrent 用来做什么? 当通过HTTP协议来下载一个文件的时候,所有的上载开销都在主机上。而使用 BitTorrent,当多个人同时下载同一个文件的时候,他们之间也相互为对方提供文件的部分片断的下载。这样,就把上载的开销分摊到每个下载者那里,也就可以在理论上支持无限多个下载者来下载同一个文件。 研究人员以前也在寻找一种达到这种效果的可实用的技术[3]。这种技术原来并没有在大的范围内运用过,因为逻辑和的问题非常棘手。如果仅仅计算哪些 peers 拥有文件的哪些片断以及这些片断应该被发送给谁,那么很难只产生比较小的系统开销。Peers之间的连接很少会超过几个小时,通常是几分钟而已。最后,有一个普遍的问题,就是公平性。 我们将解释BitTorrent 是如何很好的解决这些问题的。 1.1、BitTorrent接口 BitTorrent 的接口可能是最简单的。用户点击希望下载的文件的超级链接,然后会弹出一个标准的“保存到”对话框。此后,出现一个下载进度的窗口,在这个窗口中,除了显示下载速率外,还显示一个上载速率。BT在使用上非常简单,使得BT能广泛的被运用。 1.2、部署 决定采用BitTorrent的原因是因为有一些文件需要发布。而下载者使用 BitTorrent,是因为这是他们获取所需要的文件的唯一途径。下载者经常一完成下载,就停止为别人上载,虽然说,在BT 客户端完成下载之后,继续为别人提供一段时间的上载是一种礼貌的行为。标准的实现是让客户端一直保持上载,除非窗口被关闭。 在一个典型的部署中,未完成的下载者 一台主机负责提供原始的文件,下载者通过BT来下载这个文件。下载者在下载的同时,为其它人提供上载,最后,离开这个系统。 2、技术框架 2.1发布内容 为了部署 BT,首先将一个扩展名为 .torrent 的文件放在一个普通的web服务器上。.torrent文件包含了要共享的文件的信息,包括文件名、大小、文件的散列信息和一个指向tracker的url。Tracker负责帮助下载者能够获取其它下载者的信息。Tracker和下载者之间使用一种很简单的基于HTTP的协议进行交互,下载者告诉tracker自己要下载的文件、自己使用的端口以及类似的信息,tracker告诉下载者其它下载同样文件的下载者的联系信息。下载者利用这些信息相互之间建立连接。一个被成为“种子”的下载者,必须首先被启动,它知道完整的文件信息。对tracker和web服务器的带宽需求很低,而种子必须至少发送原始文件的一份完整拷贝。 译注: P2P的核心思想就是没有服务器的概念,任何一个下载者既是client,又是server。 下载者从别人那里取文件的时候,称为下载,而为别人提供文件的时候,称为上载(传)。 为了完成一次部署,至少需要一个 tracker和一个seed。所谓tracker,是一个服务器,负责帮助peers之间相互建立连接。而seed,通常是第一个向tracker注册,然后它就开始进入循环,等待为别人提供文件,也就是说,第一个seed只负责上传文件。一旦有一个peer向tracker注册后,就可以取得seed的信息,从而与seed建立连接。从seed处读取文件。由于原始的文件,只有seed拥有,所有说,seed至少要上传原始文件的一份完整拷贝。如果又有一个peer加入进来,那么它可以同时和seed和前一个peer建立连接,然后从这两者处获取文件。 2.2对等发布 所有和文件下载相关的逻辑问题,通过 peers之间的交互来解决。一些关于下载和上传的速率的信息被发送给tracker,tracker搜集这些信息用于统计。Tracker的职责被严格限定为“帮助peers相互发现对方”。 尽管tracker是peers之间相互发现的唯一途径,也是peers之间相互协作的唯一地点,标准的tracker算法返回一个随机的 peers的列表。随机图具有非常强大的特性,许多的peer选择算法最终产生了一个幂律图,幂律图能以少量的搅拌来获得分片。注意,peers之间的连接都是双向传输的。 为了跟踪每个peers都拥有什么,BT将文件切割为固定大小的片(典型的大小是256k)。每个下载者必须通知其它peers,它拥有哪些片。为了验证文件的完整性,对每个片断都通过SHA1算法计算出它的hash信息,并保存在torrent文件中。Peers只有在检查了片断的完整性之后,才会通知其它peers它拥有这个片断。删除代码是一种被建议使用的帮助文件分布的技术,但是这种更简单的方法(既分片)也是可用的。 Peers不断的从它能连接到的peers那里下载文件片断。当然,它不能从没有跟它建立连接的peers那里下载任何东西。即使是建立了连接的peers,有的也并不包含它想要的片断,或者还不允许它去下载。关于不允许其它peers从它那里下载文件片断的策略,被称为 阻塞choking,后文将进行讨论。其它关于文件分布的方法通常都要用到麻烦的树结构,而且树叶的上载能力并没有被利用起来。简单的让 peers 宣布它拥有什么会导致不到 10 % 的带宽开支,却可以可靠的使用所有的上载能力。 2.3流水作业 构架在TCP之上的应用层协议,例如BT,很重要的一点是应该同时发送多个请求,以避免在两个片断发送之间的延迟,因为那样会严重影响传输速率。为了达到这种目的,BT将每个片断又进一步分为子片断,每个子片断的大小一般是16k,同时,它一直保持几个请求(通常是5个)被流水的同时发送。流水作业所选择的data(应该是指的同时发送的请求数目,也就是5个request)的依据是能使得大多数连接变得饱和。 译注:也就是说,每次发送5个请求,然后过一段时间,又发送5个请求。流水作业在HTTP 协议1.1版本中被广泛运用。 2.4片断选择 选择一个好的顺序来下载片断,对提高性能非常重要。一个差的片断选择算法可能导致所有的片断都处于下载中,或者另一种情况,没有任何片断被上载给其它 peers。 2.4.1严格的优先级 片断选择的第一个策略是:一旦请求了某个片断的子片断,那么该片断剩下的子片断优先被请求。这样,可以尽可能快的获得一个完整的片断 2.4.2最少的优先 对一个下载者来说,在选择下一个被下载的片断时,通常选择的是它的peers们所拥有的最少的那个片断,也就是所谓的“最少优先”。这种技术,确保了每个下载者都拥有它的peers们最希望得到的那些片断,从而一旦有需要,上载就可以开始。这也确保了那些越普通的片断越放在最后下载,从而减少了这样一种可能性,即某个peer当前正提供上载,而随后却没有任何的被别人感兴趣的片断了。 译注: 也就说说,每个peer都优先选择整个系统中最少的那些片断去下载,而那些在系统中相对较多的片断,放在后面下载,这样,整个系统就趋向于一种更优的状态。如果不用这种算法,大家都去下载最多的那些片断,那么这些片断就会在系统中分布的越来越多,而那些在系统中相对较少的片断仍然很少,最后,某些 peer 就不再拥有其它 peer 感兴趣的片断了,那么系统的参与者越来越少,整个系统的性能就下降。 在BT系统中,充分考虑了经济学的概念,处处从整个系统的性能出发,参与者越多,系统越优化。 信息理论显示除非种子上传了文件的所有片断,否则没有任何下载者可以完成所有文件的下载。如果在一个部署中,只有一个种子,而且种子的上载能力比它的大多数下载者都要差,那么,不同的下载者从种子那里下载不同的片断,性能就会变得比较好,因为,重复的下载浪费了种子获取更多信息的机会。“最少优先”使得下载者只从种子处下载新的片断(也就是整个系统中其它peer都没有的片断),因为,下载者能够看到其它peers那里已经有了种子已经上传的片断。 在某些部署中,原始的种子由于某些原因最终关闭,只好由剩下的这些下载者们来负责上传。这样显然会带来一个风险:某些片断任何一个下载者都不拥有。“最少优先”也很好的处理了这种情况。通过尽快的复制最少的片断,减少了这种由于当前的peers停止上载后带来的风险。 2.4.3随机的第一个片断 “最少优先”的一个例外是在下载刚开始的时候。此时,下载者没有任何片断可供上传,所以,需要尽快的获取一个完整的片断。而最少的片断,通常只有某一个peer拥有,所以,它可能比多个peers都拥有的那些片断下载的要慢。因此,第一个片断是随机选择的,直到第一个片断下载完成,才切换到“最少优先”的策略。 2.4.4最后阶段模式 有时候,从一个速率很慢的peer那里请求一个片断。在下载的中间阶段,这不是什么问题,但是却可能潜在的延迟下载的完成。为了防止这种情况,在最后阶段,peer向它的所有的peers们都发送某片断的子片断的请求,一旦某些子片断到了,那么就会向其它peer发送cancel 消息,取消对这些子片断的请求,以避免带宽的浪费。实际上,用这种方法并没有浪费多少带宽,而文件的结束部分也一直下载的非常快。 3 阻塞(choking)算法 BT并不集中分配资源。每个peer自己有责任来尽可能的提高它的下载速率。Peers从它可以连接的peers处下载文件,并根据对方提供的下载速率给予同等的上传回报(你敬我一尺,我敬你一丈)。对于合作者,提供上传服务,对于不合作的,就阻塞对方。所以说,阻塞是一种临时的拒绝上传策略,虽然上传停止了,但是下载仍然继续。在阻塞停止的时候,连接并不需要重新建立。 阻塞算法并不属于BT对等协议(指peers 之间交互的协议)的技术部分,但是对提高性能是必要的。一个好的阻塞算法应该利用所有可用的资源,为所有下载者提供一致可靠的下载速率,并适当惩罚那些只下载而不上传的peers。 3.1帕累托有效 帕累托有效是指资源配置已达到这样一种境地,即任何重新改变资源配置的方式,都不可能使一部分人在没有其他人受损的情况下受益。这一资源配置的状态,被称为“帕累托最优”(Pareto optimum)状态,或称为“帕累托有效”(Pareto efficient) 在计算机领域,寻求帕累托有效是一种本地优化算法 BitTorrent的阻塞算法,用一种针锋相对的方式来试图达到帕累托最优。(原文不太好翻译,我简化了)。Peers对那些向他提供上传服务的peers给予同样的回报,目的是希望在任何时候都有若干个连接正在进行着双向传输。 3.2 BitTorrent的阻塞算法 从技术层面上说,BT的每个peer一直与固定数量的其它 peers 保持疏通(通常是4个),所以问题就变成了哪些peers应该保持疏通?这种方法使得TCP的拥塞控制性能能够可靠的饱和上传容量。(也就是说,尽量让整个系统的上传能力达到最大)。 严格的根据当前的下载速率来决定哪些peers应该保持疏通。令人惊讶的是,计算当前下载速率是个大难题。当前的实现实质上是一个每隔20秒的轮询。而原来的算法是对一个长时间的网络传输进行总计,但这种方法很差劲,因为由于资源可用或者不可用,带宽会变化的很快。 为了避免因为频繁的阻塞和疏通 peers造成的资源浪费,BT每隔10秒计算一次哪个peer需要被阻塞,然后将这种状态保持到下一个10秒。10秒已经足够使得TCP来调整它的传输性能到最大。 3.3、optimistic unchoking 如果只是简单的为提供最好的下载速率的peers们提供上载,那么就没有办法来发现那些空闲的连接是否比当前正使用的连接更好。为了解决这个问题,在任何时候,每个peer都拥有一个称为“optimistic unchoking”的连接,这个连接总是保持疏通状态,而不管它的下载速率是怎样。每隔30秒,重新计算一次哪个连接应该是“optimistic unchoking”。30秒足以让上载能力达到最大,下载能力也相应的达到最大。这种和针锋相对类似的思想非常的伟大。“optimistic unchoking”非常和谐的与“囚徒困境”合作。 3.4、反对歧视 某些情况下,一个peer可能被它所有的peers都阻塞了,这种情况下,它将会保持较低的下载速率直到通过“optimistic unchoking”找到更好peers。为了减轻这种问题,如果一段时间过后,从某个peer那里一个片断也没有得到,那么这个peer认为自己被对方“怠慢”了,于是不再为对方提供上传,除非对方是“optimistic unchoking”。这种情况频繁发生,会导致多于一个的并发的“optimistic unchoking”。 3.5仅仅上传 一旦某个peer完成了下载,它不能再通过下载速率(因为下载速率已经为0了)来决定为哪些 peers 提供上载了。目前采用的解决办法是,优先选择那些从它这里得到更好的上载速率的peers。这样的理由是可以尽可能的利用上载带宽。 4、真实世界的体验 BitTorrent不仅仅早已经实现,而且早已经被广泛的使用,它为许多并发的下载者提供成百兆的文件下载。已知的最大的部署中,同时有超过1000个的下载者。当前的瓶颈(实际还没有达到)看来是tracker的带宽。它(trakcer的带宽)大概占用了带宽总量的千分之一,一些小的协议扩展可能会使它降到万分之一。 BT种子文件使用了一种叫bencoding的编码方法来保存数据。 bencoding现有四种类型的数据:srings(字符串),integers(整数),lists(列表),dictionaries(字典) 编码规则如下: strings(字符串)编码为:<字符串长度>:<字符串> 例如: 4:test 表示为字符串"test" 4:例子 表示为字符串“例子” 字符串长度单位为字节 没开始或结束标记 integers(整数)编码为:i<整数>e 开始标记i,结束标记为e 例如: i1234e 表示为整数1234 i-1234e 表示为整数-1234 整数没有大小限制 i0e 表示为整数0 i-0e 为非法 以0开头的为非法如: i01234e 为非法 lists(列表)编码为:le 开始标记为l,结束标记为e 列表里可以包含任何bencoding编码类型,包括整数,字符串,列表,字典。 例如: l4:test5abcdee 表示为二个字符串["test","abcde"] dictionaries(字典)编码为de 开始标记为d,结束标记为e 关键字必须为bencoding字符串 值可以为任何bencoding编码类型 例如: d3:agei20ee 表示为{"age"=20} d4:path3:C:\8:filename8:test.txte 表示为{"path"="C:\","filename"="test.txt"} 具体文件结构如下: 全部内容必须都为bencoding编码类型。 整个文件为一个字典结构,包含如下关键字 announce:tracker服务器的URL(字符串) announce-list(可选):备用tracker服务器列表(列表) creation date(可选):种子创建的时间,Unix标准时间格式,从1970 1月1日 00:00:00到创建时间的秒数(整数) comment(可选):备注(字符串) created by(可选):创建人或创建程序的信息(字符串) info:一个字典结构,包含文件的主要信息,为分二种情况:单文件结构或多文件结构 单文件结构如下: length:文件长度,单位字节(整数) md5sum(可选):长32个字符的文件的MD5校验和,BT不使用这个值,只是为了兼容一些程序所保留!(字符串) name:文件名(字符串) piece length:每个块的大小,单位字节(整数) pieces:每个块的20个字节的SHA1 Hash的值(二进制格式) 多文件结构如下: files:一个字典结构 length:文件长度,单位字节(整数) md5sum(可选):同单文件结构中相同 path:文件的路径和名字,是一个列表结构,如\test\test.txt 列表为l4:test8test.txte name:最上层的目录名字(字符串) piece length:同单文件结构中相同 pieces:同单文件结构中相同 实例: 用记事本打开一个.torrent可以看来类似如下内容 d8:announce35:http://www.manfen.net:7802/announce13:creation datei1076675108e4:infod6:lengthi17799e4:name62:MICROSOFT.WINDOWS.2000.AND.NT4.SOURCE.CODE-SCENELEADER.torrent12:piece lengthi32768e6:pieces20:?W ?躐?緕排T酆ee 很容易看出 announce=http://www.manfen.net:7802/announce creation date=1076675108秒(02/13/04 20:25:08) 文件名=MICROSOFT.WINDOWS.2000.AND.NT4.SOURCE.CODE-SCENELEADER.torrent 文件大小=17799字节 文件块大小=32768字节

返回列表 回复 发帖