资源描述
CTorrent程序源码分析
姚旭晨
目录
CTorrent程序源码分析 1
1. 前言 3
1.1 为什么要写这份文档 3
1.2 客户端的选择 3
1.3 CTorrent简介 4
2. 准备工作 5
2.1 知识储备 5
2.2 我对本篇源码分析的说明 5
3. 总述 6
3.1 CTorrent的命令行参数的意义 6
3.2 CTorrent的状态栏的意义 6
3.3 各个类实现的具体实例 7
3.4 BT协议的特性和CTorrent的实现情况 8
4. 源代码分析 10
4.1 ctorrent.cpp 10
4.2 downloader.cpp 11
4.3 bencode.h 13
4.4 bitfield.h 15
4.4.1 class BitField 15
4.5 btcontent.h 18
4.5.1 BTCACHE结构体 18
4.5.2 class btContent 18
4.6 btfiles.h 30
4.6.1 Struct BTFILE 30
4.6.2 Class btFiles 31
4.7 btrequest.h 35
4.7.1 class RequestQueue 35
4.7.2 class PendingQueue 37
4.8 btstream.h 38
4.8.1 class btStream 38
4.9 bufio.h 40
4.9.1 class BufIo 40
4.10 connect_nonb.h 42
4.11 httpencode.h 42
4.12 iplist.h 44
4.12.1 struct _iplist 44
4.12.2 class IpList 44
4.13 peer.h 45
4.13.1 宏 45
4.13.2 struct _btstatus 46
4.13.3 class btBasic 46
4.13.4 class btPeer:public btBasic 47
4.14 peerlist.h 56
4.14.1 struct _peernode 56
4.14.2 class PeerList 57
4.15 rate.h 70
4.15.1 变量 70
4.15.2 函数 71
4.16 setnonblock.h 71
4.17 sigint.h 71
4.18 tracker.h 72
4.18.1 宏 72
4.18.2 变量 72
4.18.3 函数 74
5. 后记 79
5.1 开源和BitTorrent,不得不说的话 79
5.2 BT的精神:共享,公平和宽容 79
5.3 本篇文档的版权和莫做害群之马 79
5.4 我的敬意 80
5.5 结语 80
图表目录
图表 1 main()函数流程图 10
图表 2 Downloader()函数流程图 12
图表 3 btFiles::_btf_recurses_directory()函数流程图 33
图表 4 btPeer::RequestPiece()函数流程图 52
图表 5 btPeer::Send_ShakeInfo()函数流程图 55
图表 6 PeerList::UnChokeCheck()函数流程图 61
图表 7 算法1流程图 62
图表 8 算法3流程图 63
图表 9 PeerList::FillFDSET()函数流程图 66
图表 10 PeerList::AnyPeerReady()函数流程图 68
图表 11 btTracker::SendRequest()函数流程图 77
表格目录
表格 1 BitField::Except()函数逻辑表 16
表格 2 m_shake_buffer[68]位填充情况 19
1. 前言
1.1 为什么要写这份文档
BitTorrent点对点文件传输协议(以下简称BT协议)及其客户端应用大行其道的今天,各种各样的客户端不胜枚举(可以参看http://wiki.theory.org/BitTorrentApplications),而各种各样的BT技术论坛讨论的却都是有关客户端软件如何使用的问题,有关底层协议细节和实现方案的讨论少之又少。我碰巧有机会研究过一阵BT协议的原理,也看过一部分源代码(CTorrent),虽然现在不再继续BT方面的研究了,但有感于当初看代码时遇到的资料的匮乏的窘境,便决心把自己的理解和心得写出来,算是自己的一份总结(这也是我的本科毕业论文),也希望帮助对BT协议实现有兴趣的人尽快上手,少走弯路。
有关BT协议的论述主要有三篇文章:
1,BT官方网站上的协议解释:http://www.bittorrent.org/protocol.html。
2,Bittorrent Protocol Specification,http://wiki.theory.org/BitTorrentSpecification。
3,Incentives Build Robustness in BitTorrent,
这三篇文章从不同方面给出了BT协议从算法到实现的一个较为简略的描述。为了更深入地理解BT协议,自己动手写一个BT客户端或阅读一个BT客户端的源代码的工作是必不可少的。
1.2 客户端的选择
Bram Cohen是BT协议的创建者。根据这份协议,他写了BT的第一个客户端,也就是BitTorrent公司的产品:BitTorrent。可以说,BitTorrent的源码和BT协议是门当户对,要理解协议,先从BitTorrent的源码开始是最好不过的了。
但Bram Cohen是用Python语言写的BitTorrent,这给很多不懂Python的人(我也在内)带来了很多麻烦:为了看懂一份源码而去新学一份计算机编程语言是不是有些不值得呢?
好在BT客户端是如此之多,我们有很大的选择空间。除了Python,还有Java(主要是Azureus,国外非常流行的多平台的客户端)和C++(其它大部分客户端)写成的程序。
经过多方比较,我选择了CTorrent这个客户端。虽然CTorrent是用C++写成的,但仅仅算是一个轻量级(light-weighted)的C++软件。它的库函数依赖型很小,只用到了Open SSL库用来计算哈希值,所以可以工作在Linux, FreeBSD和MacOS平台。CTorrent没有图形界面,工作在命令行模式。
另外,libtorrent(
1.3 CTorrent简介
CTorrent是由YuHong写的一个BT客户端。它的代码大部分都可以看作是C代码,只是用到了C++的类概念,还有一小部分构造函数,析构函数,函数和操作符重载的代码。不懂C++的人只需有一些C++的基本知识就完全能看懂源代码了。CTorrent的主页是,它遵循GPL。
作者在CTorrent主页上称自己为YuHong,这里有一篇他写完CTorrent后发的帖子:http://www.freebsdchina.org/forum/viewtopic.php?p=39082,想必是中国人吧。
用户在使用时发现CTorrent有一些bug,一个比较明显的例子是CTorrent下载完成后不会立即把缓存中的数据写入硬盘,这样如果按下Ctrl-C结束程序的话会造成数据的不完整。CTorrent的最新版本是1.3.4(2004年9月7日发布),作者后面就没有再发布新版本,软件的一些问题也没有得到修正。
虽然有一些bug,但得益于CTorrent是开源项目,很快就有人为CTorrent写了一些补丁( Holmes的人贡献颇多,他为CTorrent打了很多patch,然后重新发布,取名为Enhanced CTorrent。
Enhanced CTorrent的主页是 Holmes用Perl写的一个CTorrent Control Server,可以实现对Enhanced CTorrent运行状况的监控。
这篇CTorrent的源码分析是基于ctorrent-dhn1.2版本的,原因是由于我查看Enhanced CTorrent较早,那时还没有ctorrent-dhn2版本,再加上自己偷懒,没有赶在ctorrent-dhn2发布之前把文章写完……比较而言,dnh1.2版本已经是一个相对稳定的版本了,dnh2的改进主要是在性能方面,而非bug fix(容我再强词夺理一句,我简略看过dnh2版本的代码,在dnh1.2的基础上,看懂dnh2是没有问题的)。
另外,Dennis Holmes虽然重新发布了CTorrent,但他本人对原作者是极为尊敬的。在他的dnh版本中,原封不动地保留了原先代码的痕迹,自己的改动也加上了相应的注释。虽然CTorrent有一些bug,但正如Dennis Holmes所言:谁又说其它客户端没有bug呢?我的这篇源码分析也统一称CTorrent和Enhanced CTorrent为CTorrent,只有在需要两个版本比较时才区分开来。
2. 准备工作
2.1 知识储备
要看懂CTorrent源码和本篇源码分析,读者需要具备如下知识:
1,前面列举的BT协议的大致了解。
2,网络socket编程方面的基本知识,主要是select()函数的使用。
3,至少会C语言,了解C++的基本使用方法(主要是类,构造函数,析构函数和重载)。
2.2 我对本篇源码分析的说明
1, 源代码中如果出现一些乱码(特别是在终端中查看时),设置:
$export LANG=C
即可看到原作者写的中文注释。
2, 源码解说一般采取流程图的形式,有一些函数的具体功能不是很集中,画流程图也表示不出前后联系来,就直接写了步骤分析。有些源码比较晦涩的,会直接分析源代码。
3, 源代码中的全部变量都有分析。大部分函数都有说明,少数特别简单的函数和见名知意的函数没有说明。
4, 源代码中看似简单的表述实际蕴含着及其严格的操作要求(例如宏P_HANDSHAKE的意思是可以进行握手通信了,而不是正在进行握手通信或者已经完成握手通信了)。所以必须正确理解源代码各个宏,变量,函数的确切含义,才能真正理解程序的流程和作用。
5, 分析源码的最终目的是彻底理解BT协议的实现结构,以及BT通信性能卓越的原因。虽然程序中涉及BT协议算法的只有几个函数,但这几个函数是在其它大量代码的基础上构建的。一些有关种子文件的制作和解析的代码虽然看似和BT通信关系不大,但若前面的基础没有理解正确,会给后面的算法分析带来很大的麻烦。
6, 原作者的C语言技巧相当高,enjoy it!
7, 本文中“函数”指的是当前正在分析的函数,而“程序”指的是整个CTorrent程序。
8, 本文中“消息”指的是peer发来的固定格式的消息,例如piece消息,bitfield消息等。“数据”指的是客户端要下载的东西,例如一个游戏,一段视频等。
9, 英文中种子文件有很多说法,如.torrent file, metainfo file,本文中均用它们的中文名:种子文件。
10, 英文中关于BT协议的最小数据单元有很多说法,如slice,block,subpiece,本文中使用CTorrent源代码中的说法:slice。
3. 总述
3.1 CTorrent的命令行参数的意义
-h/-H:显示帮助命令
-x:只解码并显示种子文件信息,不下载。
-c:只检查已下载的数据,不下载。
-v:打开debug调试输出。
下载选项:
-e int 下载完毕后的做种时间(单位:小时),默认为72小时。
-p port 绑定端口,默认为2706。
-s save_as 重命名下载的文件,若是下载的是多个文件,则sava_as是包含多文件的目录。
-C cache_size 缓存大小,默认为16MB。
-f 强制做种模式,不进行SHA1 HASH检查。
-b bf_filename piece位图文件名,详见BitField::SetReferFile()。
-M max_peers 客户端最多与多少个peer通信。
-m min_peers 客户端至少与多少个peer通信。
-n file_number 多文件下,选择哪个文件去下载(例如第二个文件file_number就为2)。
-D rate 限制最大下载速率(单位:KB/s)。
-U rate 限制最大上传速率(单位:KB/s)。
-P peer_id 客户端通信的ID,默认为-CD0102-。
下载数据文件示例:
ctorrent -s new_filename -e 12 -C 32 -p 6881 eg.torrent
制作种子文件示例:
ctorrent -t file_to_make.avi -s a.torrent -u protocol://address/announce
3.2 CTorrent的状态栏的意义
CTorrent运行时输出格式如下:
$/ 1/10/40 [3/148/148] 2MB,1MB | 48,20K/s | 80,40K E:0,1
各项意义为:
/:表明客户端正在工作的符号,以”- \ | /”循环。
1:种子数目。
10:客户端正在通信的非种子的peer数目。
40:tracker服务器知道的peer数,也是整个bt通信群的peer数。
3:客户端已经下载的piece数目。
148:数据文件全部的piece数目。
148:客户端可以得到的piece数目,若此数小于全部piece数目则不会下载到完整的数据。
2MB:客户端已经下载的数据量。
1MB:客户端正在上传的数据量。
48:客户端的平均下载速率(KB/s)。
20:客户端的平均上传速率(KB/s)。
80:客户端的即时下载速率(KB/s)。
40:客户端的即时上传速率(KB/s)。
0:客户端与tracker服务器通信失败的次数。
1:客户端与tracker服务器通信成功的次数。
3.3 各个类实现的具体实例
CTorrent程序使用了C++面向对象的特性。在程序中有一些类的实例(instance),分别代表了一个BT通信群中的各个对象。
3.3.1 BTCONTENT
BTCONTENT是btContent类实现的实例。它在程序中代表种子文件和本地数据文件。
3.3.2 PENDINGQUEUE
PENDINGQUEUE是PendingQueue类实现的实例。它在程序中代表由于与peer的暂时通信中断而搁置等待的slice链表的队列。
3.3.3 IPQUEUE
IPQUEUE是IpList类实现的实例。它在程序中代表从tracker服务器传来的peer列表的链表。
3.3.4 Self
Self是btBasic类实现的实例。它在程序中代表客户端自己。
3.3.5 WORLD
WORLD是PeerList类实现的实例。它在程序中代表所有正在与客户端通信的peer的链表
3.3.6 Tracker
Tracker是btTracker类实现的实例。它在程序中代表tracker服务器。
3.4 BT协议的特性和CTorrent的实现情况
BT下载之所以性能出众是由BT协议所规定的一系列机制所保证的。判断一个BT下载软件性能优秀与否则是看这个软件对BT协议中下载机制的执行情况。BT协议主要规定了两大类机制保证其性能(详细信息请参照” Incentives Build Robustness in BitTorrent”):
3.4.1 Piece选择机制
3.4.1.1 初始模式(Initial Mode):Random First Piece。
当客户端刚开始运行时,它一个完整的piece也没有,这时需要尽快下载到一个piece以便可以提供上传服务。此时的算法为:第一个随机piece。客户端会随机找到一个piece,然后下载。
CTorrent随机选择piece,而且更进一步采取了一种加速下载的办法:虽然此时客户端没有piece,但应该有向其它peer的申请slice的队列了。客户端只要比较这些队列哪个最短,优先下载最短的队列即可最快获得第一个piece。
函数PeerList::Who_Can_Duplicate()实现了此算法的代码。
3.4.1.2 一般模式(Normal Mode):Strict Priority和Rarest First。
1,严格优先(Strict Priority)
一旦某个slice被申请,则这个slice所在的piece中的其它slice会先于其它piece的slice被申请。这样做可以尽量使最先申请的piece最先被下载完毕。
这条规则看似简单而且公平,但实现起来非常困难:BT协议规定一个piece中的多个slice可以向多个peer申请,而客户端又同时从多个peer处申请了多个piece中的slice,这么多数据传输队列同时进行,要保证严格优先是非常困难的。
CTorrent在设计时采取了一种比较简单的方法:一旦向某个peer申请了某个slice,则这个piece中的所有slice均向这个peer申请。为了保证尽快将一个piece下载完成,CTorrent会找出当前正在与之通信的那个peer(正在通信的peer通常比较活跃),然后把所有slice请求队列中最慢的那个队列找出来,交给这个peer去下载。
函数PeerList::Who_Can_Abandon()实现了此算法的代码。
2,最少优先(Rarest First)
客户端下载时选择所有peer拥有最少的那个piece优先下载。
函数BitField::Random()是有关piece选择机制的代码,但它只是随机选择了piece,没有实现最少优先。
3.4.1.3 结束模式(Endgame Mode)
由于每一个piece只向一个peer申请,当peer数大于还没有申请的piece数时,客户端便进入了结束模式。此时客户端可以向所有的peer发送还没有下载完毕的slice的请求,以便尽快下载完毕好做种子。
CTorrent变相实现了这个算法,它会找出当前正在与之通信的那个peer(正在通信的peer通常比较活跃),然后把所有slice请求队列中最长的那个队列找出来,交给这个peer去下载。
函数PeerList::Who_Can_Duplicate()实现了此算法的代码。
函数PeerList::Who_Can_Duplicate()和PeerList::Who_Can_Abandon()的调用环境均是函数btPeer::RequestPiece(),应将这三个函数一起查看才能清楚piece选择机制的实现。
3.4.2 Choking算法
Choking, Unchoking, Optimistic Unchoking三个算法是保证BT下载公平的基石,即“一报还一报”,“人人为我,我为人人”。具体实现请参照PeerList::UnChokeCheck()。
总的来说,CTorrent程序简单而巧妙地实现了BT协议中的保证下载性能的核心算法。只是最少优先算法没有实现,会给BT通信群的稳定性带来一定的影响。不过,这个问题已经在CTorrent-dnh2版本中得到了改正和优化。
点对点(Peer to Peer)通信是BT协议最大的特色,它充分利用了互联网上各个下载终端的带宽,使得由服务器上传速率有限所带来的瓶颈问题得以解决。但除此以外,BT协议本身还通过piece选择机制优化了下载:由于数据以slice的形式分块下载,一般一个slice只有32KB,即使所有的peer的上传速率都很慢,但slice是如此之小,以至于从一个很慢的peer处获得一个slice所需时间也极少,BT客户端只需合理安排对slice的请求,在较活跃的peer和下载较慢的slice中作出相应的调整和搭配,即可获得较高的下载速率。
打个比方,火车站检票口处会有多个检票员(peer)在检票。每个人(slice)手里都拿着一张票,排成很多条并排的队伍(slice队列)等候检票。由于检票员的检票速度有快有慢,控制中心(客户端程序)只需适时作出调整,将较长的队列分配给较快的检票员即可做到全体乘客的快速通过。
总结起来,BT协议的精髓便是通过化整为零,主动选择来充分利用各个下载终端的带宽,配合以相应的公平机制,保证整个BT通信群的高性能和高稳定性。
4. 源代码分析
4.1 ctorrent.cpp
CTorrent客户端的主程序,主要负责解析用户参数,然后建立磁盘文件并初始化和tracker服务器的连接,最后调用downloader()函数进入一个数据处理的大循环。流程图如下:
开始
设置产生随机数的种子
若只制作种子而不下载数据:
由硬盘数据初始化BTCONTENT,
制作种子文件,
退出程序
Random_init()
由种子文件初始化BTCONTENT
BTCONTENT.InitialFromMI(arg_metainfo_file, arg_save_as)
BTCONTENT.CreateMetainfoFile()
初始化倾听套接字。
BTCONTENT.InitialFromFS()
WORLD.Initial_ListenPort()
初始化与tracker服务器的连接
进入传送数据的大循环
Tracker.Initial()
BTCONTENT.FlushCache()
循环退出后将缓冲区数据写入磁盘。
Downloader()
程序结束
图表 1 main()函数流程图
4.2 downloader.cpp
此文件只有一个程序:Downloader(),负责客户端与tracker服务器的通信,与peer的数据交换等,函数具体的实现则是通过调用各司其职的其它函数实现的。
开始
检查做种时间
系统调用select()监视文件描述符集等待数据
WORLD.FillFDSET(&now,&rfd,&wfd)
根据当前peer状态将它们放入可读或可写文件描述符集中
Tracker.IntervalCheck(&now,&rfd, &wfd);
检查与tracker服务器的通信间隔和peer数
BTCONTENT.SeedTimeout(&now)
Tracker.SocketReady(&rfd,&wfd,&nfds)
Peer有数据到达
tracker服务器有数据到达
WORLD.AnyPeerReady(&rfd,&wfd,&nfds)
与tracker服务器的通信结束了?
结束
是
否
图表 2 Downloader()函数流程图
4.3 bencode.h
此文件提供了一系列种子文件编解码的函数,其中编码函数较为简单,解码函数比较晦涩:
u size_t buf_int(const char *b,size_t len,char beginchar,char endchar,size_t *pi)
beginchar非空时,解析i<integer encoded in base ten ASCII>e型表示的整数。例如:
b='i123e', len=5, beginchar='i', endchar='e', *pi=123, return=5。
beginchar为0时,解析<string length encoded in base ten ASCII>:<string data>型表示的字符串。例如,b = ”4:spam”, len = 6, beginchar = 0, endchar = ‘:’, *pi = 4,返回值为2(’:’和’4’的间隔)。
u size_t buf_str(const char *b,size_t len,const char **pstr,size_t* slen)
解析<string length encoded in base ten ASCII>:<string data>型表示的字符串。返回值根据pstr和slen发生变化。例如:
b="12:announce_url"
若*pstr = "announce_url", 函数将*slen赋值为12,返回值无实际意义。
若传递参数*pstr = 0, *slen = 0,则返回值为整个字符串的长度,即15。
u size_t decode_int(const char *b,size_t len)
调用buf_int(),返回整个以类似i<integer encoded in base ten ASCII>e表示的字符个数(’i’和’e’均计算在内)。
u size_t decode_str(const char *b,size_t len)
调用buf_str(),返回整个字符串的长度。
u size_t decode_dict(const char *b,size_t len,const char *keylist)
解析种子文件中dictonary用的,由于整个种子文件就是一个dictonary,而这个大的dictonary中还有小的dictonary,所以随着keylist的不同,函数会用不同的方法解析dictonary。
若kyelist=”announce”,则函数会返回位于”announce”后面的数的位置,如图,为0x0b。
若keylist=”info|length”,这表明查询info这个dictonary中的”length”后面的数的位置。如图,为0x54。
若keylist=(char *)0,则返回整个dictonary的长度(从’d’到’e’)。
u size_t decode_list(const char *b,size_t len,const char *keylist)
返回整个list的长度。例如 = “l4:spam4:eggse”,则返回16。
u size_t decode_rev(const char *b,size_t len,const char *keylist)
根据*b的数值分别调用decode_int(),decode_dict(),decode_list(),decode_str()函数解析。
u size_t decode_query(const char *b,size_t len,const char *keylist,const char **ps,size_t *pi,int method)
此函数负责解析是哪个宏函数(meta_str(), meta_int(), meta_pos())调用了它,并由解析结果去调用相应的函数。
u size_t decode_list2path(const char *b, size_t n, char *pathname)
该函数只在多文件情况下被调用,将以lists形式表示的多个文件写入pathname。函数每次只写入一个文件名,需要多次被调用才能将所有文件名解析出来。
u size_t bencode_buf(const char *buf,size_t len,FILE *fp)
以如下形式将str写入文件fp:
<string length encoded in base ten ASCII>:<string data>
例如,str=”spam”,则函数将”4:spam”(引号不包括)写入文件fp。
u size_t bencode_str(const char *str, FILE *fp)
调用bencode_buf()向文件中写入str的长度和str。
u size_t bencode_int(const int integer, FILE *fp)
以如下形式将整数integer写入文件:
i<integer encoded in base ten ASCII>e
例如,integer=19,则函数将”i19e”(引号不包括)写入文件fp。
u size_t bencode_begin_dict(FILE *fp)
向文件fp中写入字符’d’,作为dictornary的开始。Dictornary结束后必须调用bencode_end_dict_list()写入字符’e’。
u size_t bencode_begin_list(FILE *fp)
向文件fp中写入字符’l’,作为list的开始。list结束后必须调用bencode_end_dict_list()写入字符’e’
u size_t bencode_end_dict_list(FILE *fp)
向文件fp中写入字符’e’,作为dictionary或list的结束。
u size_t bencode_path2list(const char *pathname, FILE *fp)
该函数只在多文件情况下被调用,pathname里储存了一个文件信息的链表。此函数将所有的文件名以lists的形式写入文件fp。
4.4 bitfield.h
4.4.1 class BitField
BitField类是一个位图。Piece以从小到大的索引顺序在位图unsigned char *b中占有1位(bit)。
4.4.1 变量
Ø static size_t nbits
piece总数。
Ø static size_t nbytes
位图区域b的大小。若共有65个piece,则nbytes = 9。即b占9个字节,但是只有前65位有意义。
Ø unsigned char *b
位图所在的内存区域,以字符串形式表示。
Ø size_t nset
已经获得的piece数,也就是b中被置位的位数。当nset == nbits时,文件的所有piece全部获得。
4.4.2 函数
u void BitField::SetAll()
为b开辟内存区域,并使nset = nbits。
u int BitField::SetReferFile(const char *fname)
此函数在用户指定piece位图时使用。假定用户在硬盘上有一文件,其内容是要下载的数据文件的piece位图。使用”-b”参数将此文件名传给fname。SetReferFile()便会读取位图文件,并调用SetReferBuffer()将位图文件内容存储在BTCONTENT.pBF中。使用”-b”参数会使用用户指定的位图文件而非程序自己计算位图,一个bit之差都会导致下载失败,所以作者提醒用户小心使用。
u void BitField::Set(size_t idx)
将b中idx对应的位置为1,并更新nset的值。
u void BitField::UnSet(size_t idx)
将idx对应的位置置为0。注意由于SetAll只是开辟了内存区域,并没有将b全部置为1,所以Unset()函数还要调用_isfull()检查b是否已满,若是,则将b全部置为1,然后再将idx位置为0。
u int IsSet(size_t idx) const;
查看第idx个piece在piece位图中是否已经存在。
u size_t BitField::Random() const
函数随机选择BitField位图中存在的一个piece的索引idx,并返回idx。
程序中只有一处调用此函数:
idx = tmpBitField2.Random();
tmpBitField2是客户端程序还没有向peer请求的所有piece的位图。上面调用的目的是随机选择一个索引为idx的piece,然后向peer发送request信息。
但是函数Random()设计得并不好,原因是它没有体现出来BT协议中的“最少优先”原则。
当很多peer在下载同一个数据文件时,总有一些piece是大部分peer都有的,而另一些piece只有少部分peer有。客户端程序在选择piece下载时应优先选择所有peer拥有最少的那个piece,这样一来可以防止某个拥有唯一piece的peer突然退出而导致整个BT通信群下载的失败,二来可以将piece平均分布在各个peer中加快下载速率。这样一种选择piece下载的机制便叫“最少优先”(rarest first)原则。
很明显,函数只是在所有没有发出请求的piece种随机选择了一个piece而没有做到最少优先,如果一个BT通信群中有很多这样的客户端程序,那么这个BT通信群将是比较脆弱的。
u void BitField::Comb(const BitField &bf)
函数计算并设置this.nset为this和bf加起来全部的piece数(共有的只算1个),并更新piece位图this为两者全部的piece位图。若this或bf已满,则调用SetAll()设置this.nset为nbits,否则,调用_recalc()计算。
u void BitField::Except(const BitField &bf)
如果piece位图b中有某一个piece(即相应位被置1),而bf.b中没有,那么b的相应位被置1。除此以外的任何其它情况,b的相应位都被置0。
b有此piece?
bf.b有此piece?
函数调用后位图b的相应位:
1
0
1
1
1
0
0
0
0
0
1
0
表格 1 BitField::Except()函数逻辑表
注意此函数中有一个较易混淆的地方,那就是“bf.b中没有”此piece的真正含义。由于很多情况都把pBFilter传给bf,例如:
tmpBitField.Except(*BTCONTENT.pBFilter);
而根
展开阅读全文