前言:
自從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
苦笑枯 閱讀(784)
評論(0) 編輯 收藏 所屬分類:
P2P