eMule 的使用場景及初始化任務描述級eMule相關代碼分析(僅涉及ed2k)

提前說明:從這頁博客開始就會涉及eMule源代碼的分析(或者說梳理更爲合適),梳理源代碼的目的在於讓大家在看了一下原理後,想看下這些原理的具體實現(看eMule源代碼)時輕鬆些,更有目標。所以在這裏筆者也上傳了eMule官方源碼   http://download.csdn.net/detail/huang_rong12/9506732  ,若梳理的不好請見諒。

 

前面 已經介紹了ed2k的相關概念 ,本博客會先描述emule客戶端使用場景,然後按照使用場景逐步進行分析。

 

Emule 客戶端使用場景分析,當我們要下載一個ed2k 文件時 ,可以分爲以下幾步:

一 、啓動emule客戶端(這時候就開始初始化,主要是對Config文件夾下的文件(比如Server.met等)進行反序列化,生成相應實例,存儲信息(包括ed2k服務器地址及端口號))。

二. 客戶端必須連上某個服務器(可能會從服務器列表中依次嘗試,直到連上爲止,連上以後客戶端就加入了emule網絡,成爲協議節點)。

三. 用戶就向服務器傳遞ed2k連接(這是用戶已知道ed2k連接的情況,如果用戶不知道ed2k連接,就可向服務器發送關鍵詞,服務器會返回很多鏈接,這時再提交連接就一樣的過程了)。

四. 提交ed2k連接以後,就表示用戶要下載文件,服務器就返回存儲文件的文件源客戶端。

五. 用戶得到文件源客戶端的信息後,就發起到某個源客戶端的回話;

六. 原客戶端收到會話請求後就排隊等待上傳文件(排隊的是請求發起客戶端);

七. 原客戶端選擇用戶客戶端,開始上傳;

八. 用戶客戶端從原客戶端下載文件數據庫塊。

九. 結束下載完成會話(用戶客戶端)。

十. 源文件客戶端從隊列中選擇一個等待客戶端,進行數據傳送。

 

 

 

這樣就是一個文件下載的基本過程(有些已經簡化(如返回的源客戶端可能有多個(文件可能已經被分段了))但是基本過程已經描述清楚了)。

在場景描述中提到了一個很重要的東西---ed2k服務器。Ed2k服務器很重要是由於下載文件的過程離不開他。第一必須連上ed2k服務器才能開始下載。第二,必須通過ed2k服務器才能收索資源,並解析ed2k連接,得到文件源客戶端的信息。而且客戶端與源客戶端傳遞文件數據塊也受ed2k服務器影響(高用戶id和低用戶id)。

 

 

現在常用ed2k服務器軟件是 Lugdunum 。這款軟甲是不開源但免費使用的。所謂的ed2k服務器就是安裝了ed2k服務器軟件並接入ed2k網絡中另一臺ed2k服務器的電腦。

 

一.啓動客戶端,初始化信息,這裏主要做的事情是從emule的配置文件中讀取到ed2k服務器列表的信息(值得說明的是這裏的配置文件不一定存放在本地也有可能是放在網絡中的某臺服務器上但是在eMule客戶端的實現中是放在本地Conf文件夾裏,配置文件沒放在本地的主要是現在商用的ed2k軟件(包括qq旋風,迅雷),之所以採用配置信息不放在本地的方式,筆者認爲這些軟件並不希望由用戶自己改動ed2k服務器的地址信息,而是由後臺統一設置,這樣可提高ed2k網絡的穩定性)。總的來說,就是第一步是完成後,客戶端就可以知道ed2k服務器的列表(一般不是僅一個服務器,通常是多個(原因是客戶端得到服務器地址,但不知服務器的連接情況,可能服務器此時接受響應的客戶端已經很多了,這時客戶端是無法連上的,所以一般情況會有多個服務器信息))。

第一步的文字描述就到這裏。接下來結合eMule的源代碼進行分析。

eMule的代碼結構非常合理。雖然代碼量比較大,但是各個功能模塊之間的劃分都很合理。從它的工程文件裏面就可以看出這一點。eMule把表示功能的代碼文件和表示界面的代碼文件分開了,Source Files和Header Files是實現功能的代碼的源文件和頭文件,而Interface Source和Interface Header是實現圖形界面的源文件和頭文件。由於eMule的代碼量太大,本系列將跳過圖形界面的實現,着重分析eMule的功能實現部分的代碼,而且功能實現方面也主要挑主要的部分分析。本節將從emule.cpp開始分析,引出eMule中使用到的幾個主要的功能實現的類,並大體描述它們的作用,這裏開始就可以對照着源碼一步一步往下看了

 

emule.cpp爲類CemuleApp的實現。因此在運行時,首先會運行InitInstance進行一些初始化的工作。從這個函數裏面我們也可以第一次看出那些即將在整個程序中發揮作用的類了。

 

最開始的時候是計算出程序常用的一些目錄,如配置文件,日誌文件等。接下來是ProcessCommandline,它的作用有兩方面,第一是確認該eMule的運行方式,即命令行後面有沒有參數,第二是確認目前eMule是不是隻有一個實例在運行。在一般的情況下,雙擊eMule可執行文件是不會帶參數的。但是通過點擊鏈接或者打開關聯文件的方式打開eMule則相當於帶參數運行eMule。通過在註冊表裏添加一些項目可以讓一個程序和某種鏈接或者某個後綴的文件產生關聯。具體辦法可以參見OtherFunctions.cpp中的Ask4RegFix,BackupReg,RevertReg三個函數的功能。ProcessCommandline中通過創建帶有名稱的互斥信號量來確認是否有其它的eMule實例在運行。對於一個確定的名稱,CreateMutex只能創建一個互斥信號量。因此通過該信號量是否創建成功就可以知道是否有其它eMule實例運行。如果有的話,而且又是帶參數的那種模式,那麼直接把這個參數使用Windows的消息機制發給那個窗口即可,接下來的代碼無非就是如何找到另外一個叫"eMule"的傢伙以及給它發個什麼消息。pstrPendingLink是一個全局變量,表示將要被處理的命令行參數。它將會在初始化完成後一段時間後被處理。

 

下面兩個比較重要的類是CPreferences和CStatistics。前者掌握着程序的大部分配置數據,後者則進行各種統計。它們的特點都是有很多的成員變量,而且還是靜態的,這種方式可以保證它們的唯一性,而且把這些變量統一到一個類管理。但是實際上並不需要了解每個變量的含義。thePrefs和theStats是這兩個類的唯一的實例。

在emule.cpp的InitInstance 函數中有 如下兩行代碼

// create & initalize all the important stuff 

thePrefs.Init();(從配置文件中讀取配置息相關信息並初始化相應數據結構以存儲信息)

theStats.Init();(從配置文件中讀取統計相關信息並初始化相應數據結構以存儲信息)

這兩行就是完成初始化,這樣InitInstance 函數結束之後,通過thePrefs就可以找到服務器信息了。

 

eMule 服務器及服務器列表相關類

CServerList就是emule中負責管理服務器列表的類。是列表類結構,CServerList需要對外提供列表的增加,刪除,查找,修改等接口。在CServerList中,每個服務器的信息是一個CServer類。和搜索信息不一樣,但是和已知文件列表一樣,服務器的信息列表是需要長期保留的,因此CServerList和CKnownFileList類一樣提供了把它所包含的所有信息保存到一個文件中,以及從這個文件中讀回其信息的功能。

 

CServer中的結構比較簡單,只需要保留服務器的各種信息即可。它可以通過IP地址和端口來創建,也可以通過一個簡單的結構ServerMet_Struct來創建,其中後者是用來直接從文件中讀取的。該結構僅僅包含IP地址和端口以及屬性的個數,CServer中其它的屬性在保存到文件中時,均採用Tag方式保存。

 

CServerList除了提供通常的CServer信息外,還提供一些統計信息諸如所有的服務器的用戶數,共享的文件數等。這些統計信息也是基於每個單獨的CServer的相關信息計算出來的。

 

 

在處理完其它一些事情,包括創建圖形界面對象CemuleDlg後,接下來可以看到一排一排的創建新對象的語句。這些類將會實現eMule程序運行的主要功能,

 

eMule 讀取配置文件相關代碼(類)。

eMule中要讀取的配置文件數量較多,每種配置文件都是自己定義的格式,爲了方便讀取和存儲這些文件,eMule中有一個很重要的基礎設施類來複制這些文件操作,它能夠很方便得處理一些常用數據類型的讀寫,並且帶有一定的安全保護機制。這項基礎設施在SafeFile.cpp和SafeFile.h中實現。在kademlia\io目錄下有這項功能的另外一項實現。它們實現的功能基本上相似,但是kademlia\io目錄下的版本實現的時候多了一個以Tag作爲單位進行讀寫的功能。這些實現中和另外一項基礎設施,那就是字符串轉化密切相關。StringConversion.cpp和StringConversion.h是eMule中專門複製各類字符串轉化的基礎設施,什麼Unicode啊,多字節流啊,或者是UTF-8之類的,在這裏轉化全部都不是問題。關於字符串轉化,個人推薦儘量使用Unicode的寬字符,這樣可以最大程度得避免亂碼。

 

SafeFile.cpp或者kademlia\io目錄下的實現都有這樣的特點,那就是把數據操作的行爲和數據操作的對象分割開來。它們都定義了一個抽象的數據操作的基類(在SafeFile.cpp中是CFileDataIO,在kademlia目錄下是DataIO.cpp實現的Kademlia::CDataIO),這個類中只負責實現在邏輯上操作一項數據的行爲,例如,要讀取出一個32位的整型,那麼就是讀出四個字節到一個整型數值的地址中,要讀取或者寫入其它類型的數據用的是類似的方法。但是這個類把物理上進行數據操作的方法全部都聲明爲純虛函數,即讀出多少個字節,寫入多少個字節這樣的。有了這樣一個基類,就可以非常方便得在它上面進行重載,把這些純虛函數定義爲向某塊內存中進行讀寫的操作,就能很方便得將較爲複雜的數據序列化到一塊連續的內存,而如果這些純虛函數是向文件讀寫的操作,那麼自然就可以很方便得用來讀寫各種格式比較奇怪的自己定義的配置文件了。

 

這些類要讀取的數據對象通常有這些,各種整型,字符串,以及Tag類型。整型讀寫起來比較簡單,從1個字節的,2個字節的到4個,8個或者16個字節類型的數據讀寫方法都比較類似。這裏要稍微提一下16個字節的那種,16個字節是128位,是eMule中的Kad網的隨機生成的ID的長度,也是eMule中常用的MD4的hash算法生成的結果的長度。通常用來直接存取整個的這樣的一個ID。在kademlia\utils目錄下的UInt128.cpp實現了一個表示128位的整數的類,功能十分完善,可以進行一些算術操作,並且可以進行比較,這樣爲它以後作爲key出現在hash表中打下了基礎。仔細學習UInt128.cpp中的代碼實現可以學到很多在編寫這種自定義的數據對象類型時應該注意的問題。

 

數據操作中另外一項很重要的操作是字符串,總的原則是先寫一個長度,再寫內容。但是到具體的操作的時候就需要注意這些細節了,如長度是寫4個字節還是兩個字節,字符串的內容要不要用UTF-8進行編碼。這些操作就需要和StringConversion.cpp緊密合作了。其實後者的字符串轉化函數很多也是調用ATL的相關函數,只是在外面再包上一層MFC的CString。

 

在kademlia\io\DataIO.cpp中實現的CDataIO中,還另外實現了按照Tag進行讀寫的功能。這在網絡上交換共享文件的元信息非常重要,通常一個文件的元信息就可以分解成很多的Tag,如"文件名=xxx","文件長度=xxx"等等。也就是說,一個Tag就是表示某項屬性等於某個值這樣一個事實。在Opcodes.h這個文件中定義了很多的代碼,其中就有很多常見的Tag的屬性名稱。CDataIO類中存儲Tag的屬性名都是先存一個字節的類型,再存名稱,最後按照類型存值。

 

eMule中的這幾項基礎設施都是編寫得比較好的,可以很方便得拿出來複用。像字符串編碼的處理和具有一定數據結構的文件IO操作在很多地方都會很有用。eMule中這些類的實現基本上覆制到其它的工程文件中只要稍微修改一下很快就能使用。以後我們還將看到eMule中很多其它很有用的基礎設施。  

 

 

 

 

 

emule作爲一個文件共享方面的程序,首先要對自己共享的所有的文件的信息都十分清楚,類CKnownFileList的作用就是這樣的,它在emule.cpp中隨着cmuleapp類創建的時候被創建。

 

CKnownFileList類使用了MFC的CMap類來維護內部的hash表,這也可以看出emule和MFC的關係確實非常緊密。這裏如果用STL的map其實也是可以的。它內部維護了一個已知的文件的列表和取消了的文件列表。這些hash表的關鍵字都是文件的hash值。這樣能夠判斷出文件名不同而內容相同的文件,而一般要讓不同內容的文件有相同的hash值是非常困難的,這也是hash函數它設計的初衷。因此除非是碰上王小云教授這樣的牛人,我們基本上可以認爲,兩個文件hash值相同就代表了它們內容相同。再來看CKnownFileList.cpp,這個文件其實並不長,因爲管理一個列表確實不需要太多種類的操作,如果對於每個具體的文件有一個很強大的類來處理它的話。而這裏確實有,它就是CKnownFile。有了這麼一個類,我們就可以看到,CKnownFileList類所需要做的工作就是能夠根據一些信息查找到對應的CKnownFile類,能夠複製其它的列表中的信息,能夠把所有的這些信息存成文件,然後下次emule運行的時候能夠把這些信息快速恢復出來,最重要的是能夠在完成以上工作的情況下不造成內存泄漏。

 

CKnownFile類就是一個專門關注某個特定文件的信息的類,它仍然有其基類CAbstractFile。但是它和CAbstractFile類的主要區別就是CAbstractFile類只有基本的信息存取的功能,而CKnownFile能夠主動的生成這些信息,例如,給一個文件的路徑給CKnownFile,它能夠主動地去獲取和這個文件有關的一切信息,並且把它保存在自己的成員變量裏(CreateFromFile)。CKnownFile.cpp文件看上去比較長,是因爲它做的工作比較多,現在版本的emule中,除了對某個文件進行全文hash以外,還採用了BT的方式,進行分塊hash,這樣在傳輸文件的時候,即使發生出錯的情況,也可以不必重傳整個文件,而只是重傳有錯誤的那塊,這種機制叫做高級智能損壞處理(AICH,Advanced Intelligent Corruption Handling),這個機制以後再繼續分析。

 

CKnownFile把讀到的文件信息都保存成一個一個的Tag。它在運行中會盡量得獲取更多的文件信息,例如,對於媒體類型的文件,它能夠調用id3lib庫來獲取諸如作者,唱片發行年代,風格等tag信息。如果是視頻媒體文件,它還會去抓圖(功能實現:CFrameGrabThread)。

 

CKnownFile還能夠隨時掌握目前該文件的下載情況(內部有個CUpDownClient的列表),當然,還會根據要求序列化和反序列化自己,LoadFromFile和WriteToFile都以CFileDataIO爲參數,這樣方便CKnownFileList保存和讀取它的列表中的所有文件的信息。 

 

到這裏第一步涉及的分析內容就到結束了,讀者可以根據本博客的梳理,開始eMule相關源碼(初始化部分)的深入研究。

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章