BT源代碼學習心得(十二):客戶端源代碼分析(從開始到連接建立階段)

BT源代碼學習心得(十二):客戶端源代碼分析(從開始到連接建立階段)

發信人: wolfenstein (NeverSayNever), 個人文集
標  題: BT源代碼學習心得(十二):客戶端源代碼分析(從開始到連接建立階段)
發信站: 水木社區 (Tue Aug 16 20:38:34 2005), 文集
(本文包含HTML標記,終端模式下可能無法正確瀏覽)
    這一次開始恢復按照過程進行描述,即從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,通過創建一個新的線程,然後讓它開始運行
,接下來yield None,注意,從這一句開始,其實就已經返回了。hashcheck函數將在新的
線程開始執行,我們來看看hashcheck函數中都幹了什麼,主要就是創建了一個
StorageWrapper類,它初始化時就已經對硬盤上有的內容確定下來了。然後,它執行了
_contfunc()!沒錯,執行它的效果就是從yield None後面的部分繼續執行下去了,但是,
這時已經是在另一個線程中。接下來創建一個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中有沒有關鍵字'failure reason',如果有的話,那就是說到跟蹤服
務器的網絡沒有問題,但是跟蹤服務器返回了其它的失敗原因,這樣還是一種失敗的情況。
下面就是把r中的關鍵字爲'peers'部分的數據解析出來了,這部分傳回來的數據有可能是緊
湊的字符串也有可能是一個字典,在跟蹤服務器的代碼分析中我們可以看到這一點。最後就
可以調用connect函數試圖逐個得與對等客戶建立聯繫了。connect函數實際上是
Encoder.start_connection。
   下一次就可以開始分析兩個對等客戶之間的通信協議了。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章