BT源代码学习心得(十五):客户端源代码分析(下载过程中的块选取策略)
发信人: wolfenstein (NeverSayNever), 个人文集
标 题: BT源代码学习心得(十五):客户端源代码分析(下载过程中的块选取策略)
发信站: 水木社区 (Thu Aug 18 22:25:42 2005), 文集
标 题: BT源代码学习心得(十五):客户端源代码分析(下载过程中的块选取策略)
发信站: 水木社区 (Thu Aug 18 22:25:42 2005), 文集
(本文包含HTML标记,终端模式下可能无法正确浏览)
上一次介绍了对等客户之间在连接建立后的一些动作,以及BT中的阻塞控制策略。这一
次将介绍当某个连接终于畅通时,双方的数据交互,也以此为基础介绍BT中另一重要的策略
控制器PiecePicker。
Choker在选择了解除一个连接的阻塞后,Upload.unchoke()将会执行,Connection对象
的send_unchoke()也在此被执行。当网络的另一端收到这条消息后,它对应的
SingleDownload.got_unchoke()将会开始进行处理。它再检查自己的interested状态,如果
自己也感兴趣的话,那么就用_request_more()开始向对方请求数据了。
_request_more()可以给一个indices作为参数,这个参数是一个列表,意思就是说优先
下载号码在这个列表中的块。如果这个参数为None,那意思就是说你自己看着办吧,觉得下
哪块合适就下哪块。首先检查自己的active_requests,就是当前连接中已经发出去的请求
,如果已经发出去的请求太多了(而还没有数据返回),就暂时不发出新的请求了而是直接返
回。下面检查endgame,如果已经进入这个阶段则按照这个阶段的方式去下载
(fix_download_endgame(),收尾阶段特殊方式下载)。
接下来就开始生成请求了,首先检查indices,如果是None,那么让PiecePicker来挑一
块,否则,逐个的检查indices中的值,如果这个号码的块对方有(have[i])而自己又想要
(do_I_have_requests(i)),那么就是它了。PiecePicker如何进行块的选取的策略我们稍后
再分析,现在我们知道的就是它已经决定下载某一块了。然后要检查interested,如果有必
要,还要通知一下对方。下面一段就是不断向StorageWrapper要网络请求的参数,
上一次介绍了对等客户之间在连接建立后的一些动作,以及BT中的阻塞控制策略。这一
次将介绍当某个连接终于畅通时,双方的数据交互,也以此为基础介绍BT中另一重要的策略
控制器PiecePicker。
Choker在选择了解除一个连接的阻塞后,Upload.unchoke()将会执行,Connection对象
的send_unchoke()也在此被执行。当网络的另一端收到这条消息后,它对应的
SingleDownload.got_unchoke()将会开始进行处理。它再检查自己的interested状态,如果
自己也感兴趣的话,那么就用_request_more()开始向对方请求数据了。
_request_more()可以给一个indices作为参数,这个参数是一个列表,意思就是说优先
下载号码在这个列表中的块。如果这个参数为None,那意思就是说你自己看着办吧,觉得下
哪块合适就下哪块。首先检查自己的active_requests,就是当前连接中已经发出去的请求
,如果已经发出去的请求太多了(而还没有数据返回),就暂时不发出新的请求了而是直接返
回。下面检查endgame,如果已经进入这个阶段则按照这个阶段的方式去下载
(fix_download_endgame(),收尾阶段特殊方式下载)。
接下来就开始生成请求了,首先检查indices,如果是None,那么让PiecePicker来挑一
块,否则,逐个的检查indices中的值,如果这个号码的块对方有(have[i])而自己又想要
(do_I_have_requests(i)),那么就是它了。PiecePicker如何进行块的选取的策略我们稍后
再分析,现在我们知道的就是它已经决定下载某一块了。然后要检查interested,如果有必
要,还要通知一下对方。下面一段就是不断向StorageWrapper要网络请求的参数,
new_request根据自己在硬盘上的某一块的拥有情况,不断得返回块内相对偏移和长度。在
这里,我们可以看出,对等客户之间要求传输实际的数据的请求有三个参数,即第几块,块
内偏移多少,长度多少。而这个长度是根据配置文件中的参数决定的,通常就是一个slice
,它要能一次下载完。当然,一块的长度不一定是slice的整数倍,因此最后一个slice的长
度要短一些,不过,这些细节在StorageWrapper中已经处理好了。从StorageWrapper得到请
求后,就把它加到自己的active_requests中,然后让自己的Connection对象去
send_request()。现在我们也应该更加清楚active_requests和inactive_requests的意义了
,即平时StorageWrapper根据实际情况,准备好inactive_requests,然后在
SingleDownload对象中请求发出时,把它们逐渐转移到自己的active_requests中。
在两个while循环的下面,检查active_requests,意思就是说如果经过以上的所有过程
,如果active_requests还是空的,那么说明什么呢?只能说明对方根本就没有(或者说曾经
有,但是现在已经没有了)自己感兴趣的数据,而如果自己还是interested的话,要调用一
个send_not_interested(),意思是我不再对你感兴趣了。下面检查lost_interests中的值
,这些都是在下载过程中曾经是自己想要的,但是现在已经不想要了(主要原因是自己已经
拥有了)。接下来这个for循环的意思就是检查所有的SingleDownload对象,告诉它们某一块
已经有了,不用再去下了,而且有些SingleDownload要因此发出NOT_INTERESTED。最后再次
检查是否进入endgame阶段,如果是,则按照这种阶段的行为进行处理。
现在我们就可以来研究PiecePicker这个块选择策略控制器的行为了,从前面的分析我
们知道,每个PiecePicker对应一个_SingleTorrent,使用它时经历了以下几步:首先是初
始化,然后根据自己已经有的块,把它告诉给PiecePicker(complete(i)),以后就不要从这
中间选了,还有就是当一个SingleDownload对象获取对方的块拥有状况位图时,也要告诉
PiecePicker(got_have(i)),意思是这一块有人有了。最后当需要PiecePicker做出选择时
这里,我们可以看出,对等客户之间要求传输实际的数据的请求有三个参数,即第几块,块
内偏移多少,长度多少。而这个长度是根据配置文件中的参数决定的,通常就是一个slice
,它要能一次下载完。当然,一块的长度不一定是slice的整数倍,因此最后一个slice的长
度要短一些,不过,这些细节在StorageWrapper中已经处理好了。从StorageWrapper得到请
求后,就把它加到自己的active_requests中,然后让自己的Connection对象去
send_request()。现在我们也应该更加清楚active_requests和inactive_requests的意义了
,即平时StorageWrapper根据实际情况,准备好inactive_requests,然后在
SingleDownload对象中请求发出时,把它们逐渐转移到自己的active_requests中。
在两个while循环的下面,检查active_requests,意思就是说如果经过以上的所有过程
,如果active_requests还是空的,那么说明什么呢?只能说明对方根本就没有(或者说曾经
有,但是现在已经没有了)自己感兴趣的数据,而如果自己还是interested的话,要调用一
个send_not_interested(),意思是我不再对你感兴趣了。下面检查lost_interests中的值
,这些都是在下载过程中曾经是自己想要的,但是现在已经不想要了(主要原因是自己已经
拥有了)。接下来这个for循环的意思就是检查所有的SingleDownload对象,告诉它们某一块
已经有了,不用再去下了,而且有些SingleDownload要因此发出NOT_INTERESTED。最后再次
检查是否进入endgame阶段,如果是,则按照这种阶段的行为进行处理。
现在我们就可以来研究PiecePicker这个块选择策略控制器的行为了,从前面的分析我
们知道,每个PiecePicker对应一个_SingleTorrent,使用它时经历了以下几步:首先是初
始化,然后根据自己已经有的块,把它告诉给PiecePicker(complete(i)),以后就不要从这
中间选了,还有就是当一个SingleDownload对象获取对方的块拥有状况位图时,也要告诉
PiecePicker(got_have(i)),意思是这一块有人有了。最后当需要PiecePicker做出选择时
,只要调用其next函数,它需要一个判断函数(_want),以及一个对方是否是种子的标志
(self.have.numfalse == 0),_want函数就是这样的一个函数,当PiecePicker选了一块后
,要拿给它检查,看看这一块是不是它确实想要的,如果不是的话,PiecePicker会重新选
择。而_want()函数的判断标准很简单,那就是别人有而自己又想要的。
PiecePicker的初始化工作主要是对自己的内部变量进行。这里要解释一下这些变量的
作用,这样能够更加方便地对后面的部分进行理解。numpieces,总的块数。interests是按
照拥有者的数量排序的块列表的列表,就是说,它是一个列表,列表中的第0个元素是所有
的自己感兴趣而没有人有的块的列表,第1个元素是所有的自己感兴趣而只有一个人有的块
的列表,等。pos_in_interests,就是每一块在interests中的对应元素所表示的列表中的
位置,如果某一块比如说i,自己已经有了,那么pos_in_interests[i]的值没有意义。
numinterests的值就是某块有多少人拥有(不包括自己),以上三个变量保持这样的关系:如
果一块i,自己没有,那么interests[numinterests[i]][pos_in_interests[i]]=i。have是
一个布尔数组,表示自己已经有那块,在初始化完成后,它应该和StorageWrapper中的实际
情况保持一致。crosscount则是一个统计情况数组,即有多少块没有人拥有,有多少块有一
个人拥有,等,自己拥有的某一块也在这里参加计数。numgot,已经得到的块数。
scrambled,一个包含从0到numpieces-1的序列,但是被随机打乱了。
现在来看PiecePicker.complete,即自己有了某块,首先have中的值要设置,然后从
numinterests中查到自己原来有多少人拥有,把crosscount中对应的项减一,然后把它下一
项加一,如果没有下一项,那么就在后面添加一项。由此我们可以看到,crosscount数组是
逐渐增大的。然后它做的事情是把interests中的对应的项删除掉,因为它已经不在自己感
兴趣的范围内了,其它几行代码是为了保持这些变量值的一致性。然后试图从started和
seedstarted中删除这一块(如果没有这一块也无所谓,什么也不用做)。
(self.have.numfalse == 0),_want函数就是这样的一个函数,当PiecePicker选了一块后
,要拿给它检查,看看这一块是不是它确实想要的,如果不是的话,PiecePicker会重新选
择。而_want()函数的判断标准很简单,那就是别人有而自己又想要的。
PiecePicker的初始化工作主要是对自己的内部变量进行。这里要解释一下这些变量的
作用,这样能够更加方便地对后面的部分进行理解。numpieces,总的块数。interests是按
照拥有者的数量排序的块列表的列表,就是说,它是一个列表,列表中的第0个元素是所有
的自己感兴趣而没有人有的块的列表,第1个元素是所有的自己感兴趣而只有一个人有的块
的列表,等。pos_in_interests,就是每一块在interests中的对应元素所表示的列表中的
位置,如果某一块比如说i,自己已经有了,那么pos_in_interests[i]的值没有意义。
numinterests的值就是某块有多少人拥有(不包括自己),以上三个变量保持这样的关系:如
果一块i,自己没有,那么interests[numinterests[i]][pos_in_interests[i]]=i。have是
一个布尔数组,表示自己已经有那块,在初始化完成后,它应该和StorageWrapper中的实际
情况保持一致。crosscount则是一个统计情况数组,即有多少块没有人拥有,有多少块有一
个人拥有,等,自己拥有的某一块也在这里参加计数。numgot,已经得到的块数。
scrambled,一个包含从0到numpieces-1的序列,但是被随机打乱了。
现在来看PiecePicker.complete,即自己有了某块,首先have中的值要设置,然后从
numinterests中查到自己原来有多少人拥有,把crosscount中对应的项减一,然后把它下一
项加一,如果没有下一项,那么就在后面添加一项。由此我们可以看到,crosscount数组是
逐渐增大的。然后它做的事情是把interests中的对应的项删除掉,因为它已经不在自己感
兴趣的范围内了,其它几行代码是为了保持这些变量值的一致性。然后试图从started和
seedstarted中删除这一块(如果没有这一块也无所谓,什么也不用做)。
PiecePicker.got_have,处理的情况是别人有了某一块。首先还是保持crosscount的一
致,然后处理interests列表。调用_shift_over把piece从interests列表中的一个元素转移
到另一个元素(同时还要保持其它变量的值的意义的一致性)。_shift_over做的事情就是从
第一个列表中删除一个元素,然后将其插入第二个元素随机的位置,同时维护
pos_in_interests值的意义。
PiecePicker.requested,哪一块已经开始下载了,这个在SingleDownload中会被调用
,它只是维护两个列表,started和seedstarted。
PiecePicker.next,可以说是PiecePicker中提供的最重要的功能,选择一块进行下载
。它选择的第一条原则是,已经开始下载的优先把它下载完(return choice(bests)及其前
面的代码)。它检查选择的两个数组,根据对方是否是种子选择一个数组。然后在所有的这
个数组中选择出自己想要的,检查它们的numinterests,即拥有此块的人数,选出拥有人数
最少的块,放入bests中,如果有并列的,则添加到bests,因此在这里结束后,bests中的
元素是所有正在下载的且自己想要的块中拥有人数最少的块的列表,那么就从中间随机选择
一个返回即可。选择的第二条原则是,当自己拥有的块数少于一定的数量时,随机选择自己
想要的块进行下载(第一阶段结束后的那个if块),因此它用到了那个scrambled列表,而当
自己所拥有的块数超过一定的值(config['rarest_first_cutoff'])后,执行第三阶段的方
案。选择的第三条原则是,优先选择下载拥有的人数最少的块,我们看到,它从interests
中第1个元素开始检查,选择最先找到的自己想要的块,第0个元素不用检查,因为没有人拥
有的块肯定下载不到。我们可以看出,它的选择原理是比较简单但是又很有效的,优先下载
拥有人数最少的块就能够保证所有的块能够在最短的时间内尽可能得让更多的人拥有,换一
个术语说就是能尽快提高要下载的内容的副本率。
这一次我们分析了对等客户在下载的过程中,如何进行下载的策略控制。下一次将分析
致,然后处理interests列表。调用_shift_over把piece从interests列表中的一个元素转移
到另一个元素(同时还要保持其它变量的值的意义的一致性)。_shift_over做的事情就是从
第一个列表中删除一个元素,然后将其插入第二个元素随机的位置,同时维护
pos_in_interests值的意义。
PiecePicker.requested,哪一块已经开始下载了,这个在SingleDownload中会被调用
,它只是维护两个列表,started和seedstarted。
PiecePicker.next,可以说是PiecePicker中提供的最重要的功能,选择一块进行下载
。它选择的第一条原则是,已经开始下载的优先把它下载完(return choice(bests)及其前
面的代码)。它检查选择的两个数组,根据对方是否是种子选择一个数组。然后在所有的这
个数组中选择出自己想要的,检查它们的numinterests,即拥有此块的人数,选出拥有人数
最少的块,放入bests中,如果有并列的,则添加到bests,因此在这里结束后,bests中的
元素是所有正在下载的且自己想要的块中拥有人数最少的块的列表,那么就从中间随机选择
一个返回即可。选择的第二条原则是,当自己拥有的块数少于一定的数量时,随机选择自己
想要的块进行下载(第一阶段结束后的那个if块),因此它用到了那个scrambled列表,而当
自己所拥有的块数超过一定的值(config['rarest_first_cutoff'])后,执行第三阶段的方
案。选择的第三条原则是,优先选择下载拥有的人数最少的块,我们看到,它从interests
中第1个元素开始检查,选择最先找到的自己想要的块,第0个元素不用检查,因为没有人拥
有的块肯定下载不到。我们可以看出,它的选择原理是比较简单但是又很有效的,优先下载
拥有人数最少的块就能够保证所有的块能够在最短的时间内尽可能得让更多的人拥有,换一
个术语说就是能尽快提高要下载的内容的副本率。
这一次我们分析了对等客户在下载的过程中,如何进行下载的策略控制。下一次将分析
收到对方的下载请求后的处理方式等。