天天看點

eMule源代碼分析(二)

eMule源代碼解析 -2 2006-10-16 17:4

1分塊機制--正确傳輸資源的保證 為了加快内容分發的速度,分塊處理是一種簡單有效的方法。emule中對每個檔案都進行了分塊處理。另外分塊還有一個好處就是如果保留了每一分塊的hash值,就能在隻下載下傳到檔案的一部分時判斷出下載下傳内容的有效性。

emule在擷取每個共享檔案的資訊時,就對它進行了分塊處理,是以如果要知道emule中的分塊處理和恢複機制,看CKnownFile::CreateFromFile函數的實作就行了。

這個函數中牽涉到的和分塊處理以及hash計算相關的類都在SHAHashSet.cpp和SHAHashSet.h中。

下面介紹其中幾個主要的類:

 CAICHHash類隻負責一塊hash值,提供兩個CAICHHash類之間的直接指派,比較等基本操作。 CAICHHashAlgo是一個hash算法的通用的接口,其它hash算法隻要實作這種接口都能使用,這樣,可以很友善得使用不同的hash算法來計算hash值。CAICHHashTree則是一個樹狀的hash值組織方式,它有一個左子樹和右子樹成員變量,類型是指向CAICHHashTree的指針,這是一個典型的實作樹狀結構的方法。CAICHHashSet中包含了一個CAICHHashTree類型的變量,它直接向CKnownFile負責,代表的是一個檔案的分塊資訊。 SHAHashSet.h檔案的開始的注釋部分向我們解釋了它的分塊的方式。這裡要用到兩個常量9728000和184320,它們分别是9500k和 180k。這是emule中兩種不同粒度的分塊方式,即首先把一個很大的檔案分割成若幹個9500k的塊,把這些塊組織成一顆樹狀的結構,然後每一個這樣的塊又分解成若幹個180k的塊(52塊,再加一個140k的塊),仍然按照樹狀的結構組織起來。最後總的結構還是一顆樹。 CKnownFile::CreateFromFile方法是在讀取目标檔案的内容時,逐漸建立起這樣一顆樹的。 CAICHHashTree::FindHash能夠根據讀取到的目标檔案的偏移量和下一塊的大小,來找出對應的樹枝節點(就是一個指向 CAICHHashTree的指針)。如果有必要的話,還會自動建立這些樹枝節點。是以在進行分塊操作的時候,把檔案從頭到尾讀一邊,整個 CAICHHashTree就建立起來了,對應的分塊hash值也指派好了。

最後我們還需要注意的就是CKnownFile類中的hashlist變量。就是說它還單獨保留直接以 9728000位元組為機關的所有分塊的MD4算法的hash值。這樣對于一個檔案就有了兩套分塊驗證的機制,能夠适應不同場合網絡基礎設施--網絡基礎設施的基礎設施 MFC中已經有一些網絡基礎設施類,如CAsyncSocket等。但是emule在設計中,為了能夠更加高效得開發網絡相關的代碼,建構了另外的一些類作為基礎設施,這些基礎設施類的代碼也有很高的複用價值。

 首先是CAsyncSocketEx類。AsyncSocketEx.h中對這個類的特點已經給出了一定的說明。它完全相容CAsyncSocket類,即把應用程式中是以的CAsyncSocket換成CAsyncSocketEx,程式仍然能夠和原來的功能相同,是以在使用上更加友善。但是在這個基礎上,它的效率更高,主要是在消息分發機制上,即它處理和SOCKET相關的消息的效率要比原始的MFC的 CAsyncSocket類更高。

另外,CAsyncSocketEx類支援通過實作CAsyncSocketExLayer類的方式,将一個SOCKET分成若幹個層,進而可以很友善得實作許多網絡功能,如設定代理,或者是使用SSL進行加密等。

另外還有ThrottledSocket.h中定義的ThrottledControlSocket類和 ThrottledFileSocket類,這兩個類隻定義了兩個接口。任何其它的網絡套接字類如果想實作限速的功能,隻需要在其預設的發送函數(如 Send或Sendto)中不發送資料而是把資料緩存起來,然後在實作ThrottledControlSocket或者 ThrottledFileSocket接口中的SendFileAndControlData或SendControlData方法時才真正把資料發送出去,這樣就能實作上傳限速,而這也是需要UploadBandwidthThrottler類進行配合,UploadBandwidthThrottler是一個WinThread的子類,平時單獨運作一個線程。下一次會較長的描述它是如何控制全局的上傳速度的。 網絡基礎設施--全局限速器UploadBandwidthThrottler UploadBandwidthThrottler是emule中使用的全局的上傳限速器。它繼承了CWinThread類,且在該類被建立的時候,就新建立一個線程開始單獨運作。在該類被析構時也會自動停止相應的線程。這個線程的目标函數就是RunProc,然後為了避免在RunProc函數不能使用 this指針的情況,它使用了RunInternal來實際完成工作線程的工作。在emule中,還有另外一個類 LastCommonRouteFinder有類似的結構。 UploadBandwidthThrottler中儲存了若幹的套接字(Socket)隊列,這些隊列的處理方式略有不同。在标準隊列 (m_StandardOrder_list)裡面排隊的都是實作了ThrottledFileSocket接口的類,通常這些類能夠傳輸檔案内容也可以傳輸控制資訊。

而其它四個隊列都是實作ThrottledControlSocket接口的類的隊列,在這些隊列中的類主要以傳輸控制資訊為主。

這四個隊列為臨時高優先級,臨時普通優先級,正式高優先級,正式普通優先級。和把套件字直接添加到普通隊列(AddToStandardList)不同,

QueueForSendingControlPacket把要添加到隊列的套接字全部添加到兩個臨時隊列。根據它們的優先級添加到普通的臨時隊列。在RunInternal的大循環中,臨時隊列中的項目先被移到普通隊列中,然後再進行處理。

UploadBandwidthThrottler使用了兩個臨界區,兩個事件。pauseEvent是用來暫停整個大循環的動作的。而threadEndedEvent是标志整個線程停止的事件。sendLocker是大循環中使用的主要的臨界區,而 tempQueueLocker是為兩個臨時隊列額外添加的鎖,這樣可以一邊發送已有隊列中的套界字要發送的資料,一邊把新的套接字加到隊列中。

UploadBandwidthThrottler的RunInternal中的大循環是該工作線程的日常操作。這個大循環中做了以下事情,計算本次配額,即本次循環中能夠發送多少位元組,好安排排程,計算本次循環應該睡眠多少時間,然後進行相應的睡眠,進而進行限速。操作控制資訊隊列,發送該隊列中的資料,注意,控制隊列中的套接字(m_ControlQueueFirst_list和 m_ControlQueue_list)隻使用一次就離開隊列。而标準隊列中的套接字不會這樣。在一輪循環結束後,如果還有沒有用完的發送資料的配額,則會有部配置設定額儲存到下一輪。 網絡基礎設施--emule套接字CEMSocket CEMSocket是CAsyncSocketEx和ThrottledFileSocket的子類,它把若幹功能整合到了一起,是以可以作為emule 使用起來比較友善的套接字。例如它可以很友善得指定代理,把CAsyncSocketEx中的建立一個新的代理層并且添加到清單中的功能對外屏蔽了。另外它可以分出狀态,如目前是否在發送控制資訊等。

 CEMSocket中我們需要仔細考察的是它的SendControlData和 SendFileAndControlData方法。如前所述,這些方法是用來和UploadBandwidthThrottler進行配合,以便完成全局的限速功能的。它的功能應該是按照UploadBandwidthThrottler的要求,在本次輪到它發送資料時發送指定數量的位元組數。是以,應用程式的其它部分在使用CEMSocket時,如果要達到上傳資料限速的目的,不應該直接調用标準的Send或者SendTo方法,而是調用 SendPacket。這裡就有了另外一個結構Packet,它通常包含一個emule協定中完整的包,例如有協定的頭部資料等,還内置了 PackPacket和UnPackPacket方法,可以自行進行壓縮和解壓的功能。SendPacket把要發送的Packet放到自己的隊列中,這個隊列也有兩個,控制資訊包隊列,和标準資訊包隊列。如果有必要,把自己加入到UploadBandwidthThrottler的隊列中。我們注意到CEMSocket的SendControlData和SendFileAndControlData方法其實都是調用自己的另一個重載的 Send方法。而且我們也已經知道這個方法是在UploadBandwidthThrottler的工作線程中的大循環中被調用的,而這個Send方法的内容本身也是一個大循環,但是意義很明了,就是在不超過自己本次發送的配額的情況下,把自己的包隊列中的包取出來,并且發出去。同樣,這裡也用到了一個臨界區,它是為了保證從包隊列中取出包來發送和把包往隊列中放的操作是互斥的。是以,如果把它和UploadBandwidthThrottler結合起來,我們就看到了一個兩層的隊列,即所有的套接字組成了一個發送隊列,在UploadBandwidthThrottler的控制下保證了對速度的限制,而每個套接字即将發送的資料包又組成了一個隊列,保證了每次進行資料發送的時候都會滿足UploadBandwidthThrottler的要求。

繼續閱讀