BT源代码学习心得(十二):客户端源代码分析(从开始到连接建立阶段)
Author:wolfenstein
这一次开始恢复按照过程进行描述,即从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,通过创建一个新的线程,然后让它开始运行,接下来yieldNone,注意,从这一句开始,其实就已经返回了。hashcheck函数将在新的线程开始执行,我们来看看hashcheck函数中都干了什么,主要就是创建了一个StorageWrapper类,它初始化时就已经对硬盘上有的内容确定下来了。然后,它执行了_contfunc()!没错,执行它的效果就是从yieldNone后面的部分继续执行下去了,但是,这时已经是在另一个线程中。接下来创建一个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中有没有关键字'failurereason',如果有的话,那就是说到跟踪服务器的网络没有问题,但是跟踪服务器返回了其它的失败原因,这样还是一种失败的情况。
下面就是把r中的关键字为'peers'部分的数据解析出来了,这部分传回来的数据有可能是紧凑的字符串也有可能是一个字典,在跟踪服务器的代码分析中我们可以看到这一点。最后就可以调用connect函数试图逐个得与对等客户建立联系了。connect函数实际上是Encoder.start_connection。
下一次就可以开始分析两个对等客户之间的通信协议了。