首页 CTorrent分析

CTorrent分析

举报
开通vip

CTorrent分析CTorrent程序源码分析 姚旭晨 目录 1CTorrent程序源码分析 31. 前言 31.1 为什么要写这份文档 31.2 客户端的选择 41.3 CTorrent简介 52. 准备工作 52.1 知识储备 52.2 我对本篇源码分析的说明 63. 总述 63.1 CTorrent的命令行参数的意义 63.2 CTorrent的状态栏的意...

CTorrent分析
CTorrent程序源码分析 姚旭晨 目录 1CTorrent程序源码分析 31. 前言 31.1 为什么要写这份文档 31.2 客户端的选择 41.3 CTorrent简介 52. 准备工作 52.1 知识储备 52.2 我对本篇源码分析的说明 63. 总述 63.1 CTorrent的命令行参数的意义 63.2 CTorrent的状态栏的意义 73.3 各个类实现的具体实例 83.4 BT协议的特性和CTorrent的实现情况 104. 源代码分析 104.1 ctorrent.cpp 114.2 downloader.cpp 134.3 bencode.h 154.4 bitfield.h 154.4.1 class BitField 184.5 btcontent.h 184.5.1 BTCACHE结构体 184.5.2 class btContent 304.6 btfiles.h 304.6.1 Struct BTFILE 314.6.2 Class btFiles 354.7 btrequest.h 354.7.1 class RequestQueue 374.7.2 class PendingQueue 384.8 btstream.h 384.8.1 class btStream 404.9 bufio.h 404.9.1 class BufIo 424.10 connect_nonb.h 424.11 httpencode.h 444.12 iplist.h 444.12.1 struct _iplist 444.12.2 class IpList 454.13 peer.h 454.13.1 宏 464.13.2 struct _btstatus 464.13.3 class btBasic 474.13.4 class btPeer:public btBasic 564.14 peerlist.h 564.14.1 struct _peernode 574.14.2 class PeerList 704.15 rate.h 704.15.1 变量 714.15.2 函数 714.16 setnonblock.h 714.17 sigint.h 724.18 tracker.h 724.18.1 宏 724.18.2 变量 744.18.3 函数 795. 后记 795.1 开源和BitTorrent,不得不说的话 795.2 BT的精神:共享,公平和宽容 795.3 本篇文档的版权和莫做害群之马 805.4 我的敬意 805.5 结语 图表目录 10图表 1 main()函数 流程 快递问题件怎么处理流程河南自建厂房流程下载关于规范招聘需求审批流程制作流程表下载邮件下载流程设计 图 12图表 2 Downloader()函数流程图 33图表 3 btFiles::_btf_recurses_directory()函数流程图 52图表 4 btPeer::RequestPiece()函数流程图 55图表 5 btPeer::Send_ShakeInfo()函数流程图 61图表 6 PeerList::UnChokeCheck()函数流程图 62图表 7 算法1流程图 63图表 8 算法3流程图 66图表 9 PeerList::FillFDSET()函数流程图 68图表 10 PeerList::AnyPeerReady()函数流程图 77图表 11 btTracker::SendRequest()函数流程图 表格目录 16表格 1 BitField::Except()函数逻辑表 19表格 2 m_shake_buffer[68]位填充情况 1. 前言 1.1 为什么要写这份文档 BitTorrent点对点文件传输协议(以下简称BT协议)及其客户端应用大行其道的今天,各种各样的客户端不胜枚举(可以参看http://wiki.theory.org/BitTorrentApplications),而各种各样的BT技术论坛讨论的却都是有关客户端软件如何使用的问题,有关底层协议细节和实现 方案 气瓶 现场处置方案 .pdf气瓶 现场处置方案 .doc见习基地管理方案.doc关于群访事件的化解方案建筑工地扬尘治理专项方案下载 的讨论少之又少。我碰巧有机会研究过一阵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,http://www.bittorrent.com/bittorrentecon.pdf。 这三篇文章从不同方面给出了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(http://www.rasterbar.com/products/libtorrent.html)也是一个值得一看的客户端。Libtorrent用到了很多C++的模板库(主要是boost),客户端的性能非常好,而且还提供库函数给其它程序调用。只是作者的C++水平实在太低,对这种重量级的软件掌握不了。 1.3 CTorrent简介 CTorrent是由YuHong写的一个BT客户端。它的代码大部分都可以看作是C代码,只是用到了C++的类概念,还有一小部分构造函数,析构函数,函数和操作符重载的代码。不懂C++的人只需有一些C++的基本知识就完全能看懂源代码了。CTorrent的主页是http://ctorrent.sourceforge.net,它遵循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写了一些补丁(http://sourceforge.net/tracker/?group_id=91688&atid=598034)。其中一个叫Dennis Holmes的人贡献颇多,他为CTorrent打了很多patch,然后重新发布,取名为Enhanced CTorrent。 Enhanced CTorrent的主页是http://www.rahul.net/dholmes/ctorrent。目前已经更新到了ctorrent-dhn2版本,这个版本配合Dennis 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()函数进入一个数据处理的大循环。流程图如下: 图表 1 main()函数流程图 4.2 downloader.cpp 此文件只有一个程序:Downloader(),负责客户端与tracker服务器的通信,与peer的数据交换等,函数具体的实现则是通过调用各司其职的其它函数实现的。 图表 2 Downloader()函数流程图 4.3 bencode.h 此文件提供了一系列种子文件编解码的函数,其中编码函数较为简单,解码函数比较晦涩: · size_t buf_int(const char *b,size_t len,char beginchar,char endchar,size_t *pi) beginchar非空时,解析ie型表示的整数。例如: b='i123e', len=5, beginchar='i', endchar='e', *pi=123, return=5。 beginchar为0时,解析:型表示的字符串。例如,b = ”4:spam”, len = 6, beginchar = 0, endchar = ‘:’, *pi = 4,返回值为2(’:’和’4’的间隔)。 · size_t buf_str(const char *b,size_t len,const char **pstr,size_t* slen) 解析:型表示的字符串。返回值根据pstr和slen发生变化。例如: b="12:announce_url" 若*pstr = "announce_url", 函数将*slen赋值为12,返回值无实际意义。 若传递参数*pstr = 0, *slen = 0,则返回值为整个字符串的长度,即15。 · size_t decode_int(const char *b,size_t len) 调用buf_int(),返回整个以类似ie表示的字符个数(’i’和’e’均计算在内)。 · size_t decode_str(const char *b,size_t len) 调用buf_str(),返回整个字符串的长度。 · 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’)。 · size_t decode_list(const char *b,size_t len,const char *keylist) 返回整个list的长度。例如 = “l4:spam4:eggse”,则返回16。 · size_t decode_rev(const char *b,size_t len,const char *keylist) 根据*b的数值分别调用decode_int(),decode_dict(),decode_list(),decode_str()函数解析。 · 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())调用了它,并由解析结果去调用相应的函数。 · size_t decode_list2path(const char *b, size_t n, char *pathname) 该函数只在多文件情况下被调用,将以lists形式表示的多个文件写入pathname。函数每次只写入一个文件名,需要多次被调用才能将所有文件名解析出来。 · size_t bencode_buf(const char *buf,size_t len,FILE *fp) 以如下形式将str写入文件fp: : 例如,str=”spam”,则函数将”4:spam”(引号不包括)写入文件fp。 · size_t bencode_str(const char *str, FILE *fp) 调用bencode_buf()向文件中写入str的长度和str。 · size_t bencode_int(const int integer, FILE *fp) 以如下形式将整数integer写入文件: ie 例如,integer=19,则函数将”i19e”(引号不包括)写入文件fp。 · size_t bencode_begin_dict(FILE *fp) 向文件fp中写入字符’d’,作为dictornary的开始。Dictornary结束后必须调用bencode_end_dict_list()写入字符’e’。 · size_t bencode_begin_list(FILE *fp) 向文件fp中写入字符’l’,作为list的开始。list结束后必须调用bencode_end_dict_list()写入字符’e’ · size_t bencode_end_dict_list(FILE *fp) 向文件fp中写入字符’e’,作为dictionary或list的结束。 · 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 函数 · void BitField::SetAll() 为b开辟内存区域,并使nset = nbits。 · int BitField::SetReferFile(const char *fname) 此函数在用户指定piece位图时使用。假定用户在硬盘上有一文件,其 内容 财务内部控制制度的内容财务内部控制制度的内容人员招聘与配置的内容项目成本控制的内容消防安全演练内容 是要下载的数据文件的piece位图。使用”-b”参数将此文件名传给fname。SetReferFile()便会读取位图文件,并调用SetReferBuffer()将位图文件内容存储在BTCONTENT.pBF中。使用”-b”参数会使用用户指定的位图文件而非程序自己计算位图,一个bit之差都会导致下载失败,所以作者提醒用户小心使用。 · void BitField::Set(size_t idx) 将b中idx对应的位置为1,并更新nset的值。 · void BitField::UnSet(size_t idx) 将idx对应的位置置为0。注意由于SetAll只是开辟了内存区域,并没有将b全部置为1,所以Unset()函数还要调用_isfull()检查b是否已满,若是,则将b全部置为1,然后再将idx位置为0。 · int IsSet(size_t idx) const; 查看第idx个piece在piece位图中是否已经存在。 · 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通信群将是比较脆弱的。 · void BitField::Comb(const BitField &bf) 函数计算并设置this.nset为this和bf加起来全部的piece数(共有的只算1个),并更新piece位图this为两者全部的piece位图。若this或bf已满,则调用SetAll()设置this.nset为nbits,否则,调用_recalc()计算。 · 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); 而根据pBFilter的表示方式,若pBFilter某一位被置0,则表明“有”此piece,即需要下载这个piece。所以,应根据bf的具体表示方式来判断函数的作用。 · void BitField::Invert() 若b为空,函数为b重新开辟内存,并使nset与nbit相等,表示b已满。 若b已满,函数将b全部置为0,并使nset等于0,表示b为空。 若两者皆非,则函数将b里的有意义位按位取反,并重新设置nset。 · int BitField::WriteToFile(const char *fname) 程序中数次出现这样的调用: if( arg_bitfield_file ) BTCONTENT.pBF->WriteToFile(arg_bitfield_file); 当用户通过”-b”参数指定硬盘piece位图文件名称时,程序会调用WriteToFile()将BTCONTENT.pBF(也就是piece位图)写入硬盘。 如果用户在指定了”-b”的同时还使用”-c”参数令程序只检查硬盘上的已下载文件而不实际下载文件,程序会在检查完文件后将piece位图以文件名arg_bitfield_file写入硬盘。 · void BitField::SetReferBuffer(char *buf) 拷贝表示piece位图的buf到b中,并重新计算位图中已拥有的piece个数。 · void BitField::WriteToBuffer(char *buf) 如果piece位图已满,则将buf全部置为1。否则,拷贝位图到buf中。 · inline void BitField::_setall(unsigned char *buf) 将buf中有意义的区域置为1。所谓有意义是指可以buf所代表的位图中可以表示piece的位。若piece数目不能刚好被8整除,buf中最有会有几位不代表任何piece,它们一直为0。 · inline void BitField::_set(size_t idx) 函数将idx对应的位置为1。注意此函数并不重新设置nset,所以只能被已经设置好nset的函数(例如Invert())调用。 · inline void BitField::_recalc() 由位图b重新计算nset的值 4.5 btcontent.h 4.5.1 BTCACHE结构体 为提高磁盘性能,类btContent维护一个BTCACHE链表,缺省为16MB。客户端程序从磁盘读的数据或想要向磁盘写的数据会先放到BTCACHE链表中,直至链表满了才写入磁盘。 · u_int64_t bc_off 此BTCACHE链表成员的绝对偏移地址。 · size_t bc_len 此BTCACHE链表成员的长度。每个成员的长度不一定相同,取决于客户端想读或写的数据量。 · unsigned char bc_f_flush:1 flush标志,若为1则此BTCACHE链表成员中的数据会被写入磁盘。若为0则表示此数据是从磁盘读出的。 · unsigned char bc_f_reserved:7 保留项,用途不详。 · time_t bc_last_timestamp 最后一次读或写(由bc_f_flush为1或0决定)的时间。 · char *bc_buf 存储的数据。 · struct _btcache *bc_next 下一个节点。 4.5.2 class btContent btContent是有关数据文件和种子文件的类。很多变量以m开头,我认为m代表了metainfo,也就是俗称的.torrent“种子”文件。具体来说,它存储了以下一些数据: 4.5.2.1 变量 · char *m_announce m_announce存储了tracker服务器的announce URL,例如:”http://192.168.1.111/announce”。 如果用户要制作种子文件,则必须指定m_announce的值。如果用户要根据种子文件下载数据,那么m_announce便存储了种子文件中的announce URL。 · unsigned char *m_hash_table,size_t m_hashtable_length m_hash_table存储了所有piece的SHA1 hash值,它的长度是m_hashtable_length。 · size_t m_piece_length, size_t m_npieces m_piece_length是piece的长度,制作种子文件时由用户指定,一般为256KB。m_npieces则是所有piece的总数了。一般最后一个piece的长度会小一点。 由于SHA1 hash的长度为20,所以有以下关系: m_npieces = m_hashtable_length / 20 例如,数据文件为1000KB,假设piece长度定为256KB,那么m_npieces = 4,m_hashtable_lenth = 4 × 20 = 80。 · unsigned char m_shake_buffer[68] m_shake_buffer存储了client和tracker或peer握手时的数据。详见BitTorrentSpecification。Ctorrent的m_shake_buffer一般为以下数值: 位数 0 1……………….19 20….27 28….47 48……55 56….67 填充 19 BitTorrent protocol 0 计算值 -CD0102- 随机数 解释 握手信息使用的协议,为”BitTorrent protocol”,不算引号,共19位 协议保留位,全部填充为0。 种子文件的SHA1 HASH值,共20位 最后20位为Peer ID。前面是用户程序的名称(CD)和版本号(0102),以“-”开头和结尾,后面是随机数,一般为程序启动时产生的随机数。这20位唯一地确定了bt客户端的名称。 表格 2 m_shake_buffer[68]位填充情况 CTorrent本来的Peer ID是-CTorrent-,但是因为原本的CTorrent程序被很多客户端认为bug比较多(“buggy”),它们在P2P通信时一旦发现对方客户端是CTorrent,就干脆采取了放弃的方式,所以Enhanced CTorrent将其peer ID改为了CD0102,代表“Ctorrent Dnh1.2”。 · time_t m_create_date, m_seed_timestamp, m_start_timestamp 制作种子文件时,m_create_date自然是制作时间了。下载数据文件时,m_create_date是种子文件里“creation date”项的数值,用的是标准UNIX计时(1970年1月1日0点到现在的秒数)。 m_start_timestamp是客户端程序启动的标准UNIX计时。当下载完成后,Ctorrent会给出下载使用的全部时间。 当下载完成后,客户端便由下载者变为了上传者(这两个词有多种说法:下载者-种子,downloader-uploader, leecher-seeder),此时m_seed_timestamp被设置。程序需要记录这个时间以便在缺省做种时间(72小时)完毕后退出。这时是全职做种子了 · u_int64_t m_left_bytes 客户端缺少的字节数。程序刚运行时m_left_bytes就是数据文件的字节数,然后每获得一个piece,m_left_bytes便减去piece_length,直到下载完毕开始做种,此时为0。 · btFiles m_btfiles m_btfiles储存了种子文件中列出的供用户下载的数据文件的信息。具体请见btFiles类。 · BTCACHE *m_cache 详见BTCACHE结构体。 · size_t m_cache_size, m_cache_used m_cache_size为BTCACHE结构体链表中能储存的最大数据量,也就是缓存大小,一般为16MB。m_cache_used是已用缓存数。 · BitField *pBF 要下载的数据文件的piece位图。每下载成功一个piece,将相应位(bit)置1。表明客户端已经拥有此piece。如何知道文件中那个piece是不是己完成呢?通过计算SHA1的值来检查?! · BitField *pBFilter 要下载的文件的过滤器,也是piece位图。在多文件情形下,用户可能会只选择自己需要的文件下载。此时程序会调用btContent::SetFilter()将需要下载的piece在位图中所对应的位置0。程序只下载pBFilter中置0的piece。 · char *global_piece_buffer 一个piece长度的数据缓冲区,函数调用btContent::ReadPiece()或btContent::ReadSlice()从磁盘读取数据时会将数据放入global_piece_buffer中。 4.5.1.2 函数 · int btContent::InitialFromFS(const char *pathname, char *ann_url, size_t piece_length) 当用户要制作种子文件时,程序调用InitialFromFS,表示从本地获取数据文件,并通过计算文件大小,SHA1 Hash值等将btContent中的变量初始化。具体来说,此函数的执行步骤为: 1, 设置m_piece_length, m_announce, m_create_date。 2, 调用函数BuildFromFS()设置m_btfiles。 3, 由计算得到的m_piece_length为global_piece_buffer开辟内存空间。 4, 计算m_npieces, m_hashtable_length。 5, 调用GetHashValue()设置m_hash_table。 制作种子文件时用户很有可能看到这样的输出: Create hash table(already pieces/total pieces):18/19 Complete. 显示18/19后却加了一个Complete,到底有没有完成呢? 这是InitialFromsFS()在最后显示上的小bug: percent = m_npieces / 100; if( !percent ) percent = 1; for( n = 0; n < m_npieces; n++){ if( GetHashValue(n, m_hash_table + n * 20) < 0) return -1; if( 0 == n % percent ){ printf("\rCreate hash table(already pieces/total pieces): %u/%u", n, m_npieces); fflush(stdout); } } printf(" Complete.\n"); 假设文件被分成19个piece,即m_npiece为19。那么percent为1。由于piece的索引从0开始,当n=18时Hash表已经制作完了,所以当n=19时for循环退出直接显示complete了。改正的方法很简单,在 printf(" Complete.\n"); 前加一句: printf("\rCreate hash table(already pieces/total pieces): %u/%u", m_npieces , m_npieces); 即可。 · int btContent::GetHashValue(size_t idx,unsigned char *md) 此函数将以idx为索引的piece读入global_piece_buffer中(ReadPiece()),计算SHA1 Hash值(Sha1()),并将此值储存在md中。 · ssize_t btContent::ReadPiece(char *buf,size_t idx) 此函数实际是调用了ReadSlice将以idx为索引的piece读入buf中。 · ssize_t btContent::ReadSlice(char *buf,size_t idx,size_t off,size_t len) 此函数的作用是将第idx个(索引从0开始)piece中以off为偏移,len长度的数据读入buf中。刚进入函数时,有一判断: if( !m_cache_size ) return m_btfiles.IO(buf, offset, len, 0); else{…} 当用户仅仅只想制作种子文件时,并不需要m_cache,因此也就没有调用CacheConfigure()将m_cache_size赋新值。函数直接调用m_btfiles.IO(),将数据读出。 若m_cache_size已被配置(缺省为16MB),则函数除了将数据读入buf中,还会调用btContent::CacheIO()将数据放入BTCACHE *m_cache链表中。 · void btContent::CacheConfigure() 配置硬盘缓存m_cache_size大小,缺省为16MB。也可由用户指定,但最大为128MB。 · int btContent::CreateMetainfoFile(const char *mifn) 此函数用来制作种子文件,并将文件名保存为mifn。 有关种子文件的编码 规范 编程规范下载gsp规范下载钢格栅规范下载警徽规范下载建设厅规范下载 ,请参照Bit Torrent Specification v1.0。在这里我们举一个例子来说明CreatMetainfoFile()如何制作种子文件。 取源文件testdata(4841860B),假设保存为a.torrent种子文件,使用如下命令: $ ctorrent -t testdata -s a.torrent -u protocol://address/announce
本文档为【CTorrent分析】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_626452
暂无简介~
格式:doc
大小:1MB
软件:Word
页数:0
分类:互联网
上传时间:2012-03-07
浏览量:14