BT源代码学习心得(十二):客户端源代码分析(从开始到连接建立阶段)
BT源代码学习心得(十二):客户端源代码分析(从开始到连接建立阶段)
发信人: wolfenstein (NeverSayNever), 个人文集
标 题: BT源代码学习心得(十二):客户端源代码分析(从开始到连接建立阶段)
发信站: 水木社区 (Tue Aug 16 20:38:34 2005), 文集
标 题: BT源代码学习心得(十二):客户端源代码分析(从开始到连接建立阶段)
发信站: 水木社区 (Tue Aug 16 20:38:34 2005), 文集
(本文包含HTML标记,终端模式下可能无法正确浏览)
这一次开始恢复按照过程进行描述,即从Multitorrent.start_torrent函数的执行开始
。
通过前面的分析,我们知道当Multitorrent.start_torrent被调用时,一个新的种子下
载任务就开始了。这个函数本身很简单,就是创建(并返回)一个新的_SingleTorrent对象,
然后让其start_download方法开始调度。start_download这个函数一开始看上去有点奇怪,
其实这是作者设计的一个小技巧。python中的yield关键字可以使一个函数返回一个值,但
是它的内部执行状态仍然保存,这样下次调这个函数的时候,这个函数就继续从那里往下执
行。可以用诸如it = self._start_download(*args, **kwargs)这样的形式来确定一个迭代
器,注意在执行这一句的时候,_start_download并未得到执行。要使包含有yield的关键字
的函数得到执行,只需要反复调用it.next(),这将返回每次yield出来的值,当函数执行到
结尾时,将会抛出一个StopIteration异常,通过捕捉这个异常就可以知道函数以及执行完
毕。在start_download中干了以下事情,把一个函数赋值到_contfunc,并且执行了它一次
。这个函数的实际内容就是开始执行_start_download,为什么要这样费一下周折呢,这样
做的目的就是为了在合适的时候分出一个线程。到目前为止,程序还是只有一个主线程在运
行。继续往下看_start_download函数,根据元信息的文件列表和保存到硬盘上的地址,整
理出一个实际的文件列表,可以直接对它们进行存储。然后创建一个新的Storage对象,它
需要的文件名和大小的元组列表可以通过zip函数得到,这个函数的功能是从第一个参数(列
表类型)中获取第一个元素,然后和第二个参数的第一个元素组成一个元组,再将第一个参
这一次开始恢复按照过程进行描述,即从Multitorrent.start_torrent函数的执行开始
。
通过前面的分析,我们知道当Multitorrent.start_torrent被调用时,一个新的种子下
载任务就开始了。这个函数本身很简单,就是创建(并返回)一个新的_SingleTorrent对象,
然后让其start_download方法开始调度。start_download这个函数一开始看上去有点奇怪,
其实这是作者设计的一个小技巧。python中的yield关键字可以使一个函数返回一个值,但
是它的内部执行状态仍然保存,这样下次调这个函数的时候,这个函数就继续从那里往下执
行。可以用诸如it = self._start_download(*args, **kwargs)这样的形式来确定一个迭代
器,注意在执行这一句的时候,_start_download并未得到执行。要使包含有yield的关键字
的函数得到执行,只需要反复调用it.next(),这将返回每次yield出来的值,当函数执行到
结尾时,将会抛出一个StopIteration异常,通过捕捉这个异常就可以知道函数以及执行完
毕。在start_download中干了以下事情,把一个函数赋值到_contfunc,并且执行了它一次
。这个函数的实际内容就是开始执行_start_download,为什么要这样费一下周折呢,这样
做的目的就是为了在合适的时候分出一个线程。到目前为止,程序还是只有一个主线程在运
行。继续往下看_start_download函数,根据元信息的文件列表和保存到硬盘上的地址,整
理出一个实际的文件列表,可以直接对它们进行存储。然后创建一个新的Storage对象,它
需要的文件名和大小的元组列表可以通过zip函数得到,这个函数的功能是从第一个参数(列
表类型)中获取第一个元素,然后和第二个参数的第一个元素组成一个元组,再将第一个参
数和第二个参数的第二个元素组成一个元组等,最后变成了一个列表。然后进行“快速恢复
”的文件检查。接下来注意到函数hashcheck,通过创建一个新的线程,然后让它开始运行
,接下来yield None,注意,从这一句开始,其实就已经返回了。hashcheck函数将在新的
线程开始执行,我们来看看hashcheck函数中都干了什么,主要就是创建了一个
StorageWrapper类,它初始化时就已经对硬盘上有的内容确定下来了。然后,它执行了
_contfunc()!没错,执行它的效果就是从yield None后面的部分继续执行下去了,但是,
这时已经是在另一个线程中。接下来创建一个Choker,以及一些速度测量器。然后要创建一
个PiecePicker,初始化完成后,还要告诉PiecePicker哪些块已经拥有了
(PiecePicker.complete)以及哪些块已经下了一部分(PiecePicker.requested)。下面创建
一个Downloader对象,但是对于Upload,只是定义一个函数make_upload,它能够随时生成
一个Upload对象。然后创建一个Encoder对象,注意它把Downloader和make_upload做为参数
,从结构上来说,它们就被绑定到一起了。接下来要用add_torrent把一个种子文件(以
infohash为关键字)和它的Encoder绑定到一起,这样,当收到其它的对等客户的连接的时候
就能够知道对方要下载的是哪个种子文件了。最后创建Rerequester和DownloaderFeedback
这两个对象。
最后执行Rerequester.begin,启动它,让它开始和跟踪服务器交互,然后就可以调用
feedback接口的started函数,意思就是说,我们已经开始了,至于是用图形界面还是文字
界面向用户表示这一事实那就是feedback接口的事情了。
Rerequester。它的作用即为向跟踪服务器要对等客户的信息,前面通过对跟踪服务器
的代码分析已经对客户端和跟踪服务器之间的通信协议有了一个基本的了解。我们称和跟踪
服务器进行一个http请求并获取它的回应数据的过程称为一次发布(announce),
Rerequester的begin函数能够保证自己每隔60秒_check一次。我们来看_check一次要做什么
”的文件检查。接下来注意到函数hashcheck,通过创建一个新的线程,然后让它开始运行
,接下来yield None,注意,从这一句开始,其实就已经返回了。hashcheck函数将在新的
线程开始执行,我们来看看hashcheck函数中都干了什么,主要就是创建了一个
StorageWrapper类,它初始化时就已经对硬盘上有的内容确定下来了。然后,它执行了
_contfunc()!没错,执行它的效果就是从yield None后面的部分继续执行下去了,但是,
这时已经是在另一个线程中。接下来创建一个Choker,以及一些速度测量器。然后要创建一
个PiecePicker,初始化完成后,还要告诉PiecePicker哪些块已经拥有了
(PiecePicker.complete)以及哪些块已经下了一部分(PiecePicker.requested)。下面创建
一个Downloader对象,但是对于Upload,只是定义一个函数make_upload,它能够随时生成
一个Upload对象。然后创建一个Encoder对象,注意它把Downloader和make_upload做为参数
,从结构上来说,它们就被绑定到一起了。接下来要用add_torrent把一个种子文件(以
infohash为关键字)和它的Encoder绑定到一起,这样,当收到其它的对等客户的连接的时候
就能够知道对方要下载的是哪个种子文件了。最后创建Rerequester和DownloaderFeedback
这两个对象。
最后执行Rerequester.begin,启动它,让它开始和跟踪服务器交互,然后就可以调用
feedback接口的started函数,意思就是说,我们已经开始了,至于是用图形界面还是文字
界面向用户表示这一事实那就是feedback接口的事情了。
Rerequester。它的作用即为向跟踪服务器要对等客户的信息,前面通过对跟踪服务器
的代码分析已经对客户端和跟踪服务器之间的通信协议有了一个基本的了解。我们称和跟踪
服务器进行一个http请求并获取它的回应数据的过程称为一次发布(announce),
Rerequester的begin函数能够保证自己每隔60秒_check一次。我们来看_check一次要做什么
:首先要保证两次发布的时间间隔不能过短,另外要根据自己的peerid制作
url(_makeurl:http://xxx.xxxtracker.xxx:xxxx/announce?info_hash=xxxx&peer_id=xxxx
&port=xxxx&key=xxxx),根据不同的情况调用_announce进行一次发布。给_announce的参数
的意义是'event'参数的值,这些'event'的意义可以在跟踪服务器的代码分析中看到,它们
确定了下载的不同的阶段。因为平时也还需要经常补充一些对等客户的信息,所以
_announce会经常被调用。它的主要任务是对url进行进一步的加工,计算出当前发布时所用
的url,保存在s中,然后用一个新的线程开始执行发布,使用新的线程的原因是不希望到跟
踪服务器的网络阻塞影响到程序的其它部分的执行。_rerequest就基本上可以只管发出这个
http请求了,当然,它开始的部分代码是要排除一些自己的外部IP和实际IP不相同的这种情
况。Request是zurllib中的模块,可以很轻松地发送一个http请求,然后获取返回的信息。
根据是否出错来决定调用_postrequest的情况。这里出错仅仅是http请求本身发生错误,如
网络问题等,跟踪服务器也可能会返回一些其它的错误信息,我们可以在_postrequest中看
到。
_postrequest首先便是检查是否有错误信息,然后把data进行bdecode,这个过程我们
已经很熟悉了。接下来用check_peers检查看这是不是一个规范的对等客户信息数据,
check_peers在BitTorrent/btformats.py中定义,btformats.py还有其它的检查信息格式的
函数。下一步是检查r中有没有关键字'failure reason',如果有的话,那就是说到跟踪服
务器的网络没有问题,但是跟踪服务器返回了其它的失败原因,这样还是一种失败的情况。
下面就是把r中的关键字为'peers'部分的数据解析出来了,这部分传回来的数据有可能是紧
凑的字符串也有可能是一个字典,在跟踪服务器的代码分析中我们可以看到这一点。最后就
可以调用connect函数试图逐个得与对等客户建立联系了。connect函数实际上是
Encoder.start_connection。
url(_makeurl:http://xxx.xxxtracker.xxx:xxxx/announce?info_hash=xxxx&peer_id=xxxx
&port=xxxx&key=xxxx),根据不同的情况调用_announce进行一次发布。给_announce的参数
的意义是'event'参数的值,这些'event'的意义可以在跟踪服务器的代码分析中看到,它们
确定了下载的不同的阶段。因为平时也还需要经常补充一些对等客户的信息,所以
_announce会经常被调用。它的主要任务是对url进行进一步的加工,计算出当前发布时所用
的url,保存在s中,然后用一个新的线程开始执行发布,使用新的线程的原因是不希望到跟
踪服务器的网络阻塞影响到程序的其它部分的执行。_rerequest就基本上可以只管发出这个
http请求了,当然,它开始的部分代码是要排除一些自己的外部IP和实际IP不相同的这种情
况。Request是zurllib中的模块,可以很轻松地发送一个http请求,然后获取返回的信息。
根据是否出错来决定调用_postrequest的情况。这里出错仅仅是http请求本身发生错误,如
网络问题等,跟踪服务器也可能会返回一些其它的错误信息,我们可以在_postrequest中看
到。
_postrequest首先便是检查是否有错误信息,然后把data进行bdecode,这个过程我们
已经很熟悉了。接下来用check_peers检查看这是不是一个规范的对等客户信息数据,
check_peers在BitTorrent/btformats.py中定义,btformats.py还有其它的检查信息格式的
函数。下一步是检查r中有没有关键字'failure reason',如果有的话,那就是说到跟踪服
务器的网络没有问题,但是跟踪服务器返回了其它的失败原因,这样还是一种失败的情况。
下面就是把r中的关键字为'peers'部分的数据解析出来了,这部分传回来的数据有可能是紧
凑的字符串也有可能是一个字典,在跟踪服务器的代码分析中我们可以看到这一点。最后就
可以调用connect函数试图逐个得与对等客户建立联系了。connect函数实际上是
Encoder.start_connection。
下一次就可以开始分析两个对等客户之间的通信协议了。