BT用戶端源碼分析

來源:互聯網
上載者:User

BT用戶端源碼分析之一:總述

概述:

相對於 tracker 伺服器來說,BT用戶端要複雜的多,Bram Cohen 花了一年 full time 的時間來完成 BT,我估計其中大部分時間是用在 BT 用戶端的實現和調試上了。

由於 BT 用戶端涉及的代碼比較多,我不能再象分析 tracker 伺服器那樣,走上來就深入到細節之中去,那樣的話,我寫的暈暈糊糊,大家看起來也不知所云。所以第一篇文章先來談談用戶端的功能、相關協議,以及用戶端的總體架構和相關的類的階層。這樣,從整體上把握之後,大家在自己分析代碼的過程中,就能做到胸有成竹。

用戶端的功能:

不看代碼,只根據 BT 的相關原理,大致可以推測,用戶端需要完成以下功能:

1、解析 torrent 檔案,擷取要下載的檔案的詳細資料,並在磁碟上建立空檔案。

2、與 tracker伺服器 建立串連,並互動訊息。

3、根據從 tracker 得到的資訊,跟其它 peers 建立串連,並下載需要的檔案片斷

4、監聽某連接埠,等待其它peers 的串連,並提供檔案片斷的上傳。

相關協議:

對用戶端來說,它需要處理兩種協議:

1、與 tracker 伺服器互動的 track HTTP協議。

2、與其它 peers 互動的 BT 對等協議。

總體架構::

從總體上來看,BT用戶端實際是以一個伺服器的形式在運行。這一點似乎有些難以理解,但確實是這樣。

為什麼是一個伺服器了?

用戶端的主要功能是下載檔案,但作為一種P2P軟體,同時它必須提供上傳服務,也就是它必須守候在某一個連接埠上,等待其它peers 的串連請求。從這一點上來說,它必須以一個伺服器的形式運行。我們在後面實際分析代碼的時候,可以看到,用戶端複用了 RawServer 類用來實現網路伺服器。

用戶端的代碼,是從 download.py 開始的,首先完成功能1,之後就進入伺服器迴圈,在每一次迴圈過程中,完成功能 2、3、4。其中,Rerequester 類負責完成功能2,它通過 RawServer::add_task(),向 RawServer 添加自己的任務函數,這個任務函數,每隔一段時間與 tracker 伺服器進行通訊。而Encoder、Connecter 等多個類組合在一起,完成功能3和4。

類階層:

BT 用戶端涉及的類比較多,我首先大致描述一下這些類的功能,然後給出它們的一個階層。

1、RawServer:負責實現網路伺服器

2、Rerequester:負責和 tracker 通訊。它調用 RawServer::add_task() ,向 RawServer 添加自己的任務函數 Rerequester::c()。

3、Encoder:一種 Handler類(在分析 tracker 伺服器時候提到),負責處理與其它peers建立串連和以及對讀取的資料按照BT對等協議進行分析。

Encoder 類在Encrypter.py中,該檔案中,還有一個 Connection 類,而在 Connecter.py 檔案中,也有一個 Connection 類,這兩個同名的 Connection 類有些蹊蹺,為了區分,我把它們重新命名為 E-Connection 和 C-Connection。

3.1、E-Connection:負責 TCP 層次上的串連工作

這兩個 Connection 是有區別的,這是因為BT對等協議需要在兩個層次上建立串連。首先是 TCP 層次上的串連,也就是經過 TCP 的三向交握之後,建立串連,這個串連由 E-Connection 來管理。在 Encoder:: external_connection_made() 函數中可以看到,一旦有外部串連到來,則建立一個 E-Connection 類。

3.2、C-Connection:管理對等協議層次上的串連。

在 TCP 串連之上,是 BT對等協議的串連,它需要經過BT對等協議的兩次“握手”,握手的細節大家去看BT對等協議。過程是這樣的:

為了便於述說,我們假設一個BT用戶端為 A,另一個用戶端為 X。

如果是X主動向A發起串連,那麼在TCP串連建立之後,A立刻利用這個串連向X發送BT對等協議的“握手”訊息。同樣,X在串連一旦建立之後,向 A發送BT對等協議的“握手”訊息。A一旦接收到X的“握手”訊息,那麼它就認為“握手”成功,建立了BT對等協議層次上的串連。我把它叫做“對等串連”。A 發送了一個訊息,同時接收了一個訊息,所以這個握手過程是兩次“握手”。

同樣,對X 來說,因為串連是它主動發起的,所以它在發送完“握手”訊息之後,就等待A的“握手”訊息,如果收到,那麼它也認為“對等串連”建立了。

一旦“對等串連”建立之後,雙方就可以通過這個串連傳遞訊息了。

這樣,原來我所疑惑的一個問題也就有了答案。就是:如果 X 需要從 A 這裡下載資料,那麼它會同 A 建立一個串連。假如 A 又希望從 X 那裡下載資料,它需不需要重新向 X 發起另外一個串連了?答案顯然是不用,它會利用已有的一條串連。

也就是說,不管是X主動向A發起的串連,還是 A 主動向 X發起的串連,一旦建立之後,它們的效果是一樣的。這個同我們平時做 C/S結構的網路開發是有區別的。

我們可以看到在 E-Connection的初始化函數中,會主動串連的另一方發送“握手”訊息,在 E-Connection::data_came_in() 中,會首先對對方的“握手”訊息進行處理。這正是我上面所描述的情形。

在 E-Connection::read_peer_id() 中,是對“握手”訊息的最後一項 peer id進行處理,一旦正確無誤,那麼就認為“對等串連”完成,

self.encoder.connecter.connection_made(self)

在 Connecter::connection_made() 函數中,就建立了管理“對等串連”的 C-Connectinon類。所以,更高一層的“對等串連”是由 C-Connection 來管理的。

3.3、Connecter:連接器,管理下載、上傳、阻塞、片斷選擇、讀寫磁碟等等。

下載和上傳不是孤立的,它們之間相互影響。下載需要有片斷選擇演算法,上傳的時候要考慮阻塞,片斷下載之後,要寫到磁碟上。上傳的時候,也需要從磁碟讀取。v
這些任務,是由 Connecter 來統一調度的。

類階層,我用縮排來表示一種內含項目關聯性。

Encoder:
   E-Connection
       C-Connection
           Upload
           SingleDownloader
       Connecter
           Choker:負責阻塞的管理
           Downloader:
               SingleDownloader
               Picker:片斷選擇策略
               StorageWrapper:

先寫這些吧,有什麼我再補充進來。

原始碼分析2~8 (Word文檔壓縮)

Tracker 伺服器源碼分析之一:總述

tracker伺服器是BT下載中必須的角色。一個BT client 在下載開始以及下載進行的過程中,要不停的與 tracker 伺服器進行通訊,以報告自己的資訊,並擷取其它下載client的資訊。這種通訊是通過 HTTP 協議進行的,又被稱為 tracker  HTTP 協議,它的過程是這樣的:

client 向 tracker 發一個HTTP 的GET請求,並把它自己的資訊放在GET的參數中;這個請求的大致意思是:我是xxx(一個唯一的id),我想下載yyy檔案,我的ip是aaa,我用的連接埠是bbb。。。

tracker 對所有下載者的資訊進行維護,當它收到一個請求後,首先把對方的資訊記錄下來(如果已經記錄在案,那麼就檢查是否需要更新),然後將一部分(並非全部,根據設定的參數以及下載者的請求)參與下載同一個檔案(一個tracker伺服器可能同時維護多個檔案的下載)的下載者的資訊返回給對方。

Client在收到tracker的響應後,就能擷取其它下載者的資訊,那麼它就可以根據這些資訊,與其它下載者建立串連,從它們那裡下載檔案片斷(BT把一個檔案切分成多個片斷)。

關於client和tracker之間通訊協定的細節,在“BT協議規範”中已經給出,這裡不再重複。下面我們具體分析 tracker伺服器的實現細節。

從哪裡開始?

要建立一個 tracker伺服器,只要運行 bttrack.py 程式就行了,它最少需要一個參數,就是 –dfile,這個參數指定了儲存下載資訊的檔案。Bttrack.py 調用 track.py 中的 track()函數。因此,我們跟蹤到 track.py 中去看track() 函數。

Track.py:track()

這個函數首先對命令列的參數進行檢查;然後將這些參數儲存到 config 字典中。在BT中所有的工具程式,都有類似的處理方式。

接下來的代碼:

   r = RawServer(Event(), config['timeout_check_interval'], config['socket_timeout'])

   t = Tracker(config, r)

   r.bind(config['port'], config['bind'], True)

   r.listen_forever(HTTPHandler(t.get, config['min_time_between_log_flushes']))

   t.save_dfile()

首先是建立一個 RawServer 對象,這是一個伺服器對象,它將實現一個網路伺服器的一些細節封裝起來。不僅tracker伺服器用到了 RawServer,我們以後還可以看到,由於每個 client端也需要給其它 client 提供下載服務,因此也同時是一個伺服器,client的實現中,也用到了RawServer,這樣,RawServer的代碼得到了重用。關於 RawServer的詳細實現,在後面的小節中進行分析。

接著是建立一個 Tracker對象。

然後讓RawServer綁定在指定的連接埠上(通過命令列傳遞進來)。

最後,調用 RawServer::listen_forever() 函數,使得伺服器投入運行。

最後,在伺服器因某些原因結束運行以後,調用 Tracker::save_dfile() 儲存下載資訊。這樣,一旦伺服器再次投入運行,可以恢複當前的狀態。

其它參考資訊:

1、BT源碼的分布:

把BT的源碼展開之後,可以看到有一些python程式,還有一些說明檔案等等,此外還有一個BitTorrent目錄。這些 python程式,實際是一些小工具,比如製作 metafile的btmakemetafile.py、運行tracker伺服器的bttrack.py、運行BT client端的 btdownloadheadless.py 等等。而這些程式中,用到的一些 python 類的實現,都放在子目錄 BitTorrent 下面。我們的分析工作,通常是從工具程式入手,比如 bttrack.py,而隨著分析的展開,則重點是看 BitTorrenet子目錄下的代碼。

BT作者 Bram Cohen 在談到如何開發可維護的代碼的一篇文章中(http://www.advogato.org/article/258.html),其中提到的一條就是開發一些小工具以簡化工作,我想BT的這種源碼結構,也正是作者思想的一種體現吧。

2、我們看到,python和我們以前接觸的 c/c++ 不一樣的第一個地方就是它的函數在定義的時候,不用指定參數類型。既然這樣,那麼,在調用函數的時候,你可以傳遞任意類型的參數進來。例如這樣的函數:

def foo(arg):

   print type(arg)

你可以這樣來調用:

a = 100
b = “hello world”
foo(a)
foo(b)

輸出結果是:0
type ‘int’
type ‘str’

這是因為,第一次調用 foo()的時候,傳遞的是一個整數類型,而第二次調用的時候,傳遞的是一個字串類型。

這種參數具有動態類型的特性,是 c/c++等傳統的語言是所不具備的。這也是 python 被稱為動態語言的一個原因吧。C++的進階特性模板,雖然也使得參數類型可以動態化,但使用起來,遠沒有python這麼簡單方便。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.