前言:
自从7月份写完“客户端源码分析之五:Encoder 与 Connection 类”后,我就停止了继续对BT源码的分析。原因很多,最主要的还是懒惰吧。临到岁末,终于下定决心,无论如何,要完成这一系列的文章,对自己也算有个交待。
前面的几篇文章,都是深入到源码的某一部分细节之中,虽然很清晰,但无助于读者对整体构架的把握(其实我自己当时也比较糊涂)。这一次重新开始读源码,重
点顺着几条线索往下读,感觉原来杂乱无序的代码突然变得清晰明了起来,呵呵,其实不是代码杂乱,只是我原来的阅读思路比较混乱的缘故。
读者可以抛开前面几篇文章,从这一篇开始往下读,希望能有所收获。
源码分析类的文章,比较难写,小马哥毕竟没有候捷的春秋笔法(甚至连作文都写不好),能把一个深奥复杂的STL源码分析的如此透彻,所以只好尝试一些傻办法,这些傻办法包括:
在源码上直接加注释。但我只对重点的部分增加一些注释,细节的东西就不再深究,这样有助于写作的进度。
用①、②、③这样的序号来指引代码的阅读线索
用不同颜色标注出代码中值得注意的地方。
你有什么好的建议,欢迎提出来。
好,我们进入正题。
BT客户端的 main() 函数:
C
和c++的可执行程序,通常都有一个 main() 函数,一切从这里开始。而 python 这种解释性语言,并没有 main()
函数的概念。你传递给解释器一个 .py
扩展名的python源码文件,解释器就会顺序去解释执行这个文件中的代码。所以,BT客户端的执行,是从最先被 python
解释器解释执行的那个文件开始的。这个文件应该是 btdownloadheadless.py,至于谁又来通知让 python
解释器执行这个文件的,我们以后再讨论。
【btdownloadheadless.py】(在BT源码的根目录下)
②def run(params):
try:
import curses
curses.initscr()
cols = curses.COLS
# endwin() De-initialize the library, and return terminal to normal status
curses.endwin()
except:
cols = 80
h = HeadlessDisplayer()
# 调用 download.py 中的 download 函数,我们的重点将转移到 download.py 文件,注意,该文件及以后要分析的代码都在 BT源码的 BitTorrent 子目录下。
③ download(params, h.chooseFile, h.display, h.finished, h.error, Event(), cols, h.newpath)
if not h.done:
h.failed()
# 所有的 python的入门书籍中,都会告诉你下面这段代码的含义。这就是最先被解释执行的代码所在。
①if __name__ == '__main__':
run(argv[1:])
【download.py】
这个文件中,最主要的就是 download() 函数,客户端所有的一切都是从这里开始。这个函数代码比较多,我必须挑出其中最重要的代码,其它的部分,后续再分析。
④rawserver = RawServer(doneflag, config['timeout_check_interval'], config['timeout'], errorfunc
= errorfunc, maxconnects = config['max_allow_in'])
# 选择一个可用的端口作为监听端口
for listen_port in xrange(config['minport'], config['maxport'] + 1):
try:
rawserver.bind(listen_port, config['bind'])
break
except socketerror, e:
pass
else:
errorfunc("Couldn't listen - " + str(e))
return
encoder = Encoder(connecter, rawserver,
myid, config['max_message_length'], rawserver.add_task,
config['keepalive_interval'], infohash, config['max_initiate'])
rawserver.listen_forever(encoder)
客
户端的核心类就是 RawServer,这个类我在“服务器端源码分析”(可在论坛中找到)文章中分析过它的作用,这里不再赘述。通过调用它的
listen_forever() 函数,BT
客户端就进入了一个循环之中。此后,所有的下载、上传、文件存储以及其它工作都在这一次次的循环之中完成。
注意到,在进入循环之前,调用了
RawServer::bind() 函数,BT客户端会选择一个可用的端口,然后通过监听这个端口,从而可以接受其它 peer
的连接请求(也就是BT对等连接)。这个端口可以称为“监听端口”。如果你有网络服务器的编程经验,从它的循环处理逻辑、监听端口的处理可以知道,这显然
就是一个典型的网络服务器的表现。所以,我在以前的文章中曾经说过,BT客户端同时也是一个服务器。
一旦在监听端口上有某个peer发来连接请
求,BT客户端会创建一个新的端口,这个端口用来与请求者建立连接;有几个peer请求连接,就会创建几个端口。我们可以说这是一些“被动端口”。在循环
的处理过程中,BT客户端还会根据情况,向其它
peer主动发出连接请求,每个连接也需要一个端口,这些端口可以称为“主动端口”。其实,这些都是网络编程中最基本的概念,不清楚的朋友还是首先要去看
看这方面的书籍。
这样,就有了一个“监听端口”,几个“被动端口”和几个“主动端口”,BT客户端通过 select()
函数来监视这些端口,一旦“监听端口”或者“被动端口”上有数据到来,或者有数据需要从“主动端口”上发送出去,那么select()
都可以及时感知并进行处理(其实是一个轮询的过程),这就称为“I/O多路复用”。如果“被动端口”上有数据到来,那么这是BT对等连接的协议数据,需要
按照BT对等协议进行分析,并根据分析结果进行相应处理,这个分析处理的工作就是由 Encoder 类来完成的。所以在
listen_forever() 函数中传递的参数就是一个 Encoder 类对象。关于对 Encoder
类的分析,以后分析到BT对等协议的处理的时候再说。
小结:
通过这篇文章,我们了解了BT客户端是从哪里执行的,相应的源码在哪
里。同时我们知道了BT客户端是以一个服务器循环的形式运行的,它需要监听一个端口,用于接受其它peers的连接请求;在循环过程中,它通过
select 来实现“I/O多路复用”;并由 Encoder 类来完成BT对等协议的分析处理。
posted on 2007-01-19 00:22
苦笑枯 阅读(778)
评论(0) 编辑 收藏 所属分类:
P2P