BT客戶端源碼分析之一:總述
作者:小馬哥
日期:2004-6-24
概述:
相對于 tracker 服務器來說,BT客戶端要復雜的多,Bram Cohen 花了一年 full time 的時間來完成 BT,我估計其中大部分時間是用在 BT 客戶端的實現(xiàn)和調試上了。
由
于 BT 客戶端涉及的代碼比較多,我不能再象分析 tracker
服務器那樣,走上來就深入到細節(jié)之中去,那樣的話,我寫的暈暈糊糊,大家看起來也不知所云。所以第一篇文章先來談談客戶端的功能、相關協(xié)議,以及客戶端的
總體架構和相關的類的層次結構。這樣,從整體上把握之后,大家在自己分析代碼的過程中,就能做到胸有成竹。
客戶端的功能:
不看代碼,只根據(jù) BT 的相關原理,大致可以推測,客戶端需要完成以下功能:
1、解析 torrent 文件,獲取要下載的文件的詳細信息,并在磁盤上創(chuàng)建空文件。
2、與 tracker服務器 建立連接,并交互消息。
3、根據(jù)從 tracker 得到的信息,跟其它 peers 建立連接,并下載需要的文件片斷
4、監(jiān)聽某端口,等待其它peers 的連接,并提供文件片斷的上傳。
相關協(xié)議:
對客戶端來說,它需要處理兩種協(xié)議:
1、與 tracker 服務器交互的 track HTTP協(xié)議。
2、與其它 peers 交互的 BT 對等協(xié)議。
總體架構:
從總體上來看,BT客戶端實際是以一個服務器的形式在運行。這一點似乎有些難以理解,但確實是這樣。
為什么是一個服務器了?
客
戶端的主要功能是下載文件,但作為一種P2P軟件,同時它必須提供上傳服務,也就是它必須守候在某一個端口上,等待其它peers
的連接請求。從這一點上來說,它必須以一個服務器的形式運行。我們在后面實際分析代碼的時候,可以看到,客戶端復用了 RawServer
類用來實現(xiàn)網(wǎng)絡服務器。
客戶端的代碼,是從 download.py
開始的,首先完成功能1,之后就進入服務器循環(huán),在每一次循環(huán)過程中,完成功能 2、3、4。其中,Rerequester 類負責完成功能2,它通過
RawServer::add_task(),向 RawServer 添加自己的任務函數(shù),這個任務函數(shù),每隔一段時間與 tracker
服務器進行通信。而Encoder、Connecter 等多個類組合在一起,完成功能3和4。
類層次結構:
BT 客戶端涉及的類比較多,我首先大致描述一下這些類的功能,然后給出它們的一個層次結構。
1、RawServer:負責實現(xiàn)網(wǎng)絡服務器
2、Rerequester:負責和 tracker 通信。它調用 RawServer::add_task() ,向 RawServer 添加自己的任務函數(shù) Rerequester::c()。
3、Encoder:一種 Handler類(在分析 tracker 服務器時候提到),負責處理與其它peers建立連接和以及對讀取的數(shù)據(jù)按照BT對等協(xié)議進行分析。
Encoder 類在Encrypter.py中,該文件中,還有一個 Connection 類,而在 Connecter.py 文件中,也有一個 Connection 類,這兩個同名的 Connection 類有些蹊蹺,為了區(qū)分,我把它們重新命名為 E-Connection 和 C-Connection。
3.1、E-Connection:負責 TCP 層次上的連接工作
這兩個
Connection 是有區(qū)別的,這是因為BT對等協(xié)議需要在兩個層次上建立連接。首先是 TCP 層次上的連接,也就是經(jīng)過 TCP
的三次握手之后,建立連接,這個連接由 E-Connection 來管理。在 Encoder::
external_connection_made() 函數(shù)中可以看到,一旦有外部連接到來,則創(chuàng)建一個 E-Connection 類。
3.2、C-Connection:管理對等協(xié)議層次上的連接。
在 TCP 連接之上,是 BT對等協(xié)議的連接,它需要經(jīng)過BT對等協(xié)議的兩次“握手”,握手的細節(jié)大家去看BT對等協(xié)議。過程是這樣的:
為了便于述說,我們假設一個BT客戶端為 A,另一個客戶端為 X。
如
果是X主動向A發(fā)起連接,那么在TCP連接建立之后,A立刻利用這個連接向X發(fā)送BT對等協(xié)議的“握手”消息。同樣,X在連接一旦建立之后,向
A發(fā)送BT對等協(xié)議的“握手”消息。A一旦接收到X的“握手”消息,那么它就認為“握手”成功,建立了BT對等協(xié)議層次上的連接。我把它叫做“對等連
接”。A 發(fā)送了一個消息,同時接收了一個消息,所以這個握手過程是兩次“握手”。
同樣,對X 來說,因為連接是它主動發(fā)起的,所以它在發(fā)送完“握手”消息之后,就等待A的“握手”消息,如果收到,那么它也認為“對等連接”建立了。
一旦“對等連接”建立之后,雙方就可以通過這個連接傳遞消息了。
這樣,原來我所疑惑的一個問題也就有了答案。就是:如果 X 需要從 A 這里下載數(shù)據(jù),那么它會同 A 建立一個連接。假如 A 又希望從 X 那里下載數(shù)據(jù),它需不需要重新向 X 發(fā)起另外一個連接了?答案顯然是不用,它會利用已有的一條連接。
也就是說,不管是X主動向A發(fā)起的連接,還是 A 主動向 X發(fā)起的連接,一旦建立之后,它們的效果是一樣的。這個同我們平時做 C/S結構的網(wǎng)絡開發(fā)是有區(qū)別的。
我們可以看到在 E-Connection的初始化函數(shù)中,會主動連接的另一方發(fā)送“握手”消息,在 E-Connection::data_came_in() 中,會首先對對方的“握手”消息進行處理。這正是我上面所描述的情形。
在 E-Connection::read_peer_id() 中,是對“握手”消息的最后一項 peer id進行處理,一旦正確無誤,那么就認為“對等連接”完成,
self.encoder.connecter.connection_made(self)
在 Connecter::connection_made() 函數(shù)中,就創(chuàng)建了管理“對等連接”的 C-Connectinon類。所以,更高一層的“對等連接”是由 C-Connection 來管理的。
3.3、Connecter:連接器,管理下載、上傳、阻塞、片斷選擇、讀寫磁盤等等。
下載和上傳不是孤立的,它們之間相互影響。下載需要有片斷選擇算法,上傳的時候要考慮阻塞,片斷下載之后,要寫到磁盤上。上傳的時候,也需要從磁盤讀取。
這些任務,是由 Connecter 來統(tǒng)一調度的。
類層次結構,我用縮進來表示一種包含關系。
Encoder:
E-Connection
C-Connection
Upload
SingleDownloader
Connecter
Choker:負責阻塞的管理
Downloader:
SingleDownloader
Picker:片斷選擇策略
StorageWrapper:
先寫這些吧,有什么我再補充進來。