BT源代碼學習心得(十一):客戶端源代碼分析(存儲管理)

BT源代碼學習心得(十一):客戶端源代碼分析(存儲管理)

發信人: wolfenstein (NeverSayNever), 個人文集
標  題: BT源代碼學習心得(十一):客戶端源代碼分析(存儲管理)
發信站: 水木社區 (Tue Aug 16 07:15:06 2005), 文集
(本文包含HTML標記,終端模式下可能無法正確瀏覽)
    這一次分析BT的存儲管理。我們知道,BT把要共享的資源化分成統一大小的塊,並且在
種子文件中記錄每一塊的消息摘要值,以便在下載時確定某一塊是否已經正確下載。而且在
前面的種子文件的製作過程中我們已經看到,除非是最後一塊,其它的塊大小都是相同的,
因此很有可能出現在一個文件的開始多少個字節屬於某一塊,然後從中間偏移多少字節又屬
於某一塊,或者在文件比較小的情況下某一塊包含了若干文件等。而BT的存儲管理部分就對
程序的其它部分屏蔽了這些區別,即對其它部分而言,只需要按照塊來進行存取。
    首先了看FilePool類,它在Multitorrent中定義,就是說,全局只有一個。因此它可以
保證多個種子文件在下載時硬盤上的文件被打開的數量限制在一定數量。內部維護瞭如下變
量:handlebuffer爲所有已經打開的文件的列表,allfiles是一個字典,記錄所有的文件的
擁有者情況,即哪個文件是屬於哪個種子文件。它的關鍵字是各個文件的文件名,值則是對
應的_SingleTorrent對象。handles則是記錄文件名與對應句柄關係的字典,whandles還說
明瞭哪些文件是可寫的,注意,在whandles中,並沒有儲存對應句柄,即如果有一個文件出
現在handles中,可以通過handles直接獲取其句柄,避免多次打開或者關閉文件,而如果它
出現在whandles中,那麼說明它還是可寫的。
    Storage是在每個_SingleTorrent中被定義。創建Storage需要一個文件和它們對應的大
小的列表,文件的大小方面的信息可以從種子文件的元信息裏得到,另外,必須要把種子文
件的元信息的文件列表中的每一個項目都加上在硬盤中實際保存的目錄,以便可以直接對應
到某個具體的文件。Storage在創建的時候建立文件名和全局的字節之間的映射關係,即列
表ranges,該列表的每一個列表項是一個三元組,起始偏移,結束偏移,文件名。它的意思
是種子文件的內容中,從第幾個字節到第幾個字節是屬於哪個文件。另外,假設種子文件中
對所有信息的內容進行了塊的劃分,設這個塊長爲piecelen,那麼每一塊還有一個字節偏移
,如從第0個字節到第piecelen(不含)個字節是屬於第一塊,接下來屬於第二塊等。因此這
個Storage類就要解決BT的下載按塊進行和硬盤中按文件進行存儲之間的矛盾。這裏再提一
下,之所以把piece翻譯成塊,是因爲後面的BT的下載過程中,還要把每一塊再切分成若干
的slice,而我習慣於把slice翻譯成片。
    在Storage中,有兩個私有函數_intervals和_get_file_handle,它們給read和write提
供了兩項重要的功能,而read和write是Storage對外提供的重要接口。_intervals的任務是
提供一個全局的偏移量和長度,返回一張表,說明要對這些數據進行訪問應該分別訪問哪些
文件的第幾個字節開始的多少字節。這樣,在read和write裏面就可以用for ... in
_intervals(xx,xx)了。而_get_file_handle則是獲取一個文件的實際句柄,以便對其進行
讀寫,在Storage中獲取文件的句柄要用一個函數來處理的原因是必須考慮到文件打開數量
的要求限制,因而在_get_file_handle中要和FilePool打交道,在打開一個文件句柄的同時
,要對FilePool內部的一些變量進行維護。
    Storage還有一項功能就是讀寫一個“快速恢復”狀態文件。它只負責讀寫一部分,這
個文件中還有一部分數據由StorageWrapper類來進行讀寫。由Storage類負責讀寫的部分是
文件頭,包括'BitTorrent resume state file, version 1'字樣,和總的數據量,以及各
個文件的大小和更改時間等。
    StorageWrapper類則是在_SingleTorrent._start_download中定義,提供的接口要更加
高級。例如,它提供按照某一塊來進行訪問,然後在內部通過把塊號乘以一塊的大小來得到
實際的字節偏移量,然後讓Storage來進行讀寫。另外,它維護了一張本地擁有哪些塊的比
特數組have,可以方便決策。還有兩個表示塊的存儲狀況的數組,places和rplaces,它們
的意思是數據的第幾塊存儲在硬盤上的第幾塊以及硬盤上的第幾塊存儲的實際上是數據的第
幾塊。這個數組基於兩部分數據的抽象:第一部分的數據抽象是把種子文件中所表示的內容
(即共享的資源)看成是一塊連續的數據,這塊數據有若干塊。第二部分的數據抽象是把存儲
在硬盤上的文件,看成是一塊連續的數據,即連續的存儲空間,也分成若干塊。當下載任務
全部結束後,應該有對於0到self.numpieces-1,有places[i]=rplaces[i]=i。而在下載過
程中,由於採取了一定的策略,不一定是先下第0塊,再下第1塊等,因此在這個過程中有可
能places[i]和rplaces[i]中的值不等於i。
    我們先來看一下比特數組,即Bitfield,它定義在BitTorrent/bitfield.py中。它用最
節約的空間完成了比特數組的存儲,即比特數除於8的字節數。另外,它實現了
__setitem__和__getitem__,這樣就可以直接對have[i]進行讀寫操作來完成值的操作。注
意到在__setitem__的實現中有assert val,這就意味着只能把數組中的某一項賦值成1。這
項功能比較適用於表示塊擁有的狀況,即某一塊只能從沒有到有,不能“得而復失”。
    StorageWrapper還有一項很重要的功能就是對每一塊數據再次細分成若干個slice,而
一個slice就是兩個對等客戶之間通過網絡進行數據交換的最小單位。在此基礎上,它要負
責生成請求,inactive_requests儲存的就是所有可能的請求。在看這部分代碼時,注意1和
l的區別,在初始化時,inactive_requests[i]的值是1,表示某一塊還沒有(因而可以爲此
生成網絡請求),當得知某一塊已經獲取時,inactive_requests[i]的值變爲None,具體得
知某一塊已經獲取時要進行的操作爲markgot,它的意義是第piece塊在硬盤上的存儲空間中
的第pos塊被發現。另外,初始化的時候調用_check_partial和_make_partial檢查某個具體
的塊,看看有哪些slice還需要下載。把這些請求放到inactive_requests中,以後當程序其
它部分決定要開始下某一塊時,StorageWrapper爲其生成相應的網絡請求的參數(第幾塊,
偏移多少,請求多少長度的數據),new_request即完成這項工作,另外piece_came_in和
get_piece提供數據的讀寫操作,調用它們的時候都要指定index(第幾塊),begin(塊內偏移
),length(長度,在piece_came_in中是piece,即數據本身,可以直接得到它的長度)。
    最後,關於存儲管理這部分,還有一點需要提到,那就是早期的某個BT版本是在下載剛
剛開始的時候就申請好相應的硬盤空間。而現在則是隨着下載的進行文件逐漸增大。但是下
載的順序很可能不是先下第0塊,再下第1塊這樣,因此在文件中存儲的順序也不一樣,這樣
當新的下載數據到來存儲到硬盤上時,很可能就要對起進行調整,儘可能讓它們“對號入座
”。_move_piece函數就能進行數據的移動,而參考piece_came_in開頭部分對_move_piece
調用的代碼就可以理解BT在下載過程中逐漸使塊的順序“對號入座”的這個過程。 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章