BT源代码学习心得(六):跟踪服务器(Tracker)的代码分析(初始化) -- 转贴自 wolfenstein (NeverSayNever)

来源:互联网 发布:portal软件 编辑:程序博客网 时间:2024/05/30 07:11

BT源代码学习心得(六):跟踪服务器(Tracker)的代码分析(初始化)
author:wolfenstein

    Tracker在BT中是一个很重要的部分。这个名词我注意到以前的文章中都是直接引用,没有翻译过来,想了一下,决定把它翻译成跟踪服务器。
    在BT下载中,种子文件表明了要下载的文件的信息和对它进行检查的消息摘要码,但是每个对等客户(peer,以后我把peer全部翻译成对等客户,以区别 client)要获取其它对等客户的信息时,还是要和跟踪服务器联系的。跟踪服务器上面不保存任何和种子所代表的内容有关的文件,它只记录所有下载该种子 的机器的IP地址,端口等信息,并在客户向它请求是返回一些这样的信息列表,具体的实际内容,由对等客户之间完成交互。
    跟踪服务器的代码实现在BitTorrent/track.py中,在bttrack.py中只是很简单得一行:
    track(argv[1:])
    这样就把参数传到track.py的track函数。track函数本身也比较简单,处理参数和相关的配置文件,建立一个RawServer,然后用 create_serversocket创建服务器套接字,然后开始服务。关于在BT中使用网络服务上次已经有很详细地介绍,这里不再重复。只是针对 tracker函数的具体情况,分析一下运行到listen_forever后的情况,首先,建立了Tracker对象,打开了在某个端口 (config['port'])侦听的网络服务,这个函数的处理对象是一个HTTPHandler。所以我们要分析程序的流程只需要先分析 Tracker的初始化函数,看看它创建后都做了些什么,然后再看HTTPHandler实际分析它的网络协议。
    在Tracker对象的初始化函数中,首先还是对各种变量的初始化。然后要从一个状态文件中进行一些状态恢复,也就是恢复state变量。这个变量中的值 很重要,我们可以需要从一些地方来得知它的结构,状态文件的读取和保存出得不到它的信息,因为这两处的实现方式就是bencode和bdecode,只能 保证无论state的结构是什么都能合适得被保存和恢复,由此又看出bencode编码设计的巧妙。但是有一个函数对我们分析state的内部结构很有帮 助,那就是statefiletemplate,这个函数检查state中的值是否合法,因此我们可以从这里得到state的一些结构信息。
    首先,state必须是一个字典类型的变量。然后检查每一项的值。如果发现一项关键字是'peers',那么它的值必须也是一个字典,这个字典是一个以种 子文件的信息部分的消息摘要值为关键字的字典,由于sha摘要算法比较好得满足了摘要算法的要求,即不同的种子文件它们生成相同摘要的概率极小。而且由于 这是由种子文件的内容生成的摘要值,因此即使把种子文件改名,还是可以识别出来是哪个种子文件。因此'peers'的值可以看成是为每一个种子文件记录的 信息,那么为每个种子文件记录的是什么信息呢?这个信息又是一个字典,这次以每个对等客户的ID为关键字,每个对等客户在连接到跟踪服务器的时候都会为自 己生成一个ID,这个ID怎么生成的以后看客户端的代码可以知道,现在我们知道的是,它的长度必须为20。这个字典的值,嗯,又是个字典,不过这个字典的 意义就明显多啦,包括了IP是多少,端口是多少,还剩多少没有下载完。因此state的内容可以看成是这样的:{'peers':{},...},其中 peers的结构是这样的:{hash1:{ID1: {'ip':xxx.xxx.xxx.xxx,'port':xxxx,left:XXXX},ID2: {'ip':yyy.yyy.yyy.yyy,'port':yyyy,left:YYYY},...},hash2:{...},...}。以上是 state中'peers'这一项。'completed'这一项就相对结构简单了,它记录的是每个种子文件的下载完成情况,它的结构是个字典,以每个种 子的信息部分的消息摘要值为关键字,而对应的值就是一个整数,表示该种子文件已经有多少人完成了下载。接下来是'allowed'项,这项记录了该跟踪服 务器所关注的所有的种子的信息,仍然以信息部分的消息摘要值为关键字,内容就是该种子文件的实际信息,从后面的分析(对 BitTorrent/parsedir.py的分析)可以知道是哪些信息,另外由于之前对种子文件的内部结构我们已经比较清楚,所以也可以猜出部分。 state中还有'allowed_dir_files'项,这一项也是记录文件信息的字典,但它是以每个文件的文件名为关键字(而不是消息摘要值),每 个文件的项目是一个列表,结构如下:[(文件修改时间,文件大小),消息摘要值],就是说,这个以文件名为关键字的字典它的每一个值都是一个列表,这个列 表有两个元素,第一个元素是一个二元组,内容是文件修改时间和文件大小,第二个元素是消息摘要值。最后,我们注意到statefiletemplate在 处理'allowed'项和'allowed_dir_files'项时还有一些额外的检查代码,即所有在'allowed'项里面出现的元素,它的消息 摘要值都必须在'allowed_dir_files'项中出现,且'allowed_dir_files'中所有的项中的值的消息摘要部分必须在 'allowed'中出现,另外'allowed_dir_files'中不得出现重复的消息摘要值('allowed'项本身就以消息摘要值为关键字, 而字典的关键字已经保证不会重复)。
    因此现在我们知道了state中的注意部分的结构。下面我们注意这两句:
    self.downloads    = self.state.setdefault('peers', {})
    self.completed    = self.state.setdefault('completed', {})
    这样就把state中的'peers'和'completed'的值传到了downloads和completed中,更重要的是,以后在跟踪服务器的运 行过程中,如果'peers'和'completed'的值发生改变(那简直是一定的),state中的相应值也会发生变化,这样,保存dfile时,就 可以及时更新state的值了。以后我们分析跟踪服务器运行过程的时候少不了和它们打交道,现在我们可以先记住,downloads保存了所有的下载的客 户端的信息,completed保存所有的种子的下载完成情况的统计信息。
    下面的这个for循环根据配置文件处理NAT的问题,以及计算种子的个数。completed只是记录所有下载完成的客户的数目,而只有已经下载完成 (left=0),但是还在downloads中出现(即下载完毕但是没有关闭客户端)的客户端才算是一个种子。这里我们可以很容易得看 出,seedcount是一个以信息摘要为关键字,整型为值的统计种子数的一个字典。
    下面是一个计算的变量,times表示了每个种子(以信息摘要为关键字)中每个客户(以客户ID为关键字)的上次的有活动的时间。接下来增加了两个任务,每隔一段时间保存一下dfile,并且检查下载的客户端是否已经有很长时间没有反应的。
    接下来准备一个日志文件,并试图把标准输出重定向到这个日志文件中。
    最后要去寻找该跟踪服务器所关注的所有的种子,即parsedir,这个函数可以自己去看,相信在知道了种子文件的编码格式和前面的状态中的项的要求后, 不难分析。总得说来,这个函数做了以下事情,即寻找某个目录下所有的.torrent文件,把这些文件中的信息读取进来,并且排除错误,重复等等不合要求 的,然后进行加工,输出符合要求的结果,储存在allowed和allowed_dir_files中,进而影响state。
    现在tracker对象已经建立起来,它已经有它要进行跟踪的所有种子的信息,并且准备好了维护所有连接进来的客户的列表,因此它可以正式开始提供跟踪服务了。下一次我们就可以看看tracker动起来的效果。

原创粉丝点击