BT源代码学习心得(七):跟踪服务器(Tracker)的代码分析(HTTP协议处理对象)
BT源代码学习心得(七):跟踪服务器(Tracker)的代码分析(HTTP协议处理对象)
发信人: wolfenstein (NeverSayNever), 个人文集
标 题: BT源代码学习心得(七):跟踪服务器(Tracker)的代码分析(HTTP协议处理对象)
发信站: 水木社区 (Mon Aug 8 21:45:42 2005), 文集
标 题: BT源代码学习心得(七):跟踪服务器(Tracker)的代码分析(HTTP协议处理对象)
发信站: 水木社区 (Mon Aug 8 21:45:42 2005), 文集
(本文包含HTML标记,终端模式下可能无法正确浏览)
上次我们分析了Tracker类初始化的过程,现在开始具体看跟踪服务器是如何提供服务
的。
首先分析Tracker处理对象是HTTPHandler,它定义在BitTorrent/HTTPHandler.py中,
这个对象的初始化函数很简单,只是把Tracker.get函数赋值到自己的一个内部变量备用。
当有外部网络连接到达时,根据前面对RawServer的分析,我们知道,
HTTPHandler.external_connection_made函数会被调用,它维护了自己内部的一个字典
connections,以传进来的参数connection(它的类型是SingleSocket)为关键字,值为一个
新建立的HTTPConnection,新建立的HTTPConnection也主要是进行一些值的初始化,另外注
意这句:
self.next_func = self.read_type
这个变量被指向自己的一个函数,后面我们还会看到,它还会发生变化,以灵活处理数
据的不同部分。
现在可以分析客户端和跟踪服务器的网络通讯协议了。当有数据到达时,
HTTPHandler.data_came_in会被调用,从它的代码中我们可以一眼看出,起主要作用的其实
是该网络连接对应的HTTPConnection的data_came_in函数,它首先检查donereading标志和
next_func函数,即如果已经完成读的操作或者没有next_func来处理下一步,都直接返回,
然后将data(网络中读到的数据)添加到自己内部的buf中,下面是一个while循环,可以看出
,它的做法是每次从网络数据中寻找\n值,以该值做为两个不同的处理单元,然后将这个回
上次我们分析了Tracker类初始化的过程,现在开始具体看跟踪服务器是如何提供服务
的。
首先分析Tracker处理对象是HTTPHandler,它定义在BitTorrent/HTTPHandler.py中,
这个对象的初始化函数很简单,只是把Tracker.get函数赋值到自己的一个内部变量备用。
当有外部网络连接到达时,根据前面对RawServer的分析,我们知道,
HTTPHandler.external_connection_made函数会被调用,它维护了自己内部的一个字典
connections,以传进来的参数connection(它的类型是SingleSocket)为关键字,值为一个
新建立的HTTPConnection,新建立的HTTPConnection也主要是进行一些值的初始化,另外注
意这句:
self.next_func = self.read_type
这个变量被指向自己的一个函数,后面我们还会看到,它还会发生变化,以灵活处理数
据的不同部分。
现在可以分析客户端和跟踪服务器的网络通讯协议了。当有数据到达时,
HTTPHandler.data_came_in会被调用,从它的代码中我们可以一眼看出,起主要作用的其实
是该网络连接对应的HTTPConnection的data_came_in函数,它首先检查donereading标志和
next_func函数,即如果已经完成读的操作或者没有next_func来处理下一步,都直接返回,
然后将data(网络中读到的数据)添加到自己内部的buf中,下面是一个while循环,可以看出
,它的做法是每次从网络数据中寻找\n值,以该值做为两个不同的处理单元,然后将这个回
车前面的部分赋值到val,后面的部分赋值到buf(就相当于buf在这个回车前面的部分砍掉,
剩下的留待下一次处理),然后将这个val交由next_func处理,处理的结果返回给
next_func,意思就是在next_func里处理完这部分值后,它很清楚下面一部分该由哪个函数
处理,然后将next_func重新定向到它就行了,最后进行一些检查看看还要不要继续处理。
这个函数我们可以看出,设计得比较巧妙,能够自动得把一个协议的不同的部分分到不同的
函数进行处理,而且即使网络阻塞了,只来了一部分数据,下次又来一部分数据,只要它和
buf一整合,next_func永远指向处理下一部分数据的函数。
从HTTPConnection的初始化过程我们知道,第一部分的数据处理的函数read_type,首
先去除空格,然后把它们按照空格符分开,如果有三个词,那么认定它的格式为command
path garbage,否则,认为是command path。然后检查command必须是GET或者HEAD,现在也
已经可以猜出来path应该是一个URL路径,至此,我们可以看出,客户端和跟踪服务器的通
信协议其实就是HTTP协议。接下来就是read_header来读取HTTP的头部。它首先看有没有数
据,如果有的话,很简单,只是维护一个字典headers,且寻找到':',':'之前的就是关键
字,之后的就是值,然后next_func还是read_header,就是说,剩下的数据都是一行一行的
头部信息。全部读完后,检查headers里面有没有accept-encoding项,这项指定返回的数据
的编码方式,只有两种,普通模式('identity')和压缩模式('gzip'),然后调用getfunc,
其实就是Tracker.get来正式处理用户的HTTP请求,而且已经把请求转化成比较方便的参数
,即path(用户的请求URL)和headers信息。处理完毕后,如果返回的结果不是None的话,则
调用answer把处理结果返回给用户。
我们先看answer,看到它的参数,我们就知道,它把返回的结果转化成HTTP协议的要求
。传给它的参数是一个元组,包含回应代码,回应字符串,头部数据,正文数据四部分。它
首先看是否要压缩,如果是的话,就进行压缩,但是压缩后它把压缩后的数据和之前的数据
剩下的留待下一次处理),然后将这个val交由next_func处理,处理的结果返回给
next_func,意思就是在next_func里处理完这部分值后,它很清楚下面一部分该由哪个函数
处理,然后将next_func重新定向到它就行了,最后进行一些检查看看还要不要继续处理。
这个函数我们可以看出,设计得比较巧妙,能够自动得把一个协议的不同的部分分到不同的
函数进行处理,而且即使网络阻塞了,只来了一部分数据,下次又来一部分数据,只要它和
buf一整合,next_func永远指向处理下一部分数据的函数。
从HTTPConnection的初始化过程我们知道,第一部分的数据处理的函数read_type,首
先去除空格,然后把它们按照空格符分开,如果有三个词,那么认定它的格式为command
path garbage,否则,认为是command path。然后检查command必须是GET或者HEAD,现在也
已经可以猜出来path应该是一个URL路径,至此,我们可以看出,客户端和跟踪服务器的通
信协议其实就是HTTP协议。接下来就是read_header来读取HTTP的头部。它首先看有没有数
据,如果有的话,很简单,只是维护一个字典headers,且寻找到':',':'之前的就是关键
字,之后的就是值,然后next_func还是read_header,就是说,剩下的数据都是一行一行的
头部信息。全部读完后,检查headers里面有没有accept-encoding项,这项指定返回的数据
的编码方式,只有两种,普通模式('identity')和压缩模式('gzip'),然后调用getfunc,
其实就是Tracker.get来正式处理用户的HTTP请求,而且已经把请求转化成比较方便的参数
,即path(用户的请求URL)和headers信息。处理完毕后,如果返回的结果不是None的话,则
调用answer把处理结果返回给用户。
我们先看answer,看到它的参数,我们就知道,它把返回的结果转化成HTTP协议的要求
。传给它的参数是一个元组,包含回应代码,回应字符串,头部数据,正文数据四部分。它
首先看是否要压缩,如果是的话,就进行压缩,但是压缩后它把压缩后的数据和之前的数据
进行长度比较,如果压缩后数据反而更长,那么就不压缩了。接下来是进行日志的记录,诸
如某年某月某日某时某分某人在这里请求了某物,返回了某些数据等等。前面我们注意到在
Tracker初始化的时候已经把标准输出重定向到日志文件中了,因此这里的print其实就是往
日志文件中写。然后用一个StringIO来处理字符串的操作,可以不断得往里面write,我们
看到,程序按照标准的HTTP应答格式("HTTP 1.0 XXX ResponseStringBlablabla..\n")的格
式,全部处理完后,一次性地往connection里write,把它传送到网络里,RawServer里面已
经帮我们处理好了网络阻塞之类的问题,然后检查,如果数据全部写出去了,那么就关闭这
个连接。HTTP协议也确实是这样的,一个请求,一个回应,就完成了。
现在我们可以看到,在BT中客户端和跟踪服务器之间的通信协议就是HTTP协议,而且
HTTPHandler和HTTPConnection已经把HTTP的很细节的部分全部都处理好了,这就意味着
Tracker.get已经得到了一个连接对象,一个用户请求的地址,以及一个字典类型的HTTP请
求头部数据,并且这个函数只需要专心得完成处理,并把处理的结果以包含HTTP回应代码
(200,404,500等),回应字符串(如Not Found,这样和前面的代码合起来就是HTTP 1.0 404
Not Found),HTTP回应头部数据和正文数据的四元组返回即可。
下一次,我们就可以很仔细得看Tracker到底是如何得处理用户请求了。
如某年某月某日某时某分某人在这里请求了某物,返回了某些数据等等。前面我们注意到在
Tracker初始化的时候已经把标准输出重定向到日志文件中了,因此这里的print其实就是往
日志文件中写。然后用一个StringIO来处理字符串的操作,可以不断得往里面write,我们
看到,程序按照标准的HTTP应答格式("HTTP 1.0 XXX ResponseStringBlablabla..\n")的格
式,全部处理完后,一次性地往connection里write,把它传送到网络里,RawServer里面已
经帮我们处理好了网络阻塞之类的问题,然后检查,如果数据全部写出去了,那么就关闭这
个连接。HTTP协议也确实是这样的,一个请求,一个回应,就完成了。
现在我们可以看到,在BT中客户端和跟踪服务器之间的通信协议就是HTTP协议,而且
HTTPHandler和HTTPConnection已经把HTTP的很细节的部分全部都处理好了,这就意味着
Tracker.get已经得到了一个连接对象,一个用户请求的地址,以及一个字典类型的HTTP请
求头部数据,并且这个函数只需要专心得完成处理,并把处理的结果以包含HTTP回应代码
(200,404,500等),回应字符串(如Not Found,这样和前面的代码合起来就是HTTP 1.0 404
Not Found),HTTP回应头部数据和正文数据的四元组返回即可。
下一次,我们就可以很仔细得看Tracker到底是如何得处理用户请求了。